--- /dev/null
+diff -Naur linux-3.14.22.org/include/linux/netfilter/xt_layer7.h linux-3.14.22/include/linux/netfilter/xt_layer7.h
+--- linux-3.14.22.org/include/linux/netfilter/xt_layer7.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-3.14.22/include/linux/netfilter/xt_layer7.h 2014-10-24 15:33:21.281274380 +0200
+@@ -0,0 +1,14 @@
++#ifndef _XT_LAYER7_H
++#define _XT_LAYER7_H
++
++#define MAX_PATTERN_LEN 8192
++#define MAX_PROTOCOL_LEN 256
++
++struct xt_layer7_info {
++ char protocol[MAX_PROTOCOL_LEN];
++ char pattern[MAX_PATTERN_LEN];
++ u_int8_t invert;
++ u_int8_t pkt;
++};
++
++#endif /* _XT_LAYER7_H */
+diff -Naur linux-3.14.22.org/include/net/netfilter/nf_conntrack.h linux-3.14.22/include/net/netfilter/nf_conntrack.h
+--- linux-3.14.22.org/include/net/netfilter/nf_conntrack.h 2014-10-15 08:42:04.000000000 +0200
++++ linux-3.14.22/include/net/netfilter/nf_conntrack.h 2014-10-24 15:33:21.281274380 +0200
+@@ -105,6 +105,22 @@
+ struct net *ct_net;
+ #endif
+
++#if defined(CONFIG_NETFILTER_XT_MATCH_LAYER7) || \
++ defined(CONFIG_NETFILTER_XT_MATCH_LAYER7_MODULE)
++ struct {
++ /*
++ * e.g. "http". NULL before decision. "unknown" after decision
++ * if no match.
++ */
++ char *app_proto;
++ /*
++ * application layer data so far. NULL after match decision.
++ */
++ char *app_data;
++ unsigned int app_data_len;
++ } layer7;
++#endif
++
+ /* Storage reserved for other modules, must be the last member */
+ union nf_conntrack_proto proto;
+ };
+diff -Naur linux-3.14.22.org/include/uapi/linux/netfilter/Kbuild linux-3.14.22/include/uapi/linux/netfilter/Kbuild
+--- linux-3.14.22.org/include/uapi/linux/netfilter/Kbuild 2014-10-15 08:42:04.000000000 +0200
++++ linux-3.14.22/include/uapi/linux/netfilter/Kbuild 2014-10-24 15:34:39.721663344 +0200
+@@ -59,6 +59,7 @@
+ header-y += xt_iprange.h
+ header-y += xt_ipvs.h
+ header-y += xt_l2tp.h
++header-y += xt_layer7.h
+ header-y += xt_length.h
+ header-y += xt_limit.h
+ header-y += xt_mac.h
+diff -Naur linux-3.14.22.org/net/netfilter/Kconfig linux-3.14.22/net/netfilter/Kconfig
+--- linux-3.14.22.org/net/netfilter/Kconfig 2014-10-15 08:42:04.000000000 +0200
++++ linux-3.14.22/net/netfilter/Kconfig 2014-10-24 15:33:21.281274380 +0200
+@@ -1153,6 +1153,26 @@
+
+ To compile it as a module, choose M here. If unsure, say N.
+
++config NETFILTER_XT_MATCH_LAYER7
++ tristate '"layer7" match support'
++ depends on NETFILTER_XTABLES
++ depends on NETFILTER_ADVANCED
++ depends on NF_CONNTRACK
++ help
++ Say Y if you want to be able to classify connections (and their
++ packets) based on regular expression matching of their application
++ layer data. This is one way to classify applications such as
++ peer-to-peer filesharing systems that do not always use the same
++ port.
++
++ To compile it as a module, choose M here. If unsure, say N.
++
++config NETFILTER_XT_MATCH_LAYER7_DEBUG
++ bool 'Layer 7 debugging output'
++ depends on NETFILTER_XT_MATCH_LAYER7
++ help
++ Say Y to get lots of debugging output.
++
+ config NETFILTER_XT_MATCH_LENGTH
+ tristate '"length" match support'
+ depends on NETFILTER_ADVANCED
+@@ -1347,6 +1367,12 @@
+
+ To compile it as a module, choose M here. If unsure, say N.
+
++config NETFILTER_XT_MATCH_LAYER7_DEBUG
++ bool 'Layer 7 debugging output'
++ depends on NETFILTER_XT_MATCH_LAYER7
++ help
++ Say Y to get lots of debugging output.
++
+ config NETFILTER_XT_MATCH_STATISTIC
+ tristate '"statistic" match support'
+ depends on NETFILTER_ADVANCED
+diff -Naur linux-3.14.22.org/net/netfilter/Makefile linux-3.14.22/net/netfilter/Makefile
+--- linux-3.14.22.org/net/netfilter/Makefile 2014-10-15 08:42:04.000000000 +0200
++++ linux-3.14.22/net/netfilter/Makefile 2014-10-24 15:33:21.281274380 +0200
+@@ -158,6 +158,7 @@
+ obj-$(CONFIG_NETFILTER_XT_MATCH_SCTP) += xt_sctp.o
+ obj-$(CONFIG_NETFILTER_XT_MATCH_SOCKET) += xt_socket.o
+ obj-$(CONFIG_NETFILTER_XT_MATCH_STATE) += xt_state.o
++obj-$(CONFIG_NETFILTER_XT_MATCH_LAYER7) += xt_layer7.o
+ obj-$(CONFIG_NETFILTER_XT_MATCH_STATISTIC) += xt_statistic.o
+ obj-$(CONFIG_NETFILTER_XT_MATCH_STRING) += xt_string.o
+ obj-$(CONFIG_NETFILTER_XT_MATCH_TCPMSS) += xt_tcpmss.o
+diff -Naur linux-3.14.22.org/net/netfilter/nf_conntrack_core.c linux-3.14.22/net/netfilter/nf_conntrack_core.c
+--- linux-3.14.22.org/net/netfilter/nf_conntrack_core.c 2014-10-15 08:42:04.000000000 +0200
++++ linux-3.14.22/net/netfilter/nf_conntrack_core.c 2014-10-24 15:33:21.281274380 +0200
+@@ -220,6 +220,13 @@
+ * too. */
+ nf_ct_remove_expectations(ct);
+
++#if defined(CONFIG_NETFILTER_XT_MATCH_LAYER7) || defined(CONFIG_NETFILTER_XT_MATCH_LAYER7_MODULE)
++ if(ct->layer7.app_proto)
++ kfree(ct->layer7.app_proto);
++ if(ct->layer7.app_data)
++ kfree(ct->layer7.app_data);
++#endif
++
+ /* We overload first tuple to link into unconfirmed or dying list.*/
+ BUG_ON(hlist_nulls_unhashed(&ct->tuplehash[IP_CT_DIR_ORIGINAL].hnnode));
+ hlist_nulls_del_rcu(&ct->tuplehash[IP_CT_DIR_ORIGINAL].hnnode);
+diff -Naur linux-3.14.22.org/net/netfilter/nf_conntrack_standalone.c linux-3.14.22/net/netfilter/nf_conntrack_standalone.c
+--- linux-3.14.22.org/net/netfilter/nf_conntrack_standalone.c 2014-10-15 08:42:04.000000000 +0200
++++ linux-3.14.22/net/netfilter/nf_conntrack_standalone.c 2014-10-24 15:33:21.285274399 +0200
+@@ -240,6 +240,12 @@
+ if (ct_show_delta_time(s, ct))
+ goto release;
+
++#if defined(CONFIG_NETFILTER_XT_MATCH_LAYER7) || defined(CONFIG_NETFILTER_XT_MATCH_LAYER7_MODULE)
++ if(ct->layer7.app_proto &&
++ seq_printf(s, "l7proto=%s ", ct->layer7.app_proto))
++ return -ENOSPC;
++#endif
++
+ if (seq_printf(s, "use=%u\n", atomic_read(&ct->ct_general.use)))
+ goto release;
+
+diff -Naur linux-3.14.22.org/net/netfilter/regexp/regexp.c linux-3.14.22/net/netfilter/regexp/regexp.c
+--- linux-3.14.22.org/net/netfilter/regexp/regexp.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-3.14.22/net/netfilter/regexp/regexp.c 2014-10-24 15:33:21.285274399 +0200
+@@ -0,0 +1,1197 @@
++/*
++ * regcomp and regexec -- regsub and regerror are elsewhere
++ * @(#)regexp.c 1.3 of 18 April 87
++ *
++ * Copyright (c) 1986 by University of Toronto.
++ * Written by Henry Spencer. Not derived from licensed software.
++ *
++ * Permission is granted to anyone to use this software for any
++ * purpose on any computer system, and to redistribute it freely,
++ * subject to the following restrictions:
++ *
++ * 1. The author is not responsible for the consequences of use of
++ * this software, no matter how awful, even if they arise
++ * from defects in it.
++ *
++ * 2. The origin of this software must not be misrepresented, either
++ * by explicit claim or by omission.
++ *
++ * 3. Altered versions must be plainly marked as such, and must not
++ * be misrepresented as being the original software.
++ *
++ * Beware that some of this code is subtly aware of the way operator
++ * precedence is structured in regular expressions. Serious changes in
++ * regular-expression syntax might require a total rethink.
++ *
++ * This code was modified by Ethan Sommer to work within the kernel
++ * (it now uses kmalloc etc..)
++ *
++ * Modified slightly by Matthew Strait to use more modern C.
++ */
++
++#include "regexp.h"
++#include "regmagic.h"
++
++/* added by ethan and matt. Lets it work in both kernel and user space.
++(So iptables can use it, for instance.) Yea, it goes both ways... */
++#if __KERNEL__
++ #define malloc(foo) kmalloc(foo,GFP_ATOMIC)
++#else
++ #define printk(format,args...) printf(format,##args)
++#endif
++
++void regerror(char * s)
++{
++ printk("<3>Regexp: %s\n", s);
++ /* NOTREACHED */
++}
++
++/*
++ * The "internal use only" fields in regexp.h are present to pass info from
++ * compile to execute that permits the execute phase to run lots faster on
++ * simple cases. They are:
++ *
++ * regstart char that must begin a match; '\0' if none obvious
++ * reganch is the match anchored (at beginning-of-line only)?
++ * regmust string (pointer into program) that match must include, or NULL
++ * regmlen length of regmust string
++ *
++ * Regstart and reganch permit very fast decisions on suitable starting points
++ * for a match, cutting down the work a lot. Regmust permits fast rejection
++ * of lines that cannot possibly match. The regmust tests are costly enough
++ * that regcomp() supplies a regmust only if the r.e. contains something
++ * potentially expensive (at present, the only such thing detected is * or +
++ * at the start of the r.e., which can involve a lot of backup). Regmlen is
++ * supplied because the test in regexec() needs it and regcomp() is computing
++ * it anyway.
++ */
++
++/*
++ * Structure for regexp "program". This is essentially a linear encoding
++ * of a nondeterministic finite-state machine (aka syntax charts or
++ * "railroad normal form" in parsing technology). Each node is an opcode
++ * plus a "next" pointer, possibly plus an operand. "Next" pointers of
++ * all nodes except BRANCH implement concatenation; a "next" pointer with
++ * a BRANCH on both ends of it is connecting two alternatives. (Here we
++ * have one of the subtle syntax dependencies: an individual BRANCH (as
++ * opposed to a collection of them) is never concatenated with anything
++ * because of operator precedence.) The operand of some types of node is
++ * a literal string; for others, it is a node leading into a sub-FSM. In
++ * particular, the operand of a BRANCH node is the first node of the branch.
++ * (NB this is *not* a tree structure: the tail of the branch connects
++ * to the thing following the set of BRANCHes.) The opcodes are:
++ */
++
++/* definition number opnd? meaning */
++#define END 0 /* no End of program. */
++#define BOL 1 /* no Match "" at beginning of line. */
++#define EOL 2 /* no Match "" at end of line. */
++#define ANY 3 /* no Match any one character. */
++#define ANYOF 4 /* str Match any character in this string. */
++#define ANYBUT 5 /* str Match any character not in this string. */
++#define BRANCH 6 /* node Match this alternative, or the next... */
++#define BACK 7 /* no Match "", "next" ptr points backward. */
++#define EXACTLY 8 /* str Match this string. */
++#define NOTHING 9 /* no Match empty string. */
++#define STAR 10 /* node Match this (simple) thing 0 or more times. */
++#define PLUS 11 /* node Match this (simple) thing 1 or more times. */
++#define OPEN 20 /* no Mark this point in input as start of #n. */
++ /* OPEN+1 is number 1, etc. */
++#define CLOSE 30 /* no Analogous to OPEN. */
++
++/*
++ * Opcode notes:
++ *
++ * BRANCH The set of branches constituting a single choice are hooked
++ * together with their "next" pointers, since precedence prevents
++ * anything being concatenated to any individual branch. The
++ * "next" pointer of the last BRANCH in a choice points to the
++ * thing following the whole choice. This is also where the
++ * final "next" pointer of each individual branch points; each
++ * branch starts with the operand node of a BRANCH node.
++ *
++ * BACK Normal "next" pointers all implicitly point forward; BACK
++ * exists to make loop structures possible.
++ *
++ * STAR,PLUS '?', and complex '*' and '+', are implemented as circular
++ * BRANCH structures using BACK. Simple cases (one character
++ * per match) are implemented with STAR and PLUS for speed
++ * and to minimize recursive plunges.
++ *
++ * OPEN,CLOSE ...are numbered at compile time.
++ */
++
++/*
++ * A node is one char of opcode followed by two chars of "next" pointer.
++ * "Next" pointers are stored as two 8-bit pieces, high order first. The
++ * value is a positive offset from the opcode of the node containing it.
++ * An operand, if any, simply follows the node. (Note that much of the
++ * code generation knows about this implicit relationship.)
++ *
++ * Using two bytes for the "next" pointer is vast overkill for most things,
++ * but allows patterns to get big without disasters.
++ */
++#define OP(p) (*(p))
++#define NEXT(p) (((*((p)+1)&0377)<<8) + (*((p)+2)&0377))
++#define OPERAND(p) ((p) + 3)
++
++/*
++ * See regmagic.h for one further detail of program structure.
++ */
++
++
++/*
++ * Utility definitions.
++ */
++#ifndef CHARBITS
++#define UCHARAT(p) ((int)*(unsigned char *)(p))
++#else
++#define UCHARAT(p) ((int)*(p)&CHARBITS)
++#endif
++
++#define FAIL(m) { regerror(m); return(NULL); }
++#define ISMULT(c) ((c) == '*' || (c) == '+' || (c) == '?')
++#define META "^$.[()|?+*\\"
++
++/*
++ * Flags to be passed up and down.
++ */
++#define HASWIDTH 01 /* Known never to match null string. */
++#define SIMPLE 02 /* Simple enough to be STAR/PLUS operand. */
++#define SPSTART 04 /* Starts with * or +. */
++#define WORST 0 /* Worst case. */
++
++/*
++ * Global work variables for regcomp().
++ */
++struct match_globals {
++char *reginput; /* String-input pointer. */
++char *regbol; /* Beginning of input, for ^ check. */
++char **regstartp; /* Pointer to startp array. */
++char **regendp; /* Ditto for endp. */
++char *regparse; /* Input-scan pointer. */
++int regnpar; /* () count. */
++char regdummy;
++char *regcode; /* Code-emit pointer; ®dummy = don't. */
++long regsize; /* Code size. */
++};
++
++/*
++ * Forward declarations for regcomp()'s friends.
++ */
++#ifndef STATIC
++#define STATIC static
++#endif
++STATIC char *reg(struct match_globals *g, int paren,int *flagp);
++STATIC char *regbranch(struct match_globals *g, int *flagp);
++STATIC char *regpiece(struct match_globals *g, int *flagp);
++STATIC char *regatom(struct match_globals *g, int *flagp);
++STATIC char *regnode(struct match_globals *g, char op);
++STATIC char *regnext(struct match_globals *g, char *p);
++STATIC void regc(struct match_globals *g, char b);
++STATIC void reginsert(struct match_globals *g, char op, char *opnd);
++STATIC void regtail(struct match_globals *g, char *p, char *val);
++STATIC void regoptail(struct match_globals *g, char *p, char *val);
++
++
++__kernel_size_t my_strcspn(const char *s1,const char *s2)
++{
++ char *scan1;
++ char *scan2;
++ int count;
++
++ count = 0;
++ for (scan1 = (char *)s1; *scan1 != '\0'; scan1++) {
++ for (scan2 = (char *)s2; *scan2 != '\0';) /* ++ moved down. */
++ if (*scan1 == *scan2++)
++ return(count);
++ count++;
++ }
++ return(count);
++}
++
++/*
++ - regcomp - compile a regular expression into internal code
++ *
++ * We can't allocate space until we know how big the compiled form will be,
++ * but we can't compile it (and thus know how big it is) until we've got a
++ * place to put the code. So we cheat: we compile it twice, once with code
++ * generation turned off and size counting turned on, and once "for real".
++ * This also means that we don't allocate space until we are sure that the
++ * thing really will compile successfully, and we never have to move the
++ * code and thus invalidate pointers into it. (Note that it has to be in
++ * one piece because free() must be able to free it all.)
++ *
++ * Beware that the optimization-preparation code in here knows about some
++ * of the structure of the compiled regexp.
++ */
++regexp *
++regcomp(char *exp,int *patternsize)
++{
++ register regexp *r;
++ register char *scan;
++ register char *longest;
++ register int len;
++ int flags;
++ struct match_globals g;
++
++ /* commented out by ethan
++ extern char *malloc();
++ */
++
++ if (exp == NULL)
++ FAIL("NULL argument");
++
++ /* First pass: determine size, legality. */
++ g.regparse = exp;
++ g.regnpar = 1;
++ g.regsize = 0L;
++ g.regcode = &g.regdummy;
++ regc(&g, MAGIC);
++ if (reg(&g, 0, &flags) == NULL)
++ return(NULL);
++
++ /* Small enough for pointer-storage convention? */
++ if (g.regsize >= 32767L) /* Probably could be 65535L. */
++ FAIL("regexp too big");
++
++ /* Allocate space. */
++ *patternsize=sizeof(regexp) + (unsigned)g.regsize;
++ r = (regexp *)malloc(sizeof(regexp) + (unsigned)g.regsize);
++ if (r == NULL)
++ FAIL("out of space");
++
++ /* Second pass: emit code. */
++ g.regparse = exp;
++ g.regnpar = 1;
++ g.regcode = r->program;
++ regc(&g, MAGIC);
++ if (reg(&g, 0, &flags) == NULL)
++ return(NULL);
++
++ /* Dig out information for optimizations. */
++ r->regstart = '\0'; /* Worst-case defaults. */
++ r->reganch = 0;
++ r->regmust = NULL;
++ r->regmlen = 0;
++ scan = r->program+1; /* First BRANCH. */
++ if (OP(regnext(&g, scan)) == END) { /* Only one top-level choice. */
++ scan = OPERAND(scan);
++
++ /* Starting-point info. */
++ if (OP(scan) == EXACTLY)
++ r->regstart = *OPERAND(scan);
++ else if (OP(scan) == BOL)
++ r->reganch++;
++
++ /*
++ * If there's something expensive in the r.e., find the
++ * longest literal string that must appear and make it the
++ * regmust. Resolve ties in favor of later strings, since
++ * the regstart check works with the beginning of the r.e.
++ * and avoiding duplication strengthens checking. Not a
++ * strong reason, but sufficient in the absence of others.
++ */
++ if (flags&SPSTART) {
++ longest = NULL;
++ len = 0;
++ for (; scan != NULL; scan = regnext(&g, scan))
++ if (OP(scan) == EXACTLY && strlen(OPERAND(scan)) >= len) {
++ longest = OPERAND(scan);
++ len = strlen(OPERAND(scan));
++ }
++ r->regmust = longest;
++ r->regmlen = len;
++ }
++ }
++
++ return(r);
++}
++
++/*
++ - reg - regular expression, i.e. main body or parenthesized thing
++ *
++ * Caller must absorb opening parenthesis.
++ *
++ * Combining parenthesis handling with the base level of regular expression
++ * is a trifle forced, but the need to tie the tails of the branches to what
++ * follows makes it hard to avoid.
++ */
++static char *
++reg(struct match_globals *g, int paren, int *flagp /* Parenthesized? */ )
++{
++ register char *ret;
++ register char *br;
++ register char *ender;
++ register int parno = 0; /* 0 makes gcc happy */
++ int flags;
++
++ *flagp = HASWIDTH; /* Tentatively. */
++
++ /* Make an OPEN node, if parenthesized. */
++ if (paren) {
++ if (g->regnpar >= NSUBEXP)
++ FAIL("too many ()");
++ parno = g->regnpar;
++ g->regnpar++;
++ ret = regnode(g, OPEN+parno);
++ } else
++ ret = NULL;
++
++ /* Pick up the branches, linking them together. */
++ br = regbranch(g, &flags);
++ if (br == NULL)
++ return(NULL);
++ if (ret != NULL)
++ regtail(g, ret, br); /* OPEN -> first. */
++ else
++ ret = br;
++ if (!(flags&HASWIDTH))
++ *flagp &= ~HASWIDTH;
++ *flagp |= flags&SPSTART;
++ while (*g->regparse == '|') {
++ g->regparse++;
++ br = regbranch(g, &flags);
++ if (br == NULL)
++ return(NULL);
++ regtail(g, ret, br); /* BRANCH -> BRANCH. */
++ if (!(flags&HASWIDTH))
++ *flagp &= ~HASWIDTH;
++ *flagp |= flags&SPSTART;
++ }
++
++ /* Make a closing node, and hook it on the end. */
++ ender = regnode(g, (paren) ? CLOSE+parno : END);
++ regtail(g, ret, ender);
++
++ /* Hook the tails of the branches to the closing node. */
++ for (br = ret; br != NULL; br = regnext(g, br))
++ regoptail(g, br, ender);
++
++ /* Check for proper termination. */
++ if (paren && *g->regparse++ != ')') {
++ FAIL("unmatched ()");
++ } else if (!paren && *g->regparse != '\0') {
++ if (*g->regparse == ')') {
++ FAIL("unmatched ()");
++ } else
++ FAIL("junk on end"); /* "Can't happen". */
++ /* NOTREACHED */
++ }
++
++ return(ret);
++}
++
++/*
++ - regbranch - one alternative of an | operator
++ *
++ * Implements the concatenation operator.
++ */
++static char *
++regbranch(struct match_globals *g, int *flagp)
++{
++ register char *ret;
++ register char *chain;
++ register char *latest;
++ int flags;
++
++ *flagp = WORST; /* Tentatively. */
++
++ ret = regnode(g, BRANCH);
++ chain = NULL;
++ while (*g->regparse != '\0' && *g->regparse != '|' && *g->regparse != ')') {
++ latest = regpiece(g, &flags);
++ if (latest == NULL)
++ return(NULL);
++ *flagp |= flags&HASWIDTH;
++ if (chain == NULL) /* First piece. */
++ *flagp |= flags&SPSTART;
++ else
++ regtail(g, chain, latest);
++ chain = latest;
++ }
++ if (chain == NULL) /* Loop ran zero times. */
++ (void) regnode(g, NOTHING);
++
++ return(ret);
++}
++
++/*
++ - regpiece - something followed by possible [*+?]
++ *
++ * Note that the branching code sequences used for ? and the general cases
++ * of * and + are somewhat optimized: they use the same NOTHING node as
++ * both the endmarker for their branch list and the body of the last branch.
++ * It might seem that this node could be dispensed with entirely, but the
++ * endmarker role is not redundant.
++ */
++static char *
++regpiece(struct match_globals *g, int *flagp)
++{
++ register char *ret;
++ register char op;
++ register char *next;
++ int flags;
++
++ ret = regatom(g, &flags);
++ if (ret == NULL)
++ return(NULL);
++
++ op = *g->regparse;
++ if (!ISMULT(op)) {
++ *flagp = flags;
++ return(ret);
++ }
++
++ if (!(flags&HASWIDTH) && op != '?')
++ FAIL("*+ operand could be empty");
++ *flagp = (op != '+') ? (WORST|SPSTART) : (WORST|HASWIDTH);
++
++ if (op == '*' && (flags&SIMPLE))
++ reginsert(g, STAR, ret);
++ else if (op == '*') {
++ /* Emit x* as (x&|), where & means "self". */
++ reginsert(g, BRANCH, ret); /* Either x */
++ regoptail(g, ret, regnode(g, BACK)); /* and loop */
++ regoptail(g, ret, ret); /* back */
++ regtail(g, ret, regnode(g, BRANCH)); /* or */
++ regtail(g, ret, regnode(g, NOTHING)); /* null. */
++ } else if (op == '+' && (flags&SIMPLE))
++ reginsert(g, PLUS, ret);
++ else if (op == '+') {
++ /* Emit x+ as x(&|), where & means "self". */
++ next = regnode(g, BRANCH); /* Either */
++ regtail(g, ret, next);
++ regtail(g, regnode(g, BACK), ret); /* loop back */
++ regtail(g, next, regnode(g, BRANCH)); /* or */
++ regtail(g, ret, regnode(g, NOTHING)); /* null. */
++ } else if (op == '?') {
++ /* Emit x? as (x|) */
++ reginsert(g, BRANCH, ret); /* Either x */
++ regtail(g, ret, regnode(g, BRANCH)); /* or */
++ next = regnode(g, NOTHING); /* null. */
++ regtail(g, ret, next);
++ regoptail(g, ret, next);
++ }
++ g->regparse++;
++ if (ISMULT(*g->regparse))
++ FAIL("nested *?+");
++
++ return(ret);
++}
++
++/*
++ - regatom - the lowest level
++ *
++ * Optimization: gobbles an entire sequence of ordinary characters so that
++ * it can turn them into a single node, which is smaller to store and
++ * faster to run. Backslashed characters are exceptions, each becoming a
++ * separate node; the code is simpler that way and it's not worth fixing.
++ */
++static char *
++regatom(struct match_globals *g, int *flagp)
++{
++ register char *ret;
++ int flags;
++
++ *flagp = WORST; /* Tentatively. */
++
++ switch (*g->regparse++) {
++ case '^':
++ ret = regnode(g, BOL);
++ break;
++ case '$':
++ ret = regnode(g, EOL);
++ break;
++ case '.':
++ ret = regnode(g, ANY);
++ *flagp |= HASWIDTH|SIMPLE;
++ break;
++ case '[': {
++ register int class;
++ register int classend;
++
++ if (*g->regparse == '^') { /* Complement of range. */
++ ret = regnode(g, ANYBUT);
++ g->regparse++;
++ } else
++ ret = regnode(g, ANYOF);
++ if (*g->regparse == ']' || *g->regparse == '-')
++ regc(g, *g->regparse++);
++ while (*g->regparse != '\0' && *g->regparse != ']') {
++ if (*g->regparse == '-') {
++ g->regparse++;
++ if (*g->regparse == ']' || *g->regparse == '\0')
++ regc(g, '-');
++ else {
++ class = UCHARAT(g->regparse-2)+1;
++ classend = UCHARAT(g->regparse);
++ if (class > classend+1)
++ FAIL("invalid [] range");
++ for (; class <= classend; class++)
++ regc(g, class);
++ g->regparse++;
++ }
++ } else
++ regc(g, *g->regparse++);
++ }
++ regc(g, '\0');
++ if (*g->regparse != ']')
++ FAIL("unmatched []");
++ g->regparse++;
++ *flagp |= HASWIDTH|SIMPLE;
++ }
++ break;
++ case '(':
++ ret = reg(g, 1, &flags);
++ if (ret == NULL)
++ return(NULL);
++ *flagp |= flags&(HASWIDTH|SPSTART);
++ break;
++ case '\0':
++ case '|':
++ case ')':
++ FAIL("internal urp"); /* Supposed to be caught earlier. */
++ break;
++ case '?':
++ case '+':
++ case '*':
++ FAIL("?+* follows nothing");
++ break;
++ case '\\':
++ if (*g->regparse == '\0')
++ FAIL("trailing \\");
++ ret = regnode(g, EXACTLY);
++ regc(g, *g->regparse++);
++ regc(g, '\0');
++ *flagp |= HASWIDTH|SIMPLE;
++ break;
++ default: {
++ register int len;
++ register char ender;
++
++ g->regparse--;
++ len = my_strcspn((const char *)g->regparse, (const char *)META);
++ if (len <= 0)
++ FAIL("internal disaster");
++ ender = *(g->regparse+len);
++ if (len > 1 && ISMULT(ender))
++ len--; /* Back off clear of ?+* operand. */
++ *flagp |= HASWIDTH;
++ if (len == 1)
++ *flagp |= SIMPLE;
++ ret = regnode(g, EXACTLY);
++ while (len > 0) {
++ regc(g, *g->regparse++);
++ len--;
++ }
++ regc(g, '\0');
++ }
++ break;
++ }
++
++ return(ret);
++}
++
++/*
++ - regnode - emit a node
++ */
++static char * /* Location. */
++regnode(struct match_globals *g, char op)
++{
++ register char *ret;
++ register char *ptr;
++
++ ret = g->regcode;
++ if (ret == &g->regdummy) {
++ g->regsize += 3;
++ return(ret);
++ }
++
++ ptr = ret;
++ *ptr++ = op;
++ *ptr++ = '\0'; /* Null "next" pointer. */
++ *ptr++ = '\0';
++ g->regcode = ptr;
++
++ return(ret);
++}
++
++/*
++ - regc - emit (if appropriate) a byte of code
++ */
++static void
++regc(struct match_globals *g, char b)
++{
++ if (g->regcode != &g->regdummy)
++ *g->regcode++ = b;
++ else
++ g->regsize++;
++}
++
++/*
++ - reginsert - insert an operator in front of already-emitted operand
++ *
++ * Means relocating the operand.
++ */
++static void
++reginsert(struct match_globals *g, char op, char* opnd)
++{
++ register char *src;
++ register char *dst;
++ register char *place;
++
++ if (g->regcode == &g->regdummy) {
++ g->regsize += 3;
++ return;
++ }
++
++ src = g->regcode;
++ g->regcode += 3;
++ dst = g->regcode;
++ while (src > opnd)
++ *--dst = *--src;
++
++ place = opnd; /* Op node, where operand used to be. */
++ *place++ = op;
++ *place++ = '\0';
++ *place++ = '\0';
++}
++
++/*
++ - regtail - set the next-pointer at the end of a node chain
++ */
++static void
++regtail(struct match_globals *g, char *p, char *val)
++{
++ register char *scan;
++ register char *temp;
++ register int offset;
++
++ if (p == &g->regdummy)
++ return;
++
++ /* Find last node. */
++ scan = p;
++ for (;;) {
++ temp = regnext(g, scan);
++ if (temp == NULL)
++ break;
++ scan = temp;
++ }
++
++ if (OP(scan) == BACK)
++ offset = scan - val;
++ else
++ offset = val - scan;
++ *(scan+1) = (offset>>8)&0377;
++ *(scan+2) = offset&0377;
++}
++
++/*
++ - regoptail - regtail on operand of first argument; nop if operandless
++ */
++static void
++regoptail(struct match_globals *g, char *p, char *val)
++{
++ /* "Operandless" and "op != BRANCH" are synonymous in practice. */
++ if (p == NULL || p == &g->regdummy || OP(p) != BRANCH)
++ return;
++ regtail(g, OPERAND(p), val);
++}
++
++/*
++ * regexec and friends
++ */
++
++
++/*
++ * Forwards.
++ */
++STATIC int regtry(struct match_globals *g, regexp *prog, char *string);
++STATIC int regmatch(struct match_globals *g, char *prog);
++STATIC int regrepeat(struct match_globals *g, char *p);
++
++#ifdef DEBUG
++int regnarrate = 0;
++void regdump();
++STATIC char *regprop(char *op);
++#endif
++
++/*
++ - regexec - match a regexp against a string
++ */
++int
++regexec(regexp *prog, char *string)
++{
++ register char *s;
++ struct match_globals g;
++
++ /* Be paranoid... */
++ if (prog == NULL || string == NULL) {
++ printk("<3>Regexp: NULL parameter\n");
++ return(0);
++ }
++
++ /* Check validity of program. */
++ if (UCHARAT(prog->program) != MAGIC) {
++ printk("<3>Regexp: corrupted program\n");
++ return(0);
++ }
++
++ /* If there is a "must appear" string, look for it. */
++ if (prog->regmust != NULL) {
++ s = string;
++ while ((s = strchr(s, prog->regmust[0])) != NULL) {
++ if (strncmp(s, prog->regmust, prog->regmlen) == 0)
++ break; /* Found it. */
++ s++;
++ }
++ if (s == NULL) /* Not present. */
++ return(0);
++ }
++
++ /* Mark beginning of line for ^ . */
++ g.regbol = string;
++
++ /* Simplest case: anchored match need be tried only once. */
++ if (prog->reganch)
++ return(regtry(&g, prog, string));
++
++ /* Messy cases: unanchored match. */
++ s = string;
++ if (prog->regstart != '\0')
++ /* We know what char it must start with. */
++ while ((s = strchr(s, prog->regstart)) != NULL) {
++ if (regtry(&g, prog, s))
++ return(1);
++ s++;
++ }
++ else
++ /* We don't -- general case. */
++ do {
++ if (regtry(&g, prog, s))
++ return(1);
++ } while (*s++ != '\0');
++
++ /* Failure. */
++ return(0);
++}
++
++/*
++ - regtry - try match at specific point
++ */
++static int /* 0 failure, 1 success */
++regtry(struct match_globals *g, regexp *prog, char *string)
++{
++ register int i;
++ register char **sp;
++ register char **ep;
++
++ g->reginput = string;
++ g->regstartp = prog->startp;
++ g->regendp = prog->endp;
++
++ sp = prog->startp;
++ ep = prog->endp;
++ for (i = NSUBEXP; i > 0; i--) {
++ *sp++ = NULL;
++ *ep++ = NULL;
++ }
++ if (regmatch(g, prog->program + 1)) {
++ prog->startp[0] = string;
++ prog->endp[0] = g->reginput;
++ return(1);
++ } else
++ return(0);
++}
++
++/*
++ - regmatch - main matching routine
++ *
++ * Conceptually the strategy is simple: check to see whether the current
++ * node matches, call self recursively to see whether the rest matches,
++ * and then act accordingly. In practice we make some effort to avoid
++ * recursion, in particular by going through "ordinary" nodes (that don't
++ * need to know whether the rest of the match failed) by a loop instead of
++ * by recursion.
++ */
++static int /* 0 failure, 1 success */
++regmatch(struct match_globals *g, char *prog)
++{
++ register char *scan = prog; /* Current node. */
++ char *next; /* Next node. */
++
++#ifdef DEBUG
++ if (scan != NULL && regnarrate)
++ fprintf(stderr, "%s(\n", regprop(scan));
++#endif
++ while (scan != NULL) {
++#ifdef DEBUG
++ if (regnarrate)
++ fprintf(stderr, "%s...\n", regprop(scan));
++#endif
++ next = regnext(g, scan);
++
++ switch (OP(scan)) {
++ case BOL:
++ if (g->reginput != g->regbol)
++ return(0);
++ break;
++ case EOL:
++ if (*g->reginput != '\0')
++ return(0);
++ break;
++ case ANY:
++ if (*g->reginput == '\0')
++ return(0);
++ g->reginput++;
++ break;
++ case EXACTLY: {
++ register int len;
++ register char *opnd;
++
++ opnd = OPERAND(scan);
++ /* Inline the first character, for speed. */
++ if (*opnd != *g->reginput)
++ return(0);
++ len = strlen(opnd);
++ if (len > 1 && strncmp(opnd, g->reginput, len) != 0)
++ return(0);
++ g->reginput += len;
++ }
++ break;
++ case ANYOF:
++ if (*g->reginput == '\0' || strchr(OPERAND(scan), *g->reginput) == NULL)
++ return(0);
++ g->reginput++;
++ break;
++ case ANYBUT:
++ if (*g->reginput == '\0' || strchr(OPERAND(scan), *g->reginput) != NULL)
++ return(0);
++ g->reginput++;
++ break;
++ case NOTHING:
++ case BACK:
++ break;
++ case OPEN+1:
++ case OPEN+2:
++ case OPEN+3:
++ case OPEN+4:
++ case OPEN+5:
++ case OPEN+6:
++ case OPEN+7:
++ case OPEN+8:
++ case OPEN+9: {
++ register int no;
++ register char *save;
++
++ no = OP(scan) - OPEN;
++ save = g->reginput;
++
++ if (regmatch(g, next)) {
++ /*
++ * Don't set startp if some later
++ * invocation of the same parentheses
++ * already has.
++ */
++ if (g->regstartp[no] == NULL)
++ g->regstartp[no] = save;
++ return(1);
++ } else
++ return(0);
++ }
++ break;
++ case CLOSE+1:
++ case CLOSE+2:
++ case CLOSE+3:
++ case CLOSE+4:
++ case CLOSE+5:
++ case CLOSE+6:
++ case CLOSE+7:
++ case CLOSE+8:
++ case CLOSE+9:
++ {
++ register int no;
++ register char *save;
++
++ no = OP(scan) - CLOSE;
++ save = g->reginput;
++
++ if (regmatch(g, next)) {
++ /*
++ * Don't set endp if some later
++ * invocation of the same parentheses
++ * already has.
++ */
++ if (g->regendp[no] == NULL)
++ g->regendp[no] = save;
++ return(1);
++ } else
++ return(0);
++ }
++ break;
++ case BRANCH: {
++ register char *save;
++
++ if (OP(next) != BRANCH) /* No choice. */
++ next = OPERAND(scan); /* Avoid recursion. */
++ else {
++ do {
++ save = g->reginput;
++ if (regmatch(g, OPERAND(scan)))
++ return(1);
++ g->reginput = save;
++ scan = regnext(g, scan);
++ } while (scan != NULL && OP(scan) == BRANCH);
++ return(0);
++ /* NOTREACHED */
++ }
++ }
++ break;
++ case STAR:
++ case PLUS: {
++ register char nextch;
++ register int no;
++ register char *save;
++ register int min;
++
++ /*
++ * Lookahead to avoid useless match attempts
++ * when we know what character comes next.
++ */
++ nextch = '\0';
++ if (OP(next) == EXACTLY)
++ nextch = *OPERAND(next);
++ min = (OP(scan) == STAR) ? 0 : 1;
++ save = g->reginput;
++ no = regrepeat(g, OPERAND(scan));
++ while (no >= min) {
++ /* If it could work, try it. */
++ if (nextch == '\0' || *g->reginput == nextch)
++ if (regmatch(g, next))
++ return(1);
++ /* Couldn't or didn't -- back up. */
++ no--;
++ g->reginput = save + no;
++ }
++ return(0);
++ }
++ break;
++ case END:
++ return(1); /* Success! */
++ break;
++ default:
++ printk("<3>Regexp: memory corruption\n");
++ return(0);
++ break;
++ }
++
++ scan = next;
++ }
++
++ /*
++ * We get here only if there's trouble -- normally "case END" is
++ * the terminating point.
++ */
++ printk("<3>Regexp: corrupted pointers\n");
++ return(0);
++}
++
++/*
++ - regrepeat - repeatedly match something simple, report how many
++ */
++static int
++regrepeat(struct match_globals *g, char *p)
++{
++ register int count = 0;
++ register char *scan;
++ register char *opnd;
++
++ scan = g->reginput;
++ opnd = OPERAND(p);
++ switch (OP(p)) {
++ case ANY:
++ count = strlen(scan);
++ scan += count;
++ break;
++ case EXACTLY:
++ while (*opnd == *scan) {
++ count++;
++ scan++;
++ }
++ break;
++ case ANYOF:
++ while (*scan != '\0' && strchr(opnd, *scan) != NULL) {
++ count++;
++ scan++;
++ }
++ break;
++ case ANYBUT:
++ while (*scan != '\0' && strchr(opnd, *scan) == NULL) {
++ count++;
++ scan++;
++ }
++ break;
++ default: /* Oh dear. Called inappropriately. */
++ printk("<3>Regexp: internal foulup\n");
++ count = 0; /* Best compromise. */
++ break;
++ }
++ g->reginput = scan;
++
++ return(count);
++}
++
++/*
++ - regnext - dig the "next" pointer out of a node
++ */
++static char*
++regnext(struct match_globals *g, char *p)
++{
++ register int offset;
++
++ if (p == &g->regdummy)
++ return(NULL);
++
++ offset = NEXT(p);
++ if (offset == 0)
++ return(NULL);
++
++ if (OP(p) == BACK)
++ return(p-offset);
++ else
++ return(p+offset);
++}
++
++#ifdef DEBUG
++
++STATIC char *regprop();
++
++/*
++ - regdump - dump a regexp onto stdout in vaguely comprehensible form
++ */
++void
++regdump(regexp *r)
++{
++ register char *s;
++ register char op = EXACTLY; /* Arbitrary non-END op. */
++ register char *next;
++ /* extern char *strchr(); */
++
++
++ s = r->program + 1;
++ while (op != END) { /* While that wasn't END last time... */
++ op = OP(s);
++ printf("%2d%s", s-r->program, regprop(s)); /* Where, what. */
++ next = regnext(s);
++ if (next == NULL) /* Next ptr. */
++ printf("(0)");
++ else
++ printf("(%d)", (s-r->program)+(next-s));
++ s += 3;
++ if (op == ANYOF || op == ANYBUT || op == EXACTLY) {
++ /* Literal string, where present. */
++ while (*s != '\0') {
++ putchar(*s);
++ s++;
++ }
++ s++;
++ }
++ putchar('\n');
++ }
++
++ /* Header fields of interest. */
++ if (r->regstart != '\0')
++ printf("start `%c' ", r->regstart);
++ if (r->reganch)
++ printf("anchored ");
++ if (r->regmust != NULL)
++ printf("must have \"%s\"", r->regmust);
++ printf("\n");
++}
++
++/*
++ - regprop - printable representation of opcode
++ */
++static char *
++regprop(char *op)
++{
++#define BUFLEN 50
++ register char *p;
++ static char buf[BUFLEN];
++
++ strcpy(buf, ":");
++
++ switch (OP(op)) {
++ case BOL:
++ p = "BOL";
++ break;
++ case EOL:
++ p = "EOL";
++ break;
++ case ANY:
++ p = "ANY";
++ break;
++ case ANYOF:
++ p = "ANYOF";
++ break;
++ case ANYBUT:
++ p = "ANYBUT";
++ break;
++ case BRANCH:
++ p = "BRANCH";
++ break;
++ case EXACTLY:
++ p = "EXACTLY";
++ break;
++ case NOTHING:
++ p = "NOTHING";
++ break;
++ case BACK:
++ p = "BACK";
++ break;
++ case END:
++ p = "END";
++ break;
++ case OPEN+1:
++ case OPEN+2:
++ case OPEN+3:
++ case OPEN+4:
++ case OPEN+5:
++ case OPEN+6:
++ case OPEN+7:
++ case OPEN+8:
++ case OPEN+9:
++ snprintf(buf+strlen(buf),BUFLEN-strlen(buf), "OPEN%d", OP(op)-OPEN);
++ p = NULL;
++ break;
++ case CLOSE+1:
++ case CLOSE+2:
++ case CLOSE+3:
++ case CLOSE+4:
++ case CLOSE+5:
++ case CLOSE+6:
++ case CLOSE+7:
++ case CLOSE+8:
++ case CLOSE+9:
++ snprintf(buf+strlen(buf),BUFLEN-strlen(buf), "CLOSE%d", OP(op)-CLOSE);
++ p = NULL;
++ break;
++ case STAR:
++ p = "STAR";
++ break;
++ case PLUS:
++ p = "PLUS";
++ break;
++ default:
++ printk("<3>Regexp: corrupted opcode\n");
++ break;
++ }
++ if (p != NULL)
++ strncat(buf, p, BUFLEN-strlen(buf));
++ return(buf);
++}
++#endif
++
++
+diff -Naur linux-3.14.22.org/net/netfilter/regexp/regexp.h linux-3.14.22/net/netfilter/regexp/regexp.h
+--- linux-3.14.22.org/net/netfilter/regexp/regexp.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-3.14.22/net/netfilter/regexp/regexp.h 2014-10-24 15:33:21.285274399 +0200
+@@ -0,0 +1,41 @@
++/*
++ * Definitions etc. for regexp(3) routines.
++ *
++ * Caveat: this is V8 regexp(3) [actually, a reimplementation thereof],
++ * not the System V one.
++ */
++
++#ifndef REGEXP_H
++#define REGEXP_H
++
++
++/*
++http://www.opensource.apple.com/darwinsource/10.3/expect-1/expect/expect.h ,
++which contains a version of this library, says:
++
++ *
++ * NSUBEXP must be at least 10, and no greater than 117 or the parser
++ * will not work properly.
++ *
++
++However, it looks rather like this library is limited to 10. If you think
++otherwise, let us know.
++*/
++
++#define NSUBEXP 10
++typedef struct regexp {
++ char *startp[NSUBEXP];
++ char *endp[NSUBEXP];
++ char regstart; /* Internal use only. */
++ char reganch; /* Internal use only. */
++ char *regmust; /* Internal use only. */
++ int regmlen; /* Internal use only. */
++ char program[1]; /* Unwarranted chumminess with compiler. */
++} regexp;
++
++regexp * regcomp(char *exp, int *patternsize);
++int regexec(regexp *prog, char *string);
++void regsub(regexp *prog, char *source, char *dest);
++void regerror(char *s);
++
++#endif
+diff -Naur linux-3.14.22.org/net/netfilter/regexp/regmagic.h linux-3.14.22/net/netfilter/regexp/regmagic.h
+--- linux-3.14.22.org/net/netfilter/regexp/regmagic.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-3.14.22/net/netfilter/regexp/regmagic.h 2014-10-24 15:33:21.285274399 +0200
+@@ -0,0 +1,5 @@
++/*
++ * The first byte of the regexp internal "program" is actually this magic
++ * number; the start node begins in the second byte.
++ */
++#define MAGIC 0234
+diff -Naur linux-3.14.22.org/net/netfilter/regexp/regsub.c linux-3.14.22/net/netfilter/regexp/regsub.c
+--- linux-3.14.22.org/net/netfilter/regexp/regsub.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-3.14.22/net/netfilter/regexp/regsub.c 2014-10-24 15:33:21.285274399 +0200
+@@ -0,0 +1,95 @@
++/*
++ * regsub
++ * @(#)regsub.c 1.3 of 2 April 86
++ *
++ * Copyright (c) 1986 by University of Toronto.
++ * Written by Henry Spencer. Not derived from licensed software.
++ *
++ * Permission is granted to anyone to use this software for any
++ * purpose on any computer system, and to redistribute it freely,
++ * subject to the following restrictions:
++ *
++ * 1. The author is not responsible for the consequences of use of
++ * this software, no matter how awful, even if they arise
++ * from defects in it.
++ *
++ * 2. The origin of this software must not be misrepresented, either
++ * by explicit claim or by omission.
++ *
++ * 3. Altered versions must be plainly marked as such, and must not
++ * be misrepresented as being the original software.
++ *
++ *
++ * This code was modified by Ethan Sommer to work within the kernel
++ * (it now uses kmalloc etc..)
++ *
++ */
++#include "regexp.h"
++#include "regmagic.h"
++#include <linux/string.h>
++
++
++#ifndef CHARBITS
++#define UCHARAT(p) ((int)*(unsigned char *)(p))
++#else
++#define UCHARAT(p) ((int)*(p)&CHARBITS)
++#endif
++
++#if 0
++//void regerror(char * s)
++//{
++// printk("regexp(3): %s", s);
++// /* NOTREACHED */
++//}
++#endif
++
++/*
++ - regsub - perform substitutions after a regexp match
++ */
++void
++regsub(regexp * prog, char * source, char * dest)
++{
++ register char *src;
++ register char *dst;
++ register char c;
++ register int no;
++ register int len;
++
++ /* Not necessary and gcc doesn't like it -MLS */
++ /*extern char *strncpy();*/
++
++ if (prog == NULL || source == NULL || dest == NULL) {
++ regerror("NULL parm to regsub");
++ return;
++ }
++ if (UCHARAT(prog->program) != MAGIC) {
++ regerror("damaged regexp fed to regsub");
++ return;
++ }
++
++ src = source;
++ dst = dest;
++ while ((c = *src++) != '\0') {
++ if (c == '&')
++ no = 0;
++ else if (c == '\\' && '0' <= *src && *src <= '9')
++ no = *src++ - '0';
++ else
++ no = -1;
++
++ if (no < 0) { /* Ordinary character. */
++ if (c == '\\' && (*src == '\\' || *src == '&'))
++ c = *src++;
++ *dst++ = c;
++ } else if (prog->startp[no] != NULL && prog->endp[no] != NULL) {
++ len = prog->endp[no] - prog->startp[no];
++ (void) strncpy(dst, prog->startp[no], len);
++ dst += len;
++ if (len != 0 && *(dst-1) == '\0') { /* strncpy hit NUL. */
++ regerror("damaged match string");
++ return;
++ }
++ }
++ }
++ *dst++ = '\0';
++}
+diff -Naur linux-3.14.22.org/net/netfilter/xt_layer7.c linux-3.14.22/net/netfilter/xt_layer7.c
+--- linux-3.14.22.org/net/netfilter/xt_layer7.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-3.14.22/net/netfilter/xt_layer7.c 2014-10-24 15:33:21.285274399 +0200
+@@ -0,0 +1,665 @@
++/*
++ Kernel module to match application layer (OSI layer 7) data in connections.
++
++ http://l7-filter.sf.net
++
++ (C) 2003-2009 Matthew Strait and Ethan Sommer.
++
++ This program is free software; you can redistribute it and/or
++ modify it under the terms of the GNU General Public License
++ as published by the Free Software Foundation; either version
++ 2 of the License, or (at your option) any later version.
++ http://www.gnu.org/licenses/gpl.txt
++
++ Based on ipt_string.c (C) 2000 Emmanuel Roger <winfield@freegates.be>,
++ xt_helper.c (C) 2002 Harald Welte and cls_layer7.c (C) 2003 Matthew Strait,
++ Ethan Sommer, Justin Levandoski.
++*/
++
++#include <linux/spinlock.h>
++#include <linux/version.h>
++#include <net/ip.h>
++#include <net/tcp.h>
++#include <linux/module.h>
++#include <linux/seq_file.h>
++#include <linux/skbuff.h>
++#include <linux/netfilter.h>
++#include <net/netfilter/nf_conntrack.h>
++#include <net/netfilter/nf_conntrack_core.h>
++#include <net/netfilter/nf_conntrack_extend.h>
++#include <net/netfilter/nf_conntrack_acct.h>
++#include <linux/netfilter/x_tables.h>
++#include <linux/netfilter/xt_layer7.h>
++#include <linux/ctype.h>
++#include <linux/proc_fs.h>
++
++#include "regexp/regexp.c"
++
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Matthew Strait <quadong@users.sf.net>, Ethan Sommer <sommere@users.sf.net>");
++MODULE_DESCRIPTION("iptables application layer match module");
++MODULE_ALIAS("ipt_layer7");
++MODULE_VERSION("2.21");
++
++static int maxdatalen = 2048; // this is the default
++module_param(maxdatalen, int, 0444);
++MODULE_PARM_DESC(maxdatalen, "maximum bytes of data looked at by l7-filter");
++#ifdef CONFIG_NETFILTER_XT_MATCH_LAYER7_DEBUG
++ #define DPRINTK(format,args...) printk(format,##args)
++#else
++ #define DPRINTK(format,args...)
++#endif
++
++/* Number of packets whose data we look at.
++This can be modified through /proc/net/layer7_numpackets */
++static int num_packets = 10;
++
++static struct pattern_cache {
++ char * regex_string;
++ regexp * pattern;
++ struct pattern_cache * next;
++} * first_pattern_cache = NULL;
++
++DEFINE_SPINLOCK(l7_lock);
++
++static int total_acct_packets(struct nf_conn *ct)
++{
++ struct nf_conn_counter *acct;
++
++ BUG_ON(ct == NULL);
++ acct = nf_conn_acct_find(ct);
++ if (!acct)
++ return 0;
++ return (atomic64_read(&acct[IP_CT_DIR_ORIGINAL].packets) + atomic64_read(&acct[IP_CT_DIR_REPLY].packets));
++}
++
++#ifdef CONFIG_IP_NF_MATCH_LAYER7_DEBUG
++/* Converts an unfriendly string into a friendly one by
++replacing unprintables with periods and all whitespace with " ". */
++static char * friendly_print(unsigned char * s)
++{
++ char * f = kmalloc(strlen(s) + 1, GFP_ATOMIC);
++ int i;
++
++ if(!f) {
++ if (net_ratelimit())
++ printk(KERN_ERR "layer7: out of memory in "
++ "friendly_print, bailing.\n");
++ return NULL;
++ }
++
++ for(i = 0; i < strlen(s); i++){
++ if(isprint(s[i]) && s[i] < 128) f[i] = s[i];
++ else if(isspace(s[i])) f[i] = ' ';
++ else f[i] = '.';
++ }
++ f[i] = '\0';
++ return f;
++}
++
++static char dec2hex(int i)
++{
++ switch (i) {
++ case 0 ... 9:
++ return (i + '0');
++ break;
++ case 10 ... 15:
++ return (i - 10 + 'a');
++ break;
++ default:
++ if (net_ratelimit())
++ printk("layer7: Problem in dec2hex\n");
++ return '\0';
++ }
++}
++
++static char * hex_print(unsigned char * s)
++{
++ char * g = kmalloc(strlen(s)*3 + 1, GFP_ATOMIC);
++ int i;
++
++ if(!g) {
++ if (net_ratelimit())
++ printk(KERN_ERR "layer7: out of memory in hex_print, "
++ "bailing.\n");
++ return NULL;
++ }
++
++ for(i = 0; i < strlen(s); i++) {
++ g[i*3 ] = dec2hex(s[i]/16);
++ g[i*3 + 1] = dec2hex(s[i]%16);
++ g[i*3 + 2] = ' ';
++ }
++ g[i*3] = '\0';
++
++ return g;
++}
++#endif // DEBUG
++
++/* Use instead of regcomp. As we expect to be seeing the same regexps over and
++over again, it make sense to cache the results. */
++static regexp * compile_and_cache(const char * regex_string,
++ const char * protocol)
++{
++ struct pattern_cache * node = first_pattern_cache;
++ struct pattern_cache * last_pattern_cache = first_pattern_cache;
++ struct pattern_cache * tmp;
++ unsigned int len;
++
++ while (node != NULL) {
++ if (!strcmp(node->regex_string, regex_string))
++ return node->pattern;
++
++ last_pattern_cache = node;/* points at the last non-NULL node */
++ node = node->next;
++ }
++
++ /* If we reach the end of the list, then we have not yet cached
++ the pattern for this regex. Let's do that now.
++ Be paranoid about running out of memory to avoid list corruption. */
++ tmp = kmalloc(sizeof(struct pattern_cache), GFP_ATOMIC);
++
++ if(!tmp) {
++ if (net_ratelimit())
++ printk(KERN_ERR "layer7: out of memory in "
++ "compile_and_cache, bailing.\n");
++ return NULL;
++ }
++
++ tmp->regex_string = kmalloc(strlen(regex_string) + 1, GFP_ATOMIC);
++ tmp->pattern = kmalloc(sizeof(struct regexp), GFP_ATOMIC);
++ tmp->next = NULL;
++
++ if(!tmp->regex_string || !tmp->pattern) {
++ if (net_ratelimit())
++ printk(KERN_ERR "layer7: out of memory in "
++ "compile_and_cache, bailing.\n");
++ kfree(tmp->regex_string);
++ kfree(tmp->pattern);
++ kfree(tmp);
++ return NULL;
++ }
++
++ /* Ok. The new node is all ready now. */
++ node = tmp;
++
++ if(first_pattern_cache == NULL) /* list is empty */
++ first_pattern_cache = node; /* make node the beginning */
++ else
++ last_pattern_cache->next = node; /* attach node to the end */
++
++ /* copy the string and compile the regex */
++ len = strlen(regex_string);
++ DPRINTK("About to compile this: \"%s\"\n", regex_string);
++ node->pattern = regcomp((char *)regex_string, &len);
++ if ( !node->pattern ) {
++ if (net_ratelimit())
++ printk(KERN_ERR "layer7: Error compiling regexp "
++ "\"%s\" (%s)\n",
++ regex_string, protocol);
++ /* pattern is now cached as NULL, so we won't try again. */
++ }
++
++ strcpy(node->regex_string, regex_string);
++ return node->pattern;
++}
++
++static int can_handle(const struct sk_buff *skb)
++{
++ struct iphdr iphdr_tmp;
++ struct iphdr *iphdr;
++ int offset;
++
++ if (!ip_hdr(skb))
++ return 0;
++
++ offset = ((uintptr_t)ip_hdr(skb)) - ((uintptr_t)skb->data);
++
++ iphdr = skb_header_pointer(skb, offset, sizeof(*iphdr), &iphdr_tmp);
++ if (!iphdr)
++ return 0;
++
++ if (iphdr->protocol == IPPROTO_TCP ||
++ iphdr->protocol == IPPROTO_UDP ||
++ iphdr->protocol == IPPROTO_ICMP)
++ return 1;
++
++ return 0;
++}
++
++static int app_data_offset(const struct sk_buff *skb)
++{
++ int offset;
++ struct iphdr iphdr_tmp;
++ struct iphdr *iphdr;
++ struct tcphdr tcphdr_tmp;
++ struct tcphdr *tcphdr;
++
++ if (!ip_hdr(skb))
++ return -1;
++
++ offset = ((uintptr_t)ip_hdr(skb)) - ((uintptr_t)skb->data);
++
++ iphdr = skb_header_pointer(skb, offset, sizeof(*iphdr), &iphdr_tmp);
++ if (!iphdr)
++ return -1;
++
++ offset += iphdr->ihl * 4;
++
++ if (iphdr->protocol == IPPROTO_TCP) {
++ tcphdr = skb_header_pointer(skb, offset, sizeof(*tcphdr),
++ &tcphdr_tmp);
++ if (!tcphdr)
++ return -1;
++
++ offset += tcphdr->doff * 4;
++
++ return offset;
++ }
++
++ if (iphdr->protocol == IPPROTO_UDP)
++ return offset + 8;
++
++ if (iphdr->protocol == IPPROTO_ICMP)
++ return offset + 8;
++
++ if (net_ratelimit())
++ pr_err(KERN_ERR "layer7: tried to handle unknown protocol!\n");
++
++ return offset + 8; /* something reasonable */
++}
++
++/* handles whether there's a match when we aren't appending data anymore */
++static int match_no_append(struct nf_conn * conntrack,
++ struct nf_conn * master_conntrack,
++ enum ip_conntrack_info ctinfo,
++ enum ip_conntrack_info master_ctinfo,
++ const struct xt_layer7_info * info)
++{
++ /* If we're in here, throw the app data away */
++ if(master_conntrack->layer7.app_data != NULL) {
++
++ #ifdef CONFIG_IP_NF_MATCH_LAYER7_DEBUG
++ if(!master_conntrack->layer7.app_proto) {
++ char * f =
++ friendly_print(master_conntrack->layer7.app_data);
++ char * g =
++ hex_print(master_conntrack->layer7.app_data);
++ DPRINTK("\nl7-filter gave up after %d bytes "
++ "(%d packets):\n%s\n",
++ strlen(f), total_acct_packets(master_conntrack), f);
++ kfree(f);
++ DPRINTK("In hex: %s\n", g);
++ kfree(g);
++ }
++ #endif
++
++ kfree(master_conntrack->layer7.app_data);
++ master_conntrack->layer7.app_data = NULL; /* don't free again */
++ }
++
++ if(master_conntrack->layer7.app_proto){
++ /* Here child connections set their .app_proto (for /proc) */
++ if(!conntrack->layer7.app_proto) {
++ conntrack->layer7.app_proto =
++ kmalloc(strlen(master_conntrack->layer7.app_proto)+1,
++ GFP_ATOMIC);
++ if(!conntrack->layer7.app_proto){
++ if (net_ratelimit())
++ printk(KERN_ERR "layer7: out of memory "
++ "in match_no_append, "
++ "bailing.\n");
++ return 1;
++ }
++ strcpy(conntrack->layer7.app_proto,
++ master_conntrack->layer7.app_proto);
++ }
++
++ return (!strcmp(master_conntrack->layer7.app_proto,
++ info->protocol));
++ }
++ else {
++ /* If not classified, set to "unknown" to distinguish from
++ connections that are still being tested. */
++ master_conntrack->layer7.app_proto =
++ kmalloc(strlen("unknown")+1, GFP_ATOMIC);
++ if(!master_conntrack->layer7.app_proto){
++ if (net_ratelimit())
++ printk(KERN_ERR "layer7: out of memory in "
++ "match_no_append, bailing.\n");
++ return 1;
++ }
++ strcpy(master_conntrack->layer7.app_proto, "unknown");
++ return 0;
++ }
++}
++
++/* add the new app data to the conntrack. Return number of bytes added. */
++static int add_datastr(char *target, int offset, char *app_data, int len)
++{
++ int length = 0, i;
++ if (!target) return 0;
++
++ /* Strip nulls. Make everything lower case (our regex lib doesn't
++ do case insensitivity). Add it to the end of the current data. */
++ for(i = 0; i < maxdatalen-offset-1 && i < len; i++) {
++ if(app_data[i] != '\0') {
++ /* the kernel version of tolower mungs 'upper ascii' */
++ target[length+offset] =
++ isascii(app_data[i])?
++ tolower(app_data[i]) : app_data[i];
++ length++;
++ }
++ }
++ target[length+offset] = '\0';
++
++ return length;
++}
++
++/* add the new app data to the buffer. Return number of bytes added. */
++static int add_data(char *target, int offset, const struct sk_buff *skb)
++{
++ int length, length_sum = 0;
++ int data_start = app_data_offset(skb);
++ int remaining = skb->len - data_start;
++ int to_copy;
++ uint8_t buf[512];
++ uint8_t *data;
++
++ while ((offset < maxdatalen - 1) && (remaining > 0)) {
++ to_copy = min_t(int, remaining, sizeof(buf));
++
++ data = skb_header_pointer(skb, data_start, to_copy, buf);
++ length = add_datastr(target, offset, data, to_copy);
++
++ remaining -= to_copy;
++ data_start += to_copy;
++ offset += length;
++ length_sum += length;
++ }
++
++ return length_sum;
++}
++
++/* add the new app data to the conntrack. Return number of bytes added. */
++static int add_data_conntrack(struct nf_conn *master_conntrack,
++ const struct sk_buff *skb)
++{
++ int length;
++
++ length = add_data(master_conntrack->layer7.app_data,
++ master_conntrack->layer7.app_data_len, skb);
++ master_conntrack->layer7.app_data_len += length;
++
++ return length;
++}
++
++/* taken from drivers/video/modedb.c */
++static int my_atoi(const char *s)
++{
++ int val = 0;
++
++ for (;; s++) {
++ switch (*s) {
++ case '0'...'9':
++ val = 10*val+(*s-'0');
++ break;
++ default:
++ return val;
++ }
++ }
++}
++
++static int layer7_numpackets_proc_show(struct seq_file *s, void *p) {
++ seq_printf(s, "%d\n", num_packets);
++
++ return 0;
++}
++
++static int layer7_numpackets_proc_open(struct inode *inode, struct file *file) {
++ return single_open(file, layer7_numpackets_proc_show, NULL);
++}
++
++/* Read in num_packets from userland */
++static ssize_t layer7_numpackets_write_proc(struct file* file, const char __user *buffer,
++ size_t count, loff_t *data) {
++ char value[1024];
++ int new_num_packets;
++
++ if (copy_from_user(&value, buffer, sizeof(value)))
++ return -EFAULT;
++
++ new_num_packets = my_atoi(value);
++
++ if ((new_num_packets < 1) || (new_num_packets > 99)) {
++ printk(KERN_WARNING "layer7: numpackets must be between 1 and 99\n");
++ return -EFAULT;
++ }
++
++ num_packets = new_num_packets;
++
++ return count;
++}
++
++static bool match(const struct sk_buff *skbin, struct xt_action_param *par)
++{
++ /* sidestep const without getting a compiler warning... */
++ struct sk_buff *skb = (struct sk_buff *)skbin;
++
++ const struct xt_layer7_info * info = par->matchinfo;
++
++ enum ip_conntrack_info master_ctinfo, ctinfo;
++ struct nf_conn *master_conntrack, *conntrack;
++ unsigned char *tmp_data;
++ unsigned int pattern_result;
++ regexp * comppattern;
++
++ /* Be paranoid/incompetent - lock the entire match function. */
++ spin_lock_bh(&l7_lock);
++
++ if (!can_handle(skbin)) {
++ DPRINTK("layer7: This is some protocol I can't handle.\n");
++ spin_unlock_bh(&l7_lock);
++ return info->invert;
++ }
++
++ /* Treat parent & all its children together as one connection, except
++ for the purpose of setting conntrack->layer7.app_proto in the actual
++ connection. This makes /proc/net/ip_conntrack more satisfying. */
++ conntrack = nf_ct_get(skbin, &ctinfo);
++ master_conntrack = nf_ct_get(skbin, &master_ctinfo);
++ if (!conntrack || !master_conntrack) {
++ DPRINTK("layer7: couldn't get conntrack.\n");
++ spin_unlock_bh(&l7_lock);
++ return info->invert;
++ }
++
++ /* Try to get a master conntrack (and its master etc) for FTP, etc. */
++ while (master_ct(master_conntrack) != NULL)
++ master_conntrack = master_ct(master_conntrack);
++
++ /* if we've classified it or seen too many packets */
++ if(!info->pkt && (total_acct_packets(master_conntrack) > num_packets ||
++ master_conntrack->layer7.app_proto)) {
++
++ pattern_result = match_no_append(conntrack, master_conntrack,
++ ctinfo, master_ctinfo, info);
++
++ /* skb->cb[0] == seen. Don't do things twice if there are
++ multiple l7 rules. I'm not sure that using cb for this purpose
++ is correct, even though it says "put your private variables
++ there". But it doesn't look like it is being used for anything
++ else in the skbs that make it here. */
++ skb->cb[0] = 1; /* marking it seen here's probably irrelevant */
++
++ spin_unlock_bh(&l7_lock);
++ return (pattern_result ^ info->invert);
++ }
++
++ /* the return value gets checked later, when we're ready to use it */
++ comppattern = compile_and_cache(info->pattern, info->protocol);
++
++ if (info->pkt) {
++ tmp_data = kmalloc(maxdatalen, GFP_ATOMIC);
++ if(!tmp_data){
++ if (net_ratelimit())
++ printk(KERN_ERR "layer7: out of memory in match, bailing.\n");
++ return info->invert;
++ }
++
++ tmp_data[0] = '\0';
++ add_data(tmp_data, 0, skbin);
++ pattern_result = ((comppattern && regexec(comppattern, tmp_data)) ? 1 : 0);
++
++ kfree(tmp_data);
++ tmp_data = NULL;
++ spin_unlock_bh(&l7_lock);
++
++ return (pattern_result ^ info->invert);
++ }
++
++ /* On the first packet of a connection, allocate space for app data */
++ if(total_acct_packets(master_conntrack) == 1 && !skb->cb[0] &&
++ !master_conntrack->layer7.app_data){
++ master_conntrack->layer7.app_data =
++ kmalloc(maxdatalen, GFP_ATOMIC);
++ if(!master_conntrack->layer7.app_data){
++ if (net_ratelimit())
++ printk(KERN_ERR "layer7: out of memory in "
++ "match, bailing.\n");
++ spin_unlock_bh(&l7_lock);
++ return info->invert;
++ }
++
++ master_conntrack->layer7.app_data[0] = '\0';
++ }
++
++ /* Can be here, but unallocated, if numpackets is increased near
++ the beginning of a connection */
++ if(master_conntrack->layer7.app_data == NULL){
++ spin_unlock_bh(&l7_lock);
++ return info->invert; /* unmatched */
++ }
++
++ if(!skb->cb[0]){
++ int newbytes;
++ newbytes = add_data_conntrack(master_conntrack, skb);
++
++ if(newbytes == 0) { /* didn't add any data */
++ skb->cb[0] = 1;
++ /* Didn't match before, not going to match now */
++ spin_unlock_bh(&l7_lock);
++ return info->invert;
++ }
++ }
++
++ /* If looking for "unknown", then never match. "Unknown" means that
++ we've given up; we're still trying with these packets. */
++ if(!strcmp(info->protocol, "unknown")) {
++ pattern_result = 0;
++ /* If looking for "unset", then always match. "Unset" means that we
++ haven't yet classified the connection. */
++ } else if(!strcmp(info->protocol, "unset")) {
++ pattern_result = 2;
++ DPRINTK("layer7: matched unset: not yet classified "
++ "(%d/%d packets)\n",
++ total_acct_packets(master_conntrack), num_packets);
++ /* If the regexp failed to compile, don't bother running it */
++ } else if(comppattern &&
++ regexec(comppattern, master_conntrack->layer7.app_data)){
++ DPRINTK("layer7: matched %s\n", info->protocol);
++ pattern_result = 1;
++ } else pattern_result = 0;
++
++ if(pattern_result == 1) {
++ master_conntrack->layer7.app_proto =
++ kmalloc(strlen(info->protocol)+1, GFP_ATOMIC);
++ if(!master_conntrack->layer7.app_proto){
++ if (net_ratelimit())
++ printk(KERN_ERR "layer7: out of memory in "
++ "match, bailing.\n");
++ spin_unlock_bh(&l7_lock);
++ return (pattern_result ^ info->invert);
++ }
++ strcpy(master_conntrack->layer7.app_proto, info->protocol);
++ } else if(pattern_result > 1) { /* cleanup from "unset" */
++ pattern_result = 1;
++ }
++
++ /* mark the packet seen */
++ skb->cb[0] = 1;
++
++ spin_unlock_bh(&l7_lock);
++ return (pattern_result ^ info->invert);
++}
++
++// load nf_conntrack_ipv4
++static int check(const struct xt_mtchk_param *par)
++{
++ if (nf_ct_l3proto_try_module_get(par->match->family) < 0) {
++ printk(KERN_WARNING "can't load conntrack support for "
++ "proto=%d\n", par->match->family);
++ return -EINVAL;
++ }
++ return 0;
++}
++
++
++static void destroy(const struct xt_mtdtor_param *par)
++{
++ nf_ct_l3proto_module_put(par->match->family);
++}
++
++static struct xt_match xt_layer7_match[] __read_mostly = {
++{
++ .name = "layer7",
++ .family = AF_INET,
++ .checkentry = check,
++ .match = match,
++ .destroy = destroy,
++ .matchsize = sizeof(struct xt_layer7_info),
++ .me = THIS_MODULE
++}
++};
++
++static const struct file_operations layer7_numpackets_proc_fops = {
++ .owner = THIS_MODULE,
++ .open = layer7_numpackets_proc_open,
++ .read = seq_read,
++ .llseek = seq_lseek,
++ .release = single_release,
++ .write = layer7_numpackets_write_proc,
++};
++
++static int __init xt_layer7_init(void)
++{
++ need_conntrack();
++
++ // Register proc interface
++ proc_create_data("layer7_numpackets", 0644,
++ init_net.proc_net, &layer7_numpackets_proc_fops, NULL);
++
++ if(maxdatalen < 1) {
++ printk(KERN_WARNING "layer7: maxdatalen can't be < 1, "
++ "using 1\n");
++ maxdatalen = 1;
++ }
++ /* This is not a hard limit. It's just here to prevent people from
++ bringing their slow machines to a grinding halt. */
++ else if(maxdatalen > 65536) {
++ printk(KERN_WARNING "layer7: maxdatalen can't be > 65536, "
++ "using 65536\n");
++ maxdatalen = 65536;
++ }
++ return xt_register_matches(xt_layer7_match,
++ ARRAY_SIZE(xt_layer7_match));
++}
++
++static void __exit xt_layer7_fini(void)
++{
++ remove_proc_entry("layer7_numpackets", init_net.proc_net);
++ xt_unregister_matches(xt_layer7_match, ARRAY_SIZE(xt_layer7_match));
++}
++
++module_init(xt_layer7_init);
++module_exit(xt_layer7_fini);
--- /dev/null
+diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig
+index dd12a1e..969cd15 100644
+--- a/drivers/media/dvb-frontends/Kconfig
++++ b/drivers/media/dvb-frontends/Kconfig
+@@ -726,6 +726,20 @@ config DVB_TDA665x
+ Currently supported tuners:
+ * Panasonic ENV57H12D5 (ET-50DT)
+
++config DVB_DVBSKY_M88DS3103
++ tristate "Montage M88DS3103 based(DVBSky)"
++ depends on DVB_CORE && I2C
++ default m if !MEDIA_SUBDRV_AUTOSELECT
++ help
++ A DVB-S/S2 tuner module. Say Y when you want to support this frontend.
++
++config DVB_M88DC2800
++ tristate "Montage M88DC2800 based"
++ depends on DVB_CORE && I2C
++ default m if !MEDIA_SUBDRV_AUTOSELECT
++ help
++ A DVB-C tuner module. Say Y when you want to support this frontend.
++
+ config DVB_IX2505V
+ tristate "Sharp IX2505V silicon tuner"
+ depends on DVB_CORE && I2C
+diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile
+index 0c75a6a..37c57f9 100644
+--- a/drivers/media/dvb-frontends/Makefile
++++ b/drivers/media/dvb-frontends/Makefile
+@@ -106,3 +106,5 @@ obj-$(CONFIG_DVB_RTL2832) += rtl2832.o
+ obj-$(CONFIG_DVB_M88RS2000) += m88rs2000.o
+ obj-$(CONFIG_DVB_AF9033) += af9033.o
+
++obj-$(CONFIG_DVB_DVBSKY_M88DS3103) += dvbsky_m88ds3103.o
++obj-$(CONFIG_DVB_M88DC2800) += m88dc2800.o
+diff --git a/drivers/media/dvb-frontends/dvbsky_m88ds3103.c b/drivers/media/dvb-frontends/dvbsky_m88ds3103.c
+new file mode 100644
+index 0000000..5ad1157
+--- /dev/null
++++ b/drivers/media/dvb-frontends/dvbsky_m88ds3103.c
+@@ -0,0 +1,1723 @@
++/*
++ Montage Technology M88DS3103/M88TS2022 - DVBS/S2 Satellite demod/tuner driver
++
++ Copyright (C) 2011 Max nibble<nibble.max@gmail.com>
++ Copyright (C) 2010 Montage Technology<www.montage-tech.com>
++ Copyright (C) 2009 Konstantin Dimitrov.
++
++ This program is free software; you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation; either version 2 of the License, or
++ (at your option) any later version.
++
++ This program is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with this program; if not, write to the Free Software
++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
++ */
++
++#include <linux/slab.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/moduleparam.h>
++#include <linux/init.h>
++#include <linux/firmware.h>
++
++#include "dvb_frontend.h"
++#include "dvbsky_m88ds3103.h"
++#include "dvbsky_m88ds3103_priv.h"
++
++static int debug;
++module_param(debug, int, 0644);
++MODULE_PARM_DESC(debug, "Activates frontend debugging (default:0)");
++
++#define dprintk(args...) \
++ do { \
++ if (debug) \
++ printk(KERN_INFO "m88ds3103: " args); \
++ } while (0)
++
++#define _AUTO_S2_
++
++/*demod register operations.*/
++static int m88ds3103_writereg(struct m88ds3103_state *state, int reg, int data)
++{
++ u8 buf[] = { reg, data };
++ struct i2c_msg msg = { .addr = state->config->demod_address,
++ .flags = 0, .buf = buf, .len = 2 };
++ int err;
++
++ if (debug > 1)
++ printk("m88ds3103: %s: write reg 0x%02x, value 0x%02x\n",
++ __func__, reg, data);
++
++ err = i2c_transfer(state->i2c, &msg, 1);
++ if (err != 1) {
++ printk(KERN_ERR "%s: writereg error(err == %i, reg == 0x%02x,"
++ " value == 0x%02x)\n", __func__, err, reg, data);
++ return -EREMOTEIO;
++ }
++ return 0;
++}
++
++static int m88ds3103_readreg(struct m88ds3103_state *state, u8 reg)
++{
++ int ret;
++ u8 b0[] = { reg };
++ u8 b1[] = { 0 };
++ struct i2c_msg msg[] = {
++ { .addr = state->config->demod_address, .flags = 0,
++ .buf = b0, .len = 1 },
++ { .addr = state->config->demod_address, .flags = I2C_M_RD,
++ .buf = b1, .len = 1 }
++ };
++ ret = i2c_transfer(state->i2c, msg, 2);
++
++ if (ret != 2) {
++ printk(KERN_ERR "%s: reg=0x%x (error=%d)\n",
++ __func__, reg, ret);
++ return ret;
++ }
++
++ if (debug > 1)
++ printk(KERN_INFO "m88ds3103: read reg 0x%02x, value 0x%02x\n",
++ reg, b1[0]);
++
++ return b1[0];
++}
++
++/*tuner register operations.*/
++static int m88ds3103_tuner_writereg(struct m88ds3103_state *state, int reg, int data)
++{
++ u8 buf[] = { reg, data };
++ struct i2c_msg msg = { .addr = 0x60,
++ .flags = 0, .buf = buf, .len = 2 };
++ int err;
++
++ m88ds3103_writereg(state, 0x03, 0x11);
++ err = i2c_transfer(state->i2c, &msg, 1);
++
++ if (err != 1) {
++ printk("%s: writereg error(err == %i, reg == 0x%02x,"
++ " value == 0x%02x)\n", __func__, err, reg, data);
++ return -EREMOTEIO;
++ }
++
++ return 0;
++}
++
++static int m88ds3103_tuner_readreg(struct m88ds3103_state *state, u8 reg)
++{
++ int ret;
++ u8 b0[] = { reg };
++ u8 b1[] = { 0 };
++ struct i2c_msg msg[] = {
++ { .addr = 0x60, .flags = 0,
++ .buf = b0, .len = 1 },
++ { .addr = 0x60, .flags = I2C_M_RD,
++ .buf = b1, .len = 1 }
++ };
++
++ m88ds3103_writereg(state, 0x03, (0x11 + state->config->tuner_readstops));
++ ret = i2c_transfer(state->i2c, msg, 2);
++
++ if (ret != 2) {
++ printk(KERN_ERR "%s: reg=0x%x(error=%d)\n", __func__, reg, ret);
++ return ret;
++ }
++
++ return b1[0];
++}
++
++/* Bulk demod I2C write, for firmware download. */
++static int m88ds3103_writeregN(struct m88ds3103_state *state, int reg,
++ const u8 *data, u16 len)
++{
++ int ret = -EREMOTEIO;
++ struct i2c_msg msg;
++ u8 *buf;
++
++ buf = kmalloc(len + 1, GFP_KERNEL);
++ if (buf == NULL) {
++ printk("Unable to kmalloc\n");
++ ret = -ENOMEM;
++ goto error;
++ }
++
++ *(buf) = reg;
++ memcpy(buf + 1, data, len);
++
++ msg.addr = state->config->demod_address;
++ msg.flags = 0;
++ msg.buf = buf;
++ msg.len = len + 1;
++
++ if (debug > 1)
++ printk(KERN_INFO "m88ds3103: %s: write regN 0x%02x, len = %d\n",
++ __func__, reg, len);
++
++ ret = i2c_transfer(state->i2c, &msg, 1);
++ if (ret != 1) {
++ printk(KERN_ERR "%s: writereg error(err == %i, reg == 0x%02x\n",
++ __func__, ret, reg);
++ ret = -EREMOTEIO;
++ }
++
++error:
++ kfree(buf);
++
++ return ret;
++}
++
++static int m88ds3103_load_firmware(struct dvb_frontend *fe)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++ const struct firmware *fw;
++ int i, ret = 0;
++
++ dprintk("%s()\n", __func__);
++
++ if (state->skip_fw_load)
++ return 0;
++ /* Load firmware */
++ /* request the firmware, this will block until someone uploads it */
++ if(state->demod_id == DS3000_ID){
++ printk(KERN_INFO "%s: Waiting for firmware upload (%s)...\n", __func__,
++ DS3000_DEFAULT_FIRMWARE);
++ ret = request_firmware(&fw, DS3000_DEFAULT_FIRMWARE,
++ state->i2c->dev.parent);
++ }else if(state->demod_id == DS3103_ID){
++ printk(KERN_INFO "%s: Waiting for firmware upload (%s)...\n", __func__,
++ DS3103_DEFAULT_FIRMWARE);
++ ret = request_firmware(&fw, DS3103_DEFAULT_FIRMWARE,
++ state->i2c->dev.parent);
++ }
++
++ printk(KERN_INFO "%s: Waiting for firmware upload(2)...\n", __func__);
++ if (ret) {
++ printk(KERN_ERR "%s: No firmware uploaded (timeout or file not "
++ "found?)\n", __func__);
++ return ret;
++ }
++
++ /* Make sure we don't recurse back through here during loading */
++ state->skip_fw_load = 1;
++
++ dprintk("Firmware is %zu bytes (%02x %02x .. %02x %02x)\n",
++ fw->size,
++ fw->data[0],
++ fw->data[1],
++ fw->data[fw->size - 2],
++ fw->data[fw->size - 1]);
++
++ /* stop internal mcu. */
++ m88ds3103_writereg(state, 0xb2, 0x01);
++ /* split firmware to download.*/
++ for(i = 0; i < FW_DOWN_LOOP; i++){
++ ret = m88ds3103_writeregN(state, 0xb0, &(fw->data[FW_DOWN_SIZE*i]), FW_DOWN_SIZE);
++ if(ret != 1) break;
++ }
++ /* start internal mcu. */
++ if(ret == 1)
++ m88ds3103_writereg(state, 0xb2, 0x00);
++
++ release_firmware(fw);
++
++ dprintk("%s: Firmware upload %s\n", __func__,
++ ret == 1 ? "complete" : "failed");
++
++ if(ret == 1) ret = 0;
++
++ /* Ensure firmware is always loaded if required */
++ state->skip_fw_load = 0;
++
++ return ret;
++}
++
++
++static int m88ds3103_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++ u8 data;
++
++ dprintk("%s(%d)\n", __func__, voltage);
++
++ dprintk("m88ds3103:pin_ctrl = (%02x)\n", state->config->pin_ctrl);
++
++ if(state->config->set_voltage)
++ state->config->set_voltage(fe, voltage);
++
++ data = m88ds3103_readreg(state, 0xa2);
++
++ if(state->config->pin_ctrl & 0x80){ /*If control pin is assigned.*/
++ data &= ~0x03; /* bit0 V/H, bit1 off/on */
++ if(state->config->pin_ctrl & 0x02)
++ data |= 0x02;
++
++ switch (voltage) {
++ case SEC_VOLTAGE_18:
++ if((state->config->pin_ctrl & 0x01) == 0)
++ data |= 0x01;
++ break;
++ case SEC_VOLTAGE_13:
++ if(state->config->pin_ctrl & 0x01)
++ data |= 0x01;
++ break;
++ case SEC_VOLTAGE_OFF:
++ if(state->config->pin_ctrl & 0x02)
++ data &= ~0x02;
++ else
++ data |= 0x02;
++ break;
++ }
++ }
++
++ m88ds3103_writereg(state, 0xa2, data);
++
++ return 0;
++}
++
++static int m88ds3103_read_status(struct dvb_frontend *fe, fe_status_t* status)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++ int lock = 0;
++
++ *status = 0;
++
++ switch (state->delivery_system){
++ case SYS_DVBS:
++ lock = m88ds3103_readreg(state, 0xd1);
++ dprintk("%s: SYS_DVBS status=%x.\n", __func__, lock);
++
++ if ((lock & 0x07) == 0x07){
++ /*if((m88ds3103_readreg(state, 0x0d) & 0x07) == 0x07)*/
++ *status = FE_HAS_SIGNAL | FE_HAS_CARRIER
++ | FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK;
++
++ }
++ break;
++ case SYS_DVBS2:
++ lock = m88ds3103_readreg(state, 0x0d);
++ dprintk("%s: SYS_DVBS2 status=%x.\n", __func__, lock);
++
++ if ((lock & 0x8f) == 0x8f)
++ *status = FE_HAS_SIGNAL | FE_HAS_CARRIER
++ | FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK;
++
++ break;
++ default:
++ break;
++ }
++
++ return 0;
++}
++
++static int m88ds3103_read_ber(struct dvb_frontend *fe, u32* ber)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++ u8 tmp1, tmp2, tmp3;
++ u32 ldpc_frame_cnt, pre_err_packags, code_rate_fac = 0;
++
++ dprintk("%s()\n", __func__);
++
++ switch (state->delivery_system) {
++ case SYS_DVBS:
++ m88ds3103_writereg(state, 0xf9, 0x04);
++ tmp3 = m88ds3103_readreg(state, 0xf8);
++ if ((tmp3&0x10) == 0){
++ tmp1 = m88ds3103_readreg(state, 0xf7);
++ tmp2 = m88ds3103_readreg(state, 0xf6);
++ tmp3 |= 0x10;
++ m88ds3103_writereg(state, 0xf8, tmp3);
++ state->preBer = (tmp1<<8) | tmp2;
++ }
++ break;
++ case SYS_DVBS2:
++ tmp1 = m88ds3103_readreg(state, 0x7e) & 0x0f;
++ switch(tmp1){
++ case 0: code_rate_fac = 16008 - 80; break;
++ case 1: code_rate_fac = 21408 - 80; break;
++ case 2: code_rate_fac = 25728 - 80; break;
++ case 3: code_rate_fac = 32208 - 80; break;
++ case 4: code_rate_fac = 38688 - 80; break;
++ case 5: code_rate_fac = 43040 - 80; break;
++ case 6: code_rate_fac = 48408 - 80; break;
++ case 7: code_rate_fac = 51648 - 80; break;
++ case 8: code_rate_fac = 53840 - 80; break;
++ case 9: code_rate_fac = 57472 - 80; break;
++ case 10: code_rate_fac = 58192 - 80; break;
++ }
++
++ tmp1 = m88ds3103_readreg(state, 0xd7) & 0xff;
++ tmp2 = m88ds3103_readreg(state, 0xd6) & 0xff;
++ tmp3 = m88ds3103_readreg(state, 0xd5) & 0xff;
++ ldpc_frame_cnt = (tmp1 << 16) | (tmp2 << 8) | tmp3;
++
++ tmp1 = m88ds3103_readreg(state, 0xf8) & 0xff;
++ tmp2 = m88ds3103_readreg(state, 0xf7) & 0xff;
++ pre_err_packags = tmp1<<8 | tmp2;
++
++ if (ldpc_frame_cnt > 1000){
++ m88ds3103_writereg(state, 0xd1, 0x01);
++ m88ds3103_writereg(state, 0xf9, 0x01);
++ m88ds3103_writereg(state, 0xf9, 0x00);
++ m88ds3103_writereg(state, 0xd1, 0x00);
++ state->preBer = pre_err_packags;
++ }
++ break;
++ default:
++ break;
++ }
++ *ber = state->preBer;
++
++ return 0;
++}
++
++static int m88ds3103_read_signal_strength(struct dvb_frontend *fe,
++ u16 *signal_strength)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++ u16 gain;
++ u8 gain1, gain2, gain3 = 0;
++
++ dprintk("%s()\n", __func__);
++
++ gain1 = m88ds3103_tuner_readreg(state, 0x3d) & 0x1f;
++ dprintk("%s: gain1 = 0x%02x \n", __func__, gain1);
++
++ if (gain1 > 15) gain1 = 15;
++ gain2 = m88ds3103_tuner_readreg(state, 0x21) & 0x1f;
++ dprintk("%s: gain2 = 0x%02x \n", __func__, gain2);
++
++ if(state->tuner_id == TS2022_ID){
++ gain3 = (m88ds3103_tuner_readreg(state, 0x66)>>3) & 0x07;
++ dprintk("%s: gain3 = 0x%02x \n", __func__, gain3);
++
++ if (gain2 > 16) gain2 = 16;
++ if (gain2 < 2) gain2 = 2;
++ if (gain3 > 6) gain3 = 6;
++ }else{
++ if (gain2 > 13) gain2 = 13;
++ gain3 = 0;
++ }
++
++ gain = gain1*23 + gain2*35 + gain3*29;
++ *signal_strength = 60000 - gain*55;
++
++ return 0;
++}
++
++
++static int m88ds3103_read_snr(struct dvb_frontend *fe, u16 *p_snr)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++ u8 val, npow1, npow2, spow1, cnt;
++ u16 tmp, snr;
++ u32 npow, spow, snr_total;
++ static const u16 mes_log10[] ={
++ 0, 3010, 4771, 6021, 6990, 7781, 8451, 9031, 9542, 10000,
++ 10414, 10792, 11139, 11461, 11761, 12041, 12304, 12553, 12788, 13010,
++ 13222, 13424, 13617, 13802, 13979, 14150, 14314, 14472, 14624, 14771,
++ 14914, 15052, 15185, 15315, 15441, 15563, 15682, 15798, 15911, 16021,
++ 16128, 16232, 16335, 16435, 16532, 16628, 16721, 16812, 16902, 16990,
++ 17076, 17160, 17243, 17324, 17404, 17482, 17559, 17634, 17709, 17782,
++ 17853, 17924, 17993, 18062, 18129, 18195, 18261, 18325, 18388, 18451,
++ 18513, 18573, 18633, 18692, 18751, 18808, 18865, 18921, 18976, 19031
++ };
++ static const u16 mes_loge[] ={
++ 0, 6931, 10986, 13863, 16094, 17918, 19459, 20794, 21972, 23026,
++ 23979, 24849, 25649, 26391, 27081, 27726, 28332, 28904, 29444, 29957,
++ 30445, 30910, 31355, 31781, 32189, 32581, 32958, 33322, 33673, 34012,
++ 34340, 34657,
++ };
++
++ dprintk("%s()\n", __func__);
++
++ snr = 0;
++
++ switch (state->delivery_system){
++ case SYS_DVBS:
++ cnt = 10; snr_total = 0;
++ while(cnt > 0){
++ val = m88ds3103_readreg(state, 0xff);
++ snr_total += val;
++ cnt--;
++ }
++ tmp = (u16)(snr_total/80);
++ if(tmp > 0){
++ if (tmp > 32) tmp = 32;
++ snr = (mes_loge[tmp - 1] * 100) / 45;
++ }else{
++ snr = 0;
++ }
++ break;
++ case SYS_DVBS2:
++ cnt = 10; npow = 0; spow = 0;
++ while(cnt >0){
++ npow1 = m88ds3103_readreg(state, 0x8c) & 0xff;
++ npow2 = m88ds3103_readreg(state, 0x8d) & 0xff;
++ npow += (((npow1 & 0x3f) + (u16)(npow2 << 6)) >> 2);
++
++ spow1 = m88ds3103_readreg(state, 0x8e) & 0xff;
++ spow += ((spow1 * spow1) >> 1);
++ cnt--;
++ }
++ npow /= 10; spow /= 10;
++ if(spow == 0){
++ snr = 0;
++ }else if(npow == 0){
++ snr = 19;
++ }else{
++ if(spow > npow){
++ tmp = (u16)(spow / npow);
++ if (tmp > 80) tmp = 80;
++ snr = mes_log10[tmp - 1]*3;
++ }else{
++ tmp = (u16)(npow / spow);
++ if (tmp > 80) tmp = 80;
++ snr = -(mes_log10[tmp - 1] / 1000);
++ }
++ }
++ break;
++ default:
++ break;
++ }
++ *p_snr = snr;
++
++ return 0;
++}
++
++
++static int m88ds3103_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++ u8 tmp1, tmp2, tmp3, data;
++
++ dprintk("%s()\n", __func__);
++
++ switch (state->delivery_system) {
++ case SYS_DVBS:
++ data = m88ds3103_readreg(state, 0xf8);
++ data |= 0x40;
++ m88ds3103_writereg(state, 0xf8, data);
++ tmp1 = m88ds3103_readreg(state, 0xf5);
++ tmp2 = m88ds3103_readreg(state, 0xf4);
++ *ucblocks = (tmp1 <<8) | tmp2;
++ data &= ~0x20;
++ m88ds3103_writereg(state, 0xf8, data);
++ data |= 0x20;
++ m88ds3103_writereg(state, 0xf8, data);
++ data &= ~0x40;
++ m88ds3103_writereg(state, 0xf8, data);
++ break;
++ case SYS_DVBS2:
++ tmp1 = m88ds3103_readreg(state, 0xda);
++ tmp2 = m88ds3103_readreg(state, 0xd9);
++ tmp3 = m88ds3103_readreg(state, 0xd8);
++ *ucblocks = (tmp1 <<16)|(tmp2 <<8)|tmp3;
++ data = m88ds3103_readreg(state, 0xd1);
++ data |= 0x01;
++ m88ds3103_writereg(state, 0xd1, data);
++ data &= ~0x01;
++ m88ds3103_writereg(state, 0xd1, data);
++ break;
++ default:
++ break;
++ }
++ return 0;
++}
++
++static int m88ds3103_set_tone(struct dvb_frontend *fe, fe_sec_tone_mode_t tone)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++ u8 data_a1, data_a2;
++
++ dprintk("%s(%d)\n", __func__, tone);
++ if ((tone != SEC_TONE_ON) && (tone != SEC_TONE_OFF)) {
++ printk(KERN_ERR "%s: Invalid, tone=%d\n", __func__, tone);
++ return -EINVAL;
++ }
++
++ data_a1 = m88ds3103_readreg(state, 0xa1);
++ data_a2 = m88ds3103_readreg(state, 0xa2);
++ if(state->demod_id == DS3103_ID)
++ data_a2 &= 0xdf; /* Normal mode */
++ switch (tone) {
++ case SEC_TONE_ON:
++ dprintk("%s: SEC_TONE_ON\n", __func__);
++ data_a1 |= 0x04;
++ data_a1 &= ~0x03;
++ data_a1 &= ~0x40;
++ data_a2 &= ~0xc0;
++ break;
++ case SEC_TONE_OFF:
++ dprintk("%s: SEC_TONE_OFF\n", __func__);
++ data_a2 &= ~0xc0;
++ data_a2 |= 0x80;
++ break;
++ }
++ m88ds3103_writereg(state, 0xa2, data_a2);
++ m88ds3103_writereg(state, 0xa1, data_a1);
++ return 0;
++}
++
++static int m88ds3103_send_diseqc_msg(struct dvb_frontend *fe,
++ struct dvb_diseqc_master_cmd *d)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++ int i, ret = 0;
++ u8 tmp, time_out;
++
++ /* Dump DiSEqC message */
++ if (debug) {
++ printk(KERN_INFO "m88ds3103: %s(", __func__);
++ for (i = 0 ; i < d->msg_len ;) {
++ printk(KERN_INFO "0x%02x", d->msg[i]);
++ if (++i < d->msg_len)
++ printk(KERN_INFO ", ");
++ }
++ }
++
++ tmp = m88ds3103_readreg(state, 0xa2);
++ tmp &= ~0xc0;
++ if(state->demod_id == DS3103_ID)
++ tmp &= ~0x20;
++ m88ds3103_writereg(state, 0xa2, tmp);
++
++ for (i = 0; i < d->msg_len; i ++)
++ m88ds3103_writereg(state, (0xa3+i), d->msg[i]);
++
++ tmp = m88ds3103_readreg(state, 0xa1);
++ tmp &= ~0x38;
++ tmp &= ~0x40;
++ tmp |= ((d->msg_len-1) << 3) | 0x07;
++ tmp &= ~0x80;
++ m88ds3103_writereg(state, 0xa1, tmp);
++ /* 1.5 * 9 * 8 = 108ms */
++ time_out = 150;
++ while (time_out > 0){
++ msleep(10);
++ time_out -= 10;
++ tmp = m88ds3103_readreg(state, 0xa1);
++ if ((tmp & 0x40) == 0)
++ break;
++ }
++ if (time_out == 0){
++ tmp = m88ds3103_readreg(state, 0xa1);
++ tmp &= ~0x80;
++ tmp |= 0x40;
++ m88ds3103_writereg(state, 0xa1, tmp);
++ ret = 1;
++ }
++ tmp = m88ds3103_readreg(state, 0xa2);
++ tmp &= ~0xc0;
++ tmp |= 0x80;
++ m88ds3103_writereg(state, 0xa2, tmp);
++ return ret;
++}
++
++
++static int m88ds3103_diseqc_send_burst(struct dvb_frontend *fe,
++ fe_sec_mini_cmd_t burst)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++ u8 val, time_out;
++
++ dprintk("%s()\n", __func__);
++
++ val = m88ds3103_readreg(state, 0xa2);
++ val &= ~0xc0;
++ if(state->demod_id == DS3103_ID)
++ val &= 0xdf; /* Normal mode */
++ m88ds3103_writereg(state, 0xa2, val);
++ /* DiSEqC burst */
++ if (burst == SEC_MINI_B)
++ m88ds3103_writereg(state, 0xa1, 0x01);
++ else
++ m88ds3103_writereg(state, 0xa1, 0x02);
++
++ msleep(13);
++
++ time_out = 5;
++ do{
++ val = m88ds3103_readreg(state, 0xa1);
++ if ((val & 0x40) == 0)
++ break;
++ msleep(1);
++ time_out --;
++ } while (time_out > 0);
++
++ val = m88ds3103_readreg(state, 0xa2);
++ val &= ~0xc0;
++ val |= 0x80;
++ m88ds3103_writereg(state, 0xa2, val);
++
++ return 0;
++}
++
++static void m88ds3103_release(struct dvb_frontend *fe)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++
++ dprintk("%s\n", __func__);
++ kfree(state);
++}
++
++static int m88ds3103_check_id(struct m88ds3103_state *state)
++{
++ int val_00, val_01;
++
++ /*check demod id*/
++ val_01 = m88ds3103_readreg(state, 0x01);
++ printk(KERN_INFO "DS3000 chip version: %x attached.\n", val_01);
++
++ if(val_01 == 0xD0)
++ state->demod_id = DS3103_ID;
++ else if(val_01 == 0xC0)
++ state->demod_id = DS3000_ID;
++ else
++ state->demod_id = UNKNOW_ID;
++
++ /*check tuner id*/
++ val_00 = m88ds3103_tuner_readreg(state, 0x00);
++ printk(KERN_INFO "TS202x chip version[1]: %x attached.\n", val_00);
++ val_00 &= 0x03;
++ if(val_00 == 0)
++ {
++ m88ds3103_tuner_writereg(state, 0x00, 0x01);
++ msleep(3);
++ }
++ m88ds3103_tuner_writereg(state, 0x00, 0x03);
++ msleep(5);
++
++ val_00 = m88ds3103_tuner_readreg(state, 0x00);
++ printk(KERN_INFO "TS202x chip version[2]: %x attached.\n", val_00);
++ val_00 &= 0xff;
++ if((val_00 == 0x01) || (val_00 == 0x41) || (val_00 == 0x81))
++ state->tuner_id = TS2020_ID;
++ else if(((val_00 & 0xc0)== 0xc0) || (val_00 == 0x83))
++ state->tuner_id = TS2022_ID;
++ else
++ state->tuner_id = UNKNOW_ID;
++
++ return state->demod_id;
++}
++
++static struct dvb_frontend_ops m88ds3103_ops;
++static int m88ds3103_initilaze(struct dvb_frontend *fe);
++
++struct dvb_frontend *dvbsky_m88ds3103_attach(const struct dvbsky_m88ds3103_config *config,
++ struct i2c_adapter *i2c)
++{
++ struct m88ds3103_state *state = NULL;
++
++ dprintk("%s\n", __func__);
++
++ /* allocate memory for the internal state */
++ state = kzalloc(sizeof(struct m88ds3103_state), GFP_KERNEL);
++ if (state == NULL) {
++ printk(KERN_ERR "Unable to kmalloc\n");
++ goto error2;
++ }
++
++ state->config = config;
++ state->i2c = i2c;
++ state->preBer = 0xffff;
++ state->delivery_system = SYS_DVBS; /*Default to DVB-S.*/
++
++ /* check demod id */
++ if(m88ds3103_check_id(state) == UNKNOW_ID){
++ printk(KERN_ERR "Unable to find Montage chip\n");
++ goto error3;
++ }
++
++ memcpy(&state->frontend.ops, &m88ds3103_ops,
++ sizeof(struct dvb_frontend_ops));
++ state->frontend.demodulator_priv = state;
++
++ m88ds3103_initilaze(&state->frontend);
++
++ return &state->frontend;
++
++error3:
++ kfree(state);
++error2:
++ return NULL;
++}
++EXPORT_SYMBOL(dvbsky_m88ds3103_attach);
++
++static int m88ds3103_set_carrier_offset(struct dvb_frontend *fe,
++ s32 carrier_offset_khz)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++ s32 tmp;
++
++ tmp = carrier_offset_khz;
++ tmp *= 65536;
++
++ tmp = (2*tmp + MT_FE_MCLK_KHZ) / (2*MT_FE_MCLK_KHZ);
++
++ if (tmp < 0)
++ tmp += 65536;
++
++ m88ds3103_writereg(state, 0x5f, tmp >> 8);
++ m88ds3103_writereg(state, 0x5e, tmp & 0xff);
++
++ return 0;
++}
++
++static int m88ds3103_set_symrate(struct dvb_frontend *fe)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
++ u16 value;
++
++ value = (((c->symbol_rate / 1000) << 15) + (MT_FE_MCLK_KHZ / 4)) / (MT_FE_MCLK_KHZ / 2);
++ m88ds3103_writereg(state, 0x61, value & 0x00ff);
++ m88ds3103_writereg(state, 0x62, (value & 0xff00) >> 8);
++
++ return 0;
++}
++
++static int m88ds3103_set_CCI(struct dvb_frontend *fe)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++ u8 tmp;
++
++ tmp = m88ds3103_readreg(state, 0x56);
++ tmp &= ~0x01;
++ m88ds3103_writereg(state, 0x56, tmp);
++
++ tmp = m88ds3103_readreg(state, 0x76);
++ tmp &= ~0x80;
++ m88ds3103_writereg(state, 0x76, tmp);
++
++ return 0;
++}
++
++static int m88ds3103_init_reg(struct m88ds3103_state *state, const u8 *p_reg_tab, u32 size)
++{
++ u32 i;
++
++ for(i = 0; i < size; i+=2)
++ m88ds3103_writereg(state, p_reg_tab[i], p_reg_tab[i+1]);
++
++ return 0;
++}
++
++static int m88ds3103_get_locked_sym_rate(struct m88ds3103_state *state, u32 *sym_rate_KSs)
++{
++ u16 tmp;
++ u32 sym_rate_tmp;
++ u8 val_0x6d, val_0x6e;
++
++ val_0x6d = m88ds3103_readreg(state, 0x6d);
++ val_0x6e = m88ds3103_readreg(state, 0x6e);
++
++ tmp = (u16)((val_0x6e<<8) | val_0x6d);
++
++ sym_rate_tmp = (u32)(tmp * MT_FE_MCLK_KHZ);
++ sym_rate_tmp = (u32)(sym_rate_tmp / (1<<16));
++ *sym_rate_KSs = sym_rate_tmp;
++
++ return 0;
++}
++
++static int m88ds3103_get_channel_info(struct m88ds3103_state *state, u8 *p_mode, u8 *p_coderate)
++{
++ u8 tmp, val_0x7E;
++
++ if(state->delivery_system == SYS_DVBS2){
++ val_0x7E = m88ds3103_readreg(state, 0x7e);
++ tmp = (u8)((val_0x7E&0xC0) >> 6);
++ *p_mode = tmp;
++ tmp = (u8)(val_0x7E & 0x0f);
++ *p_coderate = tmp;
++ } else {
++ *p_mode = 0;
++ tmp = m88ds3103_readreg(state, 0xe6);
++ tmp = (u8)(tmp >> 5);
++ *p_coderate = tmp;
++ }
++
++ return 0;
++}
++
++static int m88ds3103_set_clock_ratio(struct m88ds3103_state *state)
++{
++ u8 val, mod_fac, tmp1, tmp2;
++ u32 input_datarate, locked_sym_rate_KSs;
++ u32 MClk_KHz = 96000;
++ u8 mod_mode, code_rate, divid_ratio = 0;
++
++ locked_sym_rate_KSs = 0;
++ m88ds3103_get_locked_sym_rate(state, &locked_sym_rate_KSs);
++ if(locked_sym_rate_KSs == 0)
++ return 0;
++
++ m88ds3103_get_channel_info(state, &mod_mode, &code_rate);
++
++ if (state->delivery_system == SYS_DVBS2)
++ {
++ switch(mod_mode) {
++ case 1: mod_fac = 3; break;
++ case 2: mod_fac = 4; break;
++ case 3: mod_fac = 5; break;
++ default: mod_fac = 2; break;
++ }
++
++ switch(code_rate) {
++ case 0: input_datarate = locked_sym_rate_KSs*mod_fac/8/4; break;
++ case 1: input_datarate = locked_sym_rate_KSs*mod_fac/8/3; break;
++ case 2: input_datarate = locked_sym_rate_KSs*mod_fac*2/8/5; break;
++ case 3: input_datarate = locked_sym_rate_KSs*mod_fac/8/2; break;
++ case 4: input_datarate = locked_sym_rate_KSs*mod_fac*3/8/5; break;
++ case 5: input_datarate = locked_sym_rate_KSs*mod_fac*2/8/3; break;
++ case 6: input_datarate = locked_sym_rate_KSs*mod_fac*3/8/4; break;
++ case 7: input_datarate = locked_sym_rate_KSs*mod_fac*4/8/5; break;
++ case 8: input_datarate = locked_sym_rate_KSs*mod_fac*5/8/6; break;
++ case 9: input_datarate = locked_sym_rate_KSs*mod_fac*8/8/9; break;
++ case 10: input_datarate = locked_sym_rate_KSs*mod_fac*9/8/10; break;
++ default: input_datarate = locked_sym_rate_KSs*mod_fac*2/8/3; break;
++ }
++
++ if(state->demod_id == DS3000_ID)
++ input_datarate = input_datarate * 115 / 100;
++
++ if(input_datarate < 4800) {tmp1 = 15;tmp2 = 15;} //4.8MHz TS clock
++ else if(input_datarate < 4966) {tmp1 = 14;tmp2 = 15;} //4.966MHz TS clock
++ else if(input_datarate < 5143) {tmp1 = 14;tmp2 = 14;} //5.143MHz TS clock
++ else if(input_datarate < 5333) {tmp1 = 13;tmp2 = 14;} //5.333MHz TS clock
++ else if(input_datarate < 5538) {tmp1 = 13;tmp2 = 13;} //5.538MHz TS clock
++ else if(input_datarate < 5760) {tmp1 = 12;tmp2 = 13;} //5.76MHz TS clock allan 0809
++ else if(input_datarate < 6000) {tmp1 = 12;tmp2 = 12;} //6MHz TS clock
++ else if(input_datarate < 6260) {tmp1 = 11;tmp2 = 12;} //6.26MHz TS clock
++ else if(input_datarate < 6545) {tmp1 = 11;tmp2 = 11;} //6.545MHz TS clock
++ else if(input_datarate < 6857) {tmp1 = 10;tmp2 = 11;} //6.857MHz TS clock
++ else if(input_datarate < 7200) {tmp1 = 10;tmp2 = 10;} //7.2MHz TS clock
++ else if(input_datarate < 7578) {tmp1 = 9;tmp2 = 10;} //7.578MHz TS clock
++ else if(input_datarate < 8000) {tmp1 = 9;tmp2 = 9;} //8MHz TS clock
++ else if(input_datarate < 8470) {tmp1 = 8;tmp2 = 9;} //8.47MHz TS clock
++ else if(input_datarate < 9000) {tmp1 = 8;tmp2 = 8;} //9MHz TS clock
++ else if(input_datarate < 9600) {tmp1 = 7;tmp2 = 8;} //9.6MHz TS clock
++ else if(input_datarate < 10285) {tmp1 = 7;tmp2 = 7;} //10.285MHz TS clock
++ else if(input_datarate < 12000) {tmp1 = 6;tmp2 = 6;} //12MHz TS clock
++ else if(input_datarate < 14400) {tmp1 = 5;tmp2 = 5;} //14.4MHz TS clock
++ else if(input_datarate < 18000) {tmp1 = 4;tmp2 = 4;} //18MHz TS clock
++ else {tmp1 = 3;tmp2 = 3;} //24MHz TS clock
++
++ if(state->demod_id == DS3000_ID) {
++ val = (u8)((tmp1<<4) + tmp2);
++ m88ds3103_writereg(state, 0xfe, val);
++ } else {
++ tmp1 = m88ds3103_readreg(state, 0x22);
++ tmp2 = m88ds3103_readreg(state, 0x24);
++
++ tmp1 >>= 6;
++ tmp1 &= 0x03;
++ tmp2 >>= 6;
++ tmp2 &= 0x03;
++
++ if((tmp1 == 0x00) && (tmp2 == 0x01))
++ MClk_KHz = 144000;
++ else if((tmp1 == 0x00) && (tmp2 == 0x03))
++ MClk_KHz = 72000;
++ else if((tmp1 == 0x01) && (tmp2 == 0x01))
++ MClk_KHz = 115200;
++ else if((tmp1 == 0x02) && (tmp2 == 0x01))
++ MClk_KHz = 96000;
++ else if((tmp1 == 0x03) && (tmp2 == 0x00))
++ MClk_KHz = 192000;
++ else
++ return 0;
++
++ if(input_datarate < 5200) /*Max. 2011-12-23 11:55*/
++ input_datarate = 5200;
++
++ if(input_datarate != 0)
++ divid_ratio = (u8)(MClk_KHz / input_datarate);
++ else
++ divid_ratio = 0xFF;
++
++ if(divid_ratio > 128)
++ divid_ratio = 128;
++
++ if(divid_ratio < 2)
++ divid_ratio = 2;
++
++ tmp1 = (u8)(divid_ratio / 2);
++ tmp2 = (u8)(divid_ratio / 2);
++
++ if((divid_ratio % 2) != 0)
++ tmp2 += 1;
++
++ tmp1 -= 1;
++ tmp2 -= 1;
++
++ tmp1 &= 0x3f;
++ tmp2 &= 0x3f;
++
++ val = m88ds3103_readreg(state, 0xfe);
++ val &= 0xF0;
++ val |= (tmp2 >> 2) & 0x0f;
++ m88ds3103_writereg(state, 0xfe, val);
++
++ val = (u8)((tmp2 & 0x03) << 6);
++ val |= tmp1;
++ m88ds3103_writereg(state, 0xea, val);
++ }
++ } else {
++ mod_fac = 2;
++
++ switch(code_rate) {
++ case 4: input_datarate = locked_sym_rate_KSs*mod_fac/2/8; break;
++ case 3: input_datarate = locked_sym_rate_KSs*mod_fac*2/3/8; break;
++ case 2: input_datarate = locked_sym_rate_KSs*mod_fac*3/4/8; break;
++ case 1: input_datarate = locked_sym_rate_KSs*mod_fac*5/6/8; break;
++ case 0: input_datarate = locked_sym_rate_KSs*mod_fac*7/8/8; break;
++ default: input_datarate = locked_sym_rate_KSs*mod_fac*3/4/8; break;
++ }
++
++ if(state->demod_id == DS3000_ID)
++ input_datarate = input_datarate * 115 / 100;
++
++ if(input_datarate < 6857) {tmp1 = 7;tmp2 = 7;} //6.857MHz TS clock
++ else if(input_datarate < 7384) {tmp1 = 6;tmp2 = 7;} //7.384MHz TS clock
++ else if(input_datarate < 8000) {tmp1 = 6;tmp2 = 6;} //8MHz TS clock
++ else if(input_datarate < 8727) {tmp1 = 5;tmp2 = 6;} //8.727MHz TS clock
++ else if(input_datarate < 9600) {tmp1 = 5;tmp2 = 5;} //9.6MHz TS clock
++ else if(input_datarate < 10666) {tmp1 = 4;tmp2 = 5;} //10.666MHz TS clock
++ else if(input_datarate < 12000) {tmp1 = 4;tmp2 = 4;} //12MHz TS clock
++ else if(input_datarate < 13714) {tmp1 = 3;tmp2 = 4;} //13.714MHz TS clock
++ else if(input_datarate < 16000) {tmp1 = 3;tmp2 = 3;} //16MHz TS clock
++ else if(input_datarate < 19200) {tmp1 = 2;tmp2 = 3;} //19.2MHz TS clock
++ else {tmp1 = 2;tmp2 = 2;} //24MHz TS clock
++
++ if(state->demod_id == DS3000_ID) {
++ val = m88ds3103_readreg(state, 0xfe);
++ val &= 0xc0;
++ val |= ((u8)((tmp1<<3) + tmp2));
++ m88ds3103_writereg(state, 0xfe, val);
++ } else {
++ if(input_datarate < 5200) /*Max. 2011-12-23 11:55*/
++ input_datarate = 5200;
++
++ if(input_datarate != 0)
++ divid_ratio = (u8)(MClk_KHz / input_datarate);
++ else
++ divid_ratio = 0xFF;
++
++ if(divid_ratio > 128)
++ divid_ratio = 128;
++
++ if(divid_ratio < 2)
++ divid_ratio = 2;
++
++ tmp1 = (u8)(divid_ratio / 2);
++ tmp2 = (u8)(divid_ratio / 2);
++
++ if((divid_ratio % 2) != 0)
++ tmp2 += 1;
++
++ tmp1 -= 1;
++ tmp2 -= 1;
++
++ tmp1 &= 0x3f;
++ tmp2 &= 0x3f;
++
++ val = m88ds3103_readreg(state, 0xfe);
++ val &= 0xF0;
++ val |= (tmp2 >> 2) & 0x0f;
++ m88ds3103_writereg(state, 0xfe, val);
++
++ val = (u8)((tmp2 & 0x03) << 6);
++ val |= tmp1;
++ m88ds3103_writereg(state, 0xea, val);
++ }
++ }
++ return 0;
++}
++
++static int m88ds3103_demod_connect(struct dvb_frontend *fe, s32 carrier_offset_khz)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
++ u16 value;
++ u8 val1,val2,data;
++
++ dprintk("connect delivery system = %d\n", state->delivery_system);
++
++ /* ds3000 global reset */
++ m88ds3103_writereg(state, 0x07, 0x80);
++ m88ds3103_writereg(state, 0x07, 0x00);
++ /* ds3000 build-in uC reset */
++ m88ds3103_writereg(state, 0xb2, 0x01);
++ /* ds3000 software reset */
++ m88ds3103_writereg(state, 0x00, 0x01);
++
++ switch (state->delivery_system) {
++ case SYS_DVBS:
++ /* initialise the demod in DVB-S mode */
++ if(state->demod_id == DS3000_ID){
++ m88ds3103_init_reg(state, ds3000_dvbs_init_tab, sizeof(ds3000_dvbs_init_tab));
++
++ value = m88ds3103_readreg(state, 0xfe);
++ value &= 0xc0;
++ value |= 0x1b;
++ m88ds3103_writereg(state, 0xfe, value);
++
++ if(state->config->ci_mode)
++ val1 = 0x80;
++ else if(state->config->ts_mode)
++ val1 = 0x60;
++ else
++ val1 = 0x20;
++ m88ds3103_writereg(state, 0xfd, val1);
++
++ }else if(state->demod_id == DS3103_ID){
++ m88ds3103_init_reg(state, ds3103_dvbs_init_tab, sizeof(ds3103_dvbs_init_tab));
++
++ /* set ts clock */
++ if(state->config->ci_mode == 2){
++ val1 = 6; val2 = 6;
++ }else if(state->config->ts_mode == 0) {
++ val1 = 3; val2 = 3;
++ }else{
++ val1 = 0; val2 = 0;
++ }
++ val1 -= 1; val2 -= 1;
++ val1 &= 0x3f; val2 &= 0x3f;
++ data = m88ds3103_readreg(state, 0xfe);
++ data &= 0xf0;
++ data |= (val2 >> 2) & 0x0f;
++ m88ds3103_writereg(state, 0xfe, data);
++ data = (val2 & 0x03) << 6;
++ data |= val1;
++ m88ds3103_writereg(state, 0xea, data);
++
++ m88ds3103_writereg(state, 0x4d, 0xfd & m88ds3103_readreg(state, 0x4d));
++ m88ds3103_writereg(state, 0x30, 0xef & m88ds3103_readreg(state, 0x30));
++
++ /* set master clock */
++ val1 = m88ds3103_readreg(state, 0x22);
++ val2 = m88ds3103_readreg(state, 0x24);
++
++ val1 &= 0x3f;
++ val2 &= 0x3f;
++ val1 |= 0x80;
++ val2 |= 0x40;
++
++ m88ds3103_writereg(state, 0x22, val1);
++ m88ds3103_writereg(state, 0x24, val2);
++
++ if(state->config->ci_mode){
++ if(state->config->ci_mode == 2)
++ val1 = 0x43;
++ else
++ val1 = 0x03;
++ }
++ else if(state->config->ts_mode)
++ val1 = 0x06;
++ else
++ val1 = 0x42;
++ m88ds3103_writereg(state, 0xfd, val1);
++ }
++ break;
++ case SYS_DVBS2:
++ /* initialise the demod in DVB-S2 mode */
++ if(state->demod_id == DS3000_ID){
++ m88ds3103_init_reg(state, ds3000_dvbs2_init_tab, sizeof(ds3000_dvbs2_init_tab));
++
++ if (c->symbol_rate >= 30000000)
++ m88ds3103_writereg(state, 0xfe, 0x54);
++ else
++ m88ds3103_writereg(state, 0xfe, 0x98);
++
++ }else if(state->demod_id == DS3103_ID){
++ m88ds3103_init_reg(state, ds3103_dvbs2_init_tab, sizeof(ds3103_dvbs2_init_tab));
++
++ /* set ts clock */
++ if(state->config->ci_mode == 2){
++ val1 = 6; val2 = 6;
++ }else if(state->config->ts_mode == 0){
++ val1 = 5; val2 = 4;
++ }else{
++ val1 = 0; val2 = 0;
++ }
++ val1 -= 1; val2 -= 1;
++ val1 &= 0x3f; val2 &= 0x3f;
++ data = m88ds3103_readreg(state, 0xfe);
++ data &= 0xf0;
++ data |= (val2 >> 2) & 0x0f;
++ m88ds3103_writereg(state, 0xfe, data);
++ data = (val2 & 0x03) << 6;
++ data |= val1;
++ m88ds3103_writereg(state, 0xea, data);
++
++ m88ds3103_writereg(state, 0x4d, 0xfd & m88ds3103_readreg(state, 0x4d));
++ m88ds3103_writereg(state, 0x30, 0xef & m88ds3103_readreg(state, 0x30));
++
++ /* set master clock */
++ val1 = m88ds3103_readreg(state, 0x22);
++ val2 = m88ds3103_readreg(state, 0x24);
++
++ val1 &= 0x3f;
++ val2 &= 0x3f;
++ if((state->config->ci_mode == 2) || (state->config->ts_mode == 1)){
++ val1 |= 0x80;
++ val2 |= 0x40;
++ }else{
++ if (c->symbol_rate >= 28000000){
++ val1 |= 0xc0;
++ }else if (c->symbol_rate >= 18000000){
++ val2 |= 0x40;
++ }else{
++ val1 |= 0x80;
++ val2 |= 0x40;
++ }
++ }
++ m88ds3103_writereg(state, 0x22, val1);
++ m88ds3103_writereg(state, 0x24, val2);
++ }
++
++ if(state->config->ci_mode){
++ if(state->config->ci_mode == 2)
++ val1 = 0x43;
++ else
++ val1 = 0x03;
++ }
++ else if(state->config->ts_mode)
++ val1 = 0x06;
++ else
++ val1 = 0x42;
++ m88ds3103_writereg(state, 0xfd, val1);
++
++ break;
++ default:
++ return 1;
++ }
++ /* disable 27MHz clock output */
++ m88ds3103_writereg(state, 0x29, 0x80);
++ /* enable ac coupling */
++ m88ds3103_writereg(state, 0x25, 0x8a);
++
++ if ((c->symbol_rate / 1000) <= 3000){
++ m88ds3103_writereg(state, 0xc3, 0x08); /* 8 * 32 * 100 / 64 = 400*/
++ m88ds3103_writereg(state, 0xc8, 0x20);
++ m88ds3103_writereg(state, 0xc4, 0x08); /* 8 * 0 * 100 / 128 = 0*/
++ m88ds3103_writereg(state, 0xc7, 0x00);
++ }else if((c->symbol_rate / 1000) <= 10000){
++ m88ds3103_writereg(state, 0xc3, 0x08); /* 8 * 16 * 100 / 64 = 200*/
++ m88ds3103_writereg(state, 0xc8, 0x10);
++ m88ds3103_writereg(state, 0xc4, 0x08); /* 8 * 0 * 100 / 128 = 0*/
++ m88ds3103_writereg(state, 0xc7, 0x00);
++ }else{
++ m88ds3103_writereg(state, 0xc3, 0x08); /* 8 * 6 * 100 / 64 = 75*/
++ m88ds3103_writereg(state, 0xc8, 0x06);
++ m88ds3103_writereg(state, 0xc4, 0x08); /* 8 * 0 * 100 / 128 = 0*/
++ m88ds3103_writereg(state, 0xc7, 0x00);
++ }
++
++ m88ds3103_set_symrate(fe);
++
++ m88ds3103_set_CCI(fe);
++
++ m88ds3103_set_carrier_offset(fe, carrier_offset_khz);
++
++ /* ds3000 out of software reset */
++ m88ds3103_writereg(state, 0x00, 0x00);
++ /* start ds3000 build-in uC */
++ m88ds3103_writereg(state, 0xb2, 0x00);
++
++ return 0;
++}
++
++static int m88ds3103_set_frontend(struct dvb_frontend *fe)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
++
++ int i;
++ fe_status_t status;
++ u8 lpf_mxdiv, mlpf_max, mlpf_min, nlpf, div4, capCode, changePLL;
++ s32 offset_khz, lpf_offset_KHz;
++ u16 value, ndiv, N, lpf_coeff;
++ u32 f3db, gdiv28, realFreq;
++ u8 RFgain;
++
++ dprintk("%s() ", __func__);
++ dprintk("c frequency = %d\n", c->frequency);
++ dprintk("symbol rate = %d\n", c->symbol_rate);
++ dprintk("delivery system = %d\n", c->delivery_system);
++
++ realFreq = c->frequency;
++ lpf_offset_KHz = 0;
++ if(c->symbol_rate < 5000000){
++ lpf_offset_KHz = FREQ_OFFSET_AT_SMALL_SYM_RATE_KHz;
++ realFreq += FREQ_OFFSET_AT_SMALL_SYM_RATE_KHz;
++ }
++
++ if (state->config->set_ts_params)
++ state->config->set_ts_params(fe, 0);
++
++ div4 = 0;
++ RFgain = 0;
++ if(state->tuner_id == TS2022_ID){
++ m88ds3103_tuner_writereg(state, 0x10, 0x0a);
++ m88ds3103_tuner_writereg(state, 0x11, 0x40);
++ if (realFreq < 1103000) {
++ m88ds3103_tuner_writereg(state, 0x10, 0x1b);
++ div4 = 1;
++ ndiv = (realFreq * (6 + 8) * 4)/MT_FE_CRYSTAL_KHZ;
++ }else {
++ ndiv = (realFreq * (6 + 8) * 2)/MT_FE_CRYSTAL_KHZ;
++ }
++ ndiv = ndiv + ndiv%2;
++ if(ndiv < 4095)
++ N = ndiv - 1024;
++ else if (ndiv < 6143)
++ N = ndiv + 1024;
++ else
++ N = ndiv + 3072;
++
++ m88ds3103_tuner_writereg(state, 0x01, (N & 0x3f00) >> 8);
++ }else{
++ m88ds3103_tuner_writereg(state, 0x10, 0x00);
++ if (realFreq < 1146000){
++ m88ds3103_tuner_writereg(state, 0x10, 0x11);
++ div4 = 1;
++ ndiv = (realFreq * (6 + 8) * 4) / MT_FE_CRYSTAL_KHZ;
++ }else{
++ m88ds3103_tuner_writereg(state, 0x10, 0x01);
++ ndiv = (realFreq * (6 + 8) * 2) / MT_FE_CRYSTAL_KHZ;
++ }
++ ndiv = ndiv + ndiv%2;
++ N = ndiv - 1024;
++ m88ds3103_tuner_writereg(state, 0x01, (N>>8)&0x0f);
++ }
++ /* set pll */
++ m88ds3103_tuner_writereg(state, 0x02, N & 0x00ff);
++ m88ds3103_tuner_writereg(state, 0x03, 0x06);
++ m88ds3103_tuner_writereg(state, 0x51, 0x0f);
++ m88ds3103_tuner_writereg(state, 0x51, 0x1f);
++ m88ds3103_tuner_writereg(state, 0x50, 0x10);
++ m88ds3103_tuner_writereg(state, 0x50, 0x00);
++
++ if(state->tuner_id == TS2022_ID){
++ if(( realFreq >= 1650000 ) && (realFreq <= 1850000)){
++ msleep(5);
++ value = m88ds3103_tuner_readreg(state, 0x14);
++ value &= 0x7f;
++ if(value < 64){
++ m88ds3103_tuner_writereg(state, 0x10, 0x82);
++ m88ds3103_tuner_writereg(state, 0x11, 0x6f);
++
++ m88ds3103_tuner_writereg(state, 0x51, 0x0f);
++ m88ds3103_tuner_writereg(state, 0x51, 0x1f);
++ m88ds3103_tuner_writereg(state, 0x50, 0x10);
++ m88ds3103_tuner_writereg(state, 0x50, 0x00);
++ }
++ }
++ msleep(5);
++ value = m88ds3103_tuner_readreg(state, 0x14);
++ value &= 0x1f;
++
++ if(value > 19){
++ value = m88ds3103_tuner_readreg(state, 0x10);
++ value &= 0x1d;
++ m88ds3103_tuner_writereg(state, 0x10, value);
++ }
++ }else{
++ msleep(5);
++ value = m88ds3103_tuner_readreg(state, 0x66);
++ changePLL = (((value & 0x80) >> 7) != div4);
++
++ if(changePLL){
++ m88ds3103_tuner_writereg(state, 0x10, 0x11);
++ div4 = 1;
++ ndiv = (realFreq * (6 + 8) * 4)/MT_FE_CRYSTAL_KHZ;
++ ndiv = ndiv + ndiv%2;
++ N = ndiv - 1024;
++
++ m88ds3103_tuner_writereg(state, 0x01, (N>>8) & 0x0f);
++ m88ds3103_tuner_writereg(state, 0x02, N & 0xff);
++
++ m88ds3103_tuner_writereg(state, 0x51, 0x0f);
++ m88ds3103_tuner_writereg(state, 0x51, 0x1f);
++ m88ds3103_tuner_writereg(state, 0x50, 0x10);
++ m88ds3103_tuner_writereg(state, 0x50, 0x00);
++ }
++ }
++ /*set the RF gain*/
++ if(state->tuner_id == TS2020_ID)
++ m88ds3103_tuner_writereg(state, 0x60, 0x79);
++
++ m88ds3103_tuner_writereg(state, 0x51, 0x17);
++ m88ds3103_tuner_writereg(state, 0x51, 0x1f);
++ m88ds3103_tuner_writereg(state, 0x50, 0x08);
++ m88ds3103_tuner_writereg(state, 0x50, 0x00);
++ msleep(5);
++
++ if(state->tuner_id == TS2020_ID){
++ RFgain = m88ds3103_tuner_readreg(state, 0x3d);
++ RFgain &= 0x0f;
++ if(RFgain < 15){
++ if(RFgain < 4)
++ RFgain = 0;
++ else
++ RFgain = RFgain -3;
++ value = ((RFgain << 3) | 0x01) & 0x79;
++ m88ds3103_tuner_writereg(state, 0x60, value);
++ m88ds3103_tuner_writereg(state, 0x51, 0x17);
++ m88ds3103_tuner_writereg(state, 0x51, 0x1f);
++ m88ds3103_tuner_writereg(state, 0x50, 0x08);
++ m88ds3103_tuner_writereg(state, 0x50, 0x00);
++ }
++ }
++
++ /* set the LPF */
++ if(state->tuner_id == TS2022_ID){
++ m88ds3103_tuner_writereg(state, 0x25, 0x00);
++ m88ds3103_tuner_writereg(state, 0x27, 0x70);
++ m88ds3103_tuner_writereg(state, 0x41, 0x09);
++ m88ds3103_tuner_writereg(state, 0x08, 0x0b);
++ }
++
++ f3db = ((c->symbol_rate / 1000) *135) / 200 + 2000;
++ f3db += lpf_offset_KHz;
++ if (f3db < 7000)
++ f3db = 7000;
++ if (f3db > 40000)
++ f3db = 40000;
++
++ gdiv28 = (MT_FE_CRYSTAL_KHZ / 1000 * 1694 + 500) / 1000;
++ m88ds3103_tuner_writereg(state, 0x04, gdiv28 & 0xff);
++ m88ds3103_tuner_writereg(state, 0x51, 0x1b);
++ m88ds3103_tuner_writereg(state, 0x51, 0x1f);
++ m88ds3103_tuner_writereg(state, 0x50, 0x04);
++ m88ds3103_tuner_writereg(state, 0x50, 0x00);
++ msleep(5);
++
++ value = m88ds3103_tuner_readreg(state, 0x26);
++ capCode = value & 0x3f;
++ if(state->tuner_id == TS2022_ID){
++ m88ds3103_tuner_writereg(state, 0x41, 0x0d);
++
++ m88ds3103_tuner_writereg(state, 0x51, 0x1b);
++ m88ds3103_tuner_writereg(state, 0x51, 0x1f);
++ m88ds3103_tuner_writereg(state, 0x50, 0x04);
++ m88ds3103_tuner_writereg(state, 0x50, 0x00);
++
++ msleep(2);
++
++ value = m88ds3103_tuner_readreg(state, 0x26);
++ value &= 0x3f;
++ value = (capCode + value) / 2;
++ }
++ else
++ value = capCode;
++
++ gdiv28 = gdiv28 * 207 / (value * 2 + 151);
++ mlpf_max = gdiv28 * 135 / 100;
++ mlpf_min = gdiv28 * 78 / 100;
++ if (mlpf_max > 63)
++ mlpf_max = 63;
++
++ if(state->tuner_id == TS2022_ID)
++ lpf_coeff = 3200;
++ else
++ lpf_coeff = 2766;
++
++ nlpf = (f3db * gdiv28 * 2 / lpf_coeff / (MT_FE_CRYSTAL_KHZ / 1000) + 1) / 2 ;
++ if (nlpf > 23) nlpf = 23;
++ if (nlpf < 1) nlpf = 1;
++
++ lpf_mxdiv = (nlpf * (MT_FE_CRYSTAL_KHZ / 1000) * lpf_coeff * 2 / f3db + 1) / 2;
++
++ if (lpf_mxdiv < mlpf_min){
++ nlpf++;
++ lpf_mxdiv = (nlpf * (MT_FE_CRYSTAL_KHZ / 1000) * lpf_coeff * 2 / f3db + 1) / 2;
++ }
++
++ if (lpf_mxdiv > mlpf_max)
++ lpf_mxdiv = mlpf_max;
++
++ m88ds3103_tuner_writereg(state, 0x04, lpf_mxdiv);
++ m88ds3103_tuner_writereg(state, 0x06, nlpf);
++ m88ds3103_tuner_writereg(state, 0x51, 0x1b);
++ m88ds3103_tuner_writereg(state, 0x51, 0x1f);
++ m88ds3103_tuner_writereg(state, 0x50, 0x04);
++ m88ds3103_tuner_writereg(state, 0x50, 0x00);
++ msleep(5);
++
++ if(state->tuner_id == TS2022_ID){
++ msleep(2);
++ value = m88ds3103_tuner_readreg(state, 0x26);
++ capCode = value & 0x3f;
++
++ m88ds3103_tuner_writereg(state, 0x41, 0x09);
++
++ m88ds3103_tuner_writereg(state, 0x51, 0x1b);
++ m88ds3103_tuner_writereg(state, 0x51, 0x1f);
++ m88ds3103_tuner_writereg(state, 0x50, 0x04);
++ m88ds3103_tuner_writereg(state, 0x50, 0x00);
++
++ msleep(2);
++ value = m88ds3103_tuner_readreg(state, 0x26);
++ value &= 0x3f;
++ value = (capCode + value) / 2;
++
++ value = value | 0x80;
++ m88ds3103_tuner_writereg(state, 0x25, value);
++ m88ds3103_tuner_writereg(state, 0x27, 0x30);
++
++ m88ds3103_tuner_writereg(state, 0x08, 0x09);
++ }
++
++ /* Set the BB gain */
++ m88ds3103_tuner_writereg(state, 0x51, 0x1e);
++ m88ds3103_tuner_writereg(state, 0x51, 0x1f);
++ m88ds3103_tuner_writereg(state, 0x50, 0x01);
++ m88ds3103_tuner_writereg(state, 0x50, 0x00);
++ if(state->tuner_id == TS2020_ID){
++ if(RFgain == 15){
++ msleep(40);
++ value = m88ds3103_tuner_readreg(state, 0x21);
++ value &= 0x0f;
++ if(value < 3){
++ m88ds3103_tuner_writereg(state, 0x60, 0x61);
++ m88ds3103_tuner_writereg(state, 0x51, 0x17);
++ m88ds3103_tuner_writereg(state, 0x51, 0x1f);
++ m88ds3103_tuner_writereg(state, 0x50, 0x08);
++ m88ds3103_tuner_writereg(state, 0x50, 0x00);
++ }
++ }
++ }
++ msleep(60);
++
++ offset_khz = (ndiv) * MT_FE_CRYSTAL_KHZ
++ / (6 + 8) / (div4 + 1) / 2 - realFreq;
++
++ m88ds3103_demod_connect(fe, offset_khz+lpf_offset_KHz);
++
++ for (i = 0; i < 30 ; i++) {
++ m88ds3103_read_status(fe, &status);
++ if (status & FE_HAS_LOCK){
++ break;
++ }
++ msleep(20);
++ }
++
++#ifdef _AUTO_S2_
++ if((status & FE_HAS_LOCK) == 0){
++ state->delivery_system = (state->delivery_system == SYS_DVBS) ? SYS_DVBS2 : SYS_DVBS;
++ m88ds3103_demod_connect(fe, offset_khz);
++
++ for (i = 0; i < 30 ; i++) {
++ m88ds3103_read_status(fe, &status);
++ if (status & FE_HAS_LOCK){
++ break;
++ }
++ msleep(20);
++ }
++ }
++#else
++ state->delivery_system = c->delivery_system;
++#endif
++ if (status & FE_HAS_LOCK){
++ if(state->config->ci_mode == 2)
++ m88ds3103_set_clock_ratio(state);
++ if(state->config->start_ctrl){
++ if(state->first_lock == 0){
++ state->config->start_ctrl(fe);
++ state->first_lock = 1;
++ }
++ }
++ }
++
++ return 0;
++}
++
++static int m88ds3103_tune(struct dvb_frontend *fe,
++ bool re_tune,
++ unsigned int mode_flags,
++ unsigned int *delay,
++ fe_status_t *status)
++{
++ *delay = HZ / 5;
++
++ dprintk("%s() ", __func__);
++ dprintk("re_tune = %d\n", re_tune);
++
++ if (re_tune) {
++ int ret = m88ds3103_set_frontend(fe);
++ if (ret)
++ return ret;
++ }
++
++ return m88ds3103_read_status(fe, status);
++}
++
++static enum dvbfe_algo m88ds3103_get_algo(struct dvb_frontend *fe)
++{
++ return DVBFE_ALGO_HW;
++}
++
++ /*
++ * Power config will reset and load initial firmware if required
++ */
++static int m88ds3103_initilaze(struct dvb_frontend *fe)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++ int ret;
++
++ dprintk("%s()\n", __func__);
++ /* hard reset */
++ m88ds3103_writereg(state, 0x07, 0x80);
++ m88ds3103_writereg(state, 0x07, 0x00);
++ msleep(1);
++
++ m88ds3103_writereg(state, 0x08, 0x01 | m88ds3103_readreg(state, 0x08));
++ msleep(1);
++
++ if(state->tuner_id == TS2020_ID){
++ /* TS2020 init */
++ m88ds3103_tuner_writereg(state, 0x42, 0x73);
++ msleep(2);
++ m88ds3103_tuner_writereg(state, 0x05, 0x01);
++ m88ds3103_tuner_writereg(state, 0x62, 0xb5);
++ m88ds3103_tuner_writereg(state, 0x07, 0x02);
++ m88ds3103_tuner_writereg(state, 0x08, 0x01);
++ }
++ else if(state->tuner_id == TS2022_ID){
++ /* TS2022 init */
++ m88ds3103_tuner_writereg(state, 0x62, 0x6c);
++ msleep(2);
++ m88ds3103_tuner_writereg(state, 0x42, 0x6c);
++ msleep(2);
++ m88ds3103_tuner_writereg(state, 0x7d, 0x9d);
++ m88ds3103_tuner_writereg(state, 0x7c, 0x9a);
++ m88ds3103_tuner_writereg(state, 0x7a, 0x76);
++
++ m88ds3103_tuner_writereg(state, 0x3b, 0x01);
++ m88ds3103_tuner_writereg(state, 0x63, 0x88);
++
++ m88ds3103_tuner_writereg(state, 0x61, 0x85);
++ m88ds3103_tuner_writereg(state, 0x22, 0x30);
++ m88ds3103_tuner_writereg(state, 0x30, 0x40);
++ m88ds3103_tuner_writereg(state, 0x20, 0x23);
++ m88ds3103_tuner_writereg(state, 0x24, 0x02);
++ m88ds3103_tuner_writereg(state, 0x12, 0xa0);
++ }
++
++ if(state->demod_id == DS3103_ID){
++ m88ds3103_writereg(state, 0x07, 0xe0);
++ m88ds3103_writereg(state, 0x07, 0x00);
++ msleep(1);
++ }
++ m88ds3103_writereg(state, 0xb2, 0x01);
++
++ /* Load the firmware if required */
++ ret = m88ds3103_load_firmware(fe);
++ if (ret != 0){
++ printk(KERN_ERR "%s: Unable initialize firmware\n", __func__);
++ return ret;
++ }
++ if(state->demod_id == DS3103_ID){
++ m88ds3103_writereg(state, 0x4d, 0xfd & m88ds3103_readreg(state, 0x4d));
++ m88ds3103_writereg(state, 0x30, 0xef & m88ds3103_readreg(state, 0x30));
++ }
++
++ return 0;
++}
++
++/*
++ * Initialise or wake up device
++ */
++static int m88ds3103_initfe(struct dvb_frontend *fe)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++ u8 val;
++
++ dprintk("%s()\n", __func__);
++
++ /* 1st step to wake up demod */
++ m88ds3103_writereg(state, 0x08, 0x01 | m88ds3103_readreg(state, 0x08));
++ m88ds3103_writereg(state, 0x04, 0xfe & m88ds3103_readreg(state, 0x04));
++ m88ds3103_writereg(state, 0x23, 0xef & m88ds3103_readreg(state, 0x23));
++
++ /* 2nd step to wake up tuner */
++ val = m88ds3103_tuner_readreg(state, 0x00) & 0xff;
++ if((val & 0x01) == 0){
++ m88ds3103_tuner_writereg(state, 0x00, 0x01);
++ msleep(50);
++ }
++ m88ds3103_tuner_writereg(state, 0x00, 0x03);
++ msleep(50);
++
++ return 0;
++}
++
++/* Put device to sleep */
++static int m88ds3103_sleep(struct dvb_frontend *fe)
++{
++ struct m88ds3103_state *state = fe->demodulator_priv;
++
++ dprintk("%s()\n", __func__);
++
++ /* 1st step to sleep tuner */
++ m88ds3103_tuner_writereg(state, 0x00, 0x00);
++
++ /* 2nd step to sleep demod */
++ m88ds3103_writereg(state, 0x08, 0xfe & m88ds3103_readreg(state, 0x08));
++ m88ds3103_writereg(state, 0x04, 0x01 | m88ds3103_readreg(state, 0x04));
++ m88ds3103_writereg(state, 0x23, 0x10 | m88ds3103_readreg(state, 0x23));
++
++
++ return 0;
++}
++
++static struct dvb_frontend_ops m88ds3103_ops = {
++ .delsys = { SYS_DVBS, SYS_DVBS2},
++ .info = {
++ .name = "Montage DS3103/TS2022",
++ .type = FE_QPSK,
++ .frequency_min = 950000,
++ .frequency_max = 2150000,
++ .frequency_stepsize = 1011, /* kHz for QPSK frontends */
++ .frequency_tolerance = 5000,
++ .symbol_rate_min = 1000000,
++ .symbol_rate_max = 45000000,
++ .caps = FE_CAN_INVERSION_AUTO |
++ FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
++ FE_CAN_FEC_4_5 | FE_CAN_FEC_5_6 | FE_CAN_FEC_6_7 |
++ FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |
++ FE_CAN_2G_MODULATION |
++ FE_CAN_QPSK | FE_CAN_RECOVER
++ },
++
++ .release = m88ds3103_release,
++
++ .init = m88ds3103_initfe,
++ .sleep = m88ds3103_sleep,
++ .read_status = m88ds3103_read_status,
++ .read_ber = m88ds3103_read_ber,
++ .read_signal_strength = m88ds3103_read_signal_strength,
++ .read_snr = m88ds3103_read_snr,
++ .read_ucblocks = m88ds3103_read_ucblocks,
++ .set_tone = m88ds3103_set_tone,
++ .set_voltage = m88ds3103_set_voltage,
++ .diseqc_send_master_cmd = m88ds3103_send_diseqc_msg,
++ .diseqc_send_burst = m88ds3103_diseqc_send_burst,
++ .get_frontend_algo = m88ds3103_get_algo,
++ .tune = m88ds3103_tune,
++ .set_frontend = m88ds3103_set_frontend,
++};
++
++MODULE_DESCRIPTION("DVB Frontend module for Montage DS3103/TS2022 hardware");
++MODULE_AUTHOR("Max nibble");
++MODULE_LICENSE("GPL");
+diff --git a/drivers/media/dvb-frontends/dvbsky_m88ds3103.h b/drivers/media/dvb-frontends/dvbsky_m88ds3103.h
+new file mode 100644
+index 0000000..e2358d6
+--- /dev/null
++++ b/drivers/media/dvb-frontends/dvbsky_m88ds3103.h
+@@ -0,0 +1,54 @@
++/*
++ Montage Technology M88DS3103/M88TS2022 - DVBS/S2 Satellite demod/tuner driver
++
++ This program is free software; you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation; either version 2 of the License, or
++ (at your option) any later version.
++
++ This program is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with this program; if not, write to the Free Software
++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
++ */
++
++#ifndef DVBSKY_M88DS3103_H
++#define DVBSKY_M88DS3103_H
++
++#include <linux/kconfig.h>
++#include <linux/dvb/frontend.h>
++
++struct dvbsky_m88ds3103_config {
++ /* the demodulator's i2c address */
++ u8 demod_address;
++ u8 ci_mode;
++ u8 pin_ctrl;
++ u8 ts_mode; /* 0: Parallel, 1: Serial */
++ u8 tuner_readstops;
++
++ /* Set device param to start dma */
++ int (*set_ts_params)(struct dvb_frontend *fe, int is_punctured);
++ /* Start to transfer data */
++ int (*start_ctrl)(struct dvb_frontend *fe);
++ /* Set LNB voltage */
++ int (*set_voltage)(struct dvb_frontend* fe, fe_sec_voltage_t voltage);
++};
++
++#if IS_ENABLED(CONFIG_DVB_DVBSKY_M88DS3103)
++extern struct dvb_frontend *dvbsky_m88ds3103_attach(
++ const struct dvbsky_m88ds3103_config *config,
++ struct i2c_adapter *i2c);
++#else
++static inline struct dvb_frontend *dvbsky_m88ds3103_attach(
++ const struct dvbsky_m88ds3103_config *config,
++ struct i2c_adapter *i2c)
++{
++ printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
++ return NULL;
++}
++#endif /* CONFIG_DVB_DVBSKY_M88DS3103 */
++#endif /* DVBSKY_M88DS3103_H */
+diff --git a/drivers/media/dvb-frontends/dvbsky_m88ds3103_priv.h b/drivers/media/dvb-frontends/dvbsky_m88ds3103_priv.h
+new file mode 100644
+index 0000000..9966931
+--- /dev/null
++++ b/drivers/media/dvb-frontends/dvbsky_m88ds3103_priv.h
+@@ -0,0 +1,403 @@
++/*
++ Montage Technology M88DS3103/M88TS2022 - DVBS/S2 Satellite demod/tuner driver
++
++ This program is free software; you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation; either version 2 of the License, or
++ (at your option) any later version.
++
++ This program is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with this program; if not, write to the Free Software
++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
++ */
++
++#ifndef DVBSKY_M88DS3103_PRIV_H
++#define DVBSKY_M88DS3103_PRIV_H
++
++#define FW_DOWN_SIZE 32
++#define FW_DOWN_LOOP (8192/FW_DOWN_SIZE)
++#define DS3103_DEFAULT_FIRMWARE "dvb-fe-ds3103.fw"
++#define DS3000_DEFAULT_FIRMWARE "dvb-fe-ds300x.fw"
++#define MT_FE_MCLK_KHZ 96000 /* in kHz */
++#define MT_FE_CRYSTAL_KHZ 27000 /* in kHz */
++#define FREQ_OFFSET_AT_SMALL_SYM_RATE_KHz 3000
++#define DS3000_ID 0x3000
++#define DS3103_ID 0x3103
++#define TS2020_ID 0x2020
++#define TS2022_ID 0x2022
++#define UNKNOW_ID 0x0000
++
++struct m88ds3103_state {
++ struct i2c_adapter *i2c;
++ const struct dvbsky_m88ds3103_config *config;
++
++ struct dvb_frontend frontend;
++
++ u32 preBer;
++ u8 skip_fw_load;
++ u8 first_lock; /* The first time of signal lock */
++ u16 demod_id; /* demod chip type */
++ u16 tuner_id; /* tuner chip type */
++ fe_delivery_system_t delivery_system;
++};
++
++/* For M88DS3103 demod dvbs mode.*/
++static u8 ds3103_dvbs_init_tab[] = {
++ 0x23, 0x07,
++ 0x08, 0x03,
++ 0x0c, 0x02,
++ 0x21, 0x54,
++ 0x25, 0x82,
++ 0x27, 0x31,
++ 0x30, 0x08,
++ 0x31, 0x40,
++ 0x32, 0x32,
++ 0x33, 0x35,
++ 0x35, 0xff,
++ 0x3a, 0x00,
++ 0x37, 0x10,
++ 0x38, 0x10,
++ 0x39, 0x02,
++ 0x42, 0x60,
++ 0x4a, 0x80,
++ 0x4b, 0x04,
++ 0x4d, 0x91,
++ 0x5d, 0xc8,
++ 0x50, 0x36,
++ 0x51, 0x36,
++ 0x52, 0x36,
++ 0x53, 0x36,
++ 0x63, 0x0f,
++ 0x64, 0x30,
++ 0x65, 0x40,
++ 0x68, 0x26,
++ 0x69, 0x4c,
++ 0x70, 0x20,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0x40,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0x60,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0x80,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0xa0,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0x1f,
++ 0x76, 0x38,
++ 0x77, 0xa6,
++ 0x78, 0x0c,
++ 0x79, 0x80,
++ 0x7f, 0x14,
++ 0x7c, 0x00,
++ 0xae, 0x82,
++ 0x80, 0x64,
++ 0x81, 0x66,
++ 0x82, 0x44,
++ 0x85, 0x04,
++ 0xcd, 0xf4,
++ 0x90, 0x33,
++ 0xa0, 0x44,
++ 0xc0, 0x08,
++ 0xc3, 0x10,
++ 0xc4, 0x08,
++ 0xc5, 0xf0,
++ 0xc6, 0xff,
++ 0xc7, 0x00,
++ 0xc8, 0x1a,
++ 0xc9, 0x80,
++ 0xe0, 0xf8,
++ 0xe6, 0x8b,
++ 0xd0, 0x40,
++ 0xf8, 0x20,
++ 0xfa, 0x0f,
++ 0x00, 0x00,
++ 0xbd, 0x01,
++ 0xb8, 0x00,
++};
++/* For M88DS3103 demod dvbs2 mode.*/
++static u8 ds3103_dvbs2_init_tab[] = {
++ 0x23, 0x07,
++ 0x08, 0x07,
++ 0x0c, 0x02,
++ 0x21, 0x54,
++ 0x25, 0x82,
++ 0x27, 0x31,
++ 0x30, 0x08,
++ 0x32, 0x32,
++ 0x33, 0x35,
++ 0x35, 0xff,
++ 0x3a, 0x00,
++ 0x37, 0x10,
++ 0x38, 0x10,
++ 0x39, 0x02,
++ 0x42, 0x60,
++ 0x4a, 0x80,
++ 0x4b, 0x04,
++ 0x4d, 0x91,
++ 0x5d, 0xc8,
++ 0x50, 0x36,
++ 0x51, 0x36,
++ 0x52, 0x36,
++ 0x53, 0x36,
++ 0x63, 0x0f,
++ 0x64, 0x10,
++ 0x65, 0x20,
++ 0x68, 0x46,
++ 0x69, 0xcd,
++ 0x70, 0x20,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0x40,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0x60,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0x80,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0xa0,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0x1f,
++ 0x76, 0x38,
++ 0x77, 0xa6,
++ 0x78, 0x0c,
++ 0x79, 0x80,
++ 0x7f, 0x14,
++ 0x85, 0x08,
++ 0xcd, 0xf4,
++ 0x90, 0x33,
++ 0x86, 0x00,
++ 0x87, 0x0f,
++ 0x89, 0x00,
++ 0x8b, 0x44,
++ 0x8c, 0x66,
++ 0x9d, 0xc1,
++ 0x8a, 0x10,
++ 0xad, 0x40,
++ 0xa0, 0x44,
++ 0xc0, 0x08,
++ 0xc1, 0x10,
++ 0xc2, 0x08,
++ 0xc3, 0x10,
++ 0xc4, 0x08,
++ 0xc5, 0xf0,
++ 0xc6, 0xff,
++ 0xc7, 0x00,
++ 0xc8, 0x1a,
++ 0xc9, 0x80,
++ 0xca, 0x23,
++ 0xcb, 0x24,
++ 0xcc, 0xf4,
++ 0xce, 0x74,
++ 0x00, 0x00,
++ 0xbd, 0x01,
++ 0xb8, 0x00,
++};
++
++/* For M88DS3000 demod dvbs mode.*/
++static u8 ds3000_dvbs_init_tab[] = {
++ 0x23, 0x05,
++ 0x08, 0x03,
++ 0x0c, 0x02,
++ 0x21, 0x54,
++ 0x25, 0x82,
++ 0x27, 0x31,
++ 0x30, 0x08,
++ 0x31, 0x40,
++ 0x32, 0x32,
++ 0x33, 0x35,
++ 0x35, 0xff,
++ 0x3a, 0x00,
++ 0x37, 0x10,
++ 0x38, 0x10,
++ 0x39, 0x02,
++ 0x42, 0x60,
++ 0x4a, 0x40,
++ 0x4b, 0x04,
++ 0x4d, 0x91,
++ 0x5d, 0xc8,
++ 0x50, 0x77,
++ 0x51, 0x77,
++ 0x52, 0x36,
++ 0x53, 0x36,
++ 0x56, 0x01,
++ 0x63, 0x47,
++ 0x64, 0x30,
++ 0x65, 0x40,
++ 0x68, 0x26,
++ 0x69, 0x4c,
++ 0x70, 0x20,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0x40,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0x60,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0x80,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0xa0,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0x1f,
++ 0x76, 0x00,
++ 0x77, 0xd1,
++ 0x78, 0x0c,
++ 0x79, 0x80,
++ 0x7f, 0x04,
++ 0x7c, 0x00,
++ 0x80, 0x86,
++ 0x81, 0xa6,
++ 0x85, 0x04,
++ 0xcd, 0xf4,
++ 0x90, 0x33,
++ 0xa0, 0x44,
++ 0xc0, 0x18,
++ 0xc3, 0x10,
++ 0xc4, 0x08,
++ 0xc5, 0x80,
++ 0xc6, 0x80,
++ 0xc7, 0x0a,
++ 0xc8, 0x1a,
++ 0xc9, 0x80,
++ 0xfe, 0xb6,
++ 0xe0, 0xf8,
++ 0xe6, 0x8b,
++ 0xd0, 0x40,
++ 0xf8, 0x20,
++ 0xfa, 0x0f,
++ 0xad, 0x20,
++ 0xae, 0x07,
++ 0xb8, 0x00,
++};
++
++/* For M88DS3000 demod dvbs2 mode.*/
++static u8 ds3000_dvbs2_init_tab[] = {
++ 0x23, 0x0f,
++ 0x08, 0x07,
++ 0x0c, 0x02,
++ 0x21, 0x54,
++ 0x25, 0x82,
++ 0x27, 0x31,
++ 0x30, 0x08,
++ 0x31, 0x32,
++ 0x32, 0x32,
++ 0x33, 0x35,
++ 0x35, 0xff,
++ 0x3a, 0x00,
++ 0x37, 0x10,
++ 0x38, 0x10,
++ 0x39, 0x02,
++ 0x42, 0x60,
++ 0x4a, 0x80,
++ 0x4b, 0x04,
++ 0x4d, 0x91,
++ 0x5d, 0x88,
++ 0x50, 0x36,
++ 0x51, 0x36,
++ 0x52, 0x36,
++ 0x53, 0x36,
++ 0x63, 0x60,
++ 0x64, 0x10,
++ 0x65, 0x10,
++ 0x68, 0x04,
++ 0x69, 0x29,
++ 0x70, 0x20,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0x40,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0x60,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0x80,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0xa0,
++ 0x71, 0x70,
++ 0x72, 0x04,
++ 0x73, 0x00,
++ 0x70, 0x1f,
++ 0xa0, 0x44,
++ 0xc0, 0x08,
++ 0xc1, 0x10,
++ 0xc2, 0x08,
++ 0xc3, 0x10,
++ 0xc4, 0x08,
++ 0xc5, 0xf0,
++ 0xc6, 0xf0,
++ 0xc7, 0x0a,
++ 0xc8, 0x1a,
++ 0xc9, 0x80,
++ 0xca, 0x23,
++ 0xcb, 0x24,
++ 0xce, 0x74,
++ 0x56, 0x01,
++ 0x90, 0x03,
++ 0x76, 0x80,
++ 0x77, 0x42,
++ 0x78, 0x0a,
++ 0x79, 0x80,
++ 0xad, 0x40,
++ 0xae, 0x07,
++ 0x7f, 0xd4,
++ 0x7c, 0x00,
++ 0x80, 0xa8,
++ 0x81, 0xda,
++ 0x7c, 0x01,
++ 0x80, 0xda,
++ 0x81, 0xec,
++ 0x7c, 0x02,
++ 0x80, 0xca,
++ 0x81, 0xeb,
++ 0x7c, 0x03,
++ 0x80, 0xba,
++ 0x81, 0xdb,
++ 0x85, 0x08,
++ 0x86, 0x00,
++ 0x87, 0x02,
++ 0x89, 0x80,
++ 0x8b, 0x44,
++ 0x8c, 0xaa,
++ 0x8a, 0x10,
++ 0xba, 0x00,
++ 0xf5, 0x04,
++ 0xd2, 0x32,
++ 0xb8, 0x00,
++};
++
++#endif /* DVBSKY_M88DS3103_PRIV_H */
+diff --git a/drivers/media/dvb-frontends/m88dc2800.c b/drivers/media/dvb-frontends/m88dc2800.c
+new file mode 100644
+index 0000000..f876a11
+--- /dev/null
++++ b/drivers/media/dvb-frontends/m88dc2800.c
+@@ -0,0 +1,2124 @@
++/*
++ M88DC2800/M88TC2800 - DVB-C demodulator and tuner from Montage
++
++ Copyright (C) 2012 Max nibble<nibble.max@gmail.com>
++ Copyright (C) 2011 Montage Technology / www.montage-tech.com
++
++ This program is free software; you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation; either version 2 of the License, or
++ (at your option) any later version.
++
++ This program is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with this program; if not, write to the Free Software
++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
++*/
++
++#include <linux/delay.h>
++#include <linux/errno.h>
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/string.h>
++#include <linux/slab.h>
++#include <asm/div64.h>
++#include "dvb_frontend.h"
++#include "m88dc2800.h"
++
++struct m88dc2800_state {
++ struct i2c_adapter *i2c;
++ const struct m88dc2800_config *config;
++ struct dvb_frontend frontend;
++ u32 freq;
++ u32 ber;
++ u32 sym;
++ u16 qam;
++ u8 inverted;
++ u32 xtal;
++ /* tuner state */
++ u8 tuner_init_OK; /* Tuner initialize status */
++ u8 tuner_dev_addr; /* Tuner device address */
++ u32 tuner_freq; /* RF frequency to be set, unit: KHz */
++ u16 tuner_qam; /* Reserved */
++ u16 tuner_mode;
++ u8 tuner_bandwidth; /* Bandwidth of the channel, unit: MHz, 6/7/8 */
++ u8 tuner_loopthrough; /* Tuner loop through switch, 0/1 */
++ u32 tuner_crystal; /* Tuner crystal frequency, unit: KHz */
++ u32 tuner_dac; /* Tuner DAC frequency, unit: KHz */
++ u16 tuner_mtt; /* Tuner chip version, D1: 0x0d, E0: 0x0e, E1: 0x8e */
++ u16 tuner_custom_cfg;
++ u32 tuner_version; /* Tuner driver version number */
++ u32 tuner_time;
++};
++
++static int debug;
++module_param(debug, int, 0644);
++MODULE_PARM_DESC(debug, "Activates frontend debugging (default:0)");
++
++#define dprintk(args...) \
++ do { \
++ if (debug) \
++ printk(KERN_INFO "m88dc2800: " args); \
++ } while (0)
++
++
++static int m88dc2800_i2c_write(struct m88dc2800_state *state, u8 addr,
++ u8 * p_data, u8 len)
++{
++ struct i2c_msg msg = { .flags = 0 };
++
++ msg.addr = addr;
++ msg.buf = p_data;
++ msg.len = len;
++
++ return i2c_transfer(state->i2c, &msg, 1);
++}
++
++static int m88dc2800_i2c_read(struct m88dc2800_state *state, u8 addr,
++ u8 * p_data, u8 len)
++{
++ struct i2c_msg msg = { .flags = I2C_M_RD };
++
++ msg.addr = addr;
++ msg.buf = p_data;
++ msg.len = len;
++
++ return i2c_transfer(state->i2c, &msg, 1);
++}
++
++/*demod register operations.*/
++static int WriteReg(struct m88dc2800_state *state, u8 reg, u8 data)
++{
++ u8 buf[] = { reg, data };
++ u8 addr = state->config->demod_address;
++ int err;
++
++ dprintk("%s: write reg 0x%02x, value 0x%02x\n", __func__, reg, data);
++
++ err = m88dc2800_i2c_write(state, addr, buf, 2);
++
++ if (err != 1) {
++ printk(KERN_ERR
++ "%s: writereg error(err == %i, reg == 0x%02x,"
++ " value == 0x%02x)\n", __func__, err, reg, data);
++ return -EIO;
++ }
++ return 0;
++}
++
++static int ReadReg(struct m88dc2800_state *state, u8 reg)
++{
++ int ret;
++ u8 b0[] = { reg };
++ u8 b1[] = { 0 };
++ u8 addr = state->config->demod_address;
++
++ ret = m88dc2800_i2c_write(state, addr, b0, 1);
++
++ if (ret != 1) {
++ printk(KERN_ERR "%s: reg=0x%x (error=%d)\n",
++ __func__, reg, ret);
++ return -EIO;
++ }
++
++ ret = m88dc2800_i2c_read(state, addr, b1, 1);
++
++ dprintk("%s: read reg 0x%02x, value 0x%02x\n", __func__, reg, b1[0]);
++ return b1[0];
++}
++
++static int _mt_fe_tn_set_reg(struct m88dc2800_state *state, u8 reg,
++ u8 data)
++{
++ int ret;
++ u8 buf[2];
++ u8 addr = state->tuner_dev_addr;
++
++ buf[1] = ReadReg(state, 0x86);
++ buf[1] |= 0x80;
++ ret = WriteReg(state, 0x86, buf[1]);
++
++ buf[0] = reg;
++ buf[1] = data;
++
++ ret = m88dc2800_i2c_write(state, addr, buf, 2);
++ if (ret != 1)
++ return -EIO;
++ return 0;
++}
++
++static int _mt_fe_tn_get_reg(struct m88dc2800_state *state, u8 reg,
++ u8 * p_data)
++{
++ int ret;
++ u8 buf[2];
++ u8 addr = state->tuner_dev_addr;
++
++ buf[1] = ReadReg(state, 0x86);
++ buf[1] |= 0x80;
++ ret = WriteReg(state, 0x86, buf[1]);
++
++ buf[0] = reg;
++ ret = m88dc2800_i2c_write(state, addr, buf, 1);
++
++ msleep(1);
++
++ buf[1] = ReadReg(state, 0x86);
++ buf[1] |= 0x80;
++ ret = WriteReg(state, 0x86, buf[1]);
++
++ return m88dc2800_i2c_read(state, addr, p_data, 1);
++}
++
++/* Tuner operation functions.*/
++static int _mt_fe_tn_set_RF_front_tc2800(struct m88dc2800_state *state)
++{
++ u32 freq_KHz = state->tuner_freq;
++ u8 a, b, c;
++ if (state->tuner_mtt == 0xD1) { /* D1 */
++ if (freq_KHz <= 123000) {
++ if (freq_KHz <= 56000) {
++ a = 0x00; b = 0x00; c = 0x00;
++ } else if (freq_KHz <= 64000) {
++ a = 0x10; b = 0x01; c = 0x08;
++ } else if (freq_KHz <= 72000) {
++ a = 0x20; b = 0x02; c = 0x10;
++ } else if (freq_KHz <= 80000) {
++ a = 0x30; b = 0x03; c = 0x18;
++ } else if (freq_KHz <= 88000) {
++ a = 0x40; b = 0x04; c = 0x20;
++ } else if (freq_KHz <= 96000) {
++ a = 0x50; b = 0x05; c = 0x28;
++ } else if (freq_KHz <= 104000) {
++ a = 0x60; b = 0x06; c = 0x30;
++ } else {
++ a = 0x70; b = 0x07; c = 0x38;
++ }
++ _mt_fe_tn_set_reg(state, 0x58, 0x9b);
++ _mt_fe_tn_set_reg(state, 0x59, a);
++ _mt_fe_tn_set_reg(state, 0x5d, b);
++ _mt_fe_tn_set_reg(state, 0x5e, c);
++ _mt_fe_tn_set_reg(state, 0x5a, 0x75);
++ _mt_fe_tn_set_reg(state, 0x73, 0x0c);
++ } else { /* if (freq_KHz > 112000) */
++ _mt_fe_tn_set_reg(state, 0x58, 0x7b);
++ if (freq_KHz <= 304000) {
++ if (freq_KHz <= 136000) {
++ _mt_fe_tn_set_reg(state, 0x5e, 0x40);
++ } else if (freq_KHz <= 160000) {
++ _mt_fe_tn_set_reg(state, 0x5e, 0x48);
++ } else if (freq_KHz <= 184000) {
++ _mt_fe_tn_set_reg(state, 0x5e, 0x50);
++ } else if (freq_KHz <= 208000) {
++ _mt_fe_tn_set_reg(state, 0x5e, 0x58);
++ } else if (freq_KHz <= 232000) {
++ _mt_fe_tn_set_reg(state, 0x5e, 0x60);
++ } else if (freq_KHz <= 256000) {
++ _mt_fe_tn_set_reg(state, 0x5e, 0x68);
++ } else if (freq_KHz <= 280000) {
++ _mt_fe_tn_set_reg(state, 0x5e, 0x70);
++ } else { /* if (freq_KHz <= 304000) */
++ _mt_fe_tn_set_reg(state, 0x5e, 0x78);
++ }
++ if (freq_KHz <= 171000) {
++ _mt_fe_tn_set_reg(state, 0x73, 0x08);
++ } else if (freq_KHz <= 211000) {
++ _mt_fe_tn_set_reg(state, 0x73, 0x0a);
++ } else {
++ _mt_fe_tn_set_reg(state, 0x73, 0x0e);
++ }
++ } else { /* if (freq_KHz > 304000) */
++ _mt_fe_tn_set_reg(state, 0x5e, 0x88);
++ if (freq_KHz <= 400000) {
++ _mt_fe_tn_set_reg(state, 0x73, 0x0c);
++ } else if (freq_KHz <= 450000) {
++ _mt_fe_tn_set_reg(state, 0x73, 0x09);
++ } else if (freq_KHz <= 550000) {
++ _mt_fe_tn_set_reg(state, 0x73, 0x0e);
++ } else if (freq_KHz <= 650000) {
++ _mt_fe_tn_set_reg(state, 0x73, 0x0d);
++ } else { /*if (freq_KHz > 650000) */
++ _mt_fe_tn_set_reg(state, 0x73, 0x0e);
++ }
++ }
++ }
++ if (freq_KHz > 800000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x24);
++ else if (freq_KHz > 700000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x34);
++ else if (freq_KHz > 500000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x44);
++ else if (freq_KHz > 300000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x43);
++ else if (freq_KHz > 220000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x54);
++ else if (freq_KHz > 110000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x14);
++ else
++ _mt_fe_tn_set_reg(state, 0x87, 0x54);
++ if (freq_KHz > 600000)
++ _mt_fe_tn_set_reg(state, 0x6a, 0x53);
++ else if (freq_KHz > 500000)
++ _mt_fe_tn_set_reg(state, 0x6a, 0x57);
++ else
++ _mt_fe_tn_set_reg(state, 0x6a, 0x59);
++ if (freq_KHz < 200000) {
++ _mt_fe_tn_set_reg(state, 0x20, 0x5d);
++ } else if (freq_KHz < 500000) {
++ _mt_fe_tn_set_reg(state, 0x20, 0x7d);
++ } else {
++ _mt_fe_tn_set_reg(state, 0x20, 0xfd);
++ } /* end of 0xD1 */
++ } else if (state->tuner_mtt == 0xE1) { /* E1 */
++ if (freq_KHz <= 112000) { /* 123MHz */
++ if (freq_KHz <= 56000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0x01);
++ } else if (freq_KHz <= 64000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0x09);
++ } else if (freq_KHz <= 72000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0x11);
++ } else if (freq_KHz <= 80000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0x19);
++ } else if (freq_KHz <= 88000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0x21);
++ } else if (freq_KHz <= 96000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0x29);
++ } else if (freq_KHz <= 104000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0x31);
++ } else { /* if (freq_KHz <= 112000) */
++ _mt_fe_tn_set_reg(state, 0x5c, 0x39);
++ }
++ _mt_fe_tn_set_reg(state, 0x5b, 0x30);
++ } else { /* if (freq_KHz > 112000) */
++ if (freq_KHz <= 304000) {
++ if (freq_KHz <= 136000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0x41);
++ } else if (freq_KHz <= 160000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0x49);
++ } else if (freq_KHz <= 184000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0x51);
++ } else if (freq_KHz <= 208000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0x59);
++ } else if (freq_KHz <= 232000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0x61);
++ } else if (freq_KHz <= 256000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0x69);
++ } else if (freq_KHz <= 280000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0x71);
++ } else { /* if (freq_KHz <= 304000) */
++ _mt_fe_tn_set_reg(state, 0x5c, 0x79);
++ }
++ if (freq_KHz <= 150000) {
++ _mt_fe_tn_set_reg(state, 0x5b, 0x28);
++ } else if (freq_KHz <= 256000) {
++ _mt_fe_tn_set_reg(state, 0x5b, 0x29);
++ } else {
++ _mt_fe_tn_set_reg(state, 0x5b, 0x2a);
++ }
++ } else { /* if (freq_KHz > 304000) */
++ if (freq_KHz <= 400000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0x89);
++ } else if (freq_KHz <= 450000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0x91);
++ } else if (freq_KHz <= 650000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0x98);
++ } else if (freq_KHz <= 850000) {
++ _mt_fe_tn_set_reg(state, 0x5c, 0xa0);
++ } else {
++ _mt_fe_tn_set_reg(state, 0x5c, 0xa8);
++ }
++ _mt_fe_tn_set_reg(state, 0x5b, 0x08);
++ }
++ }
++ } /* end of 0xE1 */
++ return 0;
++}
++
++static int _mt_fe_tn_cali_PLL_tc2800(struct m88dc2800_state *state,
++ u32 freq_KHz,
++ u32 cali_freq_thres_div2,
++ u32 cali_freq_thres_div3r,
++ u32 cali_freq_thres_div3)
++{
++ s32 N, F, MUL;
++ u8 buf, tmp, tmp2;
++ s32 M;
++ const s32 crystal_KHz = state->tuner_crystal;
++ if (state->tuner_mtt == 0xD1) {
++ M = state->tuner_crystal / 4000;
++ if (freq_KHz > cali_freq_thres_div2) {
++ MUL = 4;
++ tmp = 2;
++ } else if (freq_KHz > 300000) {
++ MUL = 8;
++ tmp = 3;
++ } else if (freq_KHz > (cali_freq_thres_div2 / 2)) {
++ MUL = 8;
++ tmp = 4;
++ } else if (freq_KHz > (cali_freq_thres_div2 / 4)) {
++ MUL = 16;
++ tmp = 5;
++ } else if (freq_KHz > (cali_freq_thres_div2 / 8)) {
++ MUL = 32;
++ tmp = 6;
++ } else if (freq_KHz > (cali_freq_thres_div2 / 16)) {
++ MUL = 64;
++ tmp = 7;
++ } else { /* invalid */
++ MUL = 0;
++ tmp = 0;
++ return 1;
++ }
++ } else if (state->tuner_mtt == 0xE1) {
++ M = state->tuner_crystal / 1000;
++ _mt_fe_tn_set_reg(state, 0x30, 0xff);
++ _mt_fe_tn_set_reg(state, 0x32, 0xe0);
++ _mt_fe_tn_set_reg(state, 0x33, 0x86);
++ _mt_fe_tn_set_reg(state, 0x37, 0x70);
++ _mt_fe_tn_set_reg(state, 0x38, 0x20);
++ _mt_fe_tn_set_reg(state, 0x39, 0x18);
++ _mt_fe_tn_set_reg(state, 0x89, 0x83);
++ if (freq_KHz > cali_freq_thres_div2) {
++ M = M / 4;
++ MUL = 4;
++ tmp = 2;
++ tmp2 = M + 16; /* 48 */
++ } else if (freq_KHz > cali_freq_thres_div3r) {
++ M = M / 3;
++ MUL = 6;
++ tmp = 2;
++ tmp2 = M + 32; /* 32 */
++ } else if (freq_KHz > cali_freq_thres_div3) {
++ M = M / 3;
++ MUL = 6;
++ tmp = 2;
++ tmp2 = M; /* 16 */
++ } else if (freq_KHz > 304000) {
++ M = M / 4;
++ MUL = 8;
++ tmp = 3;
++ tmp2 = M + 16; /* 48 */
++ } else if (freq_KHz > (cali_freq_thres_div2 / 2)) {
++ M = M / 4;
++ MUL = 8;
++ tmp = 4;
++ tmp2 = M + 16; /* 48 */
++ } else if (freq_KHz > (cali_freq_thres_div3r / 2)) {
++ M = M / 3;
++ MUL = 12;
++ tmp = 4;
++ tmp2 = M + 32; /* 32 */
++ } else if (freq_KHz > (cali_freq_thres_div3 / 2)) {
++ M = M / 3;
++ MUL = 12;
++ tmp = 4;
++ tmp2 = M; /* 16 */
++ } else if (freq_KHz > (cali_freq_thres_div2 / 4)) {
++ M = M / 4;
++ MUL = 16;
++ tmp = 5;
++ tmp2 = M + 16; /* 48 */
++ } else if (freq_KHz > (cali_freq_thres_div3r / 4)) {
++ M = M / 3;
++ MUL = 24;
++ tmp = 5;
++ tmp2 = M + 32; /* 32 */
++ } else if (freq_KHz > (cali_freq_thres_div3 / 4)) {
++ M = M / 3;
++ MUL = 24;
++ tmp = 5;
++ tmp2 = M; /* 16 */
++ } else if (freq_KHz > (cali_freq_thres_div2 / 8)) {
++ M = M / 4;
++ MUL = 32;
++ tmp = 6;
++ tmp2 = M + 16; /* 48 */
++ } else if (freq_KHz > (cali_freq_thres_div3r / 8)) {
++ M = M / 3;
++ MUL = 48;
++ tmp = 6;
++ tmp2 = M + 32; /* 32 */
++ } else if (freq_KHz > (cali_freq_thres_div3 / 8)) {
++ M = M / 3;
++ MUL = 48;
++ tmp = 6;
++ tmp2 = M; /* 16 */
++ } else if (freq_KHz > (cali_freq_thres_div2 / 16)) {
++ M = M / 4;
++ MUL = 64;
++ tmp = 7;
++ tmp2 = M + 16; /* 48 */
++ } else if (freq_KHz > (cali_freq_thres_div3r / 16)) {
++ M = M / 3;
++ MUL = 96;
++ tmp = 7;
++ tmp2 = M + 32; /* 32 */
++ } else if (freq_KHz > (cali_freq_thres_div3 / 16)) {
++ M = M / 3;
++ MUL = 96;
++ tmp = 7;
++ tmp2 = M; /* 16 */
++ } else { /* invalid */
++ M = M / 4;
++ MUL = 0;
++ tmp = 0;
++ tmp2 = 48;
++ return 1;
++ }
++ if (freq_KHz == 291000) {
++ M = state->tuner_crystal / 1000 / 3;
++ MUL = 12;
++ tmp = 4;
++ tmp2 = M + 32; /* 32 */
++ }
++ /*
++ if (freq_KHz == 578000) {
++ M = state->tuner_crystal / 1000 / 4;
++ MUL = 4;
++ tmp = 2;
++ tmp2 = M + 16; // 48
++ }
++ */
++ if (freq_KHz == 690000) {
++ M = state->tuner_crystal / 1000 / 3;
++ MUL = 4;
++ tmp = 2;
++ tmp2 = M + 16; /* 48 */
++ }
++ _mt_fe_tn_get_reg(state, 0x33, &buf);
++ buf &= 0xc0;
++ buf += tmp2;
++ _mt_fe_tn_set_reg(state, 0x33, buf);
++ } else {
++ return 1;
++ }
++ _mt_fe_tn_get_reg(state, 0x39, &buf);
++ buf &= 0xf8;
++ buf += tmp;
++ _mt_fe_tn_set_reg(state, 0x39, buf);
++ N = (freq_KHz * MUL * M / crystal_KHz) / 2 * 2 - 256;
++ buf = (N >> 8) & 0xcf;
++ if (state->tuner_mtt == 0xE1) {
++ buf |= 0x30;
++ }
++ _mt_fe_tn_set_reg(state, 0x34, buf);
++ buf = N & 0xff;
++ _mt_fe_tn_set_reg(state, 0x35, buf);
++ F = ((freq_KHz * MUL * M / (crystal_KHz / 1000) / 2) -
++ (freq_KHz * MUL * M / crystal_KHz / 2 * 1000)) * 64 / 1000;
++ buf = F & 0xff;
++ _mt_fe_tn_set_reg(state, 0x36, buf);
++ if (F == 0) {
++ if (state->tuner_mtt == 0xD1) {
++ _mt_fe_tn_set_reg(state, 0x3d, 0xca);
++ } else if (state->tuner_mtt == 0xE1) {
++ _mt_fe_tn_set_reg(state, 0x3d, 0xfe);
++ } else {
++ return 1;
++ }
++ _mt_fe_tn_set_reg(state, 0x3e, 0x9c);
++ _mt_fe_tn_set_reg(state, 0x3f, 0x34);
++ }
++ if (F > 0) {
++ if (state->tuner_mtt == 0xD1) {
++ if ((F == 32) || (F == 16) || (F == 48)) {
++ _mt_fe_tn_set_reg(state, 0x3e, 0xa4);
++ _mt_fe_tn_set_reg(state, 0x3d, 0x4a);
++ _mt_fe_tn_set_reg(state, 0x3f, 0x36);
++ } else {
++ _mt_fe_tn_set_reg(state, 0x3e, 0xa4);
++ _mt_fe_tn_set_reg(state, 0x3d, 0x4a);
++ _mt_fe_tn_set_reg(state, 0x3f, 0x36);
++ }
++ } else if (state->tuner_mtt == 0xE1) {
++ _mt_fe_tn_set_reg(state, 0x3e, 0xa4);
++ _mt_fe_tn_set_reg(state, 0x3d, 0x7e);
++ _mt_fe_tn_set_reg(state, 0x3f, 0x36);
++ _mt_fe_tn_set_reg(state, 0x89, 0x84);
++ _mt_fe_tn_get_reg(state, 0x39, &buf);
++ buf = buf & 0x1f;
++ _mt_fe_tn_set_reg(state, 0x39, buf);
++ _mt_fe_tn_get_reg(state, 0x32, &buf);
++ buf = buf | 0x02;
++ _mt_fe_tn_set_reg(state, 0x32, buf);
++ } else {
++ return 1;
++ }
++ }
++ _mt_fe_tn_set_reg(state, 0x41, 0x00);
++ if (state->tuner_mtt == 0xD1) {
++ msleep(5);
++ } else if (state->tuner_mtt == 0xE1) {
++ msleep(2);
++ } else {
++ return 1;
++ }
++ _mt_fe_tn_set_reg(state, 0x41, 0x02);
++ _mt_fe_tn_set_reg(state, 0x30, 0x7f);
++ _mt_fe_tn_set_reg(state, 0x30, 0xff);
++ _mt_fe_tn_set_reg(state, 0x31, 0x80);
++ _mt_fe_tn_set_reg(state, 0x31, 0x00);
++
++ return 0;
++}
++
++static int _mt_fe_tn_set_PLL_freq_tc2800(struct m88dc2800_state *state)
++{
++ u8 buf, buf1;
++ u32 freq_thres_div2_KHz, freq_thres_div3r_KHz,
++ freq_thres_div3_KHz;
++ const u32 freq_KHz = state->tuner_freq;
++ if (state->tuner_mtt == 0xD1) {
++ _mt_fe_tn_set_reg(state, 0x32, 0xe1);
++ _mt_fe_tn_set_reg(state, 0x33, 0xa6);
++ _mt_fe_tn_set_reg(state, 0x37, 0x7f);
++ _mt_fe_tn_set_reg(state, 0x38, 0x20);
++ _mt_fe_tn_set_reg(state, 0x39, 0x18);
++ _mt_fe_tn_set_reg(state, 0x40, 0x40);
++ freq_thres_div2_KHz = 520000;
++ _mt_fe_tn_cali_PLL_tc2800(state, freq_KHz,
++ freq_thres_div2_KHz, 0, 0);
++ msleep(5);
++ _mt_fe_tn_get_reg(state, 0x3a, &buf);
++ buf1 = buf;
++ buf = buf & 0x03;
++ buf1 = buf1 & 0x01;
++ if ((buf1 == 0) || (buf == 3)) {
++ freq_thres_div2_KHz = 420000;
++ _mt_fe_tn_cali_PLL_tc2800(state, freq_KHz,
++ freq_thres_div2_KHz, 0,
++ 0);
++ msleep(5);
++ _mt_fe_tn_get_reg(state, 0x3a, &buf);
++ buf = buf & 0x07;
++ if (buf == 5) {
++ freq_thres_div2_KHz = 520000;
++ _mt_fe_tn_cali_PLL_tc2800(state, freq_KHz,
++ freq_thres_div2_KHz,
++ 0, 0);
++ msleep(5);
++ }
++ }
++ _mt_fe_tn_get_reg(state, 0x38, &buf);
++ _mt_fe_tn_set_reg(state, 0x38, buf);
++ _mt_fe_tn_get_reg(state, 0x32, &buf);
++ buf = buf | 0x10;
++ _mt_fe_tn_set_reg(state, 0x32, buf);
++ _mt_fe_tn_set_reg(state, 0x30, 0x7f);
++ _mt_fe_tn_set_reg(state, 0x30, 0xff);
++ _mt_fe_tn_get_reg(state, 0x32, &buf);
++ buf = buf & 0xdf;
++ _mt_fe_tn_set_reg(state, 0x32, buf);
++ _mt_fe_tn_set_reg(state, 0x40, 0x0);
++ _mt_fe_tn_set_reg(state, 0x30, 0x7f);
++ _mt_fe_tn_set_reg(state, 0x30, 0xff);
++ _mt_fe_tn_set_reg(state, 0x31, 0x80);
++ _mt_fe_tn_set_reg(state, 0x31, 0x00);
++ msleep(5);
++ _mt_fe_tn_get_reg(state, 0x39, &buf);
++ buf = buf >> 5;
++ if (buf < 5) {
++ _mt_fe_tn_get_reg(state, 0x39, &buf);
++ buf = buf | 0xa0;
++ buf = buf & 0xbf;
++ _mt_fe_tn_set_reg(state, 0x39, buf);
++ _mt_fe_tn_get_reg(state, 0x32, &buf);
++ buf = buf | 0x02;
++ _mt_fe_tn_set_reg(state, 0x32, buf);
++ }
++ _mt_fe_tn_get_reg(state, 0x37, &buf);
++ if (buf > 0x70) {
++ buf = 0x7f;
++ _mt_fe_tn_set_reg(state, 0x40, 0x40);
++ }
++ _mt_fe_tn_set_reg(state, 0x37, buf);
++ _mt_fe_tn_get_reg(state, 0x38, &buf);
++ if (buf < 0x0f) {
++ buf = (buf & 0x0f) << 2;
++ buf = buf + 0x0f;
++ _mt_fe_tn_set_reg(state, 0x37, buf);
++ } else if (buf < 0x1f) {
++ buf = buf + 0x0f;
++ _mt_fe_tn_set_reg(state, 0x37, buf);
++ }
++ _mt_fe_tn_get_reg(state, 0x32, &buf);
++ buf = (buf | 0x20) & 0xef;
++ _mt_fe_tn_set_reg(state, 0x32, buf);
++ _mt_fe_tn_set_reg(state, 0x41, 0x00);
++ msleep(5);
++ _mt_fe_tn_set_reg(state, 0x41, 0x02);
++ } else if (state->tuner_mtt == 0xE1) {
++ freq_thres_div2_KHz = 580000;
++ freq_thres_div3r_KHz = 500000;
++ freq_thres_div3_KHz = 440000;
++ _mt_fe_tn_cali_PLL_tc2800(state, freq_KHz,
++ freq_thres_div2_KHz,
++ freq_thres_div3r_KHz,
++ freq_thres_div3_KHz);
++ msleep(3);
++ _mt_fe_tn_get_reg(state, 0x38, &buf);
++ _mt_fe_tn_set_reg(state, 0x38, buf);
++ _mt_fe_tn_set_reg(state, 0x30, 0x7f);
++ _mt_fe_tn_set_reg(state, 0x30, 0xff);
++ _mt_fe_tn_set_reg(state, 0x31, 0x80);
++ _mt_fe_tn_set_reg(state, 0x31, 0x00);
++ msleep(3);
++ _mt_fe_tn_get_reg(state, 0x38, &buf);
++ _mt_fe_tn_set_reg(state, 0x38, buf);
++ _mt_fe_tn_get_reg(state, 0x32, &buf);
++ buf = buf | 0x10;
++ _mt_fe_tn_set_reg(state, 0x32, buf);
++ _mt_fe_tn_set_reg(state, 0x30, 0x7f);
++ _mt_fe_tn_set_reg(state, 0x30, 0xff);
++ _mt_fe_tn_get_reg(state, 0x32, &buf);
++ buf = buf & 0xdf;
++ _mt_fe_tn_set_reg(state, 0x32, buf);
++ _mt_fe_tn_set_reg(state, 0x31, 0x80);
++ _mt_fe_tn_set_reg(state, 0x31, 0x00);
++ msleep(3);
++ _mt_fe_tn_get_reg(state, 0x37, &buf);
++ _mt_fe_tn_set_reg(state, 0x37, buf);
++ /*
++ if ((freq_KHz == 802000) || (freq_KHz == 826000)) {
++ _mt_fe_tn_set_reg(state, 0x37, 0x5e);
++ }
++ */
++ _mt_fe_tn_get_reg(state, 0x32, &buf);
++ buf = (buf & 0xef) | 0x30;
++ _mt_fe_tn_set_reg(state, 0x32, buf);
++ _mt_fe_tn_set_reg(state, 0x41, 0x00);
++ msleep(2);
++ _mt_fe_tn_set_reg(state, 0x41, 0x02);
++ } else {
++ return 1;
++ }
++ return 0;
++}
++
++static int _mt_fe_tn_set_BB_tc2800(struct m88dc2800_state *state)
++{
++ return 0;
++}
++
++ static int _mt_fe_tn_set_appendix_tc2800(struct m88dc2800_state *state)
++
++{
++ u8 buf;
++ const u32 freq_KHz = state->tuner_freq;
++ if (state->tuner_mtt == 0xD1) {
++ if ((freq_KHz == 123000) || (freq_KHz == 147000) ||
++ (freq_KHz == 171000) || (freq_KHz == 195000)) {
++ _mt_fe_tn_set_reg(state, 0x20, 0x1b);
++ }
++ if ((freq_KHz == 371000) || (freq_KHz == 419000) ||
++ (freq_KHz == 610000) || (freq_KHz == 730000) ||
++ (freq_KHz == 754000) || (freq_KHz == 826000)) {
++ _mt_fe_tn_get_reg(state, 0x0d, &buf);
++ _mt_fe_tn_set_reg(state, 0x0d, (u8) (buf + 1));
++ }
++ if ((freq_KHz == 522000) || (freq_KHz == 578000) ||
++ (freq_KHz == 634000) || (freq_KHz == 690000) ||
++ (freq_KHz == 834000)) {
++ _mt_fe_tn_get_reg(state, 0x0d, &buf);
++ _mt_fe_tn_set_reg(state, 0x0d, (u8) (buf - 1));
++ }
++ } else if (state->tuner_mtt == 0xE1) {
++ _mt_fe_tn_set_reg(state, 0x20, 0xfc);
++ if (freq_KHz == 123000 || freq_KHz == 147000 ||
++ freq_KHz == 171000 || freq_KHz == 195000 ||
++ freq_KHz == 219000 || freq_KHz == 267000 ||
++ freq_KHz == 291000 || freq_KHz == 339000 ||
++ freq_KHz == 387000 || freq_KHz == 435000 ||
++ freq_KHz == 482000 || freq_KHz == 530000 ||
++ freq_KHz == 722000 ||
++ (state->tuner_custom_cfg == 1 && freq_KHz == 315000)) {
++ _mt_fe_tn_set_reg(state, 0x20, 0x5c);
++ }
++ }
++ return 0;
++}
++
++ static int _mt_fe_tn_set_DAC_tc2800(struct m88dc2800_state *state)
++{
++ u8 buf, tempnumber;
++ s32 N;
++ s32 f1f2number, f1, f2, delta1, Totalnum1;
++ s32 cntT, cntin, NCOI, z0, z1, z2, tmp;
++ u32 fc, fadc, fsd, f2d;
++ u32 FreqTrue108_Hz;
++ s32 M = state->tuner_crystal / 4000;
++ /* const u8 bandwidth = state->tuner_bandwidth; */
++ const u16 DAC_fre = 108;
++ const u32 crystal_KHz = state->tuner_crystal;
++ const u32 DACFreq_KHz = state->tuner_dac;
++ const u32 freq_KHz = state->tuner_freq;
++
++ if (state->tuner_mtt == 0xE1) {
++ _mt_fe_tn_get_reg(state, 0x33, &buf);
++ M = buf & 0x0f;
++ if (M == 0)
++ M = 6;
++ }
++ _mt_fe_tn_get_reg(state, 0x34, &buf);
++ N = buf & 0x07;
++ _mt_fe_tn_get_reg(state, 0x35, &buf);
++ N = (N << 8) + buf;
++ buf = ((N + 256) * crystal_KHz / M / DAC_fre + 500) / 1000;
++ if (state->tuner_mtt == 0xE1) {
++ _mt_fe_tn_set_appendix_tc2800(state);
++ if (freq_KHz == 187000 || freq_KHz == 195000 ||
++ freq_KHz == 131000 || freq_KHz == 211000 ||
++ freq_KHz == 219000 || freq_KHz == 227000 ||
++ freq_KHz == 267000 || freq_KHz == 299000 ||
++ freq_KHz == 347000 || freq_KHz == 363000 ||
++ freq_KHz == 395000 || freq_KHz == 403000 ||
++ freq_KHz == 435000 || freq_KHz == 482000 ||
++ freq_KHz == 474000 || freq_KHz == 490000 ||
++ freq_KHz == 610000 || freq_KHz == 642000 ||
++ freq_KHz == 666000 || freq_KHz == 722000 ||
++ freq_KHz == 754000 ||
++ ((freq_KHz == 379000 || freq_KHz == 467000 ||
++ freq_KHz == 762000) && state->tuner_custom_cfg != 1)) {
++ buf = buf + 1;
++ }
++ if (freq_KHz == 123000 || freq_KHz == 139000 ||
++ freq_KHz == 147000 || freq_KHz == 171000 ||
++ freq_KHz == 179000 || freq_KHz == 203000 ||
++ freq_KHz == 235000 || freq_KHz == 251000 ||
++ freq_KHz == 259000 || freq_KHz == 283000 ||
++ freq_KHz == 331000 || freq_KHz == 363000 ||
++ freq_KHz == 371000 || freq_KHz == 387000 ||
++ freq_KHz == 411000 || freq_KHz == 427000 ||
++ freq_KHz == 443000 || freq_KHz == 451000 ||
++ freq_KHz == 459000 || freq_KHz == 506000 ||
++ freq_KHz == 514000 || freq_KHz == 538000 ||
++ freq_KHz == 546000 || freq_KHz == 554000 ||
++ freq_KHz == 562000 || freq_KHz == 570000 ||
++ freq_KHz == 578000 || freq_KHz == 602000 ||
++ freq_KHz == 626000 || freq_KHz == 658000 ||
++ freq_KHz == 690000 || freq_KHz == 714000 ||
++ freq_KHz == 746000 || freq_KHz == 522000 ||
++ freq_KHz == 826000 || freq_KHz == 155000 ||
++ freq_KHz == 530000 ||
++ ((freq_KHz == 275000 || freq_KHz == 355000) &&
++ state->tuner_custom_cfg != 1) ||
++ ((freq_KHz == 467000 || freq_KHz == 762000 ||
++ freq_KHz == 778000 || freq_KHz == 818000) &&
++ state->tuner_custom_cfg == 1)) {
++ buf = buf - 1;
++ }
++ }
++ _mt_fe_tn_set_reg(state, 0x0e, buf);
++ _mt_fe_tn_set_reg(state, 0x0d, buf);
++ f1f2number =
++ (((DACFreq_KHz * M * buf) / crystal_KHz) << 16) / (N + 256) +
++ (((DACFreq_KHz * M * buf) % crystal_KHz) << 16) / ((N + 256) *
++ crystal_KHz);
++ _mt_fe_tn_set_reg(state, 0xf1, (f1f2number & 0xff00) >> 8);
++ _mt_fe_tn_set_reg(state, 0xf2, f1f2number & 0x00ff);
++ FreqTrue108_Hz =
++ (N + 256) * crystal_KHz / (M * buf) * 1000 +
++ (((N + 256) * crystal_KHz) % (M * buf)) * 1000 / (M * buf);
++ f1 = 4096;
++ fc = FreqTrue108_Hz;
++ fadc = fc / 4;
++ fsd = 27000000;
++ f2d = state->tuner_bandwidth * 1000 / 2 - 150;
++ f2 = (fsd / 250) * f2d / ((fc + 500) / 1000);
++ delta1 = ((f1 - f2) << 15) / f2;
++ Totalnum1 = ((f1 - f2) << 15) - delta1 * f2;
++ cntT = f2;
++ cntin = Totalnum1;
++ NCOI = delta1;
++ z0 = cntin;
++ z1 = cntT;
++ z2 = NCOI;
++ tempnumber = (z0 & 0xff00) >> 8;
++ _mt_fe_tn_set_reg(state, 0xc9, (u8) (tempnumber & 0x0f));
++ tempnumber = (z0 & 0xff);
++ _mt_fe_tn_set_reg(state, 0xca, tempnumber);
++ tempnumber = (z1 & 0xff00) >> 8;
++ _mt_fe_tn_set_reg(state, 0xcb, tempnumber);
++ tempnumber = (z1 & 0xff);
++ _mt_fe_tn_set_reg(state, 0xcc, tempnumber);
++ tempnumber = (z2 & 0xff00) >> 8;
++ _mt_fe_tn_set_reg(state, 0xcd, tempnumber);
++ tempnumber = (z2 & 0xff);
++ _mt_fe_tn_set_reg(state, 0xce, tempnumber);
++ tmp = f1;
++ f1 = f2;
++ f2 = tmp / 2;
++ delta1 = ((f1 - f2) << 15) / f2;
++ Totalnum1 = ((f1 - f2) << 15) - delta1 * f2;
++ NCOI = (f1 << 15) / f2 - (1 << 15);
++ cntT = f2;
++ cntin = Totalnum1;
++ z0 = cntin;
++ z1 = cntT;
++ z2 = NCOI;
++ tempnumber = (z0 & 0xff00) >> 8;
++ _mt_fe_tn_set_reg(state, 0xd9, (u8) (tempnumber & 0x0f));
++ tempnumber = (z0 & 0xff);
++ _mt_fe_tn_set_reg(state, 0xda, tempnumber);
++ tempnumber = (z1 & 0xff00) >> 8;
++ _mt_fe_tn_set_reg(state, 0xdb, tempnumber);
++ tempnumber = (z1 & 0xff);
++ _mt_fe_tn_set_reg(state, 0xdc, tempnumber);
++ tempnumber = (z2 & 0xff00) >> 8;
++ _mt_fe_tn_set_reg(state, 0xdd, tempnumber);
++ tempnumber = (z2 & 0xff);
++ _mt_fe_tn_set_reg(state, 0xde, tempnumber);
++
++ return 0;
++}
++
++static int _mt_fe_tn_preset_tc2800(struct m88dc2800_state *state)
++{
++ if (state->tuner_mtt == 0xD1) {
++ _mt_fe_tn_set_reg(state, 0x19, 0x4a);
++ _mt_fe_tn_set_reg(state, 0x1b, 0x4b);
++ _mt_fe_tn_set_reg(state, 0x04, 0x04);
++ _mt_fe_tn_set_reg(state, 0x17, 0x0d);
++ _mt_fe_tn_set_reg(state, 0x62, 0x6c);
++ _mt_fe_tn_set_reg(state, 0x63, 0xf4);
++ _mt_fe_tn_set_reg(state, 0x1f, 0x0e);
++ _mt_fe_tn_set_reg(state, 0x6b, 0xf4);
++ _mt_fe_tn_set_reg(state, 0x14, 0x01);
++ _mt_fe_tn_set_reg(state, 0x5a, 0x75);
++ _mt_fe_tn_set_reg(state, 0x66, 0x74);
++ _mt_fe_tn_set_reg(state, 0x72, 0xe0);
++ _mt_fe_tn_set_reg(state, 0x70, 0x07);
++ _mt_fe_tn_set_reg(state, 0x15, 0x7b);
++ _mt_fe_tn_set_reg(state, 0x55, 0x71);
++ _mt_fe_tn_set_reg(state, 0x75, 0x55);
++ _mt_fe_tn_set_reg(state, 0x76, 0xac);
++ _mt_fe_tn_set_reg(state, 0x77, 0x6c);
++ _mt_fe_tn_set_reg(state, 0x78, 0x8b);
++ _mt_fe_tn_set_reg(state, 0x79, 0x42);
++ _mt_fe_tn_set_reg(state, 0x7a, 0xd2);
++ _mt_fe_tn_set_reg(state, 0x81, 0x01);
++ _mt_fe_tn_set_reg(state, 0x82, 0x00);
++ _mt_fe_tn_set_reg(state, 0x82, 0x02);
++ _mt_fe_tn_set_reg(state, 0x82, 0x04);
++ _mt_fe_tn_set_reg(state, 0x82, 0x06);
++ _mt_fe_tn_set_reg(state, 0x82, 0x08);
++ _mt_fe_tn_set_reg(state, 0x82, 0x09);
++ _mt_fe_tn_set_reg(state, 0x82, 0x29);
++ _mt_fe_tn_set_reg(state, 0x82, 0x49);
++ _mt_fe_tn_set_reg(state, 0x82, 0x58);
++ _mt_fe_tn_set_reg(state, 0x82, 0x59);
++ _mt_fe_tn_set_reg(state, 0x82, 0x98);
++ _mt_fe_tn_set_reg(state, 0x82, 0x99);
++ _mt_fe_tn_set_reg(state, 0x10, 0x05);
++ _mt_fe_tn_set_reg(state, 0x10, 0x0d);
++ _mt_fe_tn_set_reg(state, 0x11, 0x95);
++ _mt_fe_tn_set_reg(state, 0x11, 0x9d);
++ if (state->tuner_loopthrough != 0) {
++ _mt_fe_tn_set_reg(state, 0x67, 0x25);
++ } else {
++ _mt_fe_tn_set_reg(state, 0x67, 0x05);
++ }
++ } else if (state->tuner_mtt == 0xE1) {
++ _mt_fe_tn_set_reg(state, 0x1b, 0x47);
++ if (state->tuner_mode == 0) { /* DVB-C */
++ _mt_fe_tn_set_reg(state, 0x66, 0x74);
++ _mt_fe_tn_set_reg(state, 0x62, 0x2c);
++ _mt_fe_tn_set_reg(state, 0x63, 0x54);
++ _mt_fe_tn_set_reg(state, 0x68, 0x0b);
++ _mt_fe_tn_set_reg(state, 0x14, 0x00);
++ } else { /* CTTB */
++ _mt_fe_tn_set_reg(state, 0x66, 0x74);
++ _mt_fe_tn_set_reg(state, 0x62, 0x0c);
++ _mt_fe_tn_set_reg(state, 0x63, 0x54);
++ _mt_fe_tn_set_reg(state, 0x68, 0x0b);
++ _mt_fe_tn_set_reg(state, 0x14, 0x05);
++ }
++ _mt_fe_tn_set_reg(state, 0x6f, 0x00);
++ _mt_fe_tn_set_reg(state, 0x84, 0x04);
++ _mt_fe_tn_set_reg(state, 0x5e, 0xbe);
++ _mt_fe_tn_set_reg(state, 0x87, 0x07);
++ _mt_fe_tn_set_reg(state, 0x8a, 0x1f);
++ _mt_fe_tn_set_reg(state, 0x8b, 0x1f);
++ _mt_fe_tn_set_reg(state, 0x88, 0x30);
++ _mt_fe_tn_set_reg(state, 0x58, 0x34);
++ _mt_fe_tn_set_reg(state, 0x61, 0x8c);
++ _mt_fe_tn_set_reg(state, 0x6a, 0x42);
++ }
++ return 0;
++}
++
++static int mt_fe_tn_wakeup_tc2800(struct m88dc2800_state *state)
++{
++ _mt_fe_tn_set_reg(state, 0x16, 0xb1);
++ _mt_fe_tn_set_reg(state, 0x09, 0x7d);
++ return 0;
++}
++
++ static int mt_fe_tn_sleep_tc2800(struct m88dc2800_state *state)
++{
++ _mt_fe_tn_set_reg(state, 0x16, 0xb0);
++ _mt_fe_tn_set_reg(state, 0x09, 0x6d);
++ return 0;
++}
++
++ static int mt_fe_tn_init_tc2800(struct m88dc2800_state *state)
++{
++ if (state->tuner_init_OK != 1) {
++ state->tuner_dev_addr = 0x61; /* TUNER_I2C_ADDR_TC2800 */
++ state->tuner_freq = 650000;
++ state->tuner_qam = 0;
++ state->tuner_mode = 0; // 0: DVB-C, 1: CTTB
++ state->tuner_bandwidth = 8;
++ state->tuner_loopthrough = 0;
++ state->tuner_crystal = 24000;
++ state->tuner_dac = 7200;
++ state->tuner_mtt = 0x00;
++ state->tuner_custom_cfg = 0;
++ state->tuner_version = 30022; /* Driver version number */
++ state->tuner_time = 12092611;
++ state->tuner_init_OK = 1;
++ }
++ _mt_fe_tn_set_reg(state, 0x2b, 0x46);
++ _mt_fe_tn_set_reg(state, 0x2c, 0x75);
++ if (state->tuner_mtt == 0x00) {
++ u8 tmp = 0;
++ _mt_fe_tn_get_reg(state, 0x01, &tmp);
++ printk(KERN_INFO "m88dc2800: tuner id = 0x%02x ", tmp);
++ switch (tmp) {
++ case 0x0d:
++ state->tuner_mtt = 0xD1;
++ break;
++ case 0x8e:
++ default:
++ state->tuner_mtt = 0xE1;
++ break;
++ }
++ }
++ return 0;
++}
++
++ static int mt_fe_tn_set_freq_tc2800(struct m88dc2800_state *state,
++ u32 freq_KHz)
++{
++ u8 buf;
++ u8 buf1;
++
++ mt_fe_tn_init_tc2800(state);
++ state->tuner_freq = freq_KHz;
++ _mt_fe_tn_set_reg(state, 0x21, freq_KHz > 500000 ? 0xb9 : 0x99);
++ mt_fe_tn_wakeup_tc2800(state);
++ _mt_fe_tn_set_reg(state, 0x05, 0x7f);
++ _mt_fe_tn_set_reg(state, 0x06, 0xf8);
++ _mt_fe_tn_set_RF_front_tc2800(state);
++ _mt_fe_tn_set_PLL_freq_tc2800(state);
++ _mt_fe_tn_set_DAC_tc2800(state);
++ _mt_fe_tn_set_BB_tc2800(state);
++ _mt_fe_tn_preset_tc2800(state);
++ _mt_fe_tn_set_reg(state, 0x05, 0x00);
++ _mt_fe_tn_set_reg(state, 0x06, 0x00);
++ if (state->tuner_mtt == 0xD1) {
++ _mt_fe_tn_set_reg(state, 0x00, 0x01);
++ _mt_fe_tn_set_reg(state, 0x00, 0x00);
++ msleep(5);
++ _mt_fe_tn_set_reg(state, 0x41, 0x00);
++ msleep(5);
++ _mt_fe_tn_set_reg(state, 0x41, 0x02);
++
++ _mt_fe_tn_get_reg(state, 0x69, &buf1);
++ buf1 = buf1 & 0x0f;
++ _mt_fe_tn_get_reg(state, 0x61, &buf);
++ buf = buf & 0x0f;
++ if (buf == 0x0c)
++ _mt_fe_tn_set_reg(state, 0x6a, 0x59);
++ if (buf1 > 0x02) {
++ if (freq_KHz > 600000)
++ _mt_fe_tn_set_reg(state, 0x66, 0x44);
++ else if (freq_KHz > 500000)
++ _mt_fe_tn_set_reg(state, 0x66, 0x64);
++ else
++ _mt_fe_tn_set_reg(state, 0x66, 0x74);
++ }
++ if (buf1 < 0x03) {
++ if (freq_KHz > 800000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x64);
++ else if (freq_KHz > 600000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x54);
++ else if (freq_KHz > 500000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x54);
++ else if (freq_KHz > 300000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x43);
++ else if (freq_KHz > 220000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x54);
++ else if (freq_KHz > 110000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x14);
++ else
++ _mt_fe_tn_set_reg(state, 0x87, 0x54);
++ msleep(5);
++ } else if (buf < 0x0c) {
++ if (freq_KHz > 800000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x14);
++ else if (freq_KHz > 600000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x14);
++ else if (freq_KHz > 500000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x34);
++ else if (freq_KHz > 300000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x43);
++ else if (freq_KHz > 220000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x54);
++ else if (freq_KHz > 110000)
++ _mt_fe_tn_set_reg(state, 0x87, 0x14);
++ else
++ _mt_fe_tn_set_reg(state, 0x87, 0x54);
++ msleep(5);
++ }
++ } else if ((state->tuner_mtt == 0xE1)) {
++ _mt_fe_tn_set_reg(state, 0x00, 0x01);
++ _mt_fe_tn_set_reg(state, 0x00, 0x00);
++ msleep(20);
++ _mt_fe_tn_get_reg(state, 0x32, &buf);
++ buf = (buf & 0xef) | 0x28;
++ _mt_fe_tn_set_reg(state, 0x32, buf);
++ msleep(50);
++ _mt_fe_tn_get_reg(state, 0x38, &buf);
++ _mt_fe_tn_set_reg(state, 0x38, buf);
++ _mt_fe_tn_get_reg(state, 0x32, &buf);
++ buf = (buf & 0xf7) | 0x10;
++ _mt_fe_tn_set_reg(state, 0x32, buf);
++ msleep(10);
++ _mt_fe_tn_get_reg(state, 0x69, &buf);
++ buf = buf & 0x03;
++ _mt_fe_tn_set_reg(state, 0x2a, buf);
++ if (buf > 0) {
++ msleep(20);
++ _mt_fe_tn_get_reg(state, 0x84, &buf);
++ buf = buf & 0x1f;
++ _mt_fe_tn_set_reg(state, 0x68, 0x0a);
++ _mt_fe_tn_get_reg(state, 0x88, &buf1);
++ buf1 = buf1 & 0x1f;
++ if (buf <= buf1)
++ _mt_fe_tn_set_reg(state, 0x66, 0x44);
++ else
++ _mt_fe_tn_set_reg(state, 0x66, 0x74);
++ } else {
++ if (freq_KHz <= 600000)
++ _mt_fe_tn_set_reg(state, 0x68, 0x0c);
++ else
++ _mt_fe_tn_set_reg(state, 0x68, 0x0e);
++ _mt_fe_tn_set_reg(state, 0x30, 0xfb);
++ _mt_fe_tn_set_reg(state, 0x30, 0xff);
++ _mt_fe_tn_set_reg(state, 0x31, 0x04);
++ _mt_fe_tn_set_reg(state, 0x31, 0x00);
++ }
++ if (state->tuner_loopthrough != 0) {
++ _mt_fe_tn_get_reg(state, 0x28, &buf);
++ if (buf == 0) {
++ _mt_fe_tn_set_reg(state, 0x28, 0xff);
++ _mt_fe_tn_get_reg(state, 0x61, &buf);
++ buf = buf & 0x0f;
++ if (buf > 9)
++ _mt_fe_tn_set_reg(state, 0x67, 0x74);
++ else if (buf > 6)
++ _mt_fe_tn_set_reg(state, 0x67, 0x64);
++ else if (buf > 3)
++ _mt_fe_tn_set_reg(state, 0x67, 0x54);
++ else
++ _mt_fe_tn_set_reg(state, 0x67, 0x44);
++ }
++ } else {
++ _mt_fe_tn_set_reg(state, 0x67, 0x34);
++ }
++ } else {
++ return 1;
++ }
++ return 0;
++}
++
++
++/*
++static int mt_fe_tn_set_BB_filter_band_tc2800(struct m88dc2800_state *state,
++ u8 bandwidth)
++{
++ u8 buf, tmp;
++
++ _mt_fe_tn_get_reg(state, 0x53, &tmp);
++
++ if (bandwidth == 6)
++ buf = 0x01 << 1;
++ else if (bandwidth == 7)
++ buf = 0x02 << 1;
++ else if (bandwidth == 8)
++ buf = 0x04 << 1;
++ else
++ buf = 0x04 << 1;
++
++ tmp &= 0xf1;
++ tmp |= buf;
++ _mt_fe_tn_set_reg(state, 0x53, tmp);
++ state->tuner_bandwidth = bandwidth;
++ return 0;
++}
++*/
++
++static s32 mt_fe_tn_get_signal_strength_tc2800(struct m88dc2800_state
++ *state)
++{
++ s32 level = -107;
++ s32 tmp1, tmp2, tmp3, tmp4, tmp5, tmp6;
++ s32 val1, val2, val;
++ s32 result2, result3, result4, result5, result6;
++ s32 append;
++ u8 tmp;
++ s32 freq_KHz = (s32) state->tuner_freq;
++ if (state->tuner_mtt == 0xD1) {
++ _mt_fe_tn_get_reg(state, 0x61, &tmp);
++ tmp1 = tmp & 0x0f;
++ _mt_fe_tn_get_reg(state, 0x69, &tmp);
++ tmp2 = tmp & 0x0f;
++ _mt_fe_tn_get_reg(state, 0x73, &tmp);
++ tmp3 = tmp & 0x07;
++ _mt_fe_tn_get_reg(state, 0x7c, &tmp);
++ tmp4 = (tmp >> 4) & 0x0f;
++ _mt_fe_tn_get_reg(state, 0x7b, &tmp);
++ tmp5 = tmp & 0x0f;
++ _mt_fe_tn_get_reg(state, 0x7f, &tmp);
++ tmp6 = (tmp >> 5) & 0x01;
++ if (tmp1 > 6) {
++ val1 = 0;
++ if (freq_KHz <= 200000) {
++ val2 = (tmp1 - 6) * 267;
++ } else if (freq_KHz <= 600000) {
++ val2 = (tmp1 - 6) * 280;
++ } else {
++ val2 = (tmp1 - 6) * 290;
++ }
++ val = val1 + val2;
++ } else {
++ if (tmp1 == 0) {
++ val1 = -550;
++ } else {
++ val1 = 0;
++ }
++ if ((tmp1 < 4) && (freq_KHz >= 506000)) {
++ val1 = -850;
++ }
++ val2 = 0;
++ val = val1 + val2;
++ }
++ if (freq_KHz <= 95000) {
++ result2 = tmp2 * 289;
++ } else if (freq_KHz <= 155000) {
++ result2 = tmp2 * 278;
++ } else if (freq_KHz <= 245000) {
++ result2 = tmp2 * 267;
++ } else if (freq_KHz <= 305000) {
++ result2 = tmp2 * 256;
++ } else if (freq_KHz <= 335000) {
++ result2 = tmp2 * 244;
++ } else if (freq_KHz <= 425000) {
++ result2 = tmp2 * 233;
++ } else if (freq_KHz <= 575000) {
++ result2 = tmp2 * 222;
++ } else if (freq_KHz <= 665000) {
++ result2 = tmp2 * 211;
++ } else {
++ result2 = tmp2 * 200;
++ }
++ result3 = (6 - tmp3) * 100;
++ result4 = 300 * tmp4;
++ result5 = 50 * tmp5;
++ result6 = 300 * tmp6;
++ if (freq_KHz < 105000) {
++ append = -450;
++ } else if (freq_KHz <= 227000) {
++ append = -4 * (freq_KHz / 1000 - 100) + 150;
++ } else if (freq_KHz <= 305000) {
++ append = -4 * (freq_KHz / 1000 - 100);
++ } else if (freq_KHz <= 419000) {
++ append = 500 - 40 * (freq_KHz / 1000 - 300) / 17 + 130;
++ } else if (freq_KHz <= 640000) {
++ append = 500 - 40 * (freq_KHz / 1000 - 300) / 17;
++ } else {
++ append = -500;
++ }
++ level = append - (val + result2 + result3 + result4 +
++ result5 + result6);
++ level /= 100;
++ } else if (state->tuner_mtt == 0xE1) {
++ _mt_fe_tn_get_reg(state, 0x61, &tmp);
++ tmp1 = tmp & 0x0f;
++ _mt_fe_tn_get_reg(state, 0x84, &tmp);
++ tmp2 = tmp & 0x1f;
++ _mt_fe_tn_get_reg(state, 0x69, &tmp);
++ tmp3 = tmp & 0x03;
++ _mt_fe_tn_get_reg(state, 0x73, &tmp);
++ tmp4 = tmp & 0x0f;
++ _mt_fe_tn_get_reg(state, 0x7c, &tmp);
++ tmp5 = (tmp >> 4) & 0x0f;
++ _mt_fe_tn_get_reg(state, 0x7b, &tmp);
++ tmp6 = tmp & 0x0f;
++ if (freq_KHz < 151000) {
++ result2 = (1150 - freq_KHz / 100) * 163 / 33 + 4230;
++ result3 = (1150 - freq_KHz / 100) * 115 / 33 + 1850;
++ result4 = -3676 * (freq_KHz / 1000) / 100 + 6115;
++ } else if (freq_KHz < 257000) {
++ result2 = (1540 - freq_KHz / 100) * 11 / 4 + 3870;
++ result3 = (1540 - freq_KHz / 100) * 205 / 96 + 2100;
++ result4 = -21 * freq_KHz / 1000 + 5084;
++ } else if (freq_KHz < 305000) {
++ result2 = (2620 - freq_KHz / 100) * 5 / 3 + 2770;
++ result3 = (2620 - freq_KHz / 100) * 10 / 7 + 1700;
++ result4 = 650;
++ } else if (freq_KHz < 449000) {
++ result2 = (307 - freq_KHz / 1000) * 82 / 27 + 11270;
++ result3 = (3100 - freq_KHz / 100) * 5 / 3 + 10000;
++ result4 = 134 * freq_KHz / 10000 + 11875;
++ } else {
++ result2 = (307 - freq_KHz / 1000) * 82 / 27 + 11270;
++ result3 = 8400;
++ result4 = 5300;
++ }
++ if (tmp1 > 6) {
++ val1 = result2;
++ val2 = 2900;
++ val = 500;
++ } else if (tmp1 > 0) {
++ val1 = result3;
++ val2 = 2700;
++ val = 500;
++ } else {
++ val1 = result4;
++ val2 = 2700;
++ val = 400;
++ }
++ level = val1 - (val2 * tmp1 + 500 * tmp2 + 3000 * tmp3 -
++ 500 * tmp4 + 3000 * tmp5 + val * tmp6) - 1000;
++ level /= 1000;
++ }
++ return level;
++}
++
++
++/* m88dc2800 operation functions */
++u8 M88DC2000GetLock(struct m88dc2800_state * state)
++{
++ u8 u8ret = 0;
++ if (ReadReg(state, 0x80) < 0x06) {
++ if ((ReadReg(state, 0xdf) & 0x80) == 0x80
++ &&(ReadReg(state, 0x91) & 0x23) == 0x03
++ &&(ReadReg(state, 0x43) & 0x08) == 0x08)
++ u8ret = 1;
++ else
++ u8ret = 0;
++ } else {
++ if ((ReadReg(state, 0x85) & 0x08) == 0x08)
++ u8ret = 1;
++ else
++ u8ret = 0;
++ }
++ dprintk("%s, lock=%d\n", __func__, u8ret);
++ return u8ret;
++}
++
++static int M88DC2000SetTsType(struct m88dc2800_state *state, u8 type)
++{
++ u8 regC2H;
++
++ if (type == 3) {
++ WriteReg(state, 0x84, 0x6A);
++ WriteReg(state, 0xC0, 0x43);
++ WriteReg(state, 0xE2, 0x06);
++ regC2H = ReadReg(state, 0xC2);
++ regC2H &= 0xC0;
++ regC2H |= 0x1B;
++ WriteReg(state, 0xC2, regC2H);
++ WriteReg(state, 0xC1, 0x60); /* common interface */
++ } else if (type == 1) {
++ WriteReg(state, 0x84, 0x6A);
++ WriteReg(state, 0xC0, 0x47); /* serial format */
++ WriteReg(state, 0xE2, 0x02);
++ regC2H = ReadReg(state, 0xC2);
++ regC2H &= 0xC7;
++ WriteReg(state, 0xC2, regC2H);
++ WriteReg(state, 0xC1, 0x00);
++ } else {
++ WriteReg(state, 0x84, 0x6C);
++ WriteReg(state, 0xC0, 0x43); /* parallel format */
++ WriteReg(state, 0xE2, 0x06);
++ regC2H = ReadReg(state, 0xC2);
++ regC2H &= 0xC7;
++ WriteReg(state, 0xC2, regC2H);
++ WriteReg(state, 0xC1, 0x00);
++ }
++ return 0;
++}
++
++static int M88DC2000RegInitial_TC2800(struct m88dc2800_state *state)
++{
++ u8 RegE3H, RegE4H;
++
++ WriteReg(state, 0x00, 0x48);
++ WriteReg(state, 0x01, 0x09);
++ WriteReg(state, 0xFB, 0x0A);
++ WriteReg(state, 0xFC, 0x0B);
++ WriteReg(state, 0x02, 0x0B);
++ WriteReg(state, 0x03, 0x18);
++ WriteReg(state, 0x05, 0x0D);
++ WriteReg(state, 0x36, 0x80);
++ WriteReg(state, 0x43, 0x40);
++ WriteReg(state, 0x55, 0x7A);
++ WriteReg(state, 0x56, 0xD9);
++ WriteReg(state, 0x57, 0xDF);
++ WriteReg(state, 0x58, 0x39);
++ WriteReg(state, 0x5A, 0x00);
++ WriteReg(state, 0x5C, 0x71);
++ WriteReg(state, 0x5D, 0x23);
++ WriteReg(state, 0x86, 0x40);
++ WriteReg(state, 0xF9, 0x08);
++ WriteReg(state, 0x61, 0x40);
++ WriteReg(state, 0x62, 0x0A);
++ WriteReg(state, 0x90, 0x06);
++ WriteReg(state, 0xDE, 0x00);
++ WriteReg(state, 0xA0, 0x03);
++ WriteReg(state, 0xDF, 0x81);
++ WriteReg(state, 0xFA, 0x40);
++ WriteReg(state, 0x37, 0x10);
++ WriteReg(state, 0xF0, 0x40);
++ WriteReg(state, 0xF2, 0x9C);
++ WriteReg(state, 0xF3, 0x40);
++ RegE3H = ReadReg(state, 0xE3);
++ RegE4H = ReadReg(state, 0xE4);
++ if (((RegE3H & 0xC0) == 0x00) && ((RegE4H & 0xC0) == 0x00)) {
++ WriteReg(state, 0x30, 0xFF);
++ WriteReg(state, 0x31, 0x00);
++ WriteReg(state, 0x32, 0x00);
++ WriteReg(state, 0x33, 0x00);
++ WriteReg(state, 0x35, 0x32);
++ WriteReg(state, 0x40, 0x00);
++ WriteReg(state, 0x41, 0x10);
++ WriteReg(state, 0xF1, 0x02);
++ WriteReg(state, 0xF4, 0x04);
++ WriteReg(state, 0xF5, 0x00);
++ WriteReg(state, 0x42, 0x14);
++ WriteReg(state, 0xE1, 0x25);
++ } else if (((RegE3H & 0xC0) == 0x80) && ((RegE4H & 0xC0) == 0x40)) {
++ WriteReg(state, 0x30, 0xFF);
++ WriteReg(state, 0x31, 0x00);
++ WriteReg(state, 0x32, 0x00);
++ WriteReg(state, 0x33, 0x00);
++ WriteReg(state, 0x35, 0x32);
++ WriteReg(state, 0x39, 0x00);
++ WriteReg(state, 0x3A, 0x00);
++ WriteReg(state, 0x40, 0x00);
++ WriteReg(state, 0x41, 0x10);
++ WriteReg(state, 0xF1, 0x00);
++ WriteReg(state, 0xF4, 0x00);
++ WriteReg(state, 0xF5, 0x40);
++ WriteReg(state, 0x42, 0x14);
++ WriteReg(state, 0xE1, 0x25);
++ } else if ((RegE3H == 0x80 || RegE3H == 0x81)
++ && (RegE4H == 0x80 || RegE4H == 0x81)) {
++ WriteReg(state, 0x30, 0xFF);
++ WriteReg(state, 0x31, 0x00);
++ WriteReg(state, 0x32, 0x00);
++ WriteReg(state, 0x33, 0x00);
++ WriteReg(state, 0x35, 0x32);
++ WriteReg(state, 0x39, 0x00);
++ WriteReg(state, 0x3A, 0x00);
++ WriteReg(state, 0xF1, 0x00);
++ WriteReg(state, 0xF4, 0x00);
++ WriteReg(state, 0xF5, 0x40);
++ WriteReg(state, 0x42, 0x24);
++ WriteReg(state, 0xE1, 0x25);
++ WriteReg(state, 0x92, 0x7F);
++ WriteReg(state, 0x93, 0x91);
++ WriteReg(state, 0x95, 0x00);
++ WriteReg(state, 0x2B, 0x33);
++ WriteReg(state, 0x2A, 0x2A);
++ WriteReg(state, 0x2E, 0x80);
++ WriteReg(state, 0x25, 0x25);
++ WriteReg(state, 0x2D, 0xFF);
++ WriteReg(state, 0x26, 0xFF);
++ WriteReg(state, 0x27, 0x00);
++ WriteReg(state, 0x24, 0x25);
++ WriteReg(state, 0xA4, 0xFF);
++ WriteReg(state, 0xA3, 0x0D);
++ } else {
++ WriteReg(state, 0x30, 0xFF);
++ WriteReg(state, 0x31, 0x00);
++ WriteReg(state, 0x32, 0x00);
++ WriteReg(state, 0x33, 0x00);
++ WriteReg(state, 0x35, 0x32);
++ WriteReg(state, 0x39, 0x00);
++ WriteReg(state, 0x3A, 0x00);
++ WriteReg(state, 0xF1, 0x00);
++ WriteReg(state, 0xF4, 0x00);
++ WriteReg(state, 0xF5, 0x40);
++ WriteReg(state, 0x42, 0x24);
++ WriteReg(state, 0xE1, 0x27);
++ WriteReg(state, 0x92, 0x7F);
++ WriteReg(state, 0x93, 0x91);
++ WriteReg(state, 0x95, 0x00);
++ WriteReg(state, 0x2B, 0x33);
++ WriteReg(state, 0x2A, 0x2A);
++ WriteReg(state, 0x2E, 0x80);
++ WriteReg(state, 0x25, 0x25);
++ WriteReg(state, 0x2D, 0xFF);
++ WriteReg(state, 0x26, 0xFF);
++ WriteReg(state, 0x27, 0x00);
++ WriteReg(state, 0x24, 0x25);
++ WriteReg(state, 0xA4, 0xFF);
++ WriteReg(state, 0xA3, 0x10);
++ }
++ WriteReg(state, 0xF6, 0x4E);
++ WriteReg(state, 0xF7, 0x20);
++ WriteReg(state, 0x89, 0x02);
++ WriteReg(state, 0x14, 0x08);
++ WriteReg(state, 0x6F, 0x0D);
++ WriteReg(state, 0x10, 0xFF);
++ WriteReg(state, 0x11, 0x00);
++ WriteReg(state, 0x12, 0x30);
++ WriteReg(state, 0x13, 0x23);
++ WriteReg(state, 0x60, 0x00);
++ WriteReg(state, 0x69, 0x00);
++ WriteReg(state, 0x6A, 0x03);
++ WriteReg(state, 0xE0, 0x75);
++ WriteReg(state, 0x8D, 0x29);
++ WriteReg(state, 0x4E, 0xD8);
++ WriteReg(state, 0x88, 0x80);
++ WriteReg(state, 0x52, 0x79);
++ WriteReg(state, 0x53, 0x03);
++ WriteReg(state, 0x59, 0x30);
++ WriteReg(state, 0x5E, 0x02);
++ WriteReg(state, 0x5F, 0x0F);
++ WriteReg(state, 0x71, 0x03);
++ WriteReg(state, 0x72, 0x12);
++ WriteReg(state, 0x73, 0x12);
++
++ return 0;
++}
++
++static int M88DC2000AutoTSClock_P(struct m88dc2800_state *state, u32 sym,
++ u16 qam)
++{
++ u32 dataRate;
++ u8 clk_div, value;
++ printk(KERN_INFO
++ "m88dc2800: M88DC2000AutoTSClock_P, symrate=%d qam=%d\n",
++ sym, qam);
++ switch (qam) {
++ case 16:
++ dataRate = 4;
++ break;
++ case 32:
++ dataRate = 5;
++ break;
++ case 128:
++ dataRate = 7;
++ break;
++ case 256:
++ dataRate = 8;
++ break;
++ case 64:
++ default:
++ dataRate = 6;
++ break;
++ }
++ dataRate *= sym * 105;
++ dataRate /= 800;
++ if (dataRate <= 4115)
++ clk_div = 0x05;
++ else if (dataRate <= 4800)
++ clk_div = 0x04;
++ else if (dataRate <= 5760)
++ clk_div = 0x03;
++ else if (dataRate <= 7200)
++ clk_div = 0x02;
++ else if (dataRate <= 9600)
++ clk_div = 0x01;
++ else
++ clk_div = 0x00;
++ value = ReadReg(state, 0xC2);
++ value &= 0xc0;
++ value |= clk_div;
++ WriteReg(state, 0xC2, value);
++ return 0;
++}
++
++static int M88DC2000AutoTSClock_C(struct m88dc2800_state *state, u32 sym,
++ u16 qam)
++{
++ u32 dataRate;
++ u8 clk_div, value;
++ printk(KERN_INFO
++ "m88dc2800: M88DC2000AutoTSClock_C, symrate=%d qam=%d\n",
++ sym, qam);
++ switch (qam) {
++ case 16:
++ dataRate = 4;
++ break;
++ case 32:
++ dataRate = 5;
++ break;
++ case 128:
++ dataRate = 7;
++ break;
++ case 256:
++ dataRate = 8;
++ break;
++ case 64:
++ default:
++ dataRate = 6;
++ break;
++ }
++ dataRate *= sym * 105;
++ dataRate /= 800;
++ if (dataRate <= 4115)
++ clk_div = 0x3F;
++ else if (dataRate <= 4800)
++ clk_div = 0x36;
++ else if (dataRate <= 5760)
++ clk_div = 0x2D;
++ else if (dataRate <= 7200)
++ clk_div = 0x24;
++ else if (dataRate <= 9600)
++ clk_div = 0x1B;
++ else
++ clk_div = 0x12;
++ value = ReadReg(state, 0xC2);
++ value &= 0xc0;
++ value |= clk_div;
++ WriteReg(state, 0xC2, value);
++ return 0;
++}
++
++static int M88DC2000SetTxMode(struct m88dc2800_state *state, u8 inverted,
++ u8 j83)
++{
++ u8 value = 0;
++ if (inverted)
++ value |= 0x08; /* spectrum inverted */
++ if (j83)
++ value |= 0x01; /* J83C */
++ WriteReg(state, 0x83, value);
++ return 0;
++}
++
++static int M88DC2000SoftReset(struct m88dc2800_state *state)
++{
++ WriteReg(state, 0x80, 0x01);
++ WriteReg(state, 0x82, 0x00);
++ msleep(1);
++ WriteReg(state, 0x80, 0x00);
++ return 0;
++}
++
++static int M88DC2000SetSym(struct m88dc2800_state *state, u32 sym, u32 xtal)
++{
++ u8 value;
++ u8 reg6FH, reg12H;
++ u64 fValue;
++ u32 dwValue;
++
++ printk(KERN_INFO "%s, sym=%d, xtal=%d\n", __func__, sym, xtal);
++ fValue = 4294967296 * (sym + 10);
++ do_div(fValue, xtal);
++
++ /* fValue = 4294967296 * (sym + 10) / xtal; */
++ dwValue = (u32) fValue;
++ printk(KERN_INFO "%s, fvalue1=%x\n", __func__, dwValue);
++ WriteReg(state, 0x58, (u8) ((dwValue >> 24) & 0xff));
++ WriteReg(state, 0x57, (u8) ((dwValue >> 16) & 0xff));
++ WriteReg(state, 0x56, (u8) ((dwValue >> 8) & 0xff));
++ WriteReg(state, 0x55, (u8) ((dwValue >> 0) & 0xff));
++
++ /* fValue = 2048 * xtal / sym; */
++ fValue = 2048 * xtal;
++ do_div(fValue, sym);
++ dwValue = (u32) fValue;
++ printk(KERN_INFO "%s, fvalue2=%x\n", __func__, dwValue);
++ WriteReg(state, 0x5D, (u8) ((dwValue >> 8) & 0xff));
++ WriteReg(state, 0x5C, (u8) ((dwValue >> 0) & 0xff));
++ value = ReadReg(state, 0x5A);
++ if (((dwValue >> 16) & 0x0001) == 0)
++ value &= 0x7F;
++ else
++ value |= 0x80;
++ WriteReg(state, 0x5A, value);
++ value = ReadReg(state, 0x89);
++ if (sym <= 1800)
++ value |= 0x01;
++ else
++ value &= 0xFE;
++ WriteReg(state, 0x89, value);
++ if (sym >= 6700) {
++ reg6FH = 0x0D;
++ reg12H = 0x30;
++ } else if (sym >= 4000) {
++ fValue = 22 * 4096 / sym;
++ reg6FH = (u8) fValue;
++ reg12H = 0x30;
++ } else if (sym >= 2000) {
++ fValue = 14 * 4096 / sym;
++ reg6FH = (u8) fValue;
++ reg12H = 0x20;
++ } else {
++ fValue = 7 * 4096 / sym;
++ reg6FH = (u8) fValue;
++ reg12H = 0x10;
++ }
++ WriteReg(state, 0x6F, reg6FH);
++ WriteReg(state, 0x12, reg12H);
++ if (((ReadReg(state, 0xE3) & 0x80) == 0x80)
++ && ((ReadReg(state, 0xE4) & 0x80) == 0x80)) {
++ if (sym < 3000) {
++ WriteReg(state, 0x6C, 0x16);
++ WriteReg(state, 0x6D, 0x10);
++ WriteReg(state, 0x6E, 0x18);
++ } else {
++ WriteReg(state, 0x6C, 0x14);
++ WriteReg(state, 0x6D, 0x0E);
++ WriteReg(state, 0x6E, 0x36);
++ }
++ } else {
++ WriteReg(state, 0x6C, 0x16);
++ WriteReg(state, 0x6D, 0x10);
++ WriteReg(state, 0x6E, 0x18);
++ }
++ return 0;
++}
++
++static int M88DC2000SetQAM(struct m88dc2800_state *state, u16 qam)
++{
++ u8 reg00H, reg4AH, regC2H, reg44H, reg4CH, reg4DH, reg74H, value;
++ u8 reg8BH, reg8EH;
++ printk(KERN_INFO "%s, qam=%d\n", __func__, qam);
++ regC2H = ReadReg(state, 0xC2);
++ regC2H &= 0xF8;
++ switch (qam) {
++ case 16: /* 16 QAM */
++ reg00H = 0x08;
++ reg4AH = 0x0F;
++ regC2H |= 0x02;
++ reg44H = 0xAA;
++ reg4CH = 0x0C;
++ reg4DH = 0xF7;
++ reg74H = 0x0E;
++ if (((ReadReg(state, 0xE3) & 0x80) == 0x80)
++ && ((ReadReg(state, 0xE4) & 0x80) == 0x80)) {
++ reg8BH = 0x5A;
++ reg8EH = 0xBD;
++ } else {
++ reg8BH = 0x5B;
++ reg8EH = 0x9D;
++ }
++ WriteReg(state, 0x6E, 0x18);
++ break;
++ case 32: /* 32 QAM */
++ reg00H = 0x18;
++ reg4AH = 0xFB;
++ regC2H |= 0x02;
++ reg44H = 0xAA;
++ reg4CH = 0x0C;
++ reg4DH = 0xF7;
++ reg74H = 0x0E;
++ if (((ReadReg(state, 0xE3) & 0x80) == 0x80)
++ && ((ReadReg(state, 0xE4) & 0x80) == 0x80)) {
++ reg8BH = 0x5A;
++ reg8EH = 0xBD;
++ } else {
++ reg8BH = 0x5B;
++ reg8EH = 0x9D;
++ }
++ WriteReg(state, 0x6E, 0x18);
++ break;
++ case 64: /* 64 QAM */
++ reg00H = 0x48;
++ reg4AH = 0xCD;
++ regC2H |= 0x02;
++ reg44H = 0xAA;
++ reg4CH = 0x0C;
++ reg4DH = 0xF7;
++ reg74H = 0x0E;
++ if (((ReadReg(state, 0xE3) & 0x80) == 0x80)
++ && ((ReadReg(state, 0xE4) & 0x80) == 0x80)) {
++ reg8BH = 0x5A;
++ reg8EH = 0xBD;
++ } else {
++ reg8BH = 0x5B;
++ reg8EH = 0x9D;
++ }
++ break;
++ case 128: /* 128 QAM */
++ reg00H = 0x28;
++ reg4AH = 0xFF;
++ regC2H |= 0x02;
++ reg44H = 0xA9;
++ reg4CH = 0x08;
++ reg4DH = 0xF5;
++ reg74H = 0x0E;
++ reg8BH = 0x5B;
++ reg8EH = 0x9D;
++ break;
++ case 256: /* 256 QAM */
++ reg00H = 0x38;
++ reg4AH = 0xCD;
++ if (((ReadReg(state, 0xE3) & 0x80) == 0x80)
++ && ((ReadReg(state, 0xE4) & 0x80) == 0x80)) {
++ regC2H |= 0x02;
++ } else {
++ regC2H |= 0x01;
++ }
++ reg44H = 0xA9;
++ reg4CH = 0x08;
++ reg4DH = 0xF5;
++ reg74H = 0x0E;
++ reg8BH = 0x5B;
++ reg8EH = 0x9D;
++ break;
++ default: /* 64 QAM */
++ reg00H = 0x48;
++ reg4AH = 0xCD;
++ regC2H |= 0x02;
++ reg44H = 0xAA;
++ reg4CH = 0x0C;
++ reg4DH = 0xF7;
++ reg74H = 0x0E;
++ if (((ReadReg(state, 0xE3) & 0x80) == 0x80)
++ && ((ReadReg(state, 0xE4) & 0x80) == 0x80)) {
++ reg8BH = 0x5A;
++ reg8EH = 0xBD;
++ } else {
++ reg8BH = 0x5B;
++ reg8EH = 0x9D;
++ }
++ break;
++ }
++ WriteReg(state, 0x00, reg00H);
++ value = ReadReg(state, 0x88);
++ value |= 0x08;
++ WriteReg(state, 0x88, value);
++ WriteReg(state, 0x4B, 0xFF);
++ WriteReg(state, 0x4A, reg4AH);
++ value &= 0xF7;
++ WriteReg(state, 0x88, value);
++ WriteReg(state, 0xC2, regC2H);
++ WriteReg(state, 0x44, reg44H);
++ WriteReg(state, 0x4C, reg4CH);
++ WriteReg(state, 0x4D, reg4DH);
++ WriteReg(state, 0x74, reg74H);
++ WriteReg(state, 0x8B, reg8BH);
++ WriteReg(state, 0x8E, reg8EH);
++ return 0;
++}
++
++static int M88DC2000WriteTuner_TC2800(struct m88dc2800_state *state,
++ u32 freq_KHz)
++{
++ printk(KERN_INFO "%s, freq=%d KHz\n", __func__, freq_KHz);
++ return mt_fe_tn_set_freq_tc2800(state, freq_KHz);
++}
++
++static int m88dc2800_init(struct dvb_frontend *fe)
++{
++ dprintk("%s()\n", __func__);
++ return 0;
++}
++
++static int m88dc2800_set_parameters(struct dvb_frontend *fe)
++{
++ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
++ u8 is_annex_c, is_update;
++ u16 temp_qam;
++ s32 waiting_time;
++ struct m88dc2800_state *state = fe->demodulator_priv;
++
++ is_annex_c = c->delivery_system == SYS_DVBC_ANNEX_C ? 1 : 0;
++
++ switch (c->modulation) {
++ case QAM_16:
++ temp_qam = 16;
++ break;
++ case QAM_32:
++ temp_qam = 32;
++ break;
++ case QAM_128:
++ temp_qam = 128;
++ break;
++ case QAM_256:
++ temp_qam = 256;
++ break;
++ default: /* QAM_64 */
++ temp_qam = 64;
++ break;
++ }
++
++ state->inverted = c->inversion == INVERSION_ON ? 1 : 0;
++
++ printk(KERN_INFO
++ "m88dc2800: state, freq=%d qam=%d sym=%d inverted=%d xtal=%d\n",
++ state->freq, state->qam, state->sym, state->inverted,
++ state->xtal);
++ printk(KERN_INFO
++ "m88dc2800: set frequency to %d qam=%d symrate=%d annex-c=%d\n",
++ c->frequency, temp_qam, c->symbol_rate, is_annex_c);
++
++ is_update = 0;
++ WriteReg(state, 0x80, 0x01);
++ if (c->frequency != state->freq) {
++ M88DC2000WriteTuner_TC2800(state, c->frequency / 1000);
++ state->freq = c->frequency;
++ }
++ if (c->symbol_rate != state->sym) {
++ M88DC2000SetSym(state, c->symbol_rate / 1000, state->xtal);
++ state->sym = c->symbol_rate;
++ is_update = 1;
++ }
++ if (temp_qam != state->qam) {
++ M88DC2000SetQAM(state, temp_qam);
++ state->qam = temp_qam;
++ is_update = 1;
++ }
++
++ if (is_update != 0) {
++ if (state->config->ts_mode == 3)
++ M88DC2000AutoTSClock_C(state, state->sym / 1000,
++ temp_qam);
++ else
++ M88DC2000AutoTSClock_P(state, state->sym / 1000,
++ temp_qam);
++ }
++
++ M88DC2000SetTxMode(state, state->inverted, is_annex_c);
++ M88DC2000SoftReset(state);
++ if (((ReadReg(state, 0xE3) & 0x80) == 0x80)
++ && ((ReadReg(state, 0xE4) & 0x80) == 0x80))
++ waiting_time = 800;
++ else
++ waiting_time = 500;
++ while (waiting_time > 0) {
++ msleep(50);
++ waiting_time -= 50;
++ if (M88DC2000GetLock(state))
++ return 0;
++ }
++
++ state->inverted = (state->inverted != 0) ? 0 : 1;
++ M88DC2000SetTxMode(state, state->inverted, is_annex_c);
++ M88DC2000SoftReset(state);
++ if (((ReadReg(state, 0xE3) & 0x80) == 0x80) &&
++ ((ReadReg(state, 0xE4) & 0x80) == 0x80))
++ waiting_time = 800;
++ else
++ waiting_time = 500;
++ while (waiting_time > 0) {
++ msleep(50);
++ waiting_time -= 50;
++ if (M88DC2000GetLock(state))
++ return 0;
++ }
++ return 0;
++}
++
++static int m88dc2800_read_status(struct dvb_frontend *fe,
++ fe_status_t * status)
++{
++ struct m88dc2800_state *state = fe->demodulator_priv;
++ *status = 0;
++
++ if (M88DC2000GetLock(state)) {
++ *status = FE_HAS_SIGNAL | FE_HAS_CARRIER
++ |FE_HAS_SYNC | FE_HAS_VITERBI | FE_HAS_LOCK;
++ }
++ return 0;
++}
++
++static int m88dc2800_read_ber(struct dvb_frontend *fe, u32 * ber)
++{
++ struct m88dc2800_state *state = fe->demodulator_priv;
++ u16 tmp;
++
++ if (M88DC2000GetLock(state) == 0) {
++ state->ber = 0;
++ } else if ((ReadReg(state, 0xA0) & 0x80) != 0x80) {
++ tmp = ReadReg(state, 0xA2) << 8;
++ tmp += ReadReg(state, 0xA1);
++ state->ber = tmp;
++ WriteReg(state, 0xA0, 0x05);
++ WriteReg(state, 0xA0, 0x85);
++ }
++ *ber = state->ber;
++ return 0;
++}
++
++static int m88dc2800_read_signal_strength(struct dvb_frontend *fe,
++ u16 * strength)
++{
++ struct m88dc2800_state *state = fe->demodulator_priv;
++ s16 tuner_strength;
++
++ tuner_strength = mt_fe_tn_get_signal_strength_tc2800(state);
++ *strength = tuner_strength < -107 ? 0 : tuner_strength + 107;
++
++ return 0;
++}
++
++static int m88dc2800_read_snr(struct dvb_frontend *fe, u16 * snr)
++{
++ static const u32 mes_log[] = {
++ 0, 3010, 4771, 6021, 6990, 7781, 8451, 9031, 9542, 10000,
++ 10414, 10792, 11139, 11461, 11761, 12041, 12304, 12553, 12788,
++ 13010, 13222, 13424, 13617, 13802, 13979, 14150, 14314, 14472,
++ 14624, 14771, 14914, 15052, 15185, 15315, 15441, 15563, 15682,
++ 15798, 15911, 16021, 16128, 16232, 16335, 16435, 16532, 16628,
++ 16721, 16812, 16902, 16990, 17076, 17160, 17243, 17324, 17404,
++ 17482, 17559, 17634, 17709, 17782, 17853, 17924, 17993, 18062,
++ 18129, 18195, 18261, 18325, 18388, 18451, 18513, 18573, 18633,
++ 18692, 18751, 18808, 18865, 18921, 18976, 19031
++ };
++ struct m88dc2800_state *state = fe->demodulator_priv;
++ u8 i;
++ u32 _snr, mse;
++
++ if ((ReadReg(state, 0x91) & 0x23) != 0x03) {
++ *snr = 0;
++ return 0;
++ }
++ mse = 0;
++ for (i = 0; i < 30; i++) {
++ mse += (ReadReg(state, 0x08) << 8) + ReadReg(state, 0x07);
++ }
++ mse /= 30;
++ if (mse > 80)
++ mse = 80;
++ switch (state->qam) {
++ case 16:
++ _snr = 34080;
++ break; /* 16QAM */
++ case 32:
++ _snr = 37600;
++ break; /* 32QAM */
++ case 64:
++ _snr = 40310;
++ break; /* 64QAM */
++ case 128:
++ _snr = 43720;
++ break; /* 128QAM */
++ case 256:
++ _snr = 46390;
++ break; /* 256QAM */
++ default:
++ _snr = 40310;
++ break;
++ }
++ _snr -= mes_log[mse - 1]; /* C - 10*log10(MSE) */
++ _snr /= 1000;
++ if (_snr > 0xff)
++ _snr = 0xff;
++ *snr = _snr;
++ return 0;
++}
++
++static int m88dc2800_read_ucblocks(struct dvb_frontend *fe, u32 * ucblocks)
++{
++ struct m88dc2800_state *state = fe->demodulator_priv;
++ u8 u8Value;
++
++ u8Value = ReadReg(state, 0xdf);
++ u8Value |= 0x02; /* Hold */
++ WriteReg(state, 0xdf, u8Value);
++
++ *ucblocks = ReadReg(state, 0xd5);
++ *ucblocks = (*ucblocks << 8) | ReadReg(state, 0xd4);
++
++ u8Value &= 0xfe; /* Clear */
++ WriteReg(state, 0xdf, u8Value);
++ u8Value &= 0xfc; /* Update */
++ u8Value |= 0x01;
++ WriteReg(state, 0xdf, u8Value);
++
++ return 0;
++}
++
++static int m88dc2800_sleep(struct dvb_frontend *fe)
++{
++ struct m88dc2800_state *state = fe->demodulator_priv;
++
++ mt_fe_tn_sleep_tc2800(state);
++ state->freq = 0;
++
++ return 0;
++}
++
++static void m88dc2800_release(struct dvb_frontend *fe)
++{
++ struct m88dc2800_state *state = fe->demodulator_priv;
++ kfree(state);
++}
++
++static struct dvb_frontend_ops m88dc2800_ops;
++
++struct dvb_frontend *m88dc2800_attach(const struct m88dc2800_config
++ *config, struct i2c_adapter *i2c)
++{
++ struct m88dc2800_state *state = NULL;
++
++ /* allocate memory for the internal state */
++ state = kzalloc(sizeof(struct m88dc2800_state), GFP_KERNEL);
++ if (state == NULL)
++ goto error;
++
++ /* setup the state */
++ state->config = config;
++ state->i2c = i2c;
++ state->xtal = 28800;
++
++ WriteReg(state, 0x80, 0x01);
++ M88DC2000RegInitial_TC2800(state);
++ M88DC2000SetTsType(state, state->config->ts_mode);
++ mt_fe_tn_init_tc2800(state);
++
++ /* create dvb_frontend */
++ memcpy(&state->frontend.ops, &m88dc2800_ops,
++ sizeof(struct dvb_frontend_ops));
++ state->frontend.demodulator_priv = state;
++ return &state->frontend;
++
++ error:
++ kfree(state);
++ return NULL;
++}
++
++EXPORT_SYMBOL(m88dc2800_attach);
++
++static struct dvb_frontend_ops m88dc2800_ops = {
++ .delsys = {SYS_DVBC_ANNEX_A, SYS_DVBC_ANNEX_C},
++ .info = {
++ .name = "Montage M88DC2800 DVB-C",
++ .frequency_stepsize = 62500,
++ .frequency_min = 48000000,
++ .frequency_max = 870000000,
++ .symbol_rate_min = 870000,
++ .symbol_rate_max = 9000000,
++ .caps = FE_CAN_QAM_16 | FE_CAN_QAM_32 | FE_CAN_QAM_64 |
++ FE_CAN_QAM_128 | FE_CAN_QAM_256 | FE_CAN_FEC_AUTO
++ },
++ .release = m88dc2800_release,
++ .init = m88dc2800_init,
++ .sleep = m88dc2800_sleep,
++ .set_frontend = m88dc2800_set_parameters,
++ .read_status = m88dc2800_read_status,
++ .read_ber = m88dc2800_read_ber,
++ .read_signal_strength = m88dc2800_read_signal_strength,
++ .read_snr = m88dc2800_read_snr,
++ .read_ucblocks = m88dc2800_read_ucblocks,
++};
++
++MODULE_DESCRIPTION("Montage DVB-C demodulator driver");
++MODULE_AUTHOR("Max Nibble <nibble.max@gmail.com>");
++MODULE_LICENSE("GPL");
++MODULE_VERSION("1.00");
+diff --git a/drivers/media/dvb-frontends/m88dc2800.h b/drivers/media/dvb-frontends/m88dc2800.h
+new file mode 100644
+index 0000000..2184322
+--- /dev/null
++++ b/drivers/media/dvb-frontends/m88dc2800.h
+@@ -0,0 +1,44 @@
++/*
++ M88DC2800/M88TC2800 - DVB-C demodulator and tuner from Montage
++
++ Copyright (C) 2012 Max Nibble <nibble.max@gmail.com>
++ Copyright (C) 2011 Montage Technology - www.montage-tech.com
++
++ This program is free software; you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation; either version 2 of the License, or
++ (at your option) any later version.
++
++ This program is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with this program; if not, write to the Free Software
++ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
++*/
++
++#ifndef M88DC2800_H
++#define M88DC2800_H
++
++#include <linux/kconfig.h>
++#include <linux/dvb/frontend.h>
++
++struct m88dc2800_config {
++ u8 demod_address;
++ u8 ts_mode;
++};
++
++#if IS_ENABLED(CONFIG_DVB_M88DC2800)
++extern struct dvb_frontend* m88dc2800_attach(const struct m88dc2800_config* config,
++ struct i2c_adapter* i2c);
++#else
++static inline struct dvb_frontend* m88dc2800_attach(const struct m88dc2800_config* config,
++ struct i2c_adapter* i2c)
++{
++ printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
++ return NULL;
++}
++#endif /* CONFIG_DVB_M88DC2800 */
++#endif /* M88DC2800_H */
+diff --git a/drivers/media/pci/cx23885/Kconfig b/drivers/media/pci/cx23885/Kconfig
+index d1dcb1d..285c1ad 100644
+--- a/drivers/media/pci/cx23885/Kconfig
++++ b/drivers/media/pci/cx23885/Kconfig
+@@ -23,6 +23,8 @@ config VIDEO_CX23885
+ select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_STV6110 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_CX24116 if MEDIA_SUBDRV_AUTOSELECT
++ select DVB_DVBSKY_M88DS3103 if MEDIA_SUBDRV_AUTOSELECT
++ select DVB_M88DC2800 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_CX24117 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_STV0900 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_DS3000 if MEDIA_SUBDRV_AUTOSELECT
+diff --git a/drivers/media/pci/cx23885/cimax2.c b/drivers/media/pci/cx23885/cimax2.c
+index 16fa7ea..2b63f78 100644
+--- a/drivers/media/pci/cx23885/cimax2.c
++++ b/drivers/media/pci/cx23885/cimax2.c
+@@ -426,7 +426,7 @@ int netup_poll_ci_slot_status(struct dvb_ca_en50221 *en50221,
+ return state->status;
+ }
+
+-int netup_ci_init(struct cx23885_tsport *port)
++int netup_ci_init(struct cx23885_tsport *port, bool isDVBSky)
+ {
+ struct netup_ci_state *state;
+ u8 cimax_init[34] = {
+@@ -475,6 +475,11 @@ int netup_ci_init(struct cx23885_tsport *port)
+ goto err;
+ }
+
++ if(isDVBSky) {
++ cimax_init[32] = 0x22;
++ cimax_init[33] = 0x00;
++ }
++
+ port->port_priv = state;
+
+ switch (port->nr) {
+@@ -548,3 +553,19 @@ void netup_ci_exit(struct cx23885_tsport *port)
+ dvb_ca_en50221_release(&state->ca);
+ kfree(state);
+ }
++
++/* CI irq handler for DVBSky board*/
++int dvbsky_ci_slot_status(struct cx23885_dev *dev)
++{
++ struct cx23885_tsport *port = NULL;
++ struct netup_ci_state *state = NULL;
++
++ ci_dbg_print("%s:\n", __func__);
++
++ port = &dev->ts1;
++ state = port->port_priv;
++ schedule_work(&state->work);
++ ci_dbg_print("%s: Wakeup CI0\n", __func__);
++
++ return 1;
++}
+diff --git a/drivers/media/pci/cx23885/cimax2.h b/drivers/media/pci/cx23885/cimax2.h
+index 518744a..39f3db7 100644
+--- a/drivers/media/pci/cx23885/cimax2.h
++++ b/drivers/media/pci/cx23885/cimax2.h
+@@ -41,7 +41,9 @@ extern int netup_ci_slot_ts_ctl(struct dvb_ca_en50221 *en50221, int slot);
+ extern int netup_ci_slot_status(struct cx23885_dev *dev, u32 pci_status);
+ extern int netup_poll_ci_slot_status(struct dvb_ca_en50221 *en50221,
+ int slot, int open);
+-extern int netup_ci_init(struct cx23885_tsport *port);
++extern int netup_ci_init(struct cx23885_tsport *port, bool isDVBSky);
+ extern void netup_ci_exit(struct cx23885_tsport *port);
+
++extern int dvbsky_ci_slot_status(struct cx23885_dev *dev);
++
+ #endif
+diff --git a/drivers/media/pci/cx23885/cx23885-cards.c b/drivers/media/pci/cx23885/cx23885-cards.c
+old mode 100644
+new mode 100755
+index 79f20c8..889bd4c
+--- a/drivers/media/pci/cx23885/cx23885-cards.c
++++ b/drivers/media/pci/cx23885/cx23885-cards.c
+@@ -613,6 +613,49 @@ struct cx23885_board cx23885_boards[] = {
+ .name = "TeVii S471",
+ .portb = CX23885_MPEG_DVB,
+ },
++ [CX23885_BOARD_BST_PS8512] = {
++ .name = "Bestunar PS8512",
++ .portb = CX23885_MPEG_DVB,
++ },
++ [CX23885_BOARD_DVBSKY_S950] = {
++ .name = "DVBSKY S950",
++ .portb = CX23885_MPEG_DVB,
++ },
++ [CX23885_BOARD_DVBSKY_S952] = {
++ .name = "DVBSKY S952",
++ .portb = CX23885_MPEG_DVB,
++ .portc = CX23885_MPEG_DVB,
++ },
++ [CX23885_BOARD_DVBSKY_S950_CI] = {
++ .ci_type = 3,
++ .name = "DVBSKY S950CI DVB-S2 CI",
++ .portb = CX23885_MPEG_DVB,
++ },
++ [CX23885_BOARD_DVBSKY_C2800E_CI] = {
++ .ci_type = 3,
++ .name = "DVBSKY C2800E DVB-C CI",
++ .portb = CX23885_MPEG_DVB,
++ },
++ [CX23885_BOARD_DVBSKY_T9580] = {
++ .name = "DVBSKY T9580",
++ .portb = CX23885_MPEG_DVB,
++ .portc = CX23885_MPEG_DVB,
++ },
++ [CX23885_BOARD_DVBSKY_T980_CI] = {
++ .ci_type = 3,
++ .name = "DVBSKY T980CI DVB-T2/C CI",
++ .portb = CX23885_MPEG_DVB,
++ },
++ [CX23885_BOARD_DVBSKY_T982] = {
++ .name = "DVBSKY T982",
++ .portb = CX23885_MPEG_DVB,
++ .portc = CX23885_MPEG_DVB,
++ },
++ [CX23885_BOARD_TT_4500_CI] = {
++ .ci_type = 3,
++ .name = "TT-budget CT2-4500 CI",
++ .portb = CX23885_MPEG_DVB,
++ },
+ [CX23885_BOARD_PROF_8000] = {
+ .name = "Prof Revolution DVB-S2 8000",
+ .portb = CX23885_MPEG_DVB,
+@@ -874,6 +917,42 @@ struct cx23885_subid cx23885_subids[] = {
+ .subdevice = 0x9022,
+ .card = CX23885_BOARD_TEVII_S471,
+ }, {
++ .subvendor = 0x14f1,
++ .subdevice = 0x8512,
++ .card = CX23885_BOARD_BST_PS8512,
++ }, {
++ .subvendor = 0x4254,
++ .subdevice = 0x0950,
++ .card = CX23885_BOARD_DVBSKY_S950,
++ }, {
++ .subvendor = 0x4254,
++ .subdevice = 0x0952,
++ .card = CX23885_BOARD_DVBSKY_S952,
++ }, {
++ .subvendor = 0x4254,
++ .subdevice = 0x950C,
++ .card = CX23885_BOARD_DVBSKY_S950_CI,
++ }, {
++ .subvendor = 0x4254,
++ .subdevice = 0x2800,
++ .card = CX23885_BOARD_DVBSKY_C2800E_CI,
++ }, {
++ .subvendor = 0x4254,
++ .subdevice = 0x9580,
++ .card = CX23885_BOARD_DVBSKY_T9580,
++ }, {
++ .subvendor = 0x4254,
++ .subdevice = 0x980C,
++ .card = CX23885_BOARD_DVBSKY_T980_CI,
++ }, {
++ .subvendor = 0x4254,
++ .subdevice = 0x0982,
++ .card = CX23885_BOARD_DVBSKY_T982,
++ }, {
++ .subvendor = 0x13C2,
++ .subdevice = 0x3013,
++ .card = CX23885_BOARD_TT_4500_CI,
++ }, {
+ .subvendor = 0x8000,
+ .subdevice = 0x3034,
+ .card = CX23885_BOARD_PROF_8000,
+@@ -1483,9 +1562,75 @@ void cx23885_gpio_setup(struct cx23885_dev *dev)
+ cx_set(GP0_IO, 0x00040004);
+ mdelay(60);
+ break;
++ case CX23885_BOARD_DVBSKY_S950:
++ case CX23885_BOARD_BST_PS8512:
++ cx23885_gpio_enable(dev, GPIO_2, 1);
++ cx23885_gpio_clear(dev, GPIO_2);
++ msleep(100);
++ cx23885_gpio_set(dev, GPIO_2);
++ break;
++ case CX23885_BOARD_DVBSKY_S952:
++ case CX23885_BOARD_DVBSKY_T9580:
++ case CX23885_BOARD_DVBSKY_T982:
++ cx_write(MC417_CTL, 0x00000037);/* enable GPIO3-18 pins */
++
++ cx23885_gpio_enable(dev, GPIO_2, 1);
++ cx23885_gpio_enable(dev, GPIO_11, 1);
++
++ cx23885_gpio_clear(dev, GPIO_2);
++ cx23885_gpio_clear(dev, GPIO_11);
++ msleep(100);
++ cx23885_gpio_set(dev, GPIO_2);
++ cx23885_gpio_set(dev, GPIO_11);
++ break;
++ case CX23885_BOARD_DVBSKY_S950_CI:
++ case CX23885_BOARD_DVBSKY_C2800E_CI:
++ case CX23885_BOARD_DVBSKY_T980_CI:
++ case CX23885_BOARD_TT_4500_CI:
++ cx_set(GP0_IO, 0x00060002); /* GPIO 1/2 as output */
++ cx_clear(GP0_IO, 0x00010004); /*GPIO 0 as input*/
++ mdelay(100);/* reset delay */
++ cx_set(GP0_IO, 0x00060004); /* GPIO as out, reset high */
++ cx_clear(GP0_IO, 0x00010002);
++ cx_write(MC417_CTL, 0x00000037);/* enable GPIO3-18 pins */
++ /* GPIO-15 IN as ~ACK, rest as OUT */
++ cx_write(MC417_OEN, 0x00001000);
++ /* ~RD, ~WR high; ADL0, ADL1 low; ~CS0, ~CS1 high */
++ cx_write(MC417_RWD, 0x0000c300);
++ /* enable irq */
++ cx_write(GPIO_ISM, 0x00000000);/* INTERRUPTS active low*/
++ break;
+ }
+ }
+
++static int cx23885_ir_patch(struct i2c_adapter *i2c, u8 reg, u8 mask)
++{
++ struct i2c_msg msgs[2];
++ u8 tx_buf[2], rx_buf[1];
++ /* Write register address */
++ tx_buf[0] = reg;
++ msgs[0].addr = 0x4c;
++ msgs[0].flags = 0;
++ msgs[0].len = 1;
++ msgs[0].buf = (char *) tx_buf;
++ /* Read data from register */
++ msgs[1].addr = 0x4c;
++ msgs[1].flags = I2C_M_RD;
++ msgs[1].len = 1;
++ msgs[1].buf = (char *) rx_buf;
++
++ i2c_transfer(i2c, msgs, 2);
++
++ tx_buf[0] = reg;
++ tx_buf[1] = rx_buf[0] | mask;
++ msgs[0].addr = 0x4c;
++ msgs[0].flags = 0;
++ msgs[0].len = 2;
++ msgs[0].buf = (char *) tx_buf;
++
++ return i2c_transfer(i2c, msgs, 1);
++}
++
+ int cx23885_ir_init(struct cx23885_dev *dev)
+ {
+ static struct v4l2_subdev_io_pin_config ir_rxtx_pin_cfg[] = {
+@@ -1573,6 +1718,26 @@ int cx23885_ir_init(struct cx23885_dev *dev)
+ v4l2_subdev_call(dev->sd_cx25840, core, s_io_pin_config,
+ ir_rx_pin_cfg_count, ir_rx_pin_cfg);
+ break;
++ case CX23885_BOARD_BST_PS8512:
++ case CX23885_BOARD_DVBSKY_S950:
++ case CX23885_BOARD_DVBSKY_S952:
++ case CX23885_BOARD_DVBSKY_S950_CI:
++ case CX23885_BOARD_DVBSKY_C2800E_CI:
++ case CX23885_BOARD_DVBSKY_T9580:
++ case CX23885_BOARD_DVBSKY_T980_CI:
++ case CX23885_BOARD_DVBSKY_T982:
++ case CX23885_BOARD_TT_4500_CI:
++ dev->sd_ir = cx23885_find_hw(dev, CX23885_HW_AV_CORE);
++ if (dev->sd_ir == NULL) {
++ ret = -ENODEV;
++ break;
++ }
++ v4l2_subdev_call(dev->sd_cx25840, core, s_io_pin_config,
++ ir_rx_pin_cfg_count, ir_rx_pin_cfg);
++
++ cx23885_ir_patch(&(dev->i2c_bus[2].i2c_adap),0x1f,0x80);
++ cx23885_ir_patch(&(dev->i2c_bus[2].i2c_adap),0x23,0x80);
++ break;
+ case CX23885_BOARD_HAUPPAUGE_HVR1250:
+ if (!enable_885_ir)
+ break;
+@@ -1608,13 +1773,22 @@ void cx23885_ir_fini(struct cx23885_dev *dev)
+ case CX23885_BOARD_MYGICA_X8507:
+ case CX23885_BOARD_TBS_6980:
+ case CX23885_BOARD_TBS_6981:
++ case CX23885_BOARD_BST_PS8512:
++ case CX23885_BOARD_DVBSKY_S950:
++ case CX23885_BOARD_DVBSKY_S952:
++ case CX23885_BOARD_DVBSKY_S950_CI:
++ case CX23885_BOARD_DVBSKY_C2800E_CI:
++ case CX23885_BOARD_DVBSKY_T9580:
++ case CX23885_BOARD_DVBSKY_T980_CI:
++ case CX23885_BOARD_DVBSKY_T982:
++ case CX23885_BOARD_TT_4500_CI:
+ cx23885_irq_remove(dev, PCI_MSK_AV_CORE);
+ /* sd_ir is a duplicate pointer to the AV Core, just clear it */
+ dev->sd_ir = NULL;
+ break;
+ }
+ }
+-
++#if 0
+ static int netup_jtag_io(void *device, int tms, int tdi, int read_tdo)
+ {
+ int data;
+@@ -1639,7 +1813,7 @@ static int netup_jtag_io(void *device, int tms, int tdi, int read_tdo)
+
+ return tdo;
+ }
+-
++#endif
+ void cx23885_ir_pci_int_enable(struct cx23885_dev *dev)
+ {
+ switch (dev->board) {
+@@ -1655,6 +1829,15 @@ void cx23885_ir_pci_int_enable(struct cx23885_dev *dev)
+ case CX23885_BOARD_MYGICA_X8507:
+ case CX23885_BOARD_TBS_6980:
+ case CX23885_BOARD_TBS_6981:
++ case CX23885_BOARD_BST_PS8512:
++ case CX23885_BOARD_DVBSKY_S950:
++ case CX23885_BOARD_DVBSKY_S952:
++ case CX23885_BOARD_DVBSKY_S950_CI:
++ case CX23885_BOARD_DVBSKY_C2800E_CI:
++ case CX23885_BOARD_DVBSKY_T9580:
++ case CX23885_BOARD_DVBSKY_T980_CI:
++ case CX23885_BOARD_DVBSKY_T982:
++ case CX23885_BOARD_TT_4500_CI:
+ if (dev->sd_ir)
+ cx23885_irq_add_enable(dev, PCI_MSK_AV_CORE);
+ break;
+@@ -1752,6 +1935,12 @@ void cx23885_card_setup(struct cx23885_dev *dev)
+ ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */
+ ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO;
+ break;
++ case CX23885_BOARD_BST_PS8512:
++ case CX23885_BOARD_DVBSKY_S950:
++ case CX23885_BOARD_DVBSKY_S950_CI:
++ case CX23885_BOARD_DVBSKY_C2800E_CI:
++ case CX23885_BOARD_DVBSKY_T980_CI:
++ case CX23885_BOARD_TT_4500_CI:
+ case CX23885_BOARD_TEVII_S470:
+ case CX23885_BOARD_TEVII_S471:
+ case CX23885_BOARD_DVBWORLD_2005:
+@@ -1795,6 +1984,23 @@ void cx23885_card_setup(struct cx23885_dev *dev)
+ ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */
+ ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO;
+ break;
++ case CX23885_BOARD_DVBSKY_S952:
++ ts1->gen_ctrl_val = 0x5; /* Parallel */
++ ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */
++ ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO;
++ ts2->gen_ctrl_val = 0xe; /* Serial bus + punctured clock */
++ ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */
++ ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO;
++ break;
++ case CX23885_BOARD_DVBSKY_T9580:
++ case CX23885_BOARD_DVBSKY_T982:
++ ts1->gen_ctrl_val = 0x5; /* Parallel */
++ ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */
++ ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO;
++ ts2->gen_ctrl_val = 0x8; /* Serial bus */
++ ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */
++ ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO;
++ break;
+ case CX23885_BOARD_HAUPPAUGE_HVR4400:
+ ts1->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */
+ ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */
+@@ -1860,6 +2066,15 @@ void cx23885_card_setup(struct cx23885_dev *dev)
+ case CX23885_BOARD_AVERMEDIA_HC81R:
+ case CX23885_BOARD_TBS_6980:
+ case CX23885_BOARD_TBS_6981:
++ case CX23885_BOARD_BST_PS8512:
++ case CX23885_BOARD_DVBSKY_S950:
++ case CX23885_BOARD_DVBSKY_S952:
++ case CX23885_BOARD_DVBSKY_S950_CI:
++ case CX23885_BOARD_DVBSKY_C2800E_CI:
++ case CX23885_BOARD_DVBSKY_T9580:
++ case CX23885_BOARD_DVBSKY_T980_CI:
++ case CX23885_BOARD_DVBSKY_T982:
++ case CX23885_BOARD_TT_4500_CI:
+ dev->sd_cx25840 = v4l2_i2c_new_subdev(&dev->v4l2_dev,
+ &dev->i2c_bus[2].i2c_adap,
+ "cx25840", 0x88 >> 1, NULL);
+@@ -1879,13 +2094,13 @@ void cx23885_card_setup(struct cx23885_dev *dev)
+ int ret;
+ const struct firmware *fw;
+ const char *filename = "dvb-netup-altera-01.fw";
+- char *action = "configure";
++// char *action = "configure";
+ static struct netup_card_info cinfo;
+- struct altera_config netup_config = {
+- .dev = dev,
+- .action = action,
+- .jtag_io = netup_jtag_io,
+- };
++// struct altera_config netup_config = {
++// .dev = dev,
++// .action = action,
++// .jtag_io = netup_jtag_io,
++// };
+
+ netup_initialize(dev);
+
+@@ -1909,8 +2124,8 @@ void cx23885_card_setup(struct cx23885_dev *dev)
+ printk(KERN_ERR "did not find the firmware file. (%s) "
+ "Please see linux/Documentation/dvb/ for more details "
+ "on firmware-problems.", filename);
+- else
+- altera_init(&netup_config, fw);
++ //else
++ // altera_init(&netup_config, fw);
+
+ release_firmware(fw);
+ break;
+diff --git a/drivers/media/pci/cx23885/cx23885-core.c b/drivers/media/pci/cx23885/cx23885-core.c
+index edcd79d..c9afeab 100644
+--- a/drivers/media/pci/cx23885/cx23885-core.c
++++ b/drivers/media/pci/cx23885/cx23885-core.c
+@@ -450,9 +450,9 @@ void cx23885_wakeup(struct cx23885_tsport *port,
+ del_timer(&q->timeout);
+ else
+ mod_timer(&q->timeout, jiffies + BUFFER_TIMEOUT);
+- if (bc != 1)
++/* if (bc != 1)
+ printk(KERN_WARNING "%s: %d buffers handled (should be 1)\n",
+- __func__, bc);
++ __func__, bc);*/
+ }
+
+ int cx23885_sram_channel_setup(struct cx23885_dev *dev,
+@@ -1909,6 +1909,10 @@ static irqreturn_t cx23885_irq(int irq, void *dev_id)
+ (pci_status & PCI_MSK_GPIO0))
+ handled += altera_ci_irq(dev);
+
++ if (cx23885_boards[dev->board].ci_type == 3 &&
++ (pci_status & PCI_MSK_GPIO0))
++ handled += dvbsky_ci_slot_status(dev);
++
+ if (ts1_status) {
+ if (cx23885_boards[dev->board].portb == CX23885_MPEG_DVB)
+ handled += cx23885_irq_ts(ts1, ts1_status);
+@@ -2141,6 +2145,10 @@ static int cx23885_initdev(struct pci_dev *pci_dev,
+ cx23885_irq_add_enable(dev, PCI_MSK_GPIO1 | PCI_MSK_GPIO0);
+ break;
+ case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF:
++ case CX23885_BOARD_DVBSKY_S950_CI:
++ case CX23885_BOARD_DVBSKY_C2800E_CI:
++ case CX23885_BOARD_DVBSKY_T980_CI:
++ case CX23885_BOARD_TT_4500_CI:
+ cx23885_irq_add_enable(dev, PCI_MSK_GPIO0);
+ break;
+ }
+diff --git a/drivers/media/pci/cx23885/cx23885-dvb.c b/drivers/media/pci/cx23885/cx23885-dvb.c
+index 0549205..7816112 100644
+--- a/drivers/media/pci/cx23885/cx23885-dvb.c
++++ b/drivers/media/pci/cx23885/cx23885-dvb.c
+@@ -51,6 +51,8 @@
+ #include "stv6110.h"
+ #include "lnbh24.h"
+ #include "cx24116.h"
++#include "dvbsky_m88ds3103.h"
++#include "m88dc2800.h"
+ #include "cx24117.h"
+ #include "cimax2.h"
+ #include "lgs8gxx.h"
+@@ -473,6 +475,7 @@ static struct ds3000_config tevii_ds3000_config = {
+ static struct ts2020_config tevii_ts2020_config = {
+ .tuner_address = 0x60,
+ .clk_out_div = 1,
++ .frequency_div = 1146000,
+ };
+
+ static struct cx24116_config dvbworld_cx24116_config = {
+@@ -507,6 +510,93 @@ static struct xc5000_config mygica_x8507_xc5000_config = {
+ .if_khz = 4000,
+ };
+
++/* bst control */
++int bst_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
++{
++ struct cx23885_tsport *port = fe->dvb->priv;
++ struct cx23885_dev *dev = port->dev;
++
++ cx23885_gpio_enable(dev, GPIO_1, 1);
++ cx23885_gpio_enable(dev, GPIO_0, 1);
++
++ switch (voltage) {
++ case SEC_VOLTAGE_13:
++ cx23885_gpio_set(dev, GPIO_1);
++ cx23885_gpio_clear(dev, GPIO_0);
++ break;
++ case SEC_VOLTAGE_18:
++ cx23885_gpio_set(dev, GPIO_1);
++ cx23885_gpio_set(dev, GPIO_0);
++ break;
++ case SEC_VOLTAGE_OFF:
++ cx23885_gpio_clear(dev, GPIO_1);
++ cx23885_gpio_clear(dev, GPIO_0);
++ break;
++ }
++ return 0;
++}
++
++int dvbsky_set_voltage_sec(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
++{
++ struct cx23885_tsport *port = fe->dvb->priv;
++ struct cx23885_dev *dev = port->dev;
++
++ cx23885_gpio_enable(dev, GPIO_12, 1);
++ cx23885_gpio_enable(dev, GPIO_13, 1);
++
++ switch (voltage) {
++ case SEC_VOLTAGE_13:
++ cx23885_gpio_set(dev, GPIO_13);
++ cx23885_gpio_clear(dev, GPIO_12);
++ break;
++ case SEC_VOLTAGE_18:
++ cx23885_gpio_set(dev, GPIO_13);
++ cx23885_gpio_set(dev, GPIO_12);
++ break;
++ case SEC_VOLTAGE_OFF:
++ cx23885_gpio_clear(dev, GPIO_13);
++ cx23885_gpio_clear(dev, GPIO_12);
++ break;
++ }
++ return 0;
++}
++
++/* bestunar single dvb-s2 */
++static struct dvbsky_m88ds3103_config bst_ds3103_config = {
++ .demod_address = 0x68,
++ .ci_mode = 0,
++ .pin_ctrl = 0x82,
++ .ts_mode = 0,
++ .set_voltage = bst_set_voltage,
++};
++/* DVBSKY dual dvb-s2 */
++static struct dvbsky_m88ds3103_config dvbsky_ds3103_config_pri = {
++ .demod_address = 0x68,
++ .ci_mode = 0,
++ .pin_ctrl = 0x82,
++ .ts_mode = 0,
++ .set_voltage = bst_set_voltage,
++};
++static struct dvbsky_m88ds3103_config dvbsky_ds3103_config_sec = {
++ .demod_address = 0x68,
++ .ci_mode = 0,
++ .pin_ctrl = 0x82,
++ .ts_mode = 1,
++ .set_voltage = dvbsky_set_voltage_sec,
++};
++
++static struct dvbsky_m88ds3103_config dvbsky_ds3103_ci_config = {
++ .demod_address = 0x68,
++ .ci_mode = 2,
++ .pin_ctrl = 0x82,
++ .ts_mode = 0,
++};
++
++static struct m88dc2800_config dvbsky_dc2800_config = {
++ .demod_address = 0x1c,
++ .ts_mode = 3,
++};
++
+ static struct stv090x_config prof_8000_stv090x_config = {
+ .device = STV0903,
+ .demod_mode = STV090x_SINGLE,
+@@ -1311,6 +1401,92 @@ static int dvb_register(struct cx23885_tsport *port)
+ &tevii_ts2020_config, &i2c_bus->i2c_adap);
+ }
+ break;
++ case CX23885_BOARD_BST_PS8512:
++ case CX23885_BOARD_DVBSKY_S950:
++ i2c_bus = &dev->i2c_bus[1];
++ fe0->dvb.frontend = dvb_attach(dvbsky_m88ds3103_attach,
++ &bst_ds3103_config,
++ &i2c_bus->i2c_adap);
++ break;
++
++ case CX23885_BOARD_DVBSKY_S952:
++ switch (port->nr) {
++ /* port B */
++ case 1:
++ i2c_bus = &dev->i2c_bus[1];
++ fe0->dvb.frontend = dvb_attach(dvbsky_m88ds3103_attach,
++ &dvbsky_ds3103_config_pri,
++ &i2c_bus->i2c_adap);
++ break;
++ /* port C */
++ case 2:
++ i2c_bus = &dev->i2c_bus[0];
++ fe0->dvb.frontend = dvb_attach(dvbsky_m88ds3103_attach,
++ &dvbsky_ds3103_config_sec,
++ &i2c_bus->i2c_adap);
++ break;
++ }
++ break;
++
++ case CX23885_BOARD_DVBSKY_S950_CI:
++ i2c_bus = &dev->i2c_bus[1];
++ fe0->dvb.frontend = dvb_attach(dvbsky_m88ds3103_attach,
++ &dvbsky_ds3103_ci_config,
++ &i2c_bus->i2c_adap);
++ break;
++
++ case CX23885_BOARD_DVBSKY_C2800E_CI:
++ i2c_bus = &dev->i2c_bus[1];
++ fe0->dvb.frontend = dvb_attach(m88dc2800_attach,
++ &dvbsky_dc2800_config,
++ &i2c_bus->i2c_adap);
++ break;
++
++ case CX23885_BOARD_DVBSKY_T9580:
++ switch (port->nr) {
++ /* port B */
++ case 1:
++ i2c_bus = &dev->i2c_bus[1];
++ fe0->dvb.frontend = dvb_attach(dvbsky_m88ds3103_attach,
++ &dvbsky_ds3103_config_pri,
++ &i2c_bus->i2c_adap);
++ break;
++ /* port C */
++// case 2:
++// i2c_bus = &dev->i2c_bus[0];
++// fe0->dvb.frontend = dvb_attach(sit2_attach,
++// &dvbsky_sit2_config_pci_s,
++// &i2c_bus->i2c_adap);
++ break;
++ }
++ break;
++
++// case CX23885_BOARD_DVBSKY_T980_CI:
++// case CX23885_BOARD_TT_4500_CI:
++// i2c_bus = &dev->i2c_bus[1];
++// fe0->dvb.frontend = dvb_attach(sit2_attach,
++// &dvbsky_sit2_config_pci_p,
++// &i2c_bus->i2c_adap);
++// break;
++
++// case CX23885_BOARD_DVBSKY_T982:
++// switch (port->nr) {
++// /* port B */
++// case 1:
++// i2c_bus = &dev->i2c_bus[1];
++// fe0->dvb.frontend = dvb_attach(sit2_attach,
++// &dvbsky_sit2_config_pci_p,
++// &i2c_bus->i2c_adap);
++// break;
++// /* port C */
++// case 2:
++// i2c_bus = &dev->i2c_bus[0];
++// fe0->dvb.frontend = dvb_attach(sit2_attach,
++// &dvbsky_sit2_config_pci_s,
++// &i2c_bus->i2c_adap);
++// break;
++// }
++// break;
+ case CX23885_BOARD_PROF_8000:
+ i2c_bus = &dev->i2c_bus[0];
+
+@@ -1386,7 +1562,7 @@ static int dvb_register(struct cx23885_tsport *port)
+ printk(KERN_INFO "NetUP Dual DVB-S2 CI card port%d MAC=%pM\n",
+ port->nr, port->frontends.adapter.proposed_mac);
+
+- netup_ci_init(port);
++ netup_ci_init(port, false);
+ break;
+ }
+ case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: {
+@@ -1413,6 +1589,44 @@ static int dvb_register(struct cx23885_tsport *port)
+ memcpy(port->frontends.adapter.proposed_mac, eeprom + 0xa0, 6);
+ break;
+ }
++ case CX23885_BOARD_BST_PS8512:
++ case CX23885_BOARD_DVBSKY_S950:
++ case CX23885_BOARD_DVBSKY_S952:
++ case CX23885_BOARD_DVBSKY_T9580:
++ case CX23885_BOARD_DVBSKY_T982:{
++ u8 eeprom[256]; /* 24C02 i2c eeprom */
++
++ if(port->nr > 2)
++ break;
++
++ dev->i2c_bus[0].i2c_client.addr = 0xa0 >> 1;
++ tveeprom_read(&dev->i2c_bus[0].i2c_client, eeprom, sizeof(eeprom));
++ printk(KERN_INFO "DVBSKY PCIe MAC= %pM\n", eeprom + 0xc0+(port->nr-1)*8);
++ memcpy(port->frontends.adapter.proposed_mac, eeprom + 0xc0 +
++ (port->nr-1)*8, 6);
++ break;
++ }
++ case CX23885_BOARD_DVBSKY_S950_CI: {
++ u8 eeprom[256]; /* 24C02 i2c eeprom */
++
++ if(port->nr > 2)
++ break;
++
++ dev->i2c_bus[0].i2c_client.addr = 0xa0 >> 1;
++ tveeprom_read(&dev->i2c_bus[0].i2c_client, eeprom, sizeof(eeprom));
++ printk(KERN_INFO "DVBSKY PCIe MAC= %pM\n", eeprom + 0xc0+(port->nr-1)*8);
++ memcpy(port->frontends.adapter.proposed_mac, eeprom + 0xc0 +
++ (port->nr-1)*8, 6);
++
++ netup_ci_init(port, true);
++ break;
++ }
++ case CX23885_BOARD_DVBSKY_C2800E_CI:
++ case CX23885_BOARD_DVBSKY_T980_CI:
++ case CX23885_BOARD_TT_4500_CI: {
++ netup_ci_init(port, true);
++ break;
++ }
+ }
+
+ return ret;
+@@ -1495,6 +1709,10 @@ int cx23885_dvb_unregister(struct cx23885_tsport *port)
+
+ switch (port->dev->board) {
+ case CX23885_BOARD_NETUP_DUAL_DVBS2_CI:
++ case CX23885_BOARD_DVBSKY_S950_CI:
++ case CX23885_BOARD_DVBSKY_C2800E_CI:
++ case CX23885_BOARD_DVBSKY_T980_CI:
++ case CX23885_BOARD_TT_4500_CI:
+ netup_ci_exit(port);
+ break;
+ case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF:
+diff --git a/drivers/media/pci/cx23885/cx23885-input.c b/drivers/media/pci/cx23885/cx23885-input.c
+index 8a49e7c..1642ae8 100644
+--- a/drivers/media/pci/cx23885/cx23885-input.c
++++ b/drivers/media/pci/cx23885/cx23885-input.c
+@@ -92,6 +92,15 @@ void cx23885_input_rx_work_handler(struct cx23885_dev *dev, u32 events)
+ case CX23885_BOARD_MYGICA_X8507:
+ case CX23885_BOARD_TBS_6980:
+ case CX23885_BOARD_TBS_6981:
++ case CX23885_BOARD_BST_PS8512:
++ case CX23885_BOARD_DVBSKY_S950:
++ case CX23885_BOARD_DVBSKY_S952:
++ case CX23885_BOARD_DVBSKY_S950_CI:
++ case CX23885_BOARD_DVBSKY_C2800E_CI:
++ case CX23885_BOARD_DVBSKY_T9580:
++ case CX23885_BOARD_DVBSKY_T980_CI:
++ case CX23885_BOARD_DVBSKY_T982:
++ case CX23885_BOARD_TT_4500_CI:
+ /*
+ * The only boards we handle right now. However other boards
+ * using the CX2388x integrated IR controller should be similar
+@@ -144,6 +153,15 @@ static int cx23885_input_ir_start(struct cx23885_dev *dev)
+ case CX23885_BOARD_HAUPPAUGE_HVR1290:
+ case CX23885_BOARD_HAUPPAUGE_HVR1250:
+ case CX23885_BOARD_MYGICA_X8507:
++ case CX23885_BOARD_BST_PS8512:
++ case CX23885_BOARD_DVBSKY_S950:
++ case CX23885_BOARD_DVBSKY_S952:
++ case CX23885_BOARD_DVBSKY_S950_CI:
++ case CX23885_BOARD_DVBSKY_C2800E_CI:
++ case CX23885_BOARD_DVBSKY_T9580:
++ case CX23885_BOARD_DVBSKY_T980_CI:
++ case CX23885_BOARD_DVBSKY_T982:
++ case CX23885_BOARD_TT_4500_CI:
+ /*
+ * The IR controller on this board only returns pulse widths.
+ * Any other mode setting will fail to set up the device.
+@@ -302,6 +320,26 @@ int cx23885_input_init(struct cx23885_dev *dev)
+ /* A guess at the remote */
+ rc_map = RC_MAP_TOTAL_MEDIA_IN_HAND_02;
+ break;
++ case CX23885_BOARD_BST_PS8512:
++ case CX23885_BOARD_DVBSKY_S950:
++ case CX23885_BOARD_DVBSKY_S952:
++ case CX23885_BOARD_DVBSKY_S950_CI:
++ case CX23885_BOARD_DVBSKY_C2800E_CI:
++ case CX23885_BOARD_DVBSKY_T9580:
++ case CX23885_BOARD_DVBSKY_T980_CI:
++ case CX23885_BOARD_DVBSKY_T982:
++ /* Integrated CX2388[58] IR controller */
++ driver_type = RC_DRIVER_IR_RAW;
++ allowed_protos = RC_BIT_ALL;
++ /* A guess at the remote */
++ rc_map = RC_MAP_DVBSKY;
++ break;
++ case CX23885_BOARD_TT_4500_CI:
++ /* Integrated CX2388[58] IR controller */
++ driver_type = RC_DRIVER_IR_RAW;
++ allowed_protos = RC_BIT_ALL;
++ rc_map = RC_MAP_TT_1500;
++ break;
+ case CX23885_BOARD_TBS_6980:
+ case CX23885_BOARD_TBS_6981:
+ /* Integrated CX23885 IR controller */
+diff --git a/drivers/media/pci/cx23885/cx23885.h b/drivers/media/pci/cx23885/cx23885.h
+index 0fa4048..7a39d82 100644
+--- a/drivers/media/pci/cx23885/cx23885.h
++++ b/drivers/media/pci/cx23885/cx23885.h
+@@ -97,6 +97,17 @@
+ #define CX23885_BOARD_TBS_6980 41
+ #define CX23885_BOARD_LEADTEK_WINFAST_PXPVR2200 42
+
++#define CX23885_BOARD_DVBSKY_PCIE 43
++#define CX23885_BOARD_BST_PS8512 (CX23885_BOARD_DVBSKY_PCIE+0)
++#define CX23885_BOARD_DVBSKY_S952 (CX23885_BOARD_DVBSKY_PCIE+1)
++#define CX23885_BOARD_DVBSKY_S950 (CX23885_BOARD_DVBSKY_PCIE+2)
++#define CX23885_BOARD_DVBSKY_S950_CI (CX23885_BOARD_DVBSKY_PCIE+3)
++#define CX23885_BOARD_DVBSKY_C2800E_CI (CX23885_BOARD_DVBSKY_PCIE+4)
++#define CX23885_BOARD_DVBSKY_T9580 (CX23885_BOARD_DVBSKY_PCIE+5)
++#define CX23885_BOARD_DVBSKY_T980_CI (CX23885_BOARD_DVBSKY_PCIE+6)
++#define CX23885_BOARD_DVBSKY_T982 (CX23885_BOARD_DVBSKY_PCIE+7)
++#define CX23885_BOARD_TT_4500_CI (CX23885_BOARD_DVBSKY_PCIE+8)
++
+ #define GPIO_0 0x00000001
+ #define GPIO_1 0x00000002
+ #define GPIO_2 0x00000004
+@@ -234,7 +245,7 @@ struct cx23885_board {
+ */
+ u32 clk_freq;
+ struct cx23885_input input[MAX_CX23885_INPUT];
+- int ci_type; /* for NetUP */
++ int ci_type; /* 1 and 2 for NetUP, 3 for DVBSky. */
+ /* Force bottom field first during DMA (888 workaround) */
+ u32 force_bff;
+ };
+diff --git a/drivers/media/pci/cx88/Kconfig b/drivers/media/pci/cx88/Kconfig
+index a63a9ad..7deb300 100644
+--- a/drivers/media/pci/cx88/Kconfig
++++ b/drivers/media/pci/cx88/Kconfig
+@@ -57,6 +57,7 @@ config VIDEO_CX88_DVB
+ select DVB_ISL6421 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_S5H1411 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_CX24116 if MEDIA_SUBDRV_AUTOSELECT
++ select DVB_DVBSKY_M88DS3103 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_STV0288 if MEDIA_SUBDRV_AUTOSELECT
+ select DVB_STB6000 if MEDIA_SUBDRV_AUTOSELECT
+diff --git a/drivers/media/pci/cx88/cx88-cards.c b/drivers/media/pci/cx88/cx88-cards.c
+index e18a7ac..317511d 100644
+--- a/drivers/media/pci/cx88/cx88-cards.c
++++ b/drivers/media/pci/cx88/cx88-cards.c
+@@ -2314,6 +2314,18 @@ static const struct cx88_board cx88_boards[] = {
+ } },
+ .mpeg = CX88_MPEG_DVB,
+ },
++ [CX88_BOARD_BST_PS8312] = {
++ .name = "Bestunar PS8312 DVB-S/S2",
++ .tuner_type = UNSET,
++ .radio_type = UNSET,
++ .tuner_addr = ADDR_UNSET,
++ .radio_addr = ADDR_UNSET,
++ .input = { {
++ .type = CX88_VMUX_DVB,
++ .vmux = 0,
++ } },
++ .mpeg = CX88_MPEG_DVB,
++ },
+ };
+
+ /* ------------------------------------------------------------------ */
+@@ -2818,6 +2830,10 @@ static const struct cx88_subid cx88_subids[] = {
+ .subvendor = 0x1822,
+ .subdevice = 0x0023,
+ .card = CX88_BOARD_TWINHAN_VP1027_DVBS,
++ }, {
++ .subvendor = 0x14f1,
++ .subdevice = 0x8312,
++ .card = CX88_BOARD_BST_PS8312,
+ },
+ };
+
+@@ -3551,6 +3567,12 @@ static void cx88_card_setup(struct cx88_core *core)
+ cx_write(MO_SRST_IO, 1);
+ msleep(100);
+ break;
++ case CX88_BOARD_BST_PS8312:
++ cx_write(MO_GP1_IO, 0x808000);
++ msleep(100);
++ cx_write(MO_GP1_IO, 0x808080);
++ msleep(100);
++ break;
+ } /*end switch() */
+
+
+diff --git a/drivers/media/pci/cx88/cx88-dvb.c b/drivers/media/pci/cx88/cx88-dvb.c
+index 053ed1b..955b916 100644
+--- a/drivers/media/pci/cx88/cx88-dvb.c
++++ b/drivers/media/pci/cx88/cx88-dvb.c
+@@ -54,6 +54,7 @@
+ #include "stv0288.h"
+ #include "stb6000.h"
+ #include "cx24116.h"
++#include "dvbsky_m88ds3103.h"
+ #include "stv0900.h"
+ #include "stb6100.h"
+ #include "stb6100_proc.h"
+@@ -459,6 +460,56 @@ static int tevii_dvbs_set_voltage(struct dvb_frontend *fe,
+ return core->prev_set_voltage(fe, voltage);
+ return 0;
+ }
++/*CX88_BOARD_BST_PS8312*/
++static int bst_dvbs_set_voltage(struct dvb_frontend *fe,
++ fe_sec_voltage_t voltage)
++{
++ struct cx8802_dev *dev= fe->dvb->priv;
++ struct cx88_core *core = dev->core;
++
++ cx_write(MO_GP1_IO, 0x111111);
++ switch (voltage) {
++ case SEC_VOLTAGE_13:
++ cx_write(MO_GP1_IO, 0x020200);
++ break;
++ case SEC_VOLTAGE_18:
++ cx_write(MO_GP1_IO, 0x020202);
++ break;
++ case SEC_VOLTAGE_OFF:
++ cx_write(MO_GP1_IO, 0x111100);
++ break;
++ }
++
++ if (core->prev_set_voltage)
++ return core->prev_set_voltage(fe, voltage);
++ return 0;
++}
++
++static int bst_dvbs_set_voltage_v2(struct dvb_frontend *fe,
++ fe_sec_voltage_t voltage)
++{
++ struct cx8802_dev *dev= fe->dvb->priv;
++ struct cx88_core *core = dev->core;
++
++ cx_write(MO_GP1_IO, 0x111101);
++ switch (voltage) {
++ case SEC_VOLTAGE_13:
++ cx_write(MO_GP1_IO, 0x020200);
++ break;
++ case SEC_VOLTAGE_18:
++
++ cx_write(MO_GP1_IO, 0x020202);
++ break;
++ case SEC_VOLTAGE_OFF:
++
++ cx_write(MO_GP1_IO, 0x111110);
++ break;
++ }
++
++ if (core->prev_set_voltage)
++ return core->prev_set_voltage(fe, voltage);
++ return 0;
++}
+
+ static int vp1027_set_voltage(struct dvb_frontend *fe,
+ fe_sec_voltage_t voltage)
+@@ -706,6 +757,11 @@ static struct ts2020_config tevii_ts2020_config = {
+ .clk_out_div = 1,
+ };
+
++static struct dvbsky_m88ds3103_config dvbsky_ds3103_config = {
++ .demod_address = 0x68,
++ .set_ts_params = ds3000_set_ts_param,
++};
++
+ static const struct stv0900_config prof_7301_stv0900_config = {
+ .demod_address = 0x6a,
+ /* demod_mode = 0,*/
+@@ -1487,6 +1543,35 @@ static int dvb_register(struct cx8802_dev *dev)
+ tevii_dvbs_set_voltage;
+ }
+ break;
++ case CX88_BOARD_BST_PS8312:
++ fe0->dvb.frontend = dvb_attach(dvbsky_m88ds3103_attach,
++ &dvbsky_ds3103_config,
++ &core->i2c_adap);
++ if (fe0->dvb.frontend != NULL){
++ int ret;
++ u8 b0[] = { 0x60 };
++ u8 b1[2] = { 0 };
++ struct i2c_msg msg[] = {
++ {
++ .addr = 0x50,
++ .flags = 0,
++ .buf = b0,
++ .len = 1
++ }, {
++ .addr = 0x50,
++ .flags = I2C_M_RD,
++ .buf = b1,
++ .len = 2
++ }
++ };
++ ret = i2c_transfer(&core->i2c_adap, msg, 2);
++ printk("PS8312: config = %02x, %02x", b1[0],b1[1]);
++ if(b1[0] == 0xaa)
++ fe0->dvb.frontend->ops.set_voltage = bst_dvbs_set_voltage_v2;
++ else
++ fe0->dvb.frontend->ops.set_voltage = bst_dvbs_set_voltage;
++ }
++ break;
+ case CX88_BOARD_OMICOM_SS4_PCI:
+ case CX88_BOARD_TBS_8920:
+ case CX88_BOARD_PROF_7300:
+diff --git a/drivers/media/pci/cx88/cx88-input.c b/drivers/media/pci/cx88/cx88-input.c
+index f29e18c..9581f4e 100644
+--- a/drivers/media/pci/cx88/cx88-input.c
++++ b/drivers/media/pci/cx88/cx88-input.c
+@@ -129,7 +129,7 @@ static void cx88_ir_handle_key(struct cx88_IR *ir)
+ u32 gpio_key = cx_read(MO_GP0_IO);
+
+ data = (data << 4) | ((gpio_key & 0xf0) >> 4);
+-
++#if LINUX_VERSION_CODE < KERNEL_VERSION(3,17,0)
+ rc_keydown(ir->dev, data, 0);
+
+ } else if (ir->mask_keydown) {
+@@ -151,6 +151,45 @@ static void cx88_ir_handle_key(struct cx88_IR *ir)
+ rc_keydown_notimeout(ir->dev, data, 0);
+ rc_keyup(ir->dev);
+ }
++#else
++ rc_keydown(ir->dev, RC_TYPE_UNKNOWN, data, 0);
++
++ } else if (ir->core->boardnr == CX88_BOARD_PROLINK_PLAYTVPVR ||
++ ir->core->boardnr == CX88_BOARD_PIXELVIEW_PLAYTV_ULTRA_PRO) {
++ /* bit cleared on keydown, NEC scancode, 0xAAAACC, A = 0x866b */
++ u16 addr;
++ u8 cmd;
++ u32 scancode;
++
++ addr = (data >> 8) & 0xffff;
++ cmd = (data >> 0) & 0x00ff;
++ scancode = RC_SCANCODE_NECX(addr, cmd);
++
++ if (0 == (gpio & ir->mask_keyup))
++ rc_keydown_notimeout(ir->dev, RC_TYPE_NEC, scancode, 0);
++ else
++ rc_keyup(ir->dev);
++
++ } else if (ir->mask_keydown) {
++ /* bit set on keydown */
++ if (gpio & ir->mask_keydown)
++ rc_keydown_notimeout(ir->dev, RC_TYPE_UNKNOWN, data, 0);
++ else
++ rc_keyup(ir->dev);
++
++ } else if (ir->mask_keyup) {
++ /* bit cleared on keydown */
++ if (0 == (gpio & ir->mask_keyup))
++ rc_keydown_notimeout(ir->dev, RC_TYPE_UNKNOWN, data, 0);
++ else
++ rc_keyup(ir->dev);
++
++ } else {
++ /* can't distinguish keydown/up :-/ */
++ rc_keydown_notimeout(ir->dev, RC_TYPE_UNKNOWN, data, 0);
++ rc_keyup(ir->dev);
++ }
++#endif
+ }
+
+ static enum hrtimer_restart cx88_ir_work(struct hrtimer *timer)
+@@ -419,6 +458,10 @@ int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci)
+ rc_type = RC_BIT_NEC;
+ ir->sampling = 0xff00; /* address */
+ break;
++ case CX88_BOARD_BST_PS8312:
++ ir_codes = RC_MAP_DVBSKY;
++ ir->sampling = 0xff00; /* address */
++ break;
+ }
+
+ if (!ir_codes) {
+@@ -471,7 +514,6 @@ int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci)
+ dev->driver_type = RC_DRIVER_SCANCODE;
+ dev->allowed_protos = rc_type;
+ }
+-
+ ir->core = core;
+ core->ir = ir;
+
+@@ -538,8 +580,12 @@ void cx88_ir_irq(struct cx88_core *core)
+ }
+ ir_raw_event_handle(ir->dev);
+ }
+-
++#if LINUX_VERSION_CODE < KERNEL_VERSION(3,17,0)
+ static int get_key_pvr2000(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw)
++#else
++static int get_key_pvr2000(struct IR_i2c *ir, enum rc_type *protocol,
++ u32 *scancode, u8 *toggle)
++#endif
+ {
+ int flags, code;
+
+@@ -562,9 +608,14 @@ static int get_key_pvr2000(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw)
+
+ dprintk("IR Key/Flags: (0x%02x/0x%02x)\n",
+ code & 0xff, flags & 0xff);
+-
++#if LINUX_VERSION_CODE < KERNEL_VERSION(3,17,0)
+ *ir_key = code & 0xff;
+ *ir_raw = code;
++#else
++ *protocol = RC_TYPE_UNKNOWN;
++ *scancode = code & 0xff;
++ *toggle = 0;
++#endif
+ return 1;
+ }
+
+diff --git a/drivers/media/pci/cx88/cx88.h b/drivers/media/pci/cx88/cx88.h
+index 28893a6..5fa5f48 100644
+--- a/drivers/media/pci/cx88/cx88.h
++++ b/drivers/media/pci/cx88/cx88.h
+@@ -237,6 +237,7 @@ extern const struct sram_channel cx88_sram_channels[];
+ #define CX88_BOARD_WINFAST_DTV1800H_XC4000 88
+ #define CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36 89
+ #define CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43 90
++#define CX88_BOARD_BST_PS8312 91
+
+ enum cx88_itype {
+ CX88_VMUX_COMPOSITE1 = 1,
+diff --git a/drivers/media/rc/keymaps/Makefile b/drivers/media/rc/keymaps/Makefile
+index 0b8c549..abf6079 100644
+--- a/drivers/media/rc/keymaps/Makefile
++++ b/drivers/media/rc/keymaps/Makefile
+@@ -28,6 +28,7 @@ obj-$(CONFIG_RC_MAP) += rc-adstech-dvb-t-pci.o \
+ rc-dm1105-nec.o \
+ rc-dntv-live-dvb-t.o \
+ rc-dntv-live-dvbt-pro.o \
++ rc-dvbsky.o \
+ rc-em-terratec.o \
+ rc-encore-enltv2.o \
+ rc-encore-enltv.o \
+diff --git a/drivers/media/rc/keymaps/rc-dvbsky.c b/drivers/media/rc/keymaps/rc-dvbsky.c
+new file mode 100644
+index 0000000..bfc41fb
+--- /dev/null
++++ b/drivers/media/rc/keymaps/rc-dvbsky.c
+@@ -0,0 +1,78 @@
++/* rc-dvbsky.c - Keytable for Dvbsky Remote Controllers
++ *
++ * keymap imported from ir-keymaps.c
++ *
++ *
++ * Copyright (c) 2010-2012 by Nibble Max <nibble.max@gmail.com>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ */
++
++#include <media/rc-map.h>
++#include <linux/module.h>
++/*
++ * This table contains the complete RC5 code, instead of just the data part
++ */
++
++static struct rc_map_table rc5_dvbsky[] = {
++ { 0x0000, KEY_0 },
++ { 0x0001, KEY_1 },
++ { 0x0002, KEY_2 },
++ { 0x0003, KEY_3 },
++ { 0x0004, KEY_4 },
++ { 0x0005, KEY_5 },
++ { 0x0006, KEY_6 },
++ { 0x0007, KEY_7 },
++ { 0x0008, KEY_8 },
++ { 0x0009, KEY_9 },
++ { 0x000a, KEY_MUTE },
++ { 0x000d, KEY_OK },
++ { 0x000b, KEY_STOP },
++ { 0x000c, KEY_EXIT },
++ { 0x000e, KEY_CAMERA }, /*Snap shot*/
++ { 0x000f, KEY_SUBTITLE }, /*PIP*/
++ { 0x0010, KEY_VOLUMEUP },
++ { 0x0011, KEY_VOLUMEDOWN },
++ { 0x0012, KEY_FAVORITES },
++ { 0x0013, KEY_LIST }, /*Info*/
++ { 0x0016, KEY_PAUSE },
++ { 0x0017, KEY_PLAY },
++ { 0x001f, KEY_RECORD },
++ { 0x0020, KEY_CHANNELDOWN },
++ { 0x0021, KEY_CHANNELUP },
++ { 0x0025, KEY_POWER2 },
++ { 0x0026, KEY_REWIND },
++ { 0x0027, KEY_FASTFORWARD },
++ { 0x0029, KEY_LAST },
++ { 0x002b, KEY_MENU },
++ { 0x002c, KEY_EPG },
++ { 0x002d, KEY_ZOOM },
++};
++
++static struct rc_map_list rc5_dvbsky_map = {
++ .map = {
++ .scan = rc5_dvbsky,
++ .size = ARRAY_SIZE(rc5_dvbsky),
++ .rc_type = RC_TYPE_RC5,
++ .name = RC_MAP_DVBSKY,
++ }
++};
++
++static int __init init_rc_map_rc5_dvbsky(void)
++{
++ return rc_map_register(&rc5_dvbsky_map);
++}
++
++static void __exit exit_rc_map_rc5_dvbsky(void)
++{
++ rc_map_unregister(&rc5_dvbsky_map);
++}
++
++module_init(init_rc_map_rc5_dvbsky)
++module_exit(exit_rc_map_rc5_dvbsky)
++
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Nibble Max <nibble.max@gmail.com>");
+diff --git a/drivers/media/usb/dvb-usb-v2/Kconfig b/drivers/media/usb/dvb-usb-v2/Kconfig
+index 2059d0c..5ff51aa 100644
+--- a/drivers/media/usb/dvb-usb-v2/Kconfig
++++ b/drivers/media/usb/dvb-usb-v2/Kconfig
+@@ -147,3 +147,11 @@ config DVB_USB_RTL28XXU
+ help
+ Say Y here to support the Realtek RTL28xxU DVB USB receiver.
+
++config DVB_USB_DVBSKY
++ tristate "DVBSky USB2.0 support"
++ depends on DVB_USB_V2
++ select DVB_SIT2 if MEDIA_SUBDRV_AUTOSELECT
++ select DVB_DVBSKY_M88DS3103 if MEDIA_SUBDRV_AUTOSELECT
++ help
++ Say Y here to support the USB receivers from DVBSky.
++
+diff --git a/drivers/media/usb/dvb-usb-v2/Makefile b/drivers/media/usb/dvb-usb-v2/Makefile
+index 2c06714..926f12d 100644
+--- a/drivers/media/usb/dvb-usb-v2/Makefile
++++ b/drivers/media/usb/dvb-usb-v2/Makefile
+@@ -40,6 +40,9 @@ obj-$(CONFIG_DVB_USB_MXL111SF) += mxl111sf-tuner.o
+ dvb-usb-rtl28xxu-objs := rtl28xxu.o
+ obj-$(CONFIG_DVB_USB_RTL28XXU) += dvb-usb-rtl28xxu.o
+
++dvb-usb-dvbsky-objs := dvbsky.o
++obj-$(CONFIG_DVB_USB_DVBSKY) += dvb-usb-dvbsky.o
++
+ ccflags-y += -I$(srctree)/drivers/media/dvb-core
+ ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
+ ccflags-y += -I$(srctree)/drivers/media/tuners
+diff --git a/drivers/media/usb/dvb-usb-v2/dvbsky.c b/drivers/media/usb/dvb-usb-v2/dvbsky.c
+new file mode 100644
+index 0000000..cd9c039
+--- /dev/null
++++ b/drivers/media/usb/dvb-usb-v2/dvbsky.c
+@@ -0,0 +1,892 @@
++/*
++ * Driver for DVBSky receiver
++ *
++ * Copyright (C) 2013 Max nibble <nibble.max@gmail.com>
++ *
++ * CIMax code is copied and modified from:
++ * CIMax2(R) SP2 driver in conjunction with NetUp Dual DVB-S2 CI card
++ * Copyright (C) 2009 NetUP Inc.
++ * Copyright (C) 2009 Igor M. Liplianin <liplianin@netup.ru>
++ * Copyright (C) 2009 Abylay Ospan <aospan@netup.ru>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
++ */
++
++#include "dvb_ca_en50221.h"
++#include "dvb_usb.h"
++#include "dvbsky_m88ds3103.h"
++
++static int dvbsky_debug;
++module_param(dvbsky_debug, int, 0644);
++MODULE_PARM_DESC(dvbsky_debug, "Activates dvbsky usb debugging (default:0)");
++
++#define DVBSKY_MSG_DELAY 0/*2000*/
++#define DVBSKY_CI_CTL 0x04
++#define DVBSKY_CI_RD 1
++#define DVBSKY_BUF_LEN 64
++
++#define dprintk(args...) \
++ do { \
++ if (dvbsky_debug) \
++ printk(KERN_INFO "dvbsky_usb: " args); \
++ } while (0)
++
++DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
++
++struct dvbsky_state {
++ struct mutex stream_mutex;
++ u8 ibuf[DVBSKY_BUF_LEN];
++ u8 obuf[DVBSKY_BUF_LEN];
++ u8 has_ci;
++ u8 ci_attached;
++ struct dvb_ca_en50221 ci;
++ unsigned long next_status_checked_time;
++ u8 ci_i2c_addr;
++ u8 current_ci_flag;
++ int ci_status;
++};
++
++static int dvbsky_usb_generic_rw(struct dvb_usb_device *d,
++ u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen)
++{
++ int ret;
++ struct dvbsky_state *state = d_to_priv(d);
++
++ mutex_lock(&d->usb_mutex);
++ if (wlen != 0)
++ memcpy(state->obuf, wbuf, wlen);
++
++ ret = dvb_usbv2_generic_rw_locked(d, state->obuf, wlen,
++ state->ibuf, rlen);
++
++ if (!ret && (rlen != 0))
++ memcpy(rbuf, state->ibuf, rlen);
++
++ mutex_unlock(&d->usb_mutex);
++ return ret;
++}
++
++static int dvbsky_stream_ctrl(struct dvb_usb_device *d, u8 onoff)
++{
++ struct dvbsky_state *state = d_to_priv(d);
++ int ret;
++ u8 obuf_pre[3] = { 0x37, 0, 0 };
++ u8 obuf_post[3] = { 0x36, 3, 0 };
++ dprintk("%s() -off \n", __func__);
++ mutex_lock(&state->stream_mutex);
++ ret = dvbsky_usb_generic_rw(d, obuf_pre, 3, NULL, 0);
++ if (!ret && onoff) {
++ msleep(20);
++ ret = dvbsky_usb_generic_rw(d, obuf_post, 3, NULL, 0);
++ dprintk("%s() -on \n", __func__);
++ }
++ mutex_unlock(&state->stream_mutex);
++ return ret;
++}
++
++/* CI opertaions */
++static int dvbsky_ci_read_i2c(struct i2c_adapter *i2c_adap, u8 addr, u8 reg,
++ u8 *buf, int len)
++{
++ int ret;
++ struct i2c_msg msg[] = {
++ {
++ .addr = addr,
++ .flags = 0,
++ .buf = ®,
++ .len = 1
++ }, {
++ .addr = addr,
++ .flags = I2C_M_RD,
++ .buf = buf,
++ .len = len
++ }
++ };
++
++ ret = i2c_transfer(i2c_adap, msg, 2);
++
++ if (ret != 2) {
++ dprintk("%s: error, Reg = 0x%02x, Status = %d\n", __func__, reg, ret);
++ return -1;
++ }
++ return 0;
++}
++
++static int dvbsky_ci_write_i2c(struct i2c_adapter *i2c_adap, u8 addr, u8 reg,
++ u8 *buf, int len)
++{
++ int ret;
++ u8 buffer[64];
++
++ struct i2c_msg msg = {
++ .addr = addr,
++ .flags = 0,
++ .buf = &buffer[0],
++ .len = len + 1
++ };
++
++ if(len + 1 > sizeof(buffer)) {
++ dprintk("%s: len overflow.\n", __func__);
++ return -1;
++ }
++
++ buffer[0] = reg;
++ memcpy(&buffer[1], buf, len);
++
++ ret = i2c_transfer(i2c_adap, &msg, 1);
++
++ if (ret != 1) {
++ dprintk("%s: error, Reg=[0x%02x], Status=%d\n", __func__, reg, ret);
++ return -1;
++ }
++ return 0;
++}
++
++static int dvbsky_ci_op_cam(struct dvb_ca_en50221 *ci, int slot,
++ u8 flag, u8 read, int addr, u8 data)
++{
++ struct dvb_usb_device *d = ci->data;
++ struct dvbsky_state *state = d_to_priv(d);
++ u8 store;
++ int ret;
++ u8 command[4], respond[2], command_size, respond_size;
++
++ /*dprintk("%s()\n", __func__);*/
++ if (0 != slot)
++ return -EINVAL;
++
++ if (state->current_ci_flag != flag) {
++ ret = dvbsky_ci_read_i2c(&d->i2c_adap, state->ci_i2c_addr,
++ 0, &store, 1);
++ if (ret != 0)
++ return ret;
++
++ store &= ~0x0c;
++ store |= flag;
++
++ ret = dvbsky_ci_write_i2c(&d->i2c_adap, state->ci_i2c_addr,
++ 0, &store, 1);
++ if (ret != 0)
++ return ret;
++ }
++ state->current_ci_flag = flag;
++
++ command[1] = (u8)((addr >> 8) & 0xff); /*high part of address*/
++ command[2] = (u8)(addr & 0xff); /*low part of address*/
++ if (read) {
++ command[0] = 0x71;
++ command_size = 3;
++ respond_size = 2;
++ } else {
++ command[0] = 0x70;
++ command[3] = data;
++ command_size = 4;
++ respond_size = 1;
++ }
++ ret = dvbsky_usb_generic_rw(d, command, command_size, respond, respond_size);
++ if(ret)
++ dev_err(&d->udev->dev, "%s: %s() " \
++ "failed=%d\n", KBUILD_MODNAME, __func__, ret);
++
++ return (read) ? respond[1] : 0;
++}
++
++static int dvbsky_ci_read_attribute_mem(struct dvb_ca_en50221 *ci,
++ int slot, int addr)
++{
++ return dvbsky_ci_op_cam(ci, slot, 0, DVBSKY_CI_RD, addr, 0);
++}
++
++static int dvbsky_ci_write_attribute_mem(struct dvb_ca_en50221 *ci,
++ int slot, int addr, u8 data)
++{
++ return dvbsky_ci_op_cam(ci, slot, 0, 0, addr, data);
++}
++
++static int dvbsky_ci_read_cam_ctl(struct dvb_ca_en50221 *ci, int slot, u8 addr)
++{
++ return dvbsky_ci_op_cam(ci, slot, DVBSKY_CI_CTL, DVBSKY_CI_RD, addr, 0);
++}
++
++static int dvbsky_ci_write_cam_ctl(struct dvb_ca_en50221 *ci, int slot,
++ u8 addr, u8 data)
++{
++ return dvbsky_ci_op_cam(ci, slot, DVBSKY_CI_CTL, 0, addr, data);
++}
++
++static int dvbsky_ci_slot_reset(struct dvb_ca_en50221 *ci, int slot)
++{
++ struct dvb_usb_device *d = ci->data;
++ struct dvbsky_state *state = d_to_priv(d);
++ u8 buf = 0x80;
++ int ret;
++ dprintk("%s() slot=%d\n", __func__, slot);
++
++ if (0 != slot)
++ return -EINVAL;
++
++ udelay(500);
++ ret = dvbsky_ci_write_i2c(&d->i2c_adap, state->ci_i2c_addr,
++ 0, &buf, 1);
++
++ if (ret != 0)
++ return ret;
++
++ udelay(500);
++
++ buf = 0x00;
++ ret = dvbsky_ci_write_i2c(&d->i2c_adap, state->ci_i2c_addr,
++ 0, &buf, 1);
++ msleep(1000);
++ dprintk("%s() slot=%d complete\n", __func__, slot);
++ return 0;
++
++}
++
++static int dvbsky_ci_slot_shutdown(struct dvb_ca_en50221 *ci, int slot)
++{
++ /* not implemented */
++ dprintk("%s()\n", __func__);
++ return 0;
++}
++
++static int dvbsky_ci_slot_ts_enable(struct dvb_ca_en50221 *ci, int slot)
++{
++ struct dvb_usb_device *d = ci->data;
++ struct dvbsky_state *state = d_to_priv(d);
++ u8 buf;
++ int ret;
++
++ dprintk("%s()\n", __func__);
++ if (0 != slot)
++ return -EINVAL;
++
++ dvbsky_ci_read_i2c(&d->i2c_adap, state->ci_i2c_addr,
++ 0, &buf, 1);
++ buf |= 0x60;
++
++ ret = dvbsky_ci_write_i2c(&d->i2c_adap, state->ci_i2c_addr,
++ 0, &buf, 1);
++ return ret;
++}
++
++static int dvbsky_ci_poll_slot_status(struct dvb_ca_en50221 *ci, int slot,
++ int open)
++{
++ struct dvb_usb_device *d = ci->data;
++ struct dvbsky_state *state = d_to_priv(d);
++ int ret = 0;
++ u8 buf = 0;
++ /*dprintk("%s()\n", __func__);*/
++
++ /* CAM module INSERT/REMOVE processing. slow operation because of i2c
++ * transfers */
++ if (time_after(jiffies, state->next_status_checked_time)) {
++ ret = dvbsky_ci_read_i2c(&d->i2c_adap, state->ci_i2c_addr,
++ 0, &buf, 1);
++
++ /*dprintk("%s() status=%x\n", __func__, buf);*/
++
++ state->next_status_checked_time = jiffies
++ + msecs_to_jiffies(1000);
++
++ if (ret != 0)
++ return 0;
++
++ if (buf & 1) {
++ state->ci_status = DVB_CA_EN50221_POLL_CAM_PRESENT |
++ DVB_CA_EN50221_POLL_CAM_READY;
++ }
++ else
++ state->ci_status = 0;
++ }
++ /*dprintk("%s() ret=%x\n", __func__, state->ci_status);*/
++ return state->ci_status;
++}
++
++static int dvbsky_ci_init(struct dvb_usb_device *d)
++{
++ struct dvbsky_state *state = d_to_priv(d);
++ int ret;
++ u8 cimax_init[34] = {
++ 0x00, /* module A control*/
++ 0x00, /* auto select mask high A */
++ 0x00, /* auto select mask low A */
++ 0x00, /* auto select pattern high A */
++ 0x00, /* auto select pattern low A */
++ 0x44, /* memory access time A */
++ 0x00, /* invert input A */
++ 0x00, /* RFU */
++ 0x00, /* RFU */
++ 0x00, /* module B control*/
++ 0x00, /* auto select mask high B */
++ 0x00, /* auto select mask low B */
++ 0x00, /* auto select pattern high B */
++ 0x00, /* auto select pattern low B */
++ 0x44, /* memory access time B */
++ 0x00, /* invert input B */
++ 0x00, /* RFU */
++ 0x00, /* RFU */
++ 0x00, /* auto select mask high Ext */
++ 0x00, /* auto select mask low Ext */
++ 0x00, /* auto select pattern high Ext */
++ 0x00, /* auto select pattern low Ext */
++ 0x00, /* RFU */
++ 0x02, /* destination - module A */
++ 0x01, /* power on (use it like store place) */
++ 0x00, /* RFU */
++ 0x00, /* int status read only */
++ 0x00, /* Max: Disable the interrupt in USB solution.*/
++ 0x05, /* EXTINT=active-high, INT=push-pull */
++ 0x00, /* USCG1 */
++ 0x04, /* ack active low */
++ 0x00, /* LOCK = 0 */
++ 0x22, /* serial mode, rising in, rising out, MSB first*/
++ 0x00 /* synchronization */
++ };
++ dprintk("%s()\n", __func__);
++ state->current_ci_flag = 0xff;
++ state->ci_status = 0;
++ state->next_status_checked_time = jiffies + msecs_to_jiffies(1000);
++ state->ci_i2c_addr = 0x40;
++
++ state->ci.owner = THIS_MODULE;
++ state->ci.read_attribute_mem = dvbsky_ci_read_attribute_mem;
++ state->ci.write_attribute_mem = dvbsky_ci_write_attribute_mem;
++ state->ci.read_cam_control = dvbsky_ci_read_cam_ctl;
++ state->ci.write_cam_control = dvbsky_ci_write_cam_ctl;
++ state->ci.slot_reset = dvbsky_ci_slot_reset;
++ state->ci.slot_shutdown = dvbsky_ci_slot_shutdown;
++ state->ci.slot_ts_enable = dvbsky_ci_slot_ts_enable;
++ state->ci.poll_slot_status = dvbsky_ci_poll_slot_status;
++ state->ci.data = d;
++
++ ret = dvbsky_ci_write_i2c(&d->i2c_adap, state->ci_i2c_addr,
++ 0, &cimax_init[0], 34);
++ /* lock registers */
++ ret |= dvbsky_ci_write_i2c(&d->i2c_adap, state->ci_i2c_addr,
++ 0x1f, &cimax_init[0x18], 1);
++ /* power on slots */
++ ret |= dvbsky_ci_write_i2c(&d->i2c_adap, state->ci_i2c_addr,
++ 0x18, &cimax_init[0x18], 1);
++ if (0 != ret)
++ return ret;
++
++ ret = dvb_ca_en50221_init(&d->adapter[0].dvb_adap, &state->ci, 0, 1);
++ if (ret)
++ return ret;
++ state->ci_attached = 1;
++ dprintk("%s() complete.\n", __func__);
++ return 0;
++}
++
++static void dvbsky_ci_release(struct dvb_usb_device *d)
++{
++ struct dvbsky_state *state = d_to_priv(d);
++
++ /* detach CI */
++ if (state->ci_attached)
++ dvb_ca_en50221_release(&state->ci);
++
++ return;
++}
++
++static int dvbsky_streaming_ctrl(struct dvb_frontend *fe, int onoff)
++{
++ struct dvb_usb_device *d = fe_to_d(fe);
++ /*dprintk("%s() %d\n", __func__, onoff);*/
++ return dvbsky_stream_ctrl(d, (onoff == 0) ? 0 : 1);
++}
++
++/* GPIO */
++static int dvbsky_gpio_ctrl(struct dvb_usb_device *d, u8 gport, u8 value)
++{
++ int ret;
++ u8 obuf[64], ibuf[64];
++ obuf[0] = 0x0e;
++ obuf[1] = gport;
++ obuf[2] = value;
++ ret = dvbsky_usb_generic_rw(d, obuf, 3, ibuf, 1);
++ if(ret)
++ dev_err(&d->udev->dev, "%s: %s() " \
++ "failed=%d\n", KBUILD_MODNAME, __func__, ret);
++ return ret;
++}
++
++/* I2C */
++static int dvbsky_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
++ int num)
++{
++ struct dvb_usb_device *d = i2c_get_adapdata(adap);
++ int ret = 0;
++ u8 ibuf[64], obuf[64];
++
++ if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
++ return -EAGAIN;
++
++ if (num > 2) {
++ printk(KERN_ERR "dvbsky_usb: too many i2c messages[%d] than 2.", num);
++ ret = -EOPNOTSUPP;
++ goto i2c_error;
++ }
++
++ if(num == 1) {
++ if (msg[0].len > 60) {
++ printk(KERN_ERR "dvbsky_usb: too many i2c bytes[%d] than 60.", msg[0].len);
++ ret = -EOPNOTSUPP;
++ goto i2c_error;
++ }
++ if (msg[0].flags & I2C_M_RD) {
++ /* single read */
++ obuf[0] = 0x09;
++ obuf[1] = 0;
++ obuf[2] = msg[0].len;
++ obuf[3] = msg[0].addr;
++ ret = dvbsky_usb_generic_rw(d, obuf, 4, ibuf, msg[0].len + 1);
++ if(ret)
++ dev_err(&d->udev->dev, "%s: %s() " \
++ "failed=%d\n", KBUILD_MODNAME, __func__, ret);
++ /*dprintk("%s(): read status = %d\n", __func__, ibuf[0]);*/
++ if (!ret)
++ memcpy(msg[0].buf, &ibuf[1], msg[0].len);
++ } else {
++ /* write */
++ obuf[0] = 0x08;
++ obuf[1] = msg[0].addr;
++ obuf[2] = msg[0].len;
++ memcpy(&obuf[3], msg[0].buf, msg[0].len);
++ ret = dvbsky_usb_generic_rw(d, obuf, msg[0].len + 3, ibuf, 1);
++ if(ret)
++ dev_err(&d->udev->dev, "%s: %s() " \
++ "failed=%d\n", KBUILD_MODNAME, __func__, ret);
++ /*dprintk("%s(): write status = %d\n", __func__, ibuf[0]);*/
++ }
++ } else {
++ if ((msg[0].len > 60) || (msg[1].len > 60)) {
++ printk(KERN_ERR "dvbsky_usb: too many i2c bytes[w-%d][r-%d] than 60.", msg[0].len, msg[1].len);
++ ret = -EOPNOTSUPP;
++ goto i2c_error;
++ }
++ /* write then read */
++ obuf[0] = 0x09;
++ obuf[1] = msg[0].len;
++ obuf[2] = msg[1].len;
++ obuf[3] = msg[0].addr;
++ memcpy(&obuf[4], msg[0].buf, msg[0].len);
++ ret = dvbsky_usb_generic_rw(d, obuf, msg[0].len + 4, ibuf, msg[1].len + 1);
++ if(ret)
++ dev_err(&d->udev->dev, "%s: %s() " \
++ "failed=%d\n", KBUILD_MODNAME, __func__, ret);
++ /*dprintk("%s(): write then read status = %d\n", __func__, ibuf[0]);*/
++ if (!ret)
++ memcpy(msg[1].buf, &ibuf[1], msg[1].len);
++ }
++i2c_error:
++ mutex_unlock(&d->i2c_mutex);
++ return (ret) ? ret : num;
++}
++
++static u32 dvbsky_i2c_func(struct i2c_adapter *adapter)
++{
++ return I2C_FUNC_I2C;
++}
++
++static struct i2c_algorithm dvbsky_i2c_algo = {
++ .master_xfer = dvbsky_i2c_xfer,
++ .functionality = dvbsky_i2c_func,
++};
++
++#if IS_ENABLED(CONFIG_RC_CORE)
++static int dvbsky_rc_query(struct dvb_usb_device *d)
++{
++
++ u32 code = 0xffff, scancode;\r
++ u8 rc5_command, rc5_system;\r
++ u8 obuf[2], ibuf[2], toggle;\r
++ int ret;
++ obuf[0] = 0x10;\r
++ ret = dvbsky_usb_generic_rw(d, obuf, 1, ibuf, 2);
++ if(ret)
++ dev_err(&d->udev->dev, "%s: %s() " \
++ "failed=%d\n", KBUILD_MODNAME, __func__, ret);\r
++ if(ret == 0)\r
++ code = (ibuf[0] << 8) | ibuf[1];\r
++\r
++ if (code != 0xffff) {\r
++ dprintk("rc code: %x \n", code);\r
++ rc5_command = code & 0x3F;\r
++ rc5_system = (code & 0x7C0) >> 6;\r
++ toggle = (code & 0x800) ? 1 : 0; \r
++ scancode = rc5_system << 8 | rc5_command;\r
++ rc_keydown(d->rc_dev, scancode, toggle);\r
++ }\r
++ return 0;
++}
++
++static int dvbsky_get_rc_config(struct dvb_usb_device *d, struct dvb_usb_rc *rc)
++{
++ rc->allowed_protos = RC_BIT_RC5;
++ rc->query = dvbsky_rc_query;
++ rc->interval = 300;
++ return 0;
++}
++#else
++ #define dvbsky_get_rc_config NULL
++#endif
++
++static int dvbsky_sync_ctrl(struct dvb_frontend *fe)
++{
++ struct dvb_usb_device *d = fe_to_d(fe);
++ return dvbsky_stream_ctrl(d, 1);
++}
++
++static int dvbsky_usb_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
++{
++ struct dvb_usb_device *d = fe_to_d(fe);
++ u8 value;
++
++ if (voltage == SEC_VOLTAGE_OFF)
++ value = 0;
++ else
++ value = 1;
++ return dvbsky_gpio_ctrl(d, 0x80, value);
++}
++
++static int dvbsky_usb_ci_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
++{
++ struct dvb_usb_device *d = fe_to_d(fe);
++ u8 value;
++
++ if (voltage == SEC_VOLTAGE_OFF)
++ value = 0;
++ else
++ value = 1;
++ return dvbsky_gpio_ctrl(d, 0x00, value);
++}
++
++static int dvbsky_t680c_attach(struct dvb_usb_adapter *adap)
++{
++ struct dvbsky_state *state = adap_to_priv(adap);
++ struct dvb_usb_device *d = adap_to_d(adap);
++ int ret = 0;
++
++ /*dprintk("%s, build on %s %s()\n", __func__, __DATE__,__TIME__);*/
++
++// adap->fe[0] = dvb_attach(sit2_attach,
++// &dvbsky_usb_sit2_config,
++// &d->i2c_adap);
++ if (!adap->fe[0]) {
++ printk(KERN_ERR "dvbsky_t680c_attach fail.");
++ ret = -ENODEV;
++ }
++
++ state->has_ci = 1;
++
++ return ret;
++}
++
++static int dvbsky_t330_attach(struct dvb_usb_adapter *adap)
++{
++ struct dvbsky_state *state = adap_to_priv(adap);
++ struct dvb_usb_device *d = adap_to_d(adap);
++ int ret = 0;
++
++ /*dprintk("%s, build on %s %s()\n", __func__, __DATE__,__TIME__);*/
++
++// adap->fe[0] = dvb_attach(sit2_attach,
++// &dvbsky_t330_sit2_config,
++// &d->i2c_adap);
++ if (!adap->fe[0]) {
++ printk(KERN_ERR "dvbsky_t330_attach fail.");
++ ret = -ENODEV;
++ }
++
++ state->has_ci = 0;
++
++ return ret;
++}
++
++static int dvbsky_read_mac_addr(struct dvb_usb_adapter *adap, u8 mac[6])
++{
++ struct dvb_usb_device *d = adap_to_d(adap);
++ u8 obuf[] = { 0x1e, 0x00 };
++ u8 ibuf[6] = { 0 };
++ struct i2c_msg msg[] = {
++ {
++ .addr = 0x51,
++ .flags = 0,
++ .buf = obuf,
++ .len = 2,
++ }, {
++ .addr = 0x51,
++ .flags = I2C_M_RD,
++ .buf = ibuf,
++ .len = 6,
++
++ }
++ };
++
++ if (i2c_transfer(&d->i2c_adap, msg, 2) == 2)
++ memcpy(mac, ibuf, 6);
++
++ printk(KERN_INFO "dvbsky_usb MAC address=%pM\n", mac);
++
++ return 0;
++}
++
++static struct dvbsky_m88ds3103_config dvbsky_usb_ds3103_config = {
++ .demod_address = 0x68,
++ .ci_mode = 1,
++ .pin_ctrl = 0x83,
++ .ts_mode = 0,
++ .start_ctrl = dvbsky_sync_ctrl,
++ .set_voltage = dvbsky_usb_set_voltage,
++};
++
++static int dvbsky_s960_attach(struct dvb_usb_adapter *adap)
++{
++ struct dvbsky_state *state = adap_to_priv(adap);
++ struct dvb_usb_device *d = adap_to_d(adap);
++ int ret = 0;
++
++ dprintk("%s()\n", __func__);
++
++ adap->fe[0] = dvb_attach(dvbsky_m88ds3103_attach,
++ &dvbsky_usb_ds3103_config,
++ &d->i2c_adap);
++ if (!adap->fe[0]) {
++ printk(KERN_ERR "dvbsky_s960_attach fail.");
++ ret = -ENODEV;
++ }
++
++ state->has_ci = 0;
++
++ return ret;
++}
++
++static struct dvbsky_m88ds3103_config dvbsky_usb_ds3103_ci_config = {
++ .demod_address = 0x68,
++ .ci_mode = 2,
++ .pin_ctrl = 0x82,
++ .ts_mode = 0,
++ .start_ctrl = dvbsky_sync_ctrl,
++ .set_voltage = dvbsky_usb_ci_set_voltage,
++};
++static int dvbsky_s960c_attach(struct dvb_usb_adapter *adap)
++{
++ struct dvbsky_state *state = adap_to_priv(adap);
++ struct dvb_usb_device *d = adap_to_d(adap);
++ int ret = 0;
++
++ /*dprintk("%s, build on %s %s()\n", __func__, __DATE__,__TIME__);*/
++
++ adap->fe[0] = dvb_attach(dvbsky_m88ds3103_attach,
++ &dvbsky_usb_ds3103_ci_config,
++ &d->i2c_adap);
++ if (!adap->fe[0]) {
++ printk(KERN_ERR "dvbsky_s960c_attach fail.");
++ ret = -ENODEV;
++ }
++
++ state->has_ci = 1;
++
++ return ret;
++}
++
++static int dvbsky_identify_state(struct dvb_usb_device *d, const char **name)
++{
++ /*
++ printk(KERN_INFO "%s, build on %s %s(),delay=%d\n",
++ __func__, __DATE__,__TIME__,d->props->generic_bulk_ctrl_delay);
++ */
++ dvbsky_gpio_ctrl(d, 0x04, 1);
++ msleep(20);
++ dvbsky_gpio_ctrl(d, 0x83, 0);
++ dvbsky_gpio_ctrl(d, 0xc0, 1);
++ msleep(100);
++ dvbsky_gpio_ctrl(d, 0x83, 1);
++ dvbsky_gpio_ctrl(d, 0xc0, 0);
++ msleep(50);
++
++ return WARM;
++}
++
++static int dvbsky_init(struct dvb_usb_device *d)
++{
++ struct dvbsky_state *state = d_to_priv(d);
++ int ret;
++
++ /* use default interface */
++ /*
++ ret = usb_set_interface(d->udev, 0, 0);
++ if (ret)
++ return ret;
++ */
++ mutex_init(&state->stream_mutex);
++
++ /* attach CI */
++ if (state->has_ci) {
++ state->ci_attached = 0;
++ ret = dvbsky_ci_init(d);
++ if (ret)
++ return ret;
++ }
++ return 0;
++}
++
++static void dvbsky_exit(struct dvb_usb_device *d)
++{
++ return dvbsky_ci_release(d);
++}
++
++/* DVB USB Driver stuff */
++static struct dvb_usb_device_properties dvbsky_t330_props = {
++ .driver_name = KBUILD_MODNAME,
++ .owner = THIS_MODULE,
++ .adapter_nr = adapter_nr,
++ .size_of_priv = sizeof(struct dvbsky_state),
++
++ .generic_bulk_ctrl_endpoint = 0x01,
++ .generic_bulk_ctrl_endpoint_response = 0x81,
++ .generic_bulk_ctrl_delay = DVBSKY_MSG_DELAY,
++
++ .i2c_algo = &dvbsky_i2c_algo,
++ .frontend_attach = dvbsky_t330_attach,
++ .init = dvbsky_init,
++ .get_rc_config = dvbsky_get_rc_config,
++ .streaming_ctrl = dvbsky_streaming_ctrl,
++ .identify_state = dvbsky_identify_state,
++ .exit = dvbsky_exit,
++ .read_mac_address = dvbsky_read_mac_addr,
++
++ .num_adapters = 1,
++ .adapter = {
++ {
++ .stream = DVB_USB_STREAM_BULK(0x82, 8, 4096),
++ }
++ }
++};
++
++static struct dvb_usb_device_properties dvbsky_s960c_props = {
++ .driver_name = KBUILD_MODNAME,
++ .owner = THIS_MODULE,
++ .adapter_nr = adapter_nr,
++ .size_of_priv = sizeof(struct dvbsky_state),
++
++ .generic_bulk_ctrl_endpoint = 0x01,
++ .generic_bulk_ctrl_endpoint_response = 0x81,
++ .generic_bulk_ctrl_delay = DVBSKY_MSG_DELAY,
++
++ .i2c_algo = &dvbsky_i2c_algo,
++ .frontend_attach = dvbsky_s960c_attach,
++ .init = dvbsky_init,
++ .get_rc_config = dvbsky_get_rc_config,
++ .streaming_ctrl = dvbsky_streaming_ctrl,
++ .identify_state = dvbsky_identify_state,
++ .exit = dvbsky_exit,
++ .read_mac_address = dvbsky_read_mac_addr,
++
++ .num_adapters = 1,
++ .adapter = {
++ {
++ .stream = DVB_USB_STREAM_BULK(0x82, 8, 4096),
++ }
++ }
++};
++
++static struct dvb_usb_device_properties dvbsky_t680c_props = {
++ .driver_name = KBUILD_MODNAME,
++ .owner = THIS_MODULE,
++ .adapter_nr = adapter_nr,
++ .size_of_priv = sizeof(struct dvbsky_state),
++
++ .generic_bulk_ctrl_endpoint = 0x01,
++ .generic_bulk_ctrl_endpoint_response = 0x81,
++ .generic_bulk_ctrl_delay = DVBSKY_MSG_DELAY,
++
++ .i2c_algo = &dvbsky_i2c_algo,
++ .frontend_attach = dvbsky_t680c_attach,
++ .init = dvbsky_init,
++ .get_rc_config = dvbsky_get_rc_config,
++ .streaming_ctrl = dvbsky_streaming_ctrl,
++ .identify_state = dvbsky_identify_state,
++ .exit = dvbsky_exit,
++ .read_mac_address = dvbsky_read_mac_addr,
++
++ .num_adapters = 1,
++ .adapter = {
++ {
++ .stream = DVB_USB_STREAM_BULK(0x82, 8, 4096),
++ }
++ }
++};
++
++static struct dvb_usb_device_properties dvbsky_s960_props = {
++ .driver_name = KBUILD_MODNAME,
++ .owner = THIS_MODULE,
++ .adapter_nr = adapter_nr,
++ .size_of_priv = sizeof(struct dvbsky_state),
++
++ .generic_bulk_ctrl_endpoint = 0x01,
++ .generic_bulk_ctrl_endpoint_response = 0x81,
++ .generic_bulk_ctrl_delay = DVBSKY_MSG_DELAY,
++
++ .i2c_algo = &dvbsky_i2c_algo,
++ .frontend_attach = dvbsky_s960_attach,
++ .init = dvbsky_init,
++ .get_rc_config = dvbsky_get_rc_config,
++ .streaming_ctrl = dvbsky_streaming_ctrl,
++ .identify_state = dvbsky_identify_state,
++ .exit = dvbsky_exit,
++ .read_mac_address = dvbsky_read_mac_addr,
++
++ .num_adapters = 1,
++ .adapter = {
++ {
++ .stream = DVB_USB_STREAM_BULK(0x82, 8, 4096),
++ }
++ }
++};
++
++static const struct usb_device_id dvbsky_id_table[] = {
++ { DVB_USB_DEVICE(0x0572, 0x0320,
++ &dvbsky_t330_props, "DVBSky T330 USB Stick", RC_MAP_DVBSKY) },
++ { DVB_USB_DEVICE(0x0572, 0x960c,
++ &dvbsky_s960c_props, "DVBSky S960CI", RC_MAP_DVBSKY) },
++ { DVB_USB_DEVICE(0x0572, 0x680c,
++ &dvbsky_t680c_props, "DVBSky T680CI", RC_MAP_DVBSKY) },
++ { DVB_USB_DEVICE(0x0572, 0x6831,
++ &dvbsky_s960_props, "DVBSky S960/S860", RC_MAP_DVBSKY) },
++ { }
++};
++MODULE_DEVICE_TABLE(usb, dvbsky_id_table);
++
++static struct usb_driver dvbsky_usb_driver = {
++ .name = KBUILD_MODNAME,
++ .id_table = dvbsky_id_table,
++ .probe = dvb_usbv2_probe,
++ .disconnect = dvb_usbv2_disconnect,
++ .suspend = dvb_usbv2_suspend,
++ .resume = dvb_usbv2_resume,
++ .reset_resume = dvb_usbv2_reset_resume,
++ .no_dynamic_id = 1,
++ .soft_unbind = 1,
++};
++
++module_usb_driver(dvbsky_usb_driver);
++
++MODULE_AUTHOR("Max nibble <nibble.max@gmail.com>");
++MODULE_DESCRIPTION("Driver for DVBSky USB2.0");
++MODULE_LICENSE("GPL");
+diff --git a/include/media/rc-map.h b/include/media/rc-map.h
+index a20ed97..73f8c92 100644
+--- a/include/media/rc-map.h
++++ b/include/media/rc-map.h
+@@ -119,6 +119,7 @@ void rc_map_init(void);
+ #define RC_MAP_DM1105_NEC "rc-dm1105-nec"
+ #define RC_MAP_DNTV_LIVE_DVBT_PRO "rc-dntv-live-dvbt-pro"
+ #define RC_MAP_DNTV_LIVE_DVB_T "rc-dntv-live-dvb-t"
++#define RC_MAP_DVBSKY "rc-dvbsky"
+ #define RC_MAP_EMPTY "rc-empty"
+ #define RC_MAP_EM_TERRATEC "rc-em-terratec"
+ #define RC_MAP_ENCORE_ENLTV2 "rc-encore-enltv2"
--- /dev/null
+diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
+index 494b888..851e01f 100644
+--- a/drivers/net/Kconfig
++++ b/drivers/net/Kconfig
+@@ -203,6 +203,125 @@ config RIONET_RX_SIZE
+ depends on RIONET
+ default "128"
+
++config IMQ
++ tristate "IMQ (intermediate queueing device) support"
++ depends on NETDEVICES && NETFILTER
++ ---help---
++ The IMQ device(s) is used as placeholder for QoS queueing
++ disciplines. Every packet entering/leaving the IP stack can be
++ directed through the IMQ device where it's enqueued/dequeued to the
++ attached qdisc. This allows you to treat network devices as classes
++ and distribute bandwidth among them. Iptables is used to specify
++ through which IMQ device, if any, packets travel.
++
++ More information at: http://www.linuximq.net/
++
++ To compile this driver as a module, choose M here: the module
++ will be called imq. If unsure, say N.
++
++choice
++ prompt "IMQ behavior (PRE/POSTROUTING)"
++ depends on IMQ
++ default IMQ_BEHAVIOR_AB
++ help
++ This setting defines how IMQ behaves in respect to its
++ hooking in PREROUTING and POSTROUTING.
++
++ IMQ can work in any of the following ways:
++
++ PREROUTING | POSTROUTING
++ -----------------|-------------------
++ #1 After NAT | After NAT
++ #2 After NAT | Before NAT
++ #3 Before NAT | After NAT
++ #4 Before NAT | Before NAT
++
++ The default behavior is to hook before NAT on PREROUTING
++ and after NAT on POSTROUTING (#3).
++
++ This settings are specially usefull when trying to use IMQ
++ to shape NATed clients.
++
++ More information can be found at: www.linuximq.net
++
++ If not sure leave the default settings alone.
++
++config IMQ_BEHAVIOR_AA
++ bool "IMQ AA"
++ help
++ This setting defines how IMQ behaves in respect to its
++ hooking in PREROUTING and POSTROUTING.
++
++ Choosing this option will make IMQ hook like this:
++
++ PREROUTING: After NAT
++ POSTROUTING: After NAT
++
++ More information can be found at: www.linuximq.net
++
++ If not sure leave the default settings alone.
++
++config IMQ_BEHAVIOR_AB
++ bool "IMQ AB"
++ help
++ This setting defines how IMQ behaves in respect to its
++ hooking in PREROUTING and POSTROUTING.
++
++ Choosing this option will make IMQ hook like this:
++
++ PREROUTING: After NAT
++ POSTROUTING: Before NAT
++
++ More information can be found at: www.linuximq.net
++
++ If not sure leave the default settings alone.
++
++config IMQ_BEHAVIOR_BA
++ bool "IMQ BA"
++ help
++ This setting defines how IMQ behaves in respect to its
++ hooking in PREROUTING and POSTROUTING.
++
++ Choosing this option will make IMQ hook like this:
++
++ PREROUTING: Before NAT
++ POSTROUTING: After NAT
++
++ More information can be found at: www.linuximq.net
++
++ If not sure leave the default settings alone.
++
++config IMQ_BEHAVIOR_BB
++ bool "IMQ BB"
++ help
++ This setting defines how IMQ behaves in respect to its
++ hooking in PREROUTING and POSTROUTING.
++
++ Choosing this option will make IMQ hook like this:
++
++ PREROUTING: Before NAT
++ POSTROUTING: Before NAT
++
++ More information can be found at: www.linuximq.net
++
++ If not sure leave the default settings alone.
++
++endchoice
++
++config IMQ_NUM_DEVS
++ int "Number of IMQ devices"
++ range 2 16
++ depends on IMQ
++ default "16"
++ help
++ This setting defines how many IMQ devices will be created.
++
++ The default value is 16.
++
++ More information can be found at: www.linuximq.net
++
++ If not sure leave the default settings alone.
++
+ config TUN
+ tristate "Universal TUN/TAP device driver support"
+ select CRC32
+diff --git a/drivers/net/Makefile b/drivers/net/Makefile
+index 3fef8a8..12dafc0 100644
+--- a/drivers/net/Makefile
++++ b/drivers/net/Makefile
+@@ -9,6 +9,7 @@ obj-$(CONFIG_BONDING) += bonding/
+ obj-$(CONFIG_DUMMY) += dummy.o
+ obj-$(CONFIG_EQUALIZER) += eql.o
+ obj-$(CONFIG_IFB) += ifb.o
++obj-$(CONFIG_IMQ) += imq.o
+ obj-$(CONFIG_MACVLAN) += macvlan.o
+ obj-$(CONFIG_MACVTAP) += macvtap.o
+ obj-$(CONFIG_MII) += mii.o
+diff --git a/drivers/net/imq.c b/drivers/net/imq.c
+new file mode 100644
+index 0000000..2140535
+--- /dev/null
++++ b/drivers/net/imq.c
+@@ -0,0 +1,1011 @@
++/*
++ * Pseudo-driver for the intermediate queue device.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version
++ * 2 of the License, or (at your option) any later version.
++ *
++ * Authors: Patrick McHardy, <kaber@trash.net>
++ *
++ * The first version was written by Martin Devera, <devik@cdi.cz>
++ *
++ * Credits: Jan Rafaj <imq2t@cedric.vabo.cz>
++ * - Update patch to 2.4.21
++ * Sebastian Strollo <sstrollo@nortelnetworks.com>
++ * - Fix "Dead-loop on netdevice imq"-issue
++ * Marcel Sebek <sebek64@post.cz>
++ * - Update to 2.6.2-rc1
++ *
++ * After some time of inactivity there is a group taking care
++ * of IMQ again: http://www.linuximq.net
++ *
++ *
++ * 2004/06/30 - New version of IMQ patch to kernels <=2.6.7
++ * including the following changes:
++ *
++ * - Correction of ipv6 support "+"s issue (Hasso Tepper)
++ * - Correction of imq_init_devs() issue that resulted in
++ * kernel OOPS unloading IMQ as module (Norbert Buchmuller)
++ * - Addition of functionality to choose number of IMQ devices
++ * during kernel config (Andre Correa)
++ * - Addition of functionality to choose how IMQ hooks on
++ * PRE and POSTROUTING (after or before NAT) (Andre Correa)
++ * - Cosmetic corrections (Norbert Buchmuller) (Andre Correa)
++ *
++ *
++ * 2005/12/16 - IMQ versions between 2.6.7 and 2.6.13 were
++ * released with almost no problems. 2.6.14-x was released
++ * with some important changes: nfcache was removed; After
++ * some weeks of trouble we figured out that some IMQ fields
++ * in skb were missing in skbuff.c - skb_clone and copy_skb_header.
++ * These functions are correctly patched by this new patch version.
++ *
++ * Thanks for all who helped to figure out all the problems with
++ * 2.6.14.x: Patrick McHardy, Rune Kock, VeNoMouS, Max CtRiX,
++ * Kevin Shanahan, Richard Lucassen, Valery Dachev (hopefully
++ * I didn't forget anybody). I apologize again for my lack of time.
++ *
++ *
++ * 2008/06/17 - 2.6.25 - Changed imq.c to use qdisc_run() instead
++ * of qdisc_restart() and moved qdisc_run() to tasklet to avoid
++ * recursive locking. New initialization routines to fix 'rmmod' not
++ * working anymore. Used code from ifb.c. (Jussi Kivilinna)
++ *
++ * 2008/08/06 - 2.6.26 - (JK)
++ * - Replaced tasklet with 'netif_schedule()'.
++ * - Cleaned up and added comments for imq_nf_queue().
++ *
++ * 2009/04/12
++ * - Add skb_save_cb/skb_restore_cb helper functions for backuping
++ * control buffer. This is needed because qdisc-layer on kernels
++ * 2.6.27 and newer overwrite control buffer. (Jussi Kivilinna)
++ * - Add better locking for IMQ device. Hopefully this will solve
++ * SMP issues. (Jussi Kivilinna)
++ * - Port to 2.6.27
++ * - Port to 2.6.28
++ * - Port to 2.6.29 + fix rmmod not working
++ *
++ * 2009/04/20 - (Jussi Kivilinna)
++ * - Use netdevice feature flags to avoid extra packet handling
++ * by core networking layer and possibly increase performance.
++ *
++ * 2009/09/26 - (Jussi Kivilinna)
++ * - Add imq_nf_reinject_lockless to fix deadlock with
++ * imq_nf_queue/imq_nf_reinject.
++ *
++ * 2009/12/08 - (Jussi Kivilinna)
++ * - Port to 2.6.32
++ * - Add check for skb->nf_queue_entry==NULL in imq_dev_xmit()
++ * - Also add better error checking for skb->nf_queue_entry usage
++ *
++ * 2010/02/25 - (Jussi Kivilinna)
++ * - Port to 2.6.33
++ *
++ * 2010/08/15 - (Jussi Kivilinna)
++ * - Port to 2.6.35
++ * - Simplify hook registration by using nf_register_hooks.
++ * - nf_reinject doesn't need spinlock around it, therefore remove
++ * imq_nf_reinject function. Other nf_reinject users protect
++ * their own data with spinlock. With IMQ however all data is
++ * needed is stored per skbuff, so no locking is needed.
++ * - Changed IMQ to use 'separate' NF_IMQ_QUEUE instead of
++ * NF_QUEUE, this allows working coexistance of IMQ and other
++ * NF_QUEUE users.
++ * - Make IMQ multi-queue. Number of IMQ device queues can be
++ * increased with 'numqueues' module parameters. Default number
++ * of queues is 1, in other words by default IMQ works as
++ * single-queue device. Multi-queue selection is based on
++ * IFB multi-queue patch by Changli Gao <xiaosuo@gmail.com>.
++ *
++ * 2011/03/18 - (Jussi Kivilinna)
++ * - Port to 2.6.38
++ *
++ * 2011/07/12 - (syoder89@gmail.com)
++ * - Crash fix that happens when the receiving interface has more
++ * than one queue (add missing skb_set_queue_mapping in
++ * imq_select_queue).
++ *
++ * 2011/07/26 - (Jussi Kivilinna)
++ * - Add queue mapping checks for packets exiting IMQ.
++ * - Port to 3.0
++ *
++ * 2011/08/16 - (Jussi Kivilinna)
++ * - Clear IFF_TX_SKB_SHARING flag that was added for linux 3.0.2
++ *
++ * 2011/11/03 - Germano Michel <germanomichel@gmail.com>
++ * - Fix IMQ for net namespaces
++ *
++ * 2011/11/04 - Jussi Kivilinna <jussi.kivilinna@mbnet.fi>
++ * - Port to 3.1
++ * - Clean-up, move 'get imq device pointer by imqX name' to
++ * separate function from imq_nf_queue().
++ *
++ * 2012/01/05 - Jussi Kivilinna <jussi.kivilinna@mbnet.fi>
++ * - Port to 3.2
++ *
++ * 2012/03/19 - Jussi Kivilinna <jussi.kivilinna@mbnet.fi>
++ * - Port to 3.3
++ *
++ * 2012/12/12 - Jussi Kivilinna <jussi.kivilinna@mbnet.fi>
++ * - Port to 3.7
++ * - Fix checkpatch.pl warnings
++ *
++ * 2013/09/10 - Jussi Kivilinna <jussi.kivilinna@iki.fi>
++ * - Fixed GSO handling for 3.10, see imq_nf_queue() for comments.
++ * - Don't copy skb->cb_next when copying or cloning skbuffs.
++ *
++ * 2013/09/16 - Jussi Kivilinna <jussi.kivilinna@iki.fi>
++ * - Port to 3.11
++ *
++ * 2013/11/12 - Jussi Kivilinna <jussi.kivilinna@iki.fi>
++ * - Port to 3.12
++ *
++ * 2014/02/07 - Jussi Kivilinna <jussi.kivilinna@iki.fi>
++ * - Port to 3.13
++ *
++ * Also, many thanks to pablo Sebastian Greco for making the initial
++ * patch and to those who helped the testing.
++ *
++ * More info at: http://www.linuximq.net/ (Andre Correa)
++ */
++
++#include <linux/module.h>
++#include <linux/kernel.h>
++#include <linux/moduleparam.h>
++#include <linux/list.h>
++#include <linux/skbuff.h>
++#include <linux/netdevice.h>
++#include <linux/etherdevice.h>
++#include <linux/rtnetlink.h>
++#include <linux/if_arp.h>
++#include <linux/netfilter.h>
++#include <linux/netfilter_ipv4.h>
++#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
++ #include <linux/netfilter_ipv6.h>
++#endif
++#include <linux/imq.h>
++#include <net/pkt_sched.h>
++#include <net/netfilter/nf_queue.h>
++#include <net/sock.h>
++#include <linux/ip.h>
++#include <linux/ipv6.h>
++#include <linux/if_vlan.h>
++#include <linux/if_pppox.h>
++#include <net/ip.h>
++#include <net/ipv6.h>
++
++static int imq_nf_queue(struct nf_queue_entry *entry, unsigned queue_num);
++
++static nf_hookfn imq_nf_hook;
++
++static struct nf_hook_ops imq_ops[] = {
++ {
++ /* imq_ingress_ipv4 */
++ .hook = imq_nf_hook,
++ .owner = THIS_MODULE,
++ .pf = PF_INET,
++ .hooknum = NF_INET_PRE_ROUTING,
++#if defined(CONFIG_IMQ_BEHAVIOR_BA) || defined(CONFIG_IMQ_BEHAVIOR_BB)
++ .priority = NF_IP_PRI_MANGLE + 1,
++#else
++ .priority = NF_IP_PRI_NAT_DST + 1,
++#endif
++ },
++ {
++ /* imq_egress_ipv4 */
++ .hook = imq_nf_hook,
++ .owner = THIS_MODULE,
++ .pf = PF_INET,
++ .hooknum = NF_INET_POST_ROUTING,
++#if defined(CONFIG_IMQ_BEHAVIOR_AA) || defined(CONFIG_IMQ_BEHAVIOR_BA)
++ .priority = NF_IP_PRI_LAST,
++#else
++ .priority = NF_IP_PRI_NAT_SRC - 1,
++#endif
++ },
++#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
++ {
++ /* imq_ingress_ipv6 */
++ .hook = imq_nf_hook,
++ .owner = THIS_MODULE,
++ .pf = PF_INET6,
++ .hooknum = NF_INET_PRE_ROUTING,
++#if defined(CONFIG_IMQ_BEHAVIOR_BA) || defined(CONFIG_IMQ_BEHAVIOR_BB)
++ .priority = NF_IP6_PRI_MANGLE + 1,
++#else
++ .priority = NF_IP6_PRI_NAT_DST + 1,
++#endif
++ },
++ {
++ /* imq_egress_ipv6 */
++ .hook = imq_nf_hook,
++ .owner = THIS_MODULE,
++ .pf = PF_INET6,
++ .hooknum = NF_INET_POST_ROUTING,
++#if defined(CONFIG_IMQ_BEHAVIOR_AA) || defined(CONFIG_IMQ_BEHAVIOR_BA)
++ .priority = NF_IP6_PRI_LAST,
++#else
++ .priority = NF_IP6_PRI_NAT_SRC - 1,
++#endif
++ },
++#endif
++};
++
++#if defined(CONFIG_IMQ_NUM_DEVS)
++static int numdevs = CONFIG_IMQ_NUM_DEVS;
++#else
++static int numdevs = IMQ_MAX_DEVS;
++#endif
++
++static struct net_device *imq_devs_cache[IMQ_MAX_DEVS];
++
++#define IMQ_MAX_QUEUES 32
++static int numqueues = 1;
++static u32 imq_hashrnd;
++
++static inline __be16 pppoe_proto(const struct sk_buff *skb)
++{
++ return *((__be16 *)(skb_mac_header(skb) + ETH_HLEN +
++ sizeof(struct pppoe_hdr)));
++}
++
++static u16 imq_hash(struct net_device *dev, struct sk_buff *skb)
++{
++ unsigned int pull_len;
++ u16 protocol = skb->protocol;
++ u32 addr1, addr2;
++ u32 hash, ihl = 0;
++ union {
++ u16 in16[2];
++ u32 in32;
++ } ports;
++ u8 ip_proto;
++
++ pull_len = 0;
++
++recheck:
++ switch (protocol) {
++ case htons(ETH_P_8021Q): {
++ if (unlikely(skb_pull(skb, VLAN_HLEN) == NULL))
++ goto other;
++
++ pull_len += VLAN_HLEN;
++ skb->network_header += VLAN_HLEN;
++
++ protocol = vlan_eth_hdr(skb)->h_vlan_encapsulated_proto;
++ goto recheck;
++ }
++
++ case htons(ETH_P_PPP_SES): {
++ if (unlikely(skb_pull(skb, PPPOE_SES_HLEN) == NULL))
++ goto other;
++
++ pull_len += PPPOE_SES_HLEN;
++ skb->network_header += PPPOE_SES_HLEN;
++
++ protocol = pppoe_proto(skb);
++ goto recheck;
++ }
++
++ case htons(ETH_P_IP): {
++ const struct iphdr *iph = ip_hdr(skb);
++
++ if (unlikely(!pskb_may_pull(skb, sizeof(struct iphdr))))
++ goto other;
++
++ addr1 = iph->daddr;
++ addr2 = iph->saddr;
++
++ ip_proto = !(ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) ?
++ iph->protocol : 0;
++ ihl = ip_hdrlen(skb);
++
++ break;
++ }
++#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
++ case htons(ETH_P_IPV6): {
++ const struct ipv6hdr *iph = ipv6_hdr(skb);
++ __be16 fo = 0;
++
++ if (unlikely(!pskb_may_pull(skb, sizeof(struct ipv6hdr))))
++ goto other;
++
++ addr1 = iph->daddr.s6_addr32[3];
++ addr2 = iph->saddr.s6_addr32[3];
++ ihl = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &ip_proto,
++ &fo);
++ if (unlikely(ihl < 0))
++ goto other;
++
++ break;
++ }
++#endif
++ default:
++other:
++ if (pull_len != 0) {
++ skb_push(skb, pull_len);
++ skb->network_header -= pull_len;
++ }
++
++ return (u16)(ntohs(protocol) % dev->real_num_tx_queues);
++ }
++
++ if (addr1 > addr2)
++ swap(addr1, addr2);
++
++ switch (ip_proto) {
++ case IPPROTO_TCP:
++ case IPPROTO_UDP:
++ case IPPROTO_DCCP:
++ case IPPROTO_ESP:
++ case IPPROTO_AH:
++ case IPPROTO_SCTP:
++ case IPPROTO_UDPLITE: {
++ if (likely(skb_copy_bits(skb, ihl, &ports.in32, 4) >= 0)) {
++ if (ports.in16[0] > ports.in16[1])
++ swap(ports.in16[0], ports.in16[1]);
++ break;
++ }
++ /* fall-through */
++ }
++ default:
++ ports.in32 = 0;
++ break;
++ }
++
++ if (pull_len != 0) {
++ skb_push(skb, pull_len);
++ skb->network_header -= pull_len;
++ }
++
++ hash = jhash_3words(addr1, addr2, ports.in32, imq_hashrnd ^ ip_proto);
++
++ return (u16)(((u64)hash * dev->real_num_tx_queues) >> 32);
++}
++
++static inline bool sk_tx_queue_recorded(struct sock *sk)
++{
++ return (sk_tx_queue_get(sk) >= 0);
++}
++
++static struct netdev_queue *imq_select_queue(struct net_device *dev,
++ struct sk_buff *skb)
++{
++ u16 queue_index = 0;
++ u32 hash;
++
++ if (likely(dev->real_num_tx_queues == 1))
++ goto out;
++
++ /* IMQ can be receiving ingress or engress packets. */
++
++ /* Check first for if rx_queue is set */
++ if (skb_rx_queue_recorded(skb)) {
++ queue_index = skb_get_rx_queue(skb);
++ goto out;
++ }
++
++ /* Check if socket has tx_queue set */
++ if (sk_tx_queue_recorded(skb->sk)) {
++ queue_index = sk_tx_queue_get(skb->sk);
++ goto out;
++ }
++
++ /* Try use socket hash */
++ if (skb->sk && skb->sk->sk_hash) {
++ hash = skb->sk->sk_hash;
++ queue_index =
++ (u16)(((u64)hash * dev->real_num_tx_queues) >> 32);
++ goto out;
++ }
++
++ /* Generate hash from packet data */
++ queue_index = imq_hash(dev, skb);
++
++out:
++ if (unlikely(queue_index >= dev->real_num_tx_queues))
++ queue_index = (u16)((u32)queue_index % dev->real_num_tx_queues);
++
++ skb_set_queue_mapping(skb, queue_index);
++ return netdev_get_tx_queue(dev, queue_index);
++}
++
++static struct net_device_stats *imq_get_stats(struct net_device *dev)
++{
++ return &dev->stats;
++}
++
++/* called for packets kfree'd in qdiscs at places other than enqueue */
++static void imq_skb_destructor(struct sk_buff *skb)
++{
++ struct nf_queue_entry *entry = skb->nf_queue_entry;
++
++ skb->nf_queue_entry = NULL;
++
++ if (entry) {
++ nf_queue_entry_release_refs(entry);
++ kfree(entry);
++ }
++
++ skb_restore_cb(skb); /* kfree backup */
++}
++
++static void imq_done_check_queue_mapping(struct sk_buff *skb,
++ struct net_device *dev)
++{
++ unsigned int queue_index;
++
++ /* Don't let queue_mapping be left too large after exiting IMQ */
++ if (likely(skb->dev != dev && skb->dev != NULL)) {
++ queue_index = skb_get_queue_mapping(skb);
++ if (unlikely(queue_index >= skb->dev->real_num_tx_queues)) {
++ queue_index = (u16)((u32)queue_index %
++ skb->dev->real_num_tx_queues);
++ skb_set_queue_mapping(skb, queue_index);
++ }
++ } else {
++ /* skb->dev was IMQ device itself or NULL, be on safe side and
++ * just clear queue mapping.
++ */
++ skb_set_queue_mapping(skb, 0);
++ }
++}
++
++static netdev_tx_t imq_dev_xmit(struct sk_buff *skb, struct net_device *dev)
++{
++ struct nf_queue_entry *entry = skb->nf_queue_entry;
++
++ skb->nf_queue_entry = NULL;
++ dev->trans_start = jiffies;
++
++ dev->stats.tx_bytes += skb->len;
++ dev->stats.tx_packets++;
++
++ if (unlikely(entry == NULL)) {
++ /* We don't know what is going on here.. packet is queued for
++ * imq device, but (probably) not by us.
++ *
++ * If this packet was not send here by imq_nf_queue(), then
++ * skb_save_cb() was not used and skb_free() should not show:
++ * WARNING: IMQ: kfree_skb: skb->cb_next:..
++ * and/or
++ * WARNING: IMQ: kfree_skb: skb->nf_queue_entry...
++ *
++ * However if this message is shown, then IMQ is somehow broken
++ * and you should report this to linuximq.net.
++ */
++
++ /* imq_dev_xmit is black hole that eats all packets, report that
++ * we eat this packet happily and increase dropped counters.
++ */
++
++ dev->stats.tx_dropped++;
++ dev_kfree_skb(skb);
++
++ return NETDEV_TX_OK;
++ }
++
++ skb_restore_cb(skb); /* restore skb->cb */
++
++ skb->imq_flags = 0;
++ skb->destructor = NULL;
++
++ imq_done_check_queue_mapping(skb, dev);
++
++ nf_reinject(entry, NF_ACCEPT);
++
++ return NETDEV_TX_OK;
++}
++
++static struct net_device *get_imq_device_by_index(int index)
++{
++ struct net_device *dev = NULL;
++ struct net *net;
++ char buf[8];
++
++ /* get device by name and cache result */
++ snprintf(buf, sizeof(buf), "imq%d", index);
++
++ /* Search device from all namespaces. */
++ for_each_net(net) {
++ dev = dev_get_by_name(net, buf);
++ if (dev)
++ break;
++ }
++
++ if (WARN_ON_ONCE(dev == NULL)) {
++ /* IMQ device not found. Exotic config? */
++ return ERR_PTR(-ENODEV);
++ }
++
++ imq_devs_cache[index] = dev;
++ dev_put(dev);
++
++ return dev;
++}
++
++static struct nf_queue_entry *nf_queue_entry_dup(struct nf_queue_entry *e)
++{
++ struct nf_queue_entry *entry = kmemdup(e, e->size, GFP_ATOMIC);
++ if (entry) {
++ if (nf_queue_entry_get_refs(entry))
++ return entry;
++ kfree(entry);
++ }
++ return NULL;
++}
++
++#ifdef CONFIG_BRIDGE_NETFILTER
++/* When called from bridge netfilter, skb->data must point to MAC header
++ * before calling skb_gso_segment(). Else, original MAC header is lost
++ * and segmented skbs will be sent to wrong destination.
++ */
++static void nf_bridge_adjust_skb_data(struct sk_buff *skb)
++{
++ if (skb->nf_bridge)
++ __skb_push(skb, skb->network_header - skb->mac_header);
++}
++
++static void nf_bridge_adjust_segmented_data(struct sk_buff *skb)
++{
++ if (skb->nf_bridge)
++ __skb_pull(skb, skb->network_header - skb->mac_header);
++}
++#else
++#define nf_bridge_adjust_skb_data(s) do {} while (0)
++#define nf_bridge_adjust_segmented_data(s) do {} while (0)
++#endif
++
++static void free_entry(struct nf_queue_entry *entry)
++{
++ nf_queue_entry_release_refs(entry);
++ kfree(entry);
++}
++
++static int __imq_nf_queue(struct nf_queue_entry *entry, struct net_device *dev);
++
++static int __imq_nf_queue_gso(struct nf_queue_entry *entry,
++ struct net_device *dev, struct sk_buff *skb)
++{
++ int ret = -ENOMEM;
++ struct nf_queue_entry *entry_seg;
++
++ nf_bridge_adjust_segmented_data(skb);
++
++ if (skb->next == NULL) { /* last packet, no need to copy entry */
++ struct sk_buff *gso_skb = entry->skb;
++ entry->skb = skb;
++ ret = __imq_nf_queue(entry, dev);
++ if (ret)
++ entry->skb = gso_skb;
++ return ret;
++ }
++
++ skb->next = NULL;
++
++ entry_seg = nf_queue_entry_dup(entry);
++ if (entry_seg) {
++ entry_seg->skb = skb;
++ ret = __imq_nf_queue(entry_seg, dev);
++ if (ret)
++ free_entry(entry_seg);
++ }
++ return ret;
++}
++
++static int imq_nf_queue(struct nf_queue_entry *entry, unsigned queue_num)
++{
++ struct sk_buff *skb, *segs;
++ struct net_device *dev;
++ unsigned int queued;
++ int index, retval, err;
++
++ index = entry->skb->imq_flags & IMQ_F_IFMASK;
++ if (unlikely(index > numdevs - 1)) {
++ if (net_ratelimit())
++ pr_warn("IMQ: invalid device specified, highest is %u\n",
++ numdevs - 1);
++ retval = -EINVAL;
++ goto out_no_dev;
++ }
++
++ /* check for imq device by index from cache */
++ dev = imq_devs_cache[index];
++ if (unlikely(!dev)) {
++ dev = get_imq_device_by_index(index);
++ if (IS_ERR(dev)) {
++ retval = PTR_ERR(dev);
++ goto out_no_dev;
++ }
++ }
++
++ if (unlikely(!(dev->flags & IFF_UP))) {
++ entry->skb->imq_flags = 0;
++ retval = -ECANCELED;
++ goto out_no_dev;
++ }
++
++ if (!skb_is_gso(entry->skb))
++ return __imq_nf_queue(entry, dev);
++
++ /* Since 3.10.x, GSO handling moved here as result of upstream commit
++ * a5fedd43d5f6c94c71053a66e4c3d2e35f1731a2 (netfilter: move
++ * skb_gso_segment into nfnetlink_queue module).
++ *
++ * Following code replicates the gso handling from
++ * 'net/netfilter/nfnetlink_queue_core.c':nfqnl_enqueue_packet().
++ */
++
++ skb = entry->skb;
++
++ switch (entry->pf) {
++ case NFPROTO_IPV4:
++ skb->protocol = htons(ETH_P_IP);
++ break;
++ case NFPROTO_IPV6:
++ skb->protocol = htons(ETH_P_IPV6);
++ break;
++ }
++
++ nf_bridge_adjust_skb_data(skb);
++ segs = skb_gso_segment(skb, 0);
++ /* Does not use PTR_ERR to limit the number of error codes that can be
++ * returned by nf_queue. For instance, callers rely on -ECANCELED to
++ * mean 'ignore this hook'.
++ */
++ err = -ENOBUFS;
++ if (IS_ERR(segs))
++ goto out_err;
++ queued = 0;
++ err = 0;
++ do {
++ struct sk_buff *nskb = segs->next;
++ if (nskb && nskb->next)
++ nskb->cb_next = NULL;
++ if (err == 0)
++ err = __imq_nf_queue_gso(entry, dev, segs);
++ if (err == 0)
++ queued++;
++ else
++ kfree_skb(segs);
++ segs = nskb;
++ } while (segs);
++
++ if (queued) {
++ if (err) /* some segments are already queued */
++ free_entry(entry);
++ kfree_skb(skb);
++ return 0;
++ }
++
++out_err:
++ nf_bridge_adjust_segmented_data(skb);
++ retval = err;
++out_no_dev:
++ return retval;
++}
++
++static int __imq_nf_queue(struct nf_queue_entry *entry, struct net_device *dev)
++{
++ struct sk_buff *skb_orig, *skb, *skb_shared;
++ struct Qdisc *q;
++ struct netdev_queue *txq;
++ spinlock_t *root_lock;
++ int users;
++ int retval = -EINVAL;
++ unsigned int orig_queue_index;
++
++ dev->last_rx = jiffies;
++
++ skb = entry->skb;
++ skb_orig = NULL;
++
++ /* skb has owner? => make clone */
++ if (unlikely(skb->destructor)) {
++ skb_orig = skb;
++ skb = skb_clone(skb, GFP_ATOMIC);
++ if (unlikely(!skb)) {
++ retval = -ENOMEM;
++ goto out;
++ }
++ skb->cb_next = NULL;
++ entry->skb = skb;
++ }
++
++ skb->nf_queue_entry = entry;
++
++ dev->stats.rx_bytes += skb->len;
++ dev->stats.rx_packets++;
++
++ if (!skb->dev) {
++ /* skb->dev == NULL causes problems, try the find cause. */
++ if (net_ratelimit()) {
++ dev_warn(&dev->dev,
++ "received packet with skb->dev == NULL\n");
++ dump_stack();
++ }
++
++ skb->dev = dev;
++ }
++
++ /* Disables softirqs for lock below */
++ rcu_read_lock_bh();
++
++ /* Multi-queue selection */
++ orig_queue_index = skb_get_queue_mapping(skb);
++ txq = imq_select_queue(dev, skb);
++
++ q = rcu_dereference(txq->qdisc);
++ if (unlikely(!q->enqueue))
++ goto packet_not_eaten_by_imq_dev;
++
++ root_lock = qdisc_lock(q);
++ spin_lock(root_lock);
++
++ users = atomic_read(&skb->users);
++
++ skb_shared = skb_get(skb); /* increase reference count by one */
++
++ /* backup skb->cb, as qdisc layer will overwrite it */
++ skb_save_cb(skb_shared);
++ qdisc_enqueue_root(skb_shared, q); /* might kfree_skb */
++
++ if (likely(atomic_read(&skb_shared->users) == users + 1)) {
++ kfree_skb(skb_shared); /* decrease reference count by one */
++
++ skb->destructor = &imq_skb_destructor;
++
++ /* cloned? */
++ if (unlikely(skb_orig))
++ kfree_skb(skb_orig); /* free original */
++
++ spin_unlock(root_lock);
++ rcu_read_unlock_bh();
++
++ /* schedule qdisc dequeue */
++ __netif_schedule(q);
++
++ retval = 0;
++ goto out;
++ } else {
++ skb_restore_cb(skb_shared); /* restore skb->cb */
++ skb->nf_queue_entry = NULL;
++ /*
++ * qdisc dropped packet and decreased skb reference count of
++ * skb, so we don't really want to and try refree as that would
++ * actually destroy the skb.
++ */
++ spin_unlock(root_lock);
++ goto packet_not_eaten_by_imq_dev;
++ }
++
++packet_not_eaten_by_imq_dev:
++ skb_set_queue_mapping(skb, orig_queue_index);
++ rcu_read_unlock_bh();
++
++ /* cloned? restore original */
++ if (unlikely(skb_orig)) {
++ kfree_skb(skb);
++ entry->skb = skb_orig;
++ }
++ retval = -1;
++out:
++ return retval;
++}
++
++static unsigned int imq_nf_hook(const struct nf_hook_ops *hook_ops,
++ struct sk_buff *pskb,
++ const struct net_device *indev,
++ const struct net_device *outdev,
++ int (*okfn)(struct sk_buff *))
++{
++ return (pskb->imq_flags & IMQ_F_ENQUEUE) ? NF_IMQ_QUEUE : NF_ACCEPT;
++}
++
++static int imq_close(struct net_device *dev)
++{
++ netif_stop_queue(dev);
++ return 0;
++}
++
++static int imq_open(struct net_device *dev)
++{
++ netif_start_queue(dev);
++ return 0;
++}
++
++static const struct net_device_ops imq_netdev_ops = {
++ .ndo_open = imq_open,
++ .ndo_stop = imq_close,
++ .ndo_start_xmit = imq_dev_xmit,
++ .ndo_get_stats = imq_get_stats,
++};
++
++static void imq_setup(struct net_device *dev)
++{
++ dev->netdev_ops = &imq_netdev_ops;
++ dev->type = ARPHRD_VOID;
++ dev->mtu = 16000; /* too small? */
++ dev->tx_queue_len = 11000; /* too big? */
++ dev->flags = IFF_NOARP;
++ dev->features = NETIF_F_SG | NETIF_F_FRAGLIST |
++ NETIF_F_GSO | NETIF_F_HW_CSUM |
++ NETIF_F_HIGHDMA;
++ dev->priv_flags &= ~(IFF_XMIT_DST_RELEASE |
++ IFF_TX_SKB_SHARING);
++}
++
++static int imq_validate(struct nlattr *tb[], struct nlattr *data[])
++{
++ int ret = 0;
++
++ if (tb[IFLA_ADDRESS]) {
++ if (nla_len(tb[IFLA_ADDRESS]) != ETH_ALEN) {
++ ret = -EINVAL;
++ goto end;
++ }
++ if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS]))) {
++ ret = -EADDRNOTAVAIL;
++ goto end;
++ }
++ }
++ return 0;
++end:
++ pr_warn("IMQ: imq_validate failed (%d)\n", ret);
++ return ret;
++}
++
++static struct rtnl_link_ops imq_link_ops __read_mostly = {
++ .kind = "imq",
++ .priv_size = 0,
++ .setup = imq_setup,
++ .validate = imq_validate,
++};
++
++static const struct nf_queue_handler imq_nfqh = {
++ .outfn = imq_nf_queue,
++};
++
++static int __init imq_init_hooks(void)
++{
++ int ret;
++
++ nf_register_queue_imq_handler(&imq_nfqh);
++
++ ret = nf_register_hooks(imq_ops, ARRAY_SIZE(imq_ops));
++ if (ret < 0)
++ nf_unregister_queue_imq_handler();
++
++ return ret;
++}
++
++static int __init imq_init_one(int index)
++{
++ struct net_device *dev;
++ int ret;
++
++ dev = alloc_netdev_mq(0, "imq%d", imq_setup, numqueues);
++ if (!dev)
++ return -ENOMEM;
++
++ ret = dev_alloc_name(dev, dev->name);
++ if (ret < 0)
++ goto fail;
++
++ dev->rtnl_link_ops = &imq_link_ops;
++ ret = register_netdevice(dev);
++ if (ret < 0)
++ goto fail;
++
++ return 0;
++fail:
++ free_netdev(dev);
++ return ret;
++}
++
++static int __init imq_init_devs(void)
++{
++ int err, i;
++
++ if (numdevs < 1 || numdevs > IMQ_MAX_DEVS) {
++ pr_err("IMQ: numdevs has to be betweed 1 and %u\n",
++ IMQ_MAX_DEVS);
++ return -EINVAL;
++ }
++
++ if (numqueues < 1 || numqueues > IMQ_MAX_QUEUES) {
++ pr_err("IMQ: numqueues has to be betweed 1 and %u\n",
++ IMQ_MAX_QUEUES);
++ return -EINVAL;
++ }
++
++ get_random_bytes(&imq_hashrnd, sizeof(imq_hashrnd));
++
++ rtnl_lock();
++ err = __rtnl_link_register(&imq_link_ops);
++
++ for (i = 0; i < numdevs && !err; i++)
++ err = imq_init_one(i);
++
++ if (err) {
++ __rtnl_link_unregister(&imq_link_ops);
++ memset(imq_devs_cache, 0, sizeof(imq_devs_cache));
++ }
++ rtnl_unlock();
++
++ return err;
++}
++
++static int __init imq_init_module(void)
++{
++ int err;
++
++#if defined(CONFIG_IMQ_NUM_DEVS)
++ BUILD_BUG_ON(CONFIG_IMQ_NUM_DEVS > 16);
++ BUILD_BUG_ON(CONFIG_IMQ_NUM_DEVS < 2);
++ BUILD_BUG_ON(CONFIG_IMQ_NUM_DEVS - 1 > IMQ_F_IFMASK);
++#endif
++
++ err = imq_init_devs();
++ if (err) {
++ pr_err("IMQ: Error trying imq_init_devs(net)\n");
++ return err;
++ }
++
++ err = imq_init_hooks();
++ if (err) {
++ pr_err(KERN_ERR "IMQ: Error trying imq_init_hooks()\n");
++ rtnl_link_unregister(&imq_link_ops);
++ memset(imq_devs_cache, 0, sizeof(imq_devs_cache));
++ return err;
++ }
++
++ pr_info("IMQ driver loaded successfully. (numdevs = %d, numqueues = %d)\n",
++ numdevs, numqueues);
++
++#if defined(CONFIG_IMQ_BEHAVIOR_BA) || defined(CONFIG_IMQ_BEHAVIOR_BB)
++ pr_info("\tHooking IMQ before NAT on PREROUTING.\n");
++#else
++ pr_info("\tHooking IMQ after NAT on PREROUTING.\n");
++#endif
++#if defined(CONFIG_IMQ_BEHAVIOR_AB) || defined(CONFIG_IMQ_BEHAVIOR_BB)
++ pr_info("\tHooking IMQ before NAT on POSTROUTING.\n");
++#else
++ pr_info("\tHooking IMQ after NAT on POSTROUTING.\n");
++#endif
++
++ return 0;
++}
++
++static void __exit imq_unhook(void)
++{
++ nf_unregister_hooks(imq_ops, ARRAY_SIZE(imq_ops));
++ nf_unregister_queue_imq_handler();
++}
++
++static void __exit imq_cleanup_devs(void)
++{
++ rtnl_link_unregister(&imq_link_ops);
++ memset(imq_devs_cache, 0, sizeof(imq_devs_cache));
++}
++
++static void __exit imq_exit_module(void)
++{
++ imq_unhook();
++ imq_cleanup_devs();
++ pr_info("IMQ driver unloaded successfully.\n");
++}
++
++module_init(imq_init_module);
++module_exit(imq_exit_module);
++
++module_param(numdevs, int, 0);
++module_param(numqueues, int, 0);
++MODULE_PARM_DESC(numdevs, "number of IMQ devices (how many imq* devices will be created)");
++MODULE_PARM_DESC(numqueues, "number of queues per IMQ device");
++MODULE_AUTHOR("http://www.linuximq.net");
++MODULE_DESCRIPTION("Pseudo-driver for the intermediate queue device. See http://www.linuximq.net/ for more information.");
++MODULE_LICENSE("GPL");
++MODULE_ALIAS_RTNL_LINK("imq");
++
+diff --git a/include/linux/imq.h b/include/linux/imq.h
+new file mode 100644
+index 0000000..1babb09
+--- /dev/null
++++ b/include/linux/imq.h
+@@ -0,0 +1,13 @@
++#ifndef _IMQ_H
++#define _IMQ_H
++
++/* IFMASK (16 device indexes, 0 to 15) and flag(s) fit in 5 bits */
++#define IMQ_F_BITS 5
++
++#define IMQ_F_IFMASK 0x0f
++#define IMQ_F_ENQUEUE 0x10
++
++#define IMQ_MAX_DEVS (IMQ_F_IFMASK + 1)
++
++#endif /* _IMQ_H */
++
+diff --git a/include/linux/netfilter/xt_IMQ.h b/include/linux/netfilter/xt_IMQ.h
+new file mode 100644
+index 0000000..9b07230
+--- /dev/null
++++ b/include/linux/netfilter/xt_IMQ.h
+@@ -0,0 +1,9 @@
++#ifndef _XT_IMQ_H
++#define _XT_IMQ_H
++
++struct xt_imq_info {
++ unsigned int todev; /* target imq device */
++};
++
++#endif /* _XT_IMQ_H */
++
+diff --git a/include/linux/netfilter_ipv4/ipt_IMQ.h b/include/linux/netfilter_ipv4/ipt_IMQ.h
+new file mode 100644
+index 0000000..7af320f
+--- /dev/null
++++ b/include/linux/netfilter_ipv4/ipt_IMQ.h
+@@ -0,0 +1,10 @@
++#ifndef _IPT_IMQ_H
++#define _IPT_IMQ_H
++
++/* Backwards compatibility for old userspace */
++#include <linux/netfilter/xt_IMQ.h>
++
++#define ipt_imq_info xt_imq_info
++
++#endif /* _IPT_IMQ_H */
++
+diff --git a/include/linux/netfilter_ipv6/ip6t_IMQ.h b/include/linux/netfilter_ipv6/ip6t_IMQ.h
+new file mode 100644
+index 0000000..198ac01
+--- /dev/null
++++ b/include/linux/netfilter_ipv6/ip6t_IMQ.h
+@@ -0,0 +1,10 @@
++#ifndef _IP6T_IMQ_H
++#define _IP6T_IMQ_H
++
++/* Backwards compatibility for old userspace */
++#include <linux/netfilter/xt_IMQ.h>
++
++#define ip6t_imq_info xt_imq_info
++
++#endif /* _IP6T_IMQ_H */
++
+diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
+index ad8f859..8473090 100644
+--- a/include/linux/skbuff.h
++++ b/include/linux/skbuff.h
+@@ -33,6 +33,9 @@
+ #include <linux/dma-mapping.h>
+ #include <linux/netdev_features.h>
+ #include <net/flow_keys.h>
++#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
++#include <linux/imq.h>
++#endif
+
+ /* A. Checksumming of received packets by device.
+ *
+@@ -441,6 +444,9 @@ struct sk_buff {
+ * first. This is owned by whoever has the skb queued ATM.
+ */
+ char cb[48] __aligned(8);
++#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
++ void *cb_next;
++#endif
+
+ unsigned long _skb_refdst;
+ #ifdef CONFIG_XFRM
+@@ -476,6 +482,9 @@ struct sk_buff {
+ #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
+ struct nf_conntrack *nfct;
+ #endif
++#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
++ struct nf_queue_entry *nf_queue_entry;
++#endif
+ #ifdef CONFIG_BRIDGE_NETFILTER
+ struct nf_bridge_info *nf_bridge;
+ #endif
+@@ -513,6 +522,9 @@ struct sk_buff {
+ */
+ __u8 encapsulation:1;
+ /* 6/8 bit hole (depending on ndisc_nodetype presence) */
++#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
++ __u8 imq_flags:IMQ_F_BITS;
++#endif
+ kmemcheck_bitfield_end(flags2);
+
+ #if defined CONFIG_NET_DMA || defined CONFIG_NET_RX_BUSY_POLL
+@@ -653,6 +665,12 @@ void kfree_skb_list(struct sk_buff *segs);
+ void skb_tx_error(struct sk_buff *skb);
+ void consume_skb(struct sk_buff *skb);
+ void __kfree_skb(struct sk_buff *skb);
++
++#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
++int skb_save_cb(struct sk_buff *skb);
++int skb_restore_cb(struct sk_buff *skb);
++#endif
++
+ extern struct kmem_cache *skbuff_head_cache;
+
+ void kfree_skb_partial(struct sk_buff *skb, bool head_stolen);
+@@ -2739,6 +2757,10 @@ static inline void __nf_copy(struct sk_buff *dst, const struct sk_buff *src)
+ nf_conntrack_get(src->nfct);
+ dst->nfctinfo = src->nfctinfo;
+ #endif
++#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
++ dst->imq_flags = src->imq_flags;
++ dst->nf_queue_entry = src->nf_queue_entry;
++#endif
+ #ifdef CONFIG_BRIDGE_NETFILTER
+ dst->nf_bridge = src->nf_bridge;
+ nf_bridge_get(src->nf_bridge);
+diff --git a/include/net/netfilter/nf_queue.h b/include/net/netfilter/nf_queue.h
+index 84a53d7..6ffb593 100644
+--- a/include/net/netfilter/nf_queue.h
++++ b/include/net/netfilter/nf_queue.h
+@@ -33,6 +33,12 @@ struct nf_queue_handler {
+ void nf_register_queue_handler(const struct nf_queue_handler *qh);
+ void nf_unregister_queue_handler(void);
+ void nf_reinject(struct nf_queue_entry *entry, unsigned int verdict);
++void nf_queue_entry_release_refs(struct nf_queue_entry *entry);
++
++#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
++void nf_register_queue_imq_handler(const struct nf_queue_handler *qh);
++void nf_unregister_queue_imq_handler(void);
++#endif
+
+ bool nf_queue_entry_get_refs(struct nf_queue_entry *entry);
+ void nf_queue_entry_release_refs(struct nf_queue_entry *entry);
+diff --git a/include/uapi/linux/netfilter.h b/include/uapi/linux/netfilter.h
+index ef1b1f8..079e5ff 100644
+--- a/include/uapi/linux/netfilter.h
++++ b/include/uapi/linux/netfilter.h
+@@ -13,7 +13,8 @@
+ #define NF_QUEUE 3
+ #define NF_REPEAT 4
+ #define NF_STOP 5
+-#define NF_MAX_VERDICT NF_STOP
++#define NF_IMQ_QUEUE 6
++#define NF_MAX_VERDICT NF_IMQ_QUEUE
+
+ /* we overload the higher bits for encoding auxiliary data such as the queue
+ * number or errno values. Not nice, but better than additional function
+diff --git a/net/core/dev.c b/net/core/dev.c
+index 3ed11a5..fd62030 100644
+--- a/net/core/dev.c
++++ b/net/core/dev.c
+@@ -132,6 +132,9 @@
+ #include <linux/hashtable.h>
+ #include <linux/vmalloc.h>
+ #include <linux/if_macvlan.h>
++#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
++#include <linux/imq.h>
++#endif
+
+ #include "net-sysfs.h"
+
+@@ -2611,7 +2614,12 @@ int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,
+ }
+ }
+
++#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
++ if (!list_empty(&ptype_all) &&
++ !(skb->imq_flags & IMQ_F_ENQUEUE))
++#else
+ if (!list_empty(&ptype_all))
++#endif
+ dev_queue_xmit_nit(skb, dev);
+
+ skb_len = skb->len;
+diff --git a/net/core/skbuff.c b/net/core/skbuff.c
+index baf6fc4..7d30d78 100644
+--- a/net/core/skbuff.c
++++ b/net/core/skbuff.c
+@@ -77,6 +77,115 @@
+
+ struct kmem_cache *skbuff_head_cache __read_mostly;
+ static struct kmem_cache *skbuff_fclone_cache __read_mostly;
++#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
++static struct kmem_cache *skbuff_cb_store_cache __read_mostly;
++#endif
++
++static void sock_pipe_buf_release(struct pipe_inode_info *pipe,
++ struct pipe_buffer *buf)
++{
++ put_page(buf->page);
++}
++
++static void sock_pipe_buf_get(struct pipe_inode_info *pipe,
++ struct pipe_buffer *buf)
++{
++ get_page(buf->page);
++}
++
++static int sock_pipe_buf_steal(struct pipe_inode_info *pipe,
++ struct pipe_buffer *buf)
++{
++ return 1;
++}
++
++#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
++/* Control buffer save/restore for IMQ devices */
++struct skb_cb_table {
++ char cb[48] __aligned(8);
++ void *cb_next;
++ atomic_t refcnt;
++};
++
++static DEFINE_SPINLOCK(skb_cb_store_lock);
++
++int skb_save_cb(struct sk_buff *skb)
++{
++ struct skb_cb_table *next;
++
++ next = kmem_cache_alloc(skbuff_cb_store_cache, GFP_ATOMIC);
++ if (!next)
++ return -ENOMEM;
++
++ BUILD_BUG_ON(sizeof(skb->cb) != sizeof(next->cb));
++
++ memcpy(next->cb, skb->cb, sizeof(skb->cb));
++ next->cb_next = skb->cb_next;
++
++ atomic_set(&next->refcnt, 1);
++
++ skb->cb_next = next;
++ return 0;
++}
++EXPORT_SYMBOL(skb_save_cb);
++
++int skb_restore_cb(struct sk_buff *skb)
++{
++ struct skb_cb_table *next;
++
++ if (!skb->cb_next)
++ return 0;
++
++ next = skb->cb_next;
++
++ BUILD_BUG_ON(sizeof(skb->cb) != sizeof(next->cb));
++
++ memcpy(skb->cb, next->cb, sizeof(skb->cb));
++ skb->cb_next = next->cb_next;
++
++ spin_lock(&skb_cb_store_lock);
++
++ if (atomic_dec_and_test(&next->refcnt))
++ kmem_cache_free(skbuff_cb_store_cache, next);
++
++ spin_unlock(&skb_cb_store_lock);
++
++ return 0;
++}
++EXPORT_SYMBOL(skb_restore_cb);
++
++static void skb_copy_stored_cb(struct sk_buff *new, const struct sk_buff *__old)
++{
++ struct skb_cb_table *next;
++ struct sk_buff *old;
++
++ if (!__old->cb_next) {
++ new->cb_next = NULL;
++ return;
++ }
++
++ spin_lock(&skb_cb_store_lock);
++
++ old = (struct sk_buff *)__old;
++
++ next = old->cb_next;
++ atomic_inc(&next->refcnt);
++ new->cb_next = next;
++
++ spin_unlock(&skb_cb_store_lock);
++}
++#endif
++
++/* Pipe buffer operations for a socket. */
++static const struct pipe_buf_operations sock_pipe_buf_ops = {
++ .can_merge = 0,
++ .map = generic_pipe_buf_map,
++ .unmap = generic_pipe_buf_unmap,
++ .confirm = generic_pipe_buf_confirm,
++ .release = sock_pipe_buf_release,
++ .steal = sock_pipe_buf_steal,
++ .get = sock_pipe_buf_get,
++};
+
+ /**
+ * skb_panic - private function for out-of-line support
+@@ -563,6 +672,28 @@ static void skb_release_head_state(struct sk_buff *skb)
+ WARN_ON(in_irq());
+ skb->destructor(skb);
+ }
++#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
++ /*
++ * This should not happen. When it does, avoid memleak by restoring
++ * the chain of cb-backups.
++ */
++ while (skb->cb_next != NULL) {
++ if (net_ratelimit())
++ pr_warn("IMQ: kfree_skb: skb->cb_next: %08x\n",
++ (unsigned int)skb->cb_next);
++
++ skb_restore_cb(skb);
++ }
++ /*
++ * This should not happen either, nf_queue_entry is nullified in
++ * imq_dev_xmit(). If we have non-NULL nf_queue_entry then we are
++ * leaking entry pointers, maybe memory. We don't know if this is
++ * pointer to already freed memory, or should this be freed.
++ * If this happens we need to add refcounting, etc for nf_queue_entry.
++ */
++ if (skb->nf_queue_entry && net_ratelimit())
++ pr_warn("%s\n", "IMQ: kfree_skb: skb->nf_queue_entry != NULL");
++#endif
+ #if IS_ENABLED(CONFIG_NF_CONNTRACK)
+ nf_conntrack_put(skb->nfct);
+ #endif
+@@ -694,6 +825,10 @@ static void __copy_skb_header(struct sk_buff *new, const struct sk_buff *old)
+ new->sp = secpath_get(old->sp);
+ #endif
+ memcpy(new->cb, old->cb, sizeof(old->cb));
++#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
++ new->cb_next = NULL;
++ /*skb_copy_stored_cb(new, old);*/
++#endif
+ new->csum = old->csum;
+ new->local_df = old->local_df;
+ new->pkt_type = old->pkt_type;
+@@ -3233,6 +3368,13 @@ void __init skb_init(void)
+ 0,
+ SLAB_HWCACHE_ALIGN|SLAB_PANIC,
+ NULL);
++#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
++ skbuff_cb_store_cache = kmem_cache_create("skbuff_cb_store_cache",
++ sizeof(struct skb_cb_table),
++ 0,
++ SLAB_HWCACHE_ALIGN|SLAB_PANIC,
++ NULL);
++#endif
+ }
+
+ /**
+diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
+index 12f7ef0..deb1c9d 100644
+--- a/net/ipv6/ip6_output.c
++++ b/net/ipv6/ip6_output.c
+@@ -64,9 +64,6 @@ static int ip6_finish_output2(struct sk_buff *skb)
+ struct in6_addr *nexthop;
+ int ret;
+
+- skb->protocol = htons(ETH_P_IPV6);
+- skb->dev = dev;
+-
+ if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr)) {
+ struct inet6_dev *idev = ip6_dst_idev(skb_dst(skb));
+
+@@ -143,6 +140,13 @@ int ip6_output(struct sk_buff *skb)
+ return 0;
+ }
+
++ /*
++ * IMQ-patch: moved setting skb->dev and skb->protocol from
++ * ip6_finish_output2 to fix crashing at netif_skb_features().
++ */
++ skb->protocol = htons(ETH_P_IPV6);
++ skb->dev = dev;
++
+ return NF_HOOK_COND(NFPROTO_IPV6, NF_INET_POST_ROUTING, skb, NULL, dev,
+ ip6_finish_output,
+ !(IP6CB(skb)->flags & IP6SKB_REROUTED));
+diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig
+index e9410d1..ba801d5 100644
+--- a/net/netfilter/Kconfig
++++ b/net/netfilter/Kconfig
+@@ -751,6 +751,18 @@ config NETFILTER_XT_TARGET_LOG
+
+ To compile it as a module, choose M here. If unsure, say N.
+
++config NETFILTER_XT_TARGET_IMQ
++ tristate '"IMQ" target support'
++ depends on NETFILTER_XTABLES
++ depends on IP_NF_MANGLE || IP6_NF_MANGLE
++ select IMQ
++ default m if NETFILTER_ADVANCED=n
++ help
++ This option adds a `IMQ' target which is used to specify if and
++ to which imq device packets should get enqueued/dequeued.
++
++ To compile it as a module, choose M here. If unsure, say N.
++
+ config NETFILTER_XT_TARGET_MARK
+ tristate '"MARK" target support'
+ depends on NETFILTER_ADVANCED
+diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile
+index bffdad7..050e613 100644
+--- a/net/netfilter/Makefile
++++ b/net/netfilter/Makefile
+@@ -103,6 +103,7 @@ obj-$(CONFIG_NETFILTER_XT_TARGET_CT) += xt_CT.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_DSCP) += xt_DSCP.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_HL) += xt_HL.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_HMARK) += xt_HMARK.o
++obj-$(CONFIG_NETFILTER_XT_TARGET_IMQ) += xt_IMQ.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_LED) += xt_LED.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_LOG) += xt_LOG.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_NETMAP) += xt_NETMAP.o
+diff --git a/net/netfilter/core.c b/net/netfilter/core.c
+index 1fbab0c..4493417 100644
+--- a/net/netfilter/core.c
++++ b/net/netfilter/core.c
+@@ -191,9 +191,11 @@ next_hook:
+ ret = NF_DROP_GETERR(verdict);
+ if (ret == 0)
+ ret = -EPERM;
+- } else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
++ } else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE ||
++ (verdict & NF_VERDICT_MASK) == NF_IMQ_QUEUE) {
+ int err = nf_queue(skb, elem, pf, hook, indev, outdev, okfn,
+- verdict >> NF_VERDICT_QBITS);
++ verdict >> NF_VERDICT_QBITS,
++ verdict & NF_VERDICT_MASK);
+ if (err < 0) {
+ if (err == -ECANCELED)
+ goto next_hook;
+diff --git a/net/netfilter/nf_internals.h b/net/netfilter/nf_internals.h
+index 61a3c92..5388a0e 100644
+--- a/net/netfilter/nf_internals.h
++++ b/net/netfilter/nf_internals.h
+@@ -23,7 +23,7 @@ unsigned int nf_iterate(struct list_head *head, struct sk_buff *skb,
+ int nf_queue(struct sk_buff *skb, struct nf_hook_ops *elem, u_int8_t pf,
+ unsigned int hook, struct net_device *indev,
+ struct net_device *outdev, int (*okfn)(struct sk_buff *),
+- unsigned int queuenum);
++ unsigned int queuenum, unsigned int queuetype);
+ int __init netfilter_queue_init(void);
+
+ /* nf_log.c */
+diff --git a/net/netfilter/nf_queue.c b/net/netfilter/nf_queue.c
+index 5d24b1f..28317dc 100644
+--- a/net/netfilter/nf_queue.c
++++ b/net/netfilter/nf_queue.c
+@@ -27,6 +27,23 @@
+ */
+ static const struct nf_queue_handler __rcu *queue_handler __read_mostly;
+
++#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
++static const struct nf_queue_handler __rcu *queue_imq_handler __read_mostly;
++
++void nf_register_queue_imq_handler(const struct nf_queue_handler *qh)
++{
++ rcu_assign_pointer(queue_imq_handler, qh);
++}
++EXPORT_SYMBOL_GPL(nf_register_queue_imq_handler);
++
++void nf_unregister_queue_imq_handler(void)
++{
++ RCU_INIT_POINTER(queue_imq_handler, NULL);
++ synchronize_rcu();
++}
++EXPORT_SYMBOL_GPL(nf_unregister_queue_imq_handler);
++#endif
++
+ /* return EBUSY when somebody else is registered, return EEXIST if the
+ * same handler is registered, return 0 in case of success. */
+ void nf_register_queue_handler(const struct nf_queue_handler *qh)
+@@ -105,7 +122,8 @@ int nf_queue(struct sk_buff *skb,
+ struct net_device *indev,
+ struct net_device *outdev,
+ int (*okfn)(struct sk_buff *),
+- unsigned int queuenum)
++ unsigned int queuenum,
++ unsigned int queuetype)
+ {
+ int status = -ENOENT;
+ struct nf_queue_entry *entry = NULL;
+@@ -115,7 +133,17 @@ int nf_queue(struct sk_buff *skb,
+ /* QUEUE == DROP if no one is waiting, to be safe. */
+ rcu_read_lock();
+
+- qh = rcu_dereference(queue_handler);
++ if (queuetype == NF_IMQ_QUEUE) {
++#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
++ qh = rcu_dereference(queue_imq_handler);
++#else
++ BUG();
++ goto err_unlock;
++#endif
++ } else {
++ qh = rcu_dereference(queue_handler);
++ }
++
+ if (!qh) {
+ status = -ESRCH;
+ goto err_unlock;
+@@ -205,9 +233,11 @@ void nf_reinject(struct nf_queue_entry *entry, unsigned int verdict)
+ local_bh_enable();
+ break;
+ case NF_QUEUE:
++ case NF_IMQ_QUEUE:
+ err = nf_queue(skb, elem, entry->pf, entry->hook,
+ entry->indev, entry->outdev, entry->okfn,
+- verdict >> NF_VERDICT_QBITS);
++ verdict >> NF_VERDICT_QBITS,
++ verdict & NF_VERDICT_MASK);
+ if (err < 0) {
+ if (err == -ECANCELED)
+ goto next_hook;
+diff --git a/net/netfilter/xt_IMQ.c b/net/netfilter/xt_IMQ.c
+new file mode 100644
+index 0000000..1c3cd66
+--- /dev/null
++++ b/net/netfilter/xt_IMQ.c
+@@ -0,0 +1,72 @@
++/*
++ * This target marks packets to be enqueued to an imq device
++ */
++#include <linux/module.h>
++#include <linux/skbuff.h>
++#include <linux/netfilter/x_tables.h>
++#include <linux/netfilter/xt_IMQ.h>
++#include <linux/imq.h>
++
++static unsigned int imq_target(struct sk_buff *pskb,
++ const struct xt_action_param *par)
++{
++ const struct xt_imq_info *mr = par->targinfo;
++
++ pskb->imq_flags = (mr->todev & IMQ_F_IFMASK) | IMQ_F_ENQUEUE;
++
++ return XT_CONTINUE;
++}
++
++static int imq_checkentry(const struct xt_tgchk_param *par)
++{
++ struct xt_imq_info *mr = par->targinfo;
++
++ if (mr->todev > IMQ_MAX_DEVS - 1) {
++ pr_warn("IMQ: invalid device specified, highest is %u\n",
++ IMQ_MAX_DEVS - 1);
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++static struct xt_target xt_imq_reg[] __read_mostly = {
++ {
++ .name = "IMQ",
++ .family = AF_INET,
++ .checkentry = imq_checkentry,
++ .target = imq_target,
++ .targetsize = sizeof(struct xt_imq_info),
++ .table = "mangle",
++ .me = THIS_MODULE
++ },
++ {
++ .name = "IMQ",
++ .family = AF_INET6,
++ .checkentry = imq_checkentry,
++ .target = imq_target,
++ .targetsize = sizeof(struct xt_imq_info),
++ .table = "mangle",
++ .me = THIS_MODULE
++ },
++};
++
++static int __init imq_init(void)
++{
++ return xt_register_targets(xt_imq_reg, ARRAY_SIZE(xt_imq_reg));
++}
++
++static void __exit imq_fini(void)
++{
++ xt_unregister_targets(xt_imq_reg, ARRAY_SIZE(xt_imq_reg));
++}
++
++module_init(imq_init);
++module_exit(imq_fini);
++
++MODULE_AUTHOR("http://www.linuximq.net");
++MODULE_DESCRIPTION("Pseudo-driver for the intermediate queue device. See http://www.linuximq.net/ for more information.");
++MODULE_LICENSE("GPL");
++MODULE_ALIAS("ipt_IMQ");
++MODULE_ALIAS("ip6t_IMQ");
++