Full Code of th0ma7/synology for AI

master ddf793ec2222 cached
9 files
85.1 KB
30.1k tokens
1 requests
Download .txt
Repository: th0ma7/synology
Branch: master
Commit: ddf793ec2222
Files: 9
Total size: 85.1 KB

Directory structure:
gitextract_iqeu8csl/

├── README-old.md
├── README.md
├── hauppauge/
│   ├── 001-Hauppauge955D-lgdt3306a-v3.patch
│   ├── 002-Hauppauge955D-em28xx-Tuner1.patch
│   ├── 003-Hauppauge955D-em28xx-Tuner2-v6.patch
│   └── hauppauge955D-SYNOApollolake-DSM622_24922-Kernel_4.4.59-20190520.tar.bz2
├── hauppauge.sh
├── kernel/
│   └── synocli-kernelmodule.sh
└── tvheadend-backup.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: README-old.md
================================================
# synology - th0ma7
Synology personnal hack, info, tools & source code

Donnations welcomed at: `0x522d164549E68681dfaC850A2cabdb95686C1fEC`

# Hauppauge WinTV DualHD HWC 955D
The following allows building kernel modules for the Hauppauge WinTV DualHD HWC 955D media adapter allowing to use TVheadend natively within the NAS.
* https://www.linuxtv.org/wiki/index.php/Hauppauge_WinTV-HVR-955Q

It was tested on the following hardware:
* model: DS918+
* OS: DSM 6.2.2 build #24922
* kernel: 4.4.59+
* arch: x86_64
* core name: Apollo Lake

Finding your running kernel:
```
$ uname -mvr
4.4.59+ #24922 SMP PREEMPT Thu Mar 28 11:07:03 CST 2019 x86_64
```

Finding your CPU type:
* https://en.wikichip.org/wiki/intel/celeron/j3455
```
$ cat /proc/cpuinfo | grep model.name | head -1
model name	: Intel(R) Celeron(R) CPU J3455 @ 1.50GHz
```

## Current status
Available patches makes both tuner detected by the kernel using the `em28xx.ko` updated driver.  The `lgdt3306a.ko` demodulator driver providing the dvb-frontend devices now works and has a few DEBUG messages output.  It originally failed because I was also sharing the USB device with a Ubuntu VM running on-top and loading the kernel modules in the wrong order.

Working:
- `em28xx`: both tuners detected & firmware loading OK
- `lgdt3306a`: now functional
End result:
- `tvheadend`: fully detects both tuners

Work is based on the backporting of the following upstream kernel patches:

Demodulator (lgdt3306a):
* https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=4f75189024f4186a7ff9d56f4a8cb690774412ec

Adaptor (em28xx):
* https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=11a2a949d05e9d2d9823f0c45fa476743d9e462b
* https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=1586342e428d80e53f9a926b2e238d2175b9f5b5
* https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=be7fd3c3a8c5e9acbc69f887ca961df5e68cf6f0

Along with backported patches from the media-tree that b-rad-NDi made available here:
* https://github.com/b-rad-NDi/Ubuntu-media-tree-kernel-builder/tree/master/patches/fedora-22-4.4.0

## Preparation
Using a Ubuntu 18.04 OS VM in order to build the updated modules.

Install a few essential packages:
```
$ sudo apt update
$ sudo apt install build-essential ncurses-dev bc libssl-dev libc6-i386 curl
```

The toolchain & kernel sources are located here:
https://sourceforge.net/projects/dsgpl/files/

I use a $HOME/synology directory to drop all the downloaded files and a $HOME/source as my working directory.  Later references make usage of theses both paths:
```
$ mkdir $HOME/synology
$ mkdir $HOME/sources
```

Download the toolchain from Home / DSM 6.2 Tool Chains / Intel x86 Linux 4.4.59 (Apollolake)
```
$ wget https://sourceforge.net/projects/dsgpl/files/DSM%206.2%20Tool%20Chains/Intel%20x86%20Linux%204.4.59%20%28Apollolake%29/apollolake-gcc493_glibc220_linaro_x86_64-GPL.txz/download -O apollolake-gcc493_glibc220_linaro_x86_64-GPL.txz -P $HOME/synology
```

Download the synology kernel sources from Home / Synology NAS GPL Source / 22259branch / apollolake-source:
```
$ wget https://sourceforge.net/projects/dsgpl/files/Synology%20NAS%20GPL%20Source/22259branch/apollolake-source/linux-4.4.x.txz/download -O linux-4.4.x.txz -P $HOME/synology
```

Extract the files:
* The toolset is decompressed into the `$HOME/synology` directory
* Kernel sources are decompresed in the `$HOME/sources` directory
```
$ tar -xvf $HOME/synology/apollolake-gcc493_glibc220_linaro_x86_64-GPL.txz -C $HOME/synology
$ tar -xvf $HOME/synology/linux-4.4.x.txz -C $HOME/sources
```

Download the `lgdt3306a` and `em28xx` patches:
```
$ wget https://raw.githubusercontent.com/th0ma7/synology/master/hauppauge/001-Hauppauge955D-lgdt3306a-v3.patch -P $HOME/sources
$ wget https://raw.githubusercontent.com/th0ma7/synology/master/hauppauge/002-Hauppauge955D-em28xx-Tuner1.patch -P $HOME/sources
$ wget https://raw.githubusercontent.com/th0ma7/synology/master/hauppauge/003-Hauppauge955D-em28xx-Tuner2-v6.patch -P $HOME/sources
```

## Patching
We now have to prepare the kernel sources for compilation.  Move down to the `$HOME/sources` directory:
```
$ cd $HOME/sources/linux-4.4.x
```

Frist need to copy the apollolake synology kernel configuration
```
~/sources/linux-4.4.x$ synoconfigs/apollolake .config
```

Secondly, using a text editor you need to adjust a few variables in the Makefile such as:
```
EXTRAVERSION = +
ARCH            ?= x86_64
CROSS_COMPILE   ?= /usr/local/x86_64-pc-linux-gnu/bin/x86_64-pc-linux-gnu-
```

Lastly, apply the necessary patches:
```
~/sources/linux-4.4.x$ patch -p1 < ../001-Hauppauge955D-lgdt3306a-v3.patch
~/sources/linux-4.4.x$ patch -p1 < ../002-Hauppauge955D-em28xx-Tuner1.patch
~/sources/linux-4.4.x$ patch -p1 < ../003-Hauppauge955D-em28xx-Tuner2-v6.patch
```

# Compilation
Modify the original configuration:
```
~/sources/linux-4.4.x$ sudo make oldconfig
~/sources/linux-4.4.x$ sudo make menuconfig
    Device Drivers  --->
    <M> Multimedia support  --->
        [*]   Media Controller API
        [*]   V4L2 sub-device userspace API
```
Everything should now all set for building the modules:
```
~/sources/linux-4.4.x$ make modules_prepare
~/sources/linux-4.4.x$ make modules M=drivers/media -j4
```

# Installation

Using SSH login as admin on the synology NAS:
```
$ ssh admin@192.168.x.x
```

Create a new local module directory (name will match kernel version):
```
$ sudo mkdir -p /usr/local/lib/modules/$(uname -r)
$ cd /usr/local/lib/modules/$(uname -r)
```

Copy the updated media drivers modules over to the NAS (or download the one from my repository on github - Use at your own risks):
```
$ sudo scp "username@192.168.x.x:~/sources/linux-4.4.x/drivers/media/usb/em28xx/*.ko" .
$ sudo scp "username@192.168.x.x:~/sources/linux-4.4.x/drivers/media/dvb-frontends/lgdt3306a.ko" .
$ sudo scp "username@192.168.x.x:~/sources/linux-4.4.x/drivers/media/dvb-frontends/media.ko" .
```

Copy the load/unload script to the NAS as well (and make it executable):
```
$ wget https://raw.githubusercontent.com/th0ma7/synology/master/hauppauge/hauppauge-load.sh
$ wget https://raw.githubusercontent.com/th0ma7/synology/master/hauppauge/hauppauge-unload.sh
$ chmod 755 hauppauge-*.sh
```

Create a local rc file locate at `/usr/local/etc/rc.d/hauppauge.sh` that will be executed at boot time (or copy `hauppauge-load.sh` script):
```
#!/bin/sh

# Load mandatory modules
sudo insmod /lib/modules/dvb-core.ko
sudo insmod /lib/modules/rc-core.ko
sudo insmod /lib/modules/dvb-usb.ko
sudo insmod /lib/modules/videodev.ko
sudo insmod /lib/modules/v4l2-common.ko
sudo insmod /lib/modules/tveeprom.ko

# Load Hauppauge updated drivers
sudo insmod /lib/modules/si2157.ko
sudo insmod /usr/local/lib/modules/$(uname -r)/media.ko
sudo insmod /usr/local/lib/modules/$(uname -r)/lgdt3306a.ko
sudo insmod /usr/local/lib/modules/$(uname -r)/em28xx.ko
sudo insmod /usr/local/lib/modules/$(uname -r)/em28xx-dvb.ko
```

Execute manually the rc script to confirm all is ok:
```
$ sudo /usr/local/etc/rc.d/hauppauge.sh
```

Normally should see the following in kernel `dmesg` (added a few DEBUG to lgdt3306a):
```
[  557.806644] em28xx: New device HCW 955D @ 480 Mbps (2040:026d, interface 0, class 0)
[  557.815308] em28xx: DVB interface 0 found: isoc
[  557.820423] em28xx: chip ID is em28174
[  558.939915] em28174 #0: EEPROM ID = 26 00 01 00, EEPROM hash = 0x3d790eca
[  558.947531] em28174 #0: EEPROM info:
[  558.951857] em28174 #0: 	microcode start address = 0x0004, boot configuration = 0x01
[  558.966683] em28174 #0: 	AC97 audio (5 sample rates)
[  558.972234] em28174 #0: 	500mA max power
[  558.976620] em28174 #0: 	Table at offset 0x27, strings=0x0a72, 0x187c, 0x086a
[  558.984753] em28174 #0: Identified as Hauppauge WinTV-dualHD 01595 ATSC/QAM (card=100)
[  558.994647] tveeprom 8-0050: Hauppauge model 204101, rev B2I6, serial# 11584195
[  559.002824] tveeprom 8-0050: tuner model is SiLabs Si2157 (idx 186, type 4)
[  559.010649] tveeprom 8-0050: TV standards PAL(B/G) NTSC(M) PAL(I) SECAM(L/L') PAL(D/D1/K) ATSC/DVB Digital (eeprom 0xfc)
[  559.023133] tveeprom 8-0050: audio processor is None (idx 0)
[  559.029491] tveeprom 8-0050: has no radio, has IR receiver, has no IR transmitter
[  559.038167] em28174 #0: dvb set to isoc mode.
[  559.043177] em28xx: chip ID is em28174
[  560.162726] em28174 #1: EEPROM ID = 26 00 01 00, EEPROM hash = 0x3d790eca
[  560.170323] em28174 #1: EEPROM info:
[  560.174326] em28174 #1: 	microcode start address = 0x0004, boot configuration = 0x01
[  560.189064] em28174 #1: 	AC97 audio (5 sample rates)
[  560.194613] em28174 #1: 	500mA max power
[  560.199009] em28174 #1: 	Table at offset 0x27, strings=0x0a72, 0x187c, 0x086a
[  560.207139] em28174 #1: Identified as Hauppauge WinTV-dualHD 01595 ATSC/QAM (card=100)
[  560.216915] tveeprom 10-0050: Hauppauge model 204101, rev B2I6, serial# 11584195
[  560.225192] tveeprom 10-0050: tuner model is SiLabs Si2157 (idx 186, type 4)
[  560.233070] tveeprom 10-0050: TV standards PAL(B/G) NTSC(M) PAL(I) SECAM(L/L') PAL(D/D1/K) ATSC/DVB Digital (eeprom 0xfc)
[  560.245327] tveeprom 10-0050: audio processor is None (idx 0)
[  560.251757] tveeprom 10-0050: has no radio, has IR receiver, has no IR transmitter
[  560.260220] em28xx: dvb ts2 set to isoc mode.
[  560.465298] em28174 #0: Binding DVB extension
[  560.476140] i2c i2c-8: Added multiplexed i2c bus 11
[  560.481615] DEBUG: Passed lgdt3306a_probe 2351 
[  560.486720] DEBUG: Passed lgdt3306a_probe 2353 
[  560.491791] DEBUG: Passed lgdt3306a_probe 2355 
[  560.496853] DEBUG: Passed lgdt3306a_probe 2357 
[  560.501921] lgdt3306a 8-0059: LG Electronics LGDT3306A successfully identified
[  560.509994] DEBUG: Passed lgdt3306a_probe 2360 
[  560.517015] si2157 11-0060: Silicon Labs Si2147/2148/2157/2158 successfully attached
[  560.525695] DVB: registering new adapter (em28174 #0)
[  560.531352] usb 1-3: DVB: registering adapter 0 frontend 0 (LG Electronics LGDT3306A VSB/QAM Frontend)...
[  560.544142] em28174 #0: DVB extension successfully initialized
[  560.550672] em28174 #1: Binding DVB extension
[  560.560027] i2c i2c-10: Added multiplexed i2c bus 12
[  560.565578] DEBUG: Passed lgdt3306a_probe 2351 
[  560.570650] DEBUG: Passed lgdt3306a_probe 2353 
[  560.575800] DEBUG: Passed lgdt3306a_probe 2355 
[  560.580886] DEBUG: Passed lgdt3306a_probe 2357 
[  560.585962] lgdt3306a 10-000e: LG Electronics LGDT3306A successfully identified
[  560.594141] DEBUG: Passed lgdt3306a_probe 2360 
[  560.601410] si2157 12-0062: Silicon Labs Si2147/2148/2157/2158 successfully attached
[  560.610075] DVB: registering new adapter (em28174 #1)
[  560.615721] usb 1-3: DVB: registering adapter 1 frontend 0 (LG Electronics LGDT3306A VSB/QAM Frontend)...
[  560.627161] em28174 #1: DVB extension successfully initialized
[  562.882976] si2157 12-0062: found a 'Silicon Labs Si2157-A30'
[  562.939717] si2157 12-0062: firmware version: 3.0.5
[  562.945214] usb 1-3: DVB: adapter 1 frontend 0 frequency 0 out of range (55000000..858000000)
```

And the following USB devices with associated modules (ID may vary depending if connected using the front or back USB ports):
```
$ lsusb -Ic
|__usb1          1d6b:0002:0404 09  2.00  480MBit/s 0mA 1IF  (Linux 4.4.59+ xhci-hcd xHCI Host Controller 0000:00:15.0)
 1-0:1.0          (IF) 09:00:00 1EP  () hub 
  |__1-1         2040:026d:0100 00  2.00  480MBit/s 500mA 1IF  (HCW 955D 0011584195)
  1-1:1.0         (IF) ff:00:00 2EPs () em28xx 
  |__1-4         f400:f400:0100 00  2.00  480MBit/s 200mA 1IF  (Synology DiskStation 6500794064E41636)
  1-4:1.0         (IF) 08:06:50 2EPs () usb-storage host5 (synoboot)
|__usb2          1d6b:0003:0404 09  3.00 5000MBit/s 0mA 1IF  (Linux 4.4.59+ xhci-hcd xHCI Host Controller 0000:00:15.0)
 2-0:1.0          (IF) 09:00:00 1EP  () hub
```

Now reboot the NAS using the admin web page and confirm after reboot that the dmesg output and lsusb are still ok.

In case you run into issue where your NAS refuses to fully shutdown (and thus reboot) with the power button led blinking, it is most probably due to tainted modules still in memory.  Using the hauppauge-unload.sh script prior to shutdown/reboot will remove all the tainted modules from memory thus allowing the NAS to properly shutdown/reboot.


================================================
FILE: README.md
================================================
# synology - th0ma7
Synology personnal hack, info, tools &amp; source code

Donnations welcomed at: `0x522d164549E68681dfaC850A2cabdb95686C1fEC`

# Hauppauge WinTV DualHD HWC 955D
The following allows building kernel modules for the Hauppauge WinTV DualHD HWC 955D media adapter allowing to use TVheadEnd (TVH) natively within the NAS.
* https://www.linuxtv.org/wiki/index.php/Hauppauge_WinTV-HVR-955Q

In theory this procedure is also valid to build most supported DVB adaptors available from the Media Tree within the Linux Media Subsystem.
* https://linuxtv.org/wiki/index.php/ATSC_USB_devices
* https://linuxtv.org/wiki/index.php/DVB-C_USB_Devices

Tested on the following hardware:
* model: DS918+
* OS: DSM 6.2.2 build #24922
* kernel: 4.4.59+
* arch: x86_64
* core name: Apollo Lake

Finding your running kernel:
```
$ uname -mvr
4.4.59+ #24922 SMP PREEMPT Thu Mar 28 11:07:03 CST 2019 x86_64
```

Finding your CPU type:
* https://en.wikichip.org/wiki/intel/celeron/j3455
```
$ cat /proc/cpuinfo | grep model.name | head -1
model name	: Intel(R) Celeron(R) CPU J3455 @ 1.50GHz
```

## Current status
I had backported patches to the Synology DSM 6.x 4.4.59+ kernel but there where a few pending issues.  Since then b-rad-NDi ended-up providing a backporting tool that allows rebuilding the media tree over the Synology DSM kernel.  This solution as been playing really nicely on my NAS over the last months.  _Big thanks to b-rad-NDi!!!_

For more details on b-rad-NDi project refer to:
* https://github.com/b-rad-NDi/Embedded-MediaDrivers

Working:
- `em28xx`: both tuners detected & firmware loading OK
- `lgdt3306a`: fully functional

End result:
- `tvheadend`: fully detects both tuners

Instead of building your own I've made available a pre-built module package for Hauppauge 955D USB DVB dongle to work on Synology NAS 6.2.2 kernel 4.4.59+ with Apollolake CPU (e.g. DS918+):
* https://github.com/th0ma7/synology/raw/master/hauppauge/hauppauge955D-SYNOApollolake-DSM622_24922-Kernel_4.4.59-20190520.tar.bz2

## Preparation
Using a Ubuntu 18.04 OS to build the updated modules install a few essential packages:
```
$ sudo apt update
$ sudo apt install build-essential ncurses-dev bc libssl-dev libc6-i386 curl libproc-processtable-perl
```

Clone b-rad-NDi git repository:
```
$ git clone https://github.com/b-rad-NDi/Embedded-MediaDrivers.git
$ cd Embedded-MediaDrivers
~/Embedded-MediaDrivers$
```

Create a `SYNO-Apollolake` download directory:
```
$ mkdir dl/SYNO-Apollolake
```

Download the toolchain
* https://sourceforge.net/projects/dsgpl/files/DSM%206.2%20Tool%20Chains/
```
$ wget --content-disposition https://sourceforge.net/projects/dsgpl/files/DSM%206.2%20Tool%20Chains/Intel%20x86%20Linux%204.4.59%20%28Apollolake%29/apollolake-gcc493_glibc220_linaro_x86_64-GPL.txz/download -P dl/SYNO-Apollolake/
```

Download the Synology DSM kernel sources:
* https://sourceforge.net/projects/dsgpl/files/Synology%20NAS%20GPL%20Source/22259branch/
```
$ wget --content-disposition https://sourceforge.net/projects/dsgpl/files/Synology%20NAS%20GPL%20Source/22259branch/apollolake-source/linux-4.4.x.txz/download -P dl/SYNO-Apollolake/
```

Initialize the repository:
```
$ ./md_builder.sh -i -d SYNO-Apollolake
```

Build a default Synology DSM kernel build (takes a while):
```
$ export MAKEOPTS="-j`nproc`"
$ ./md_builder.sh -B media -d SYNO-Apollolake
```

Configure the media tree, get the latest media tree patches that applies over the default Synology DSM kernel and build the media drivers:
```
$ ./md_builder.sh -g -d SYNO-Apollolake
$ cd build/SYNOAPOLLOLAKE/media_build
build/SYNOAPOLLOLAKE/media_build$ ./build
```

## Installation

Using SSH login as admin on the synology NAS:
```
$ ssh admin@<my.syno.nas.ip>
```

Create a new local module directory (name will match kernel version):
```
$ sudo mkdir -p /usr/local/lib/modules/$(uname -r)
$ cd /usr/local/lib/modules/$(uname -r)
```

Download the updated media drivers modules over to the NAS (the following downloads not only the mandatory modules for Hauppauge WinTV but rather all the media tree modules):
```
$ cd /usr/local/lib/modules/$(uname -r)
$ sudo scp "username@<my.ubuntu.linux.ip>:~/Embedded-MediaDrivers/build/SYNOAPOLLOLAKE/media_build/v4l/*.ko" .
```

Copy the start/stop/load/reset script to the NAS (and make it executable):
```
$ cd /usr/local/lib/modules/$(uname -r)
$ wget https://raw.githubusercontent.com/th0ma7/synology/master/hauppauge.sh
$ chmod 755 hauppauge.sh
```

Create a symbolic link to `/opt/bin/hauppauge.sh` for ease of use:
```
$ sudo ln -s -T -f /usr/local/lib/modules/$(uname -r)/hauppauge.sh /opt/bin/hauppauge.sh
```

Create a local rc file locate at `/usr/local/etc/rc.d/media.sh` that will be executed at boot time:
```
$ cat << EOF | sudo tee /usr/local/etc/rc.d/media.sh
#!/bin/sh
/usr/local/lib/modules/$(uname -r)/hauppauge.sh load
EOF
$ sudo chmod 755 /usr/local/etc/rc.d/media.sh
```

Execute manually the rc script to confirm there is no error:
```
$ sudo /usr/local/etc/rc.d/media.sh
```

Validate the status:
```
$ sudo /opt/bin/hauppauge.sh status
Status pkgctl-tvheadend...            N/A
kernel module status... 
	em28xx_dvb                    OK
	em28xx                        OK
	lgdt3306a                     OK
	si2157                        OK
	tveeprom                      OK
	v4l2_common                   OK
	dvb_usb                       OK
	rc_core                       OK
	dvb_core                      OK
	videobuf2_vmalloc             OK
	videobuf2_memops              OK
	videobuf2_v4l2                OK
	videobuf2_common              OK
	videodev                      OK
	media                         OK
kernel USB (1-3) autosuspend values...
	(1-3)autosuspend_delay_ms     [-1000] -> OK
	(1-3)autosuspend              [   -1] -> OK
kernel sysctl values... 
	vm.dirty_expire_centisecs     [  300] -> OK
	vm.swappiness                 [    1] -> OK
```

Normally should see something similar in kernel `dmesg`:
```
[  557.806644] em28xx: New device HCW 955D @ 480 Mbps (2040:026d, interface 0, class 0)
[  557.815308] em28xx: DVB interface 0 found: isoc
[  557.820423] em28xx: chip ID is em28174
[  558.939915] em28174 #0: EEPROM ID = 26 00 01 00, EEPROM hash = 0x3d790eca
[  558.947531] em28174 #0: EEPROM info:
[  558.951857] em28174 #0: 	microcode start address = 0x0004, boot configuration = 0x01
[  558.966683] em28174 #0: 	AC97 audio (5 sample rates)
[  558.972234] em28174 #0: 	500mA max power
[  558.976620] em28174 #0: 	Table at offset 0x27, strings=0x0a72, 0x187c, 0x086a
[  558.984753] em28174 #0: Identified as Hauppauge WinTV-dualHD 01595 ATSC/QAM (card=100)
[  558.994647] tveeprom 8-0050: Hauppauge model 204101, rev B2I6, serial# 11584195
[  559.002824] tveeprom 8-0050: tuner model is SiLabs Si2157 (idx 186, type 4)
[  559.010649] tveeprom 8-0050: TV standards PAL(B/G) NTSC(M) PAL(I) SECAM(L/L') PAL(D/D1/K) ATSC/DVB Digital (eeprom 0xfc)
[  559.023133] tveeprom 8-0050: audio processor is None (idx 0)
[  559.029491] tveeprom 8-0050: has no radio, has IR receiver, has no IR transmitter
[  559.038167] em28174 #0: dvb set to isoc mode.
[  559.043177] em28xx: chip ID is em28174
[  560.162726] em28174 #1: EEPROM ID = 26 00 01 00, EEPROM hash = 0x3d790eca
[  560.170323] em28174 #1: EEPROM info:
[  560.174326] em28174 #1: 	microcode start address = 0x0004, boot configuration = 0x01
[  560.189064] em28174 #1: 	AC97 audio (5 sample rates)
[  560.194613] em28174 #1: 	500mA max power
[  560.199009] em28174 #1: 	Table at offset 0x27, strings=0x0a72, 0x187c, 0x086a
[  560.207139] em28174 #1: Identified as Hauppauge WinTV-dualHD 01595 ATSC/QAM (card=100)
[  560.216915] tveeprom 10-0050: Hauppauge model 204101, rev B2I6, serial# 11584195
[  560.225192] tveeprom 10-0050: tuner model is SiLabs Si2157 (idx 186, type 4)
[  560.233070] tveeprom 10-0050: TV standards PAL(B/G) NTSC(M) PAL(I) SECAM(L/L') PAL(D/D1/K) ATSC/DVB Digital (eeprom 0xfc)
[  560.245327] tveeprom 10-0050: audio processor is None (idx 0)
[  560.251757] tveeprom 10-0050: has no radio, has IR receiver, has no IR transmitter
[  560.260220] em28xx: dvb ts2 set to isoc mode.
[  560.465298] em28174 #0: Binding DVB extension
[  560.476140] i2c i2c-8: Added multiplexed i2c bus 11
[  560.501921] lgdt3306a 8-0059: LG Electronics LGDT3306A successfully identified
[  560.509994] DEBUG: Passed lgdt3306a_probe 2360 
[  560.517015] si2157 11-0060: Silicon Labs Si2147/2148/2157/2158 successfully attached
[  560.525695] DVB: registering new adapter (em28174 #0)
[  560.531352] usb 1-3: DVB: registering adapter 0 frontend 0 (LG Electronics LGDT3306A VSB/QAM Frontend)...
[  560.544142] em28174 #0: DVB extension successfully initialized
[  560.550672] em28174 #1: Binding DVB extension
[  560.560027] i2c i2c-10: Added multiplexed i2c bus 12
[  560.585962] lgdt3306a 10-000e: LG Electronics LGDT3306A successfully identified
[  560.601410] si2157 12-0062: Silicon Labs Si2147/2148/2157/2158 successfully attached
[  560.610075] DVB: registering new adapter (em28174 #1)
[  560.615721] usb 1-3: DVB: registering adapter 1 frontend 0 (LG Electronics LGDT3306A VSB/QAM Frontend)...
[  560.627161] em28174 #1: DVB extension successfully initialized
[  562.882976] si2157 12-0062: found a 'Silicon Labs Si2157-A30'
[  562.939717] si2157 12-0062: firmware version: 3.0.5
[  562.945214] usb 1-3: DVB: adapter 1 frontend 0 frequency 0 out of range (55000000..858000000)
```

And the following USB devices with associated modules (ID may vary depending if connected using the front or back USB ports):
```
$ lsusb -Ic
|__usb1          1d6b:0002:0404 09  2.00  480MBit/s 0mA 1IF  (Linux 4.4.59+ xhci-hcd xHCI Host Controller 0000:00:15.0)
 1-0:1.0          (IF) 09:00:00 1EP  () hub 
  |__1-1         2040:026d:0100 00  2.00  480MBit/s 500mA 1IF  (HCW 955D 0011584195)
  1-1:1.0         (IF) ff:00:00 2EPs () em28xx 
  |__1-4         f400:f400:0100 00  2.00  480MBit/s 200mA 1IF  (Synology DiskStation 6500794064E41636)
  1-4:1.0         (IF) 08:06:50 2EPs () usb-storage host5 (synoboot)
|__usb2          1d6b:0003:0404 09  3.00 5000MBit/s 0mA 1IF  (Linux 4.4.59+ xhci-hcd xHCI Host Controller 0000:00:15.0)
 2-0:1.0          (IF) 09:00:00 1EP  () hub
```

Now reboot the NAS using the admin web page and confirm after reboot that the dmesg output and lsusb are still ok.

In case you run into issue where your NAS refuses to fully shutdown (and thus reboot) with the power button led blinking, it is most probably due to tainted modules still in memory.  Running `hauppauge.sh stop` prior to shutdown/reboot will remove all the tainted modules from memory thus allowing the NAS to properly shutdown/reboot.

---

# hauppauge.sh
This script is intended to provide a simple method to start|stop|restart the various perequesites into getting media modules loaded onto the Synology NAS.

Basicaly what the script does:
1. Load all the necessary modules
2. Disable USB autosuspend over the Hauppauge USB ID
3. Injects a few kernel `sysctl` for optmizations
4. Starts TVH (service name `pkgctl-tvheadend`)

## Modules
The script uses `insmod` to load|unload the modules into the appropriate order.  The `MODULES` parameter in the script can be adapted as needed for other DVB dongles than the Hauppauge WinTV 955D.

|Order | Module                 | `rmmod`             |
|:----:|:----------------------:|:-------------------:|
| 1    | `media.ko`             | `media`             |
| 2    | `videodev.ko`          | `videodev`          |
| 3    | `videobuf2-common.ko`  | `videobuf2_common`  |
| 4    | `videobuf2-v4l2.ko`    | `videobuf2_v4l2`    |
| 5    | `videobuf2-memops.ko`  | `videobuf2_memops`  |
| 6    | `videobuf2-vmalloc.ko` | `videobuf2_vmalloc` |
| 7    | `dvb-core.ko`          | `dvb_core`          |
| 8    | `rc-core.ko`           | `rc_core`           |
| 9    | `dvb-usb.ko`           | `dvb_usb`           |
| 10   | `v4l2-common.ko`       | `v4l2_common`       |
| 11   | `tveeprom.ko`          | `tveeprom`          |
| 12   | `si2157.ko`            | `si2157`            |
| 13   | `lgdt3306a.ko`         | `lgdt3306a`         |
| 14   | `em28xx.ko`            | `em28xx`            |
| 15   | `em28xx-dvb.ko`        | `em28xx_dvb`        |

## Options
**start:** Does a full start including:
1. loading all the modules
2. disabling USB autosuspend
3. `sysctl` adjustments
4. TVH startup

**stop:** Does a stop which basicaly is:
1. TVH shutdown
2. Unloading all the modules

**restart:** Basically performs a `stop` then `start`.

**reset:** This is usefull when hitting BUGS with TVH such as OOM killer where the tvheadend service is being killed by the system.  The `reset` option reduces to the minimal the impact over the already loaded modules by resetting only the DVB frontend module such as:
1. TVH shutdown (forces it if needed)
2. unload `em28xx_dvb`
3. load of `em28xx-dvb.ko`
4. TVH startup

**load:** This is the option to be used at NAS startup.  It basically does all the same things as the `start` option without the TVH startup as it's being managed by the Synology DSM stack automatically.

**status:** This provides a view on all things namely modules loaded into memory, USB autosuspend, `sysctl` adjustments and TVH service status including it's PID.


================================================
FILE: hauppauge/001-Hauppauge955D-lgdt3306a-v3.patch
================================================
diff -uprN linux-4.4.x-orig/drivers/media/dvb-frontends/lgdt3306a.c linux-4.4.x-new/drivers/media/dvb-frontends/lgdt3306a.c
--- linux-4.4.x-orig/drivers/media/dvb-frontends/lgdt3306a.c	2017-10-30 17:45:14.000000000 -0400
+++ linux-4.4.x-new/drivers/media/dvb-frontends/lgdt3306a.c	2019-05-13 20:41:32.123976946 -0400
@@ -16,17 +16,31 @@
  *    GNU General Public License for more details.
  */
 
+#undef pr_fmt
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
 #include <asm/div64.h>
+#include <linux/kernel.h>
 #include <linux/dvb/frontend.h>
 #include "dvb_math.h"
 #include "lgdt3306a.h"
+#include <linux/i2c-mux.h>
 
 static int debug;
 module_param(debug, int, 0644);
 MODULE_PARM_DESC(debug, "set debug level (info=1, reg=2 (or-able))");
 
+/*
+ * Older drivers treated QAM64 and QAM256 the same; that is the HW always
+ * used "Auto" mode during detection.  Setting "forced_manual"=1 allows
+ * the user to treat these modes as separate.  For backwards compatibility,
+ * it's off by default.  QAM_AUTO can now be specified to achive that
+ * effect even if "forced_manual"=1
+ */
+static int forced_manual;
+module_param(forced_manual, int, 0644);
+MODULE_PARM_DESC(forced_manual, "if set, QAM64 and QAM256 will only lock to modulation specified");
+
 #define DBG_INFO 1
 #define DBG_REG  2
 #define DBG_DUMP 4 /* FGR - comment out to remove dump code */
@@ -55,6 +69,8 @@ MODULE_PARM_DESC(debug, "set debug level
 	__ret;								\
 })
 
+#define MHz 1000000
+
 struct lgdt3306a_state {
 	struct i2c_adapter *i2c_adap;
 	const struct lgdt3306a_config *cfg;
@@ -558,7 +574,12 @@ static int lgdt3306a_set_qam(struct lgdt
 	/* 3. : 64QAM/256QAM detection(manual, auto) */
 	ret = lgdt3306a_read_reg(state, 0x0009, &val);
 	val &= 0xfc;
-	val |= 0x02; /* STDOPDETCMODE[1:0]=1=Manual 2=Auto */
+	/* Check for forced Manual modulation modes; otherwise always "auto" */
+	if(forced_manual && (modulation != QAM_AUTO)){
+		val |= 0x01; /* STDOPDETCMODE[1:0]= 1=Manual */
+	} else {
+		val |= 0x02; /* STDOPDETCMODE[1:0]= 2=Auto */
+	}
 	ret = lgdt3306a_write_reg(state, 0x0009, val);
 	if (lg_chkerr(ret))
 		goto fail;
@@ -590,6 +611,28 @@ static int lgdt3306a_set_qam(struct lgdt
 	if (lg_chkerr(ret))
 		goto fail;
 
+	/* 5.1 V0.36 SRDCHKALWAYS : For better QAM detection */
+	ret = lgdt3306a_read_reg(state, 0x000a, &val);
+	val &= 0xfd;
+	val |= 0x02;
+	ret = lgdt3306a_write_reg(state, 0x000a, val);
+	if (lg_chkerr(ret))
+		goto fail;
+
+	/* 5.2 V0.36 Control of "no signal" detector function */
+	ret = lgdt3306a_read_reg(state, 0x2849, &val);
+	val &= 0xdf;
+	ret = lgdt3306a_write_reg(state, 0x2849, val);
+	if (lg_chkerr(ret))
+		goto fail;
+
+	/* 5.3 Fix for Blonder Tongue HDE-2H-QAM and AQM modulators */
+	ret = lgdt3306a_read_reg(state, 0x302b, &val);
+	val &= 0x7f;  /* SELFSYNCFINDEN_CQS=0; disable auto reset */
+	ret = lgdt3306a_write_reg(state, 0x302b, val);
+	if (lg_chkerr(ret))
+		goto fail;
+
 	/* 6. Reset */
 	ret = lgdt3306a_soft_reset(state);
 	if (lg_chkerr(ret))
@@ -612,10 +655,9 @@ static int lgdt3306a_set_modulation(stru
 		ret = lgdt3306a_set_vsb(state);
 		break;
 	case QAM_64:
-		ret = lgdt3306a_set_qam(state, QAM_64);
-		break;
 	case QAM_256:
-		ret = lgdt3306a_set_qam(state, QAM_256);
+	case QAM_AUTO:
+		ret = lgdt3306a_set_qam(state, p->modulation);
 		break;
 	default:
 		return -EINVAL;
@@ -642,6 +684,7 @@ static int lgdt3306a_agc_setup(struct lg
 		break;
 	case QAM_64:
 	case QAM_256:
+	case QAM_AUTO:
 		break;
 	default:
 		return -EINVAL;
@@ -696,6 +739,7 @@ static int lgdt3306a_spectral_inversion(
 		break;
 	case QAM_64:
 	case QAM_256:
+	case QAM_AUTO:
 		/* Auto ok for QAM */
 		ret = lgdt3306a_set_inversion_auto(state, 1);
 		break;
@@ -719,6 +763,7 @@ static int lgdt3306a_set_if(struct lgdt3
 		break;
 	case QAM_64:
 	case QAM_256:
+	case QAM_AUTO:
 		if_freq_khz = state->cfg->qam_if_khz;
 		break;
 	default:
@@ -727,7 +772,7 @@ static int lgdt3306a_set_if(struct lgdt3
 
 	switch (if_freq_khz) {
 	default:
-		pr_warn("IF=%d KHz is not supportted, 3250 assumed\n",
+		pr_warn("IF=%d KHz is not supported, 3250 assumed\n",
 			if_freq_khz);
 		/* fallthrough */
 	case 3250: /* 3.25Mhz */
@@ -1577,6 +1622,7 @@ static int lgdt3306a_read_status(struct
 		switch (state->current_modulation) {
 		case QAM_256:
 		case QAM_64:
+		case QAM_AUTO:
 			if (lgdt3306a_qam_lock_poll(state) == LG3306_LOCK) {
 				*status |= FE_HAS_VITERBI;
 				*status |= FE_HAS_SYNC;
@@ -1619,6 +1665,7 @@ static int lgdt3306a_read_signal_strengt
 	 * Calculate some sort of "strength" from SNR
 	 */
 	struct lgdt3306a_state *state = fe->demodulator_priv;
+	u8 val;
 	u16 snr; /* snr_x10 */
 	int ret;
 	u32 ref_snr; /* snr*100 */
@@ -1631,11 +1678,15 @@ static int lgdt3306a_read_signal_strengt
 		 ref_snr = 1600; /* 16dB */
 		 break;
 	case QAM_64:
-		 ref_snr = 2200; /* 22dB */
-		 break;
 	case QAM_256:
-		 ref_snr = 2800; /* 28dB */
-		 break;
+	case QAM_AUTO:
+		/* need to know actual modulation to set proper SNR baseline */
+		lgdt3306a_read_reg(state, 0x00a6, &val);
+		if(val & 0x04)
+			ref_snr = 2800; /* QAM-256 28dB */
+		else
+			ref_snr = 2200; /* QAM-64  22dB */
+		break;
 	default:
 		return -EINVAL;
 	}
@@ -1729,27 +1780,19 @@ static int lgdt3306a_get_tune_settings(s
 	return 0;
 }
 
-static int lgdt3306a_search(struct dvb_frontend *fe)
+static enum dvbfe_search lgdt3306a_search(struct dvb_frontend *fe)
 {
 	enum fe_status status = 0;
-	int i, ret;
+	int ret;
 
 	/* set frontend */
 	ret = lgdt3306a_set_parameters(fe);
 	if (ret)
 		goto error;
 
-	/* wait frontend lock */
-	for (i = 20; i > 0; i--) {
-		dbg_info(": loop=%d\n", i);
-		msleep(50);
-		ret = lgdt3306a_read_status(fe, &status);
-		if (ret)
-			goto error;
-
-		if (status & FE_HAS_LOCK)
-			break;
-	}
+	ret = lgdt3306a_read_status(fe, &status);
+	if (ret)
+		goto error;
 
 	/* check if we have a valid signal */
 	if (status & FE_HAS_LOCK)
@@ -1770,7 +1813,7 @@ static void lgdt3306a_release(struct dvb
 	kfree(state);
 }
 
-static struct dvb_frontend_ops lgdt3306a_ops;
+static const struct dvb_frontend_ops lgdt3306a_ops;
 
 struct dvb_frontend *lgdt3306a_attach(const struct lgdt3306a_config *config,
 				      struct i2c_adapter *i2c_adap)
@@ -2072,7 +2115,7 @@ static const short regtab[] = {
 	0x30aa, /* MPEGLOCK */
 };
 
-#define numDumpRegs (sizeof(regtab)/sizeof(regtab[0]))
+#define numDumpRegs (ARRAY_SIZE(regtab))
 static u8 regval1[numDumpRegs] = {0, };
 static u8 regval2[numDumpRegs] = {0, };
 
@@ -2104,14 +2147,14 @@ static void lgdt3306a_DumpRegs(struct lg
 }
 #endif /* DBG_DUMP */
 
-static struct dvb_frontend_ops lgdt3306a_ops = {
+static const struct dvb_frontend_ops lgdt3306a_ops = {
 	.delsys = { SYS_ATSC, SYS_DVBC_ANNEX_B },
 	.info = {
 		.name = "LG Electronics LGDT3306A VSB/QAM Frontend",
-		.frequency_min      = 54000000,
-		.frequency_max      = 858000000,
-		.frequency_stepsize = 62500,
-		.caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB
+		.frequency_min          =  54 * MHz,
+		.frequency_max          = 858 * MHz,
+		.frequency_stepsize     = 62500,
+		.caps = FE_CAN_QAM_AUTO | FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB
 	},
 	.i2c_gate_ctrl        = lgdt3306a_i2c_gate_ctrl,
 	.init                 = lgdt3306a_init,
@@ -2132,6 +2175,234 @@ static struct dvb_frontend_ops lgdt3306a
 	.search               = lgdt3306a_search,
 };
 
+/*
+ * I2C gate logic
+ * We must use unlocked I2C I/O because I2C adapter lock is already taken
+ * by the caller (usually tuner driver).
+ * select/unselect are unlocked versions of lgdt3306a_i2c_gate_ctrl
+ */
+static int lgdt3306a_select(struct i2c_adapter *adap, void *mux_priv, u32 chan)
+{
+	struct i2c_client *client = mux_priv;
+	int ret;
+	u8 val;
+	u8 buf[3];
+
+	struct i2c_msg read_msg_1 = {
+		.addr = client->addr,
+		.flags = 0,
+		.buf = "\x00\x02",
+		.len = 2,
+	};
+	struct i2c_msg read_msg_2 = {
+		.addr = client->addr,
+		.flags = I2C_M_RD,
+		.buf = &val,
+		.len = 1,
+	};
+
+	struct i2c_msg write_msg = {
+		.addr = client->addr,
+		.flags = 0,
+		.len = 3,
+		.buf = buf,
+	};
+
+	ret = __i2c_transfer(client->adapter, &read_msg_1, 1);
+	if (ret != 1)
+	{
+		pr_err("error (addr %02x reg 0x002 error (ret == %i)\n",
+		       client->addr, ret);
+		if (ret < 0)
+			return ret;
+		else
+			return -EREMOTEIO;
+	}
+
+	ret = __i2c_transfer(client->adapter, &read_msg_2, 1);
+	if (ret != 1)
+	{
+		pr_err("error (addr %02x reg 0x002 error (ret == %i)\n",
+		       client->addr, ret);
+		if (ret < 0)
+			return ret;
+		else
+			return -EREMOTEIO;
+	}
+
+	buf[0] = 0x00;
+	buf[1] = 0x02;
+	val &= 0x7F;
+	val |= LG3306_TUNERI2C_ON;
+	buf[2] = val;
+	ret = __i2c_transfer(client->adapter, &write_msg, 1);
+	if (ret != 1) {
+		pr_err("error (addr %02x %02x <- %02x, err = %i)\n",
+		       write_msg.buf[0], write_msg.buf[1], write_msg.buf[2], ret);
+		if (ret < 0)
+			return ret;
+		else
+			return -EREMOTEIO;
+	}
+	return 0;
+}
+
+static int lgdt3306a_deselect(struct i2c_adapter *adap, void *mux_priv, u32 chan)
+{
+	struct i2c_client *client = mux_priv;
+	int ret;
+	u8 val;
+	u8 buf[3];
+
+	struct i2c_msg read_msg_1 = {
+		.addr = client->addr,
+		.flags = 0,
+		.buf = "\x00\x02",
+		.len = 2,
+	};
+	struct i2c_msg read_msg_2 = {
+		.addr = client->addr,
+		.flags = I2C_M_RD,
+		.buf = &val,
+		.len = 1,
+	};
+
+	struct i2c_msg write_msg = {
+		.addr = client->addr,
+		.flags = 0,
+		.len = 3,
+		.buf = buf,
+	};
+
+	ret = __i2c_transfer(client->adapter, &read_msg_1, 1);
+	if (ret != 1)
+	{
+		pr_err("error (addr %02x reg 0x002 error (ret == %i)\n",
+		       client->addr, ret);
+		if (ret < 0)
+			return ret;
+		else
+			return -EREMOTEIO;
+	}
+
+	ret = __i2c_transfer(client->adapter, &read_msg_2, 1);
+	if (ret != 1)
+	{
+		pr_err("error (addr %02x reg 0x002 error (ret == %i)\n",
+		       client->addr, ret);
+		if (ret < 0)
+			return ret;
+		else
+			return -EREMOTEIO;
+	}
+
+	buf[0] = 0x00;
+	buf[1] = 0x02;
+	val &= 0x7F;
+	val |= LG3306_TUNERI2C_OFF;
+	buf[2] = val;
+	ret = __i2c_transfer(client->adapter, &write_msg, 1);
+	if (ret != 1) {
+		pr_err("error (addr %02x %02x <- %02x, err = %i)\n",
+		       write_msg.buf[0], write_msg.buf[1], write_msg.buf[2], ret);
+		if (ret < 0)
+			return ret;
+		else
+			return -EREMOTEIO;
+	}
+	return 0;
+}
+
+static int lgdt3306a_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	struct lgdt3306a_config *config;
+	struct lgdt3306a_state *state;
+	struct dvb_frontend *fe;
+	int ret;
+
+	config = kmemdup(client->dev.platform_data,
+			 sizeof(struct lgdt3306a_config), GFP_KERNEL);
+	if (config == NULL) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	config->i2c_addr = client->addr;
+	fe = lgdt3306a_attach(config, client->adapter);
+	if (fe == NULL) {
+		ret = -ENODEV;
+		goto err_fe;
+	}
+
+	i2c_set_clientdata(client, fe->demodulator_priv);
+	state = fe->demodulator_priv;
+	state->frontend.ops.release = NULL;
+
+	/* create mux i2c adapter for tuner */
+	state->i2c_adap = i2c_add_mux_adapter(client->adapter, &client->dev,
+			client, 0, 0, 0, lgdt3306a_select, lgdt3306a_deselect);
+	if (state->i2c_adap == NULL) {
+		ret = -ENODEV;
+		goto err_kfree;
+	}
+
+	/* create dvb_frontend */
+	printk(KERN_ALERT "DEBUG: Passed %s %d \n",__FUNCTION__,__LINE__);
+	fe->ops.i2c_gate_ctrl = NULL;
+	printk(KERN_ALERT "DEBUG: Passed %s %d \n",__FUNCTION__,__LINE__);
+	*config->i2c_adapter = state->i2c_adap;
+	printk(KERN_ALERT "DEBUG: Passed %s %d \n",__FUNCTION__,__LINE__);
+	*config->fe = fe;
+	printk(KERN_ALERT "DEBUG: Passed %s %d \n",__FUNCTION__,__LINE__);
+
+	dev_info(&client->dev, "LG Electronics LGDT3306A successfully identified\n");
+	printk(KERN_ALERT "DEBUG: Passed %s %d \n",__FUNCTION__,__LINE__);
+
+	return 0;
+
+err_kfree:
+	kfree(state);
+err_fe:
+	kfree(config);
+fail:
+	dev_warn(&client->dev, "probe failed = %d\n", ret);
+	return ret;
+}
+
+static int lgdt3306a_remove(struct i2c_client *client)
+{
+	struct lgdt3306a_state *state = i2c_get_clientdata(client);
+
+	i2c_del_mux_adapter(state->i2c_adap);
+
+	state->frontend.ops.release = NULL;
+	state->frontend.demodulator_priv = NULL;
+
+	kfree(state->cfg);
+	kfree(state);
+
+	return 0;
+}
+
+static const struct i2c_device_id lgdt3306a_id_table[] = {
+	{"lgdt3306a", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, lgdt3306a_id_table);
+
+static struct i2c_driver lgdt3306a_driver = {
+	.driver = {
+		.name                = "lgdt3306a",
+		.suppress_bind_attrs = true,
+	},
+	.probe		= lgdt3306a_probe,
+	.remove		= lgdt3306a_remove,
+	.id_table	= lgdt3306a_id_table,
+};
+
+module_i2c_driver(lgdt3306a_driver);
+
 MODULE_DESCRIPTION("LG Electronics LGDT3306A ATSC/QAM-B Demodulator Driver");
 MODULE_AUTHOR("Fred Richter <frichter@hauppauge.com>");
 MODULE_LICENSE("GPL");
diff -uprN linux-4.4.x-orig/drivers/media/dvb-frontends/lgdt3306a.h linux-4.4.x-new/drivers/media/dvb-frontends/lgdt3306a.h
--- linux-4.4.x-orig/drivers/media/dvb-frontends/lgdt3306a.h	2017-10-30 17:45:15.000000000 -0400
+++ linux-4.4.x-new/drivers/media/dvb-frontends/lgdt3306a.h	2019-05-13 20:39:59.778870885 -0400
@@ -21,6 +21,9 @@
 #include <linux/i2c.h>
 #include "dvb_frontend.h"
 
+#define LG3306_TUNERI2C_ON  0x00
+#define LG3306_TUNERI2C_OFF 0x80
+
 enum lgdt3306a_mpeg_mode {
 	LGDT3306A_MPEG_PARALLEL = 0,
 	LGDT3306A_MPEG_SERIAL = 1,
@@ -55,6 +58,10 @@ struct lgdt3306a_config {
 
 	/* demod clock freq in MHz; 24 or 25 supported */
 	int  xtalMHz;
+
+	/* returned by driver if using i2c bus multiplexing */
+	struct dvb_frontend **fe;
+	struct i2c_adapter **i2c_adapter;
 };
 
 #if IS_REACHABLE(CONFIG_DVB_LGDT3306A)


================================================
FILE: hauppauge/002-Hauppauge955D-em28xx-Tuner1.patch
================================================
diff -uprN linux-4.4.x-orig/Documentation/video4linux/CARDLIST.em28xx linux-4.4.x-new/Documentation/video4linux/CARDLIST.em28xx
--- linux-4.4.x-orig/Documentation/video4linux/CARDLIST.em28xx	2017-09-25 22:52:52.000000000 -0400
+++ linux-4.4.x-new/Documentation/video4linux/CARDLIST.em28xx	2019-05-04 08:16:31.909682143 -0400
@@ -96,3 +96,5 @@
  95 -> Leadtek VC100                            (em2861)        [0413:6f07]
  96 -> Terratec Cinergy T2 Stick HD             (em28178)
  97 -> Elgato EyeTV Hybrid 2008 INT             (em2884)        [0fd9:0018]
+ 98 -> PLEX PX-BCUD                             (em28178)       [3275:0085]
+ 99 -> Hauppauge WinTV-dualHD DVB               (em28174)       [2040:0265]
diff -uprN linux-4.4.x-orig/drivers/media/usb/em28xx/em28xx-cards.c linux-4.4.x-new/drivers/media/usb/em28xx/em28xx-cards.c
--- linux-4.4.x-orig/drivers/media/usb/em28xx/em28xx-cards.c	2017-10-30 17:46:49.000000000 -0400
+++ linux-4.4.x-new/drivers/media/usb/em28xx/em28xx-cards.c	2019-05-04 08:20:03.092511255 -0400
@@ -492,6 +492,31 @@ static struct em28xx_reg_seq terratec_t2
 };
 
 /*
+ * 2040:0265 Hauppauge WinTV-dualHD DVB
+ * 2040:026d Hauppauge WinTV-dualHD ATSC/QAM
+ * reg 0x80/0x84:
+ * GPIO_0: Yellow LED tuner 1, 0=on, 1=off
+ * GPIO_1: Green LED tuner 1, 0=on, 1=off
+ * GPIO_2: Yellow LED tuner 2, 0=on, 1=off
+ * GPIO_3: Green LED tuner 2, 0=on, 1=off
+ * GPIO_5: Reset #2, 0=active
+ * GPIO_6: Reset #1, 0=active
+ */
+static struct em28xx_reg_seq hauppauge_dualhd_dvb[] = {
+	{EM2874_R80_GPIO_P0_CTRL,      0xff, 0xff,      0},
+	{0x0d,                         0xff, 0xff,    200},
+	{0x50,                         0x04, 0xff,    300},
+	{EM2874_R80_GPIO_P0_CTRL,      0xbf, 0xff,    100}, /* demod 1 reset */
+	{EM2874_R80_GPIO_P0_CTRL,      0xff, 0xff,    100},
+	{EM2874_R80_GPIO_P0_CTRL,      0xdf, 0xff,    100}, /* demod 2 reset */
+	{EM2874_R80_GPIO_P0_CTRL,      0xff, 0xff,    100},
+	{EM2874_R5F_TS_ENABLE,         0x44, 0xff,     50},
+	{EM2874_R5D_TS1_PKT_SIZE,      0x05, 0xff,     50},
+	{EM2874_R5E_TS2_PKT_SIZE,      0x05, 0xff,     50},
+	{-1,                             -1,   -1,     -1},
+};
+
+/*
  *  Button definitions
  */
 static struct em28xx_button std_snapshot_button[] = {
@@ -560,6 +585,22 @@ static struct em28xx_led pctv_80e_leds[]
 	{-1, 0, 0, 0},
 };
 
+static struct em28xx_led hauppauge_dualhd_leds[] = {
+	{
+		.role      = EM28XX_LED_DIGITAL_CAPTURING,
+		.gpio_reg  = EM2874_R80_GPIO_P0_CTRL,
+		.gpio_mask = EM_GPIO_1,
+		.inverted  = 1,
+	},
+	{
+		.role      = EM28XX_LED_DIGITAL_CAPTURING_TS2,
+		.gpio_reg  = EM2874_R80_GPIO_P0_CTRL,
+		.gpio_mask = EM_GPIO_3,
+		.inverted  = 1,
+	},
+	{-1, 0, 0, 0},
+};
+
 /*
  *  Board definitions
  */
@@ -2288,6 +2329,36 @@ struct em28xx_board em28xx_boards[] = {
 		.has_dvb       = 1,
 		.ir_codes      = RC_MAP_TERRATEC_SLIM_2,
 	},
+	/*
+	 * 2040:0265 Hauppauge WinTV-dualHD (DVB version).
+	 * Empia EM28274, 2x Silicon Labs Si2168, 2x Silicon Labs Si2157
+	 */
+	[EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB] = {
+		.name          = "Hauppauge WinTV-dualHD DVB",
+		.def_i2c_bus   = 1,
+		.i2c_speed     = EM28XX_I2C_CLK_WAIT_ENABLE |
+				 EM28XX_I2C_FREQ_400_KHZ,
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = hauppauge_dualhd_dvb,
+		.has_dvb       = 1,
+		.ir_codes      = RC_MAP_HAUPPAUGE,
+		.leds          = hauppauge_dualhd_leds,
+	},
+	/*
+	 * 2040:026d Hauppauge WinTV-dualHD (model 01595 - ATSC/QAM).
+	 * Empia EM28274, 2x LG LGDT3306A, 2x Silicon Labs Si2157
+	 */
+	[EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_01595] = {
+		.name          = "Hauppauge WinTV-dualHD 01595 ATSC/QAM",
+		.def_i2c_bus   = 1,
+		.i2c_speed     = EM28XX_I2C_CLK_WAIT_ENABLE |
+				 EM28XX_I2C_FREQ_400_KHZ,
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = hauppauge_dualhd_dvb,
+		.has_dvb       = 1,
+		.ir_codes      = RC_MAP_HAUPPAUGE,
+		.leds          = hauppauge_dualhd_leds,
+	},
 };
 EXPORT_SYMBOL_GPL(em28xx_boards);
 
@@ -2411,6 +2482,10 @@ struct usb_device_id em28xx_id_table[] =
 			.driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 },
 	{ USB_DEVICE(0x2040, 0x651f),
 			.driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850 },
+	{ USB_DEVICE(0x2040, 0x0265),
+			.driver_info = EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB },
+	{ USB_DEVICE(0x2040, 0x026d),
+			.driver_info = EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_01595 },
 	{ USB_DEVICE(0x0438, 0xb002),
 			.driver_info = EM2880_BOARD_AMD_ATI_TV_WONDER_HD_600 },
 	{ USB_DEVICE(0x2001, 0xf112),
@@ -2804,6 +2879,8 @@ static void em28xx_card_setup(struct em2
 	case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850:
 	case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950:
 	case EM2884_BOARD_HAUPPAUGE_WINTV_HVR_930C:
+	case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB:
+	case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_01595:
 	{
 		struct tveeprom tv;
 
diff -uprN linux-4.4.x-orig/drivers/media/usb/em28xx/em28xx-dvb.c linux-4.4.x-new/drivers/media/usb/em28xx/em28xx-dvb.c
--- linux-4.4.x-orig/drivers/media/usb/em28xx/em28xx-dvb.c	2017-10-30 17:46:44.000000000 -0400
+++ linux-4.4.x-new/drivers/media/usb/em28xx/em28xx-dvb.c	2019-05-04 08:20:03.092511255 -0400
@@ -36,6 +36,7 @@
 
 #include "lgdt330x.h"
 #include "lgdt3305.h"
+#include "lgdt3306a.h"
 #include "zl10353.h"
 #include "s5h1409.h"
 #include "mt352.h"
@@ -858,6 +859,17 @@ static struct tda18271_config pinnacle_8
 	.role    = TDA18271_MASTER,
 };
 
+static struct lgdt3306a_config hauppauge_01595_lgdt3306a_config = {
+	.qam_if_khz         = 4000,
+	.vsb_if_khz         = 3250,
+	.spectral_inversion = 0,
+	.deny_i2c_rptr      = 0,
+	.mpeg_mode          = LGDT3306A_MPEG_SERIAL,
+	.tpclk_edge         = LGDT3306A_TPCLK_RISING_EDGE,
+	.tpvalid_polarity   = LGDT3306A_TP_VALID_HIGH,
+	.xtalMHz            = 25,
+};
+
 /* ------------------------------------------------------------------ */
 
 static int em28xx_attach_xc3028(u8 addr, struct em28xx *dev)
@@ -1717,6 +1729,132 @@ static int em28xx_dvb_init(struct em28xx
 			if (!try_module_get(client->dev.driver->owner)) {
 				i2c_unregister_device(client);
 				module_put(dvb->i2c_client_demod->dev.driver->owner);
+				i2c_unregister_device(dvb->i2c_client_demod);
+				result = -ENODEV;
+				goto out_free;
+			}
+
+			dvb->i2c_client_tuner = client;
+		}
+		break;
+	case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB:
+		{
+			struct i2c_adapter *adapter;
+			struct i2c_client *client;
+			struct i2c_board_info info;
+			struct si2168_config si2168_config;
+			struct si2157_config si2157_config;
+
+			/* attach demod */
+			memset(&si2168_config, 0, sizeof(si2168_config));
+			si2168_config.i2c_adapter = &adapter;
+			si2168_config.fe = &dvb->fe[0];
+			si2168_config.ts_mode = SI2168_TS_SERIAL;
+			memset(&info, 0, sizeof(struct i2c_board_info));
+			strlcpy(info.type, "si2168", I2C_NAME_SIZE);
+			info.addr = 0x64;
+			info.platform_data = &si2168_config;
+			request_module(info.type);
+			client = i2c_new_device(&dev->i2c_adap[dev->def_i2c_bus], &info);
+			if (client == NULL || client->dev.driver == NULL) {
+				result = -ENODEV;
+				goto out_free;
+			}
+
+			if (!try_module_get(client->dev.driver->owner)) {
+				i2c_unregister_device(client);
+				result = -ENODEV;
+				goto out_free;
+			}
+
+			dvb->i2c_client_demod = client;
+
+			/* attach tuner */
+			memset(&si2157_config, 0, sizeof(si2157_config));
+			si2157_config.fe = dvb->fe[0];
+			si2157_config.if_port = 1;
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+			si2157_config.mdev = dev->media_dev;
+#endif
+			memset(&info, 0, sizeof(struct i2c_board_info));
+			strlcpy(info.type, "si2157", I2C_NAME_SIZE);
+			info.addr = 0x60;
+			info.platform_data = &si2157_config;
+			request_module(info.type);
+			client = i2c_new_device(adapter, &info);
+			if (client == NULL || client->dev.driver == NULL) {
+				module_put(dvb->i2c_client_demod->dev.driver->owner);
+				i2c_unregister_device(dvb->i2c_client_demod);
+				result = -ENODEV;
+				goto out_free;
+			}
+
+			if (!try_module_get(client->dev.driver->owner)) {
+				i2c_unregister_device(client);
+				module_put(dvb->i2c_client_demod->dev.driver->owner);
+				i2c_unregister_device(dvb->i2c_client_demod);
+				result = -ENODEV;
+				goto out_free;
+			}
+
+			dvb->i2c_client_tuner = client;
+
+		}
+		break;
+	case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_01595:
+		{
+			struct i2c_adapter *adapter;
+			struct i2c_client *client;
+			struct i2c_board_info info = {};
+			struct lgdt3306a_config lgdt3306a_config;
+			struct si2157_config si2157_config = {};
+
+			/* attach demod */
+			lgdt3306a_config = hauppauge_01595_lgdt3306a_config;
+			lgdt3306a_config.fe = &dvb->fe[0];
+			lgdt3306a_config.i2c_adapter = &adapter;
+			strlcpy(info.type, "lgdt3306a", sizeof(info.type));
+			info.addr = 0x59;
+			info.platform_data = &lgdt3306a_config;
+			request_module(info.type);
+			client = i2c_new_device(&dev->i2c_adap[dev->def_i2c_bus],
+					&info);
+			if (client == NULL || client->dev.driver == NULL) {
+				result = -ENODEV;
+				goto out_free;
+			}
+
+			if (!try_module_get(client->dev.driver->owner)) {
+				i2c_unregister_device(client);
+				result = -ENODEV;
+				goto out_free;
+			}
+
+			dvb->i2c_client_demod = client;
+
+			/* attach tuner */
+			si2157_config.fe = dvb->fe[0];
+			si2157_config.if_port = 1;
+			si2157_config.inversion = 1;
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+			si2157_config.mdev = dev->media_dev;
+#endif
+			memset(&info, 0, sizeof(struct i2c_board_info));
+			strlcpy(info.type, "si2157", sizeof(info.type));
+			info.addr = 0x60;
+			info.platform_data = &si2157_config;
+			request_module(info.type);
+
+			client = i2c_new_device(adapter, &info);
+			if (client == NULL || client->dev.driver == NULL) {
+				module_put(dvb->i2c_client_demod->dev.driver->owner);
+				i2c_unregister_device(dvb->i2c_client_demod);
+				result = -ENODEV;
+				goto out_free;
+			}
+			if (!try_module_get(client->dev.driver->owner)) {
+				i2c_unregister_device(client);
+				module_put(dvb->i2c_client_demod->dev.driver->owner);
 				i2c_unregister_device(dvb->i2c_client_demod);
 				result = -ENODEV;
 				goto out_free;
diff -uprN linux-4.4.x-orig/drivers/media/usb/em28xx/em28xx.h linux-4.4.x-new/drivers/media/usb/em28xx/em28xx.h
--- linux-4.4.x-orig/drivers/media/usb/em28xx/em28xx.h	2017-10-30 17:46:43.000000000 -0400
+++ linux-4.4.x-new/drivers/media/usb/em28xx/em28xx.h	2019-05-04 08:20:03.092511255 -0400
@@ -145,6 +145,9 @@
 #define EM2861_BOARD_LEADTEK_VC100                95
 #define EM28178_BOARD_TERRATEC_T2_STICK_HD        96
 #define EM2884_BOARD_ELGATO_EYETV_HYBRID_2008     97
+#define EM28178_BOARD_PLEX_PX_BCUD                98
+#define EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB  99
+#define EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_01595 100
 
 /* Limits minimum and default number of buffers */
 #define EM28XX_MIN_BUF 4
@@ -412,6 +415,7 @@ enum em28xx_adecoder {
 enum em28xx_led_role {
 	EM28XX_LED_ANALOG_CAPTURING = 0,
 	EM28XX_LED_DIGITAL_CAPTURING,
+	EM28XX_LED_DIGITAL_CAPTURING_TS2,
 	EM28XX_LED_ILLUMINATION,
 	EM28XX_NUM_LED_ROLES, /* must be the last */
 };
diff -uprN linux-4.4.x-orig/drivers/media/usb/em28xx/em28xx-reg.h linux-4.4.x-new/drivers/media/usb/em28xx/em28xx-reg.h
--- linux-4.4.x-orig/drivers/media/usb/em28xx/em28xx-reg.h	2017-10-30 17:46:42.000000000 -0400
+++ linux-4.4.x-new/drivers/media/usb/em28xx/em28xx-reg.h	2019-05-04 08:15:10.896596159 -0400
@@ -193,6 +193,19 @@
 /* em2874 registers */
 #define EM2874_R50_IR_CONFIG    0x50
 #define EM2874_R51_IR           0x51
+#define EM2874_R5D_TS1_PKT_SIZE 0x5d
+#define EM2874_R5E_TS2_PKT_SIZE 0x5e
+	/*
+	 * For both TS1 and TS2, In isochronous mode:
+	 *  0x01  188 bytes
+	 *  0x02  376 bytes
+	 *  0x03  564 bytes
+	 *  0x04  752 bytes
+	 *  0x05  940 bytes
+	 * In bulk mode:
+	 *  0x01..0xff  total packet count in 188-byte
+	 */
+
 #define EM2874_R5F_TS_ENABLE    0x5f
 
 /* em2874/174/84, em25xx, em276x/7x/8x GPIO registers */


================================================
FILE: hauppauge/003-Hauppauge955D-em28xx-Tuner2-v6.patch
================================================
diff -uprN linux-4.4.x-orig/drivers/media/usb/em28xx/em28xx-cards.c linux-4.4.x-new/drivers/media/usb/em28xx/em28xx-cards.c
--- linux-4.4.x-orig/drivers/media/usb/em28xx/em28xx-cards.c	2019-05-06 20:53:45.596707923 -0400
+++ linux-4.4.x-new/drivers/media/usb/em28xx/em28xx-cards.c	2019-05-06 20:55:37.646304153 -0400
@@ -2341,6 +2341,7 @@ struct em28xx_board em28xx_boards[] = {
 		.tuner_type    = TUNER_ABSENT,
 		.tuner_gpio    = hauppauge_dualhd_dvb,
 		.has_dvb       = 1,
+		.has_dual_ts   = 1,
 		.ir_codes      = RC_MAP_HAUPPAUGE,
 		.leds          = hauppauge_dualhd_leds,
 	},
@@ -2356,6 +2357,7 @@ struct em28xx_board em28xx_boards[] = {
 		.tuner_type    = TUNER_ABSENT,
 		.tuner_gpio    = hauppauge_dualhd_dvb,
 		.has_dvb       = 1,
+		.has_dual_ts   = 1,
 		.ir_codes      = RC_MAP_HAUPPAUGE,
 		.leds          = hauppauge_dualhd_leds,
 	},
@@ -3090,6 +3092,8 @@ static void flush_request_modules(struct
 */
 static void em28xx_release_resources(struct em28xx *dev)
 {
+	struct usb_device *udev;
+	
 	/*FIXME: I2C IR should be disconnected */
 
 	mutex_lock(&dev->lock);
@@ -3098,7 +3102,8 @@ static void em28xx_release_resources(str
 		em28xx_i2c_unregister(dev, 1);
 	em28xx_i2c_unregister(dev, 0);
 
-	usb_put_dev(dev->udev);
+	if (dev->ts == PRIMARY_TS)
+		usb_put_dev(udev);
 
 	/* Mark device as unused */
 	clear_bit(dev->devno, em28xx_devused);
@@ -3297,6 +3302,35 @@ static int em28xx_init_dev(struct em28xx
 	return 0;
 }
 
+int em28xx_duplicate_dev(struct em28xx *dev)
+{
+	int nr;
+	struct em28xx *sec_dev = kzalloc(sizeof(*sec_dev), GFP_KERNEL);
+
+	if (sec_dev == NULL) {
+		dev->dev_next = NULL;
+		return -ENOMEM;
+	}
+	memcpy(sec_dev, dev, sizeof(sizeof(*sec_dev)));
+	/* Check to see next free device and mark as used */
+	do {
+		nr = find_first_zero_bit(em28xx_devused, EM28XX_MAXBOARDS);
+		if (nr >= EM28XX_MAXBOARDS) {
+			/* No free device slots */
+			dev_warn(&dev->udev->dev, ": Supports only %i em28xx boards.\n",
+					EM28XX_MAXBOARDS);
+			kfree(sec_dev);
+			dev->dev_next = NULL;
+			return -ENOMEM;
+		}
+	} while (test_and_set_bit(nr, em28xx_devused));
+	sec_dev->devno = nr;
+	snprintf(sec_dev->name, 28, "em28xx #%d", nr);
+	sec_dev->dev_next = NULL;
+	dev->dev_next = sec_dev;
+	return 0;
+}
+
 /* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */
 #define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03))
 
@@ -3417,6 +3451,17 @@ static int em28xx_usb_probe(struct usb_i
 						}
 					}
 					break;
+				case 0x85:
+					if (usb_endpoint_xfer_isoc(e)) {
+						if (size > dev->dvb_max_pkt_size_isoc_ts2) {
+							dev->dvb_ep_isoc_ts2 = e->bEndpointAddress;
+							dev->dvb_max_pkt_size_isoc_ts2 = size;
+							dev->dvb_alt_isoc = i;
+						}
+					} else {
+						dev->dvb_ep_bulk_ts2 = e->bEndpointAddress;
+					}
+					break;
 				}
 			}
 			/* NOTE:
@@ -3431,6 +3476,8 @@ static int em28xx_usb_probe(struct usb_i
 			 *  0x83	isoc*		=> audio
 			 *  0x84	isoc		=> digital
 			 *  0x84	bulk		=> analog or digital**
+			 *  0x85	isoc		=> digital TS2
+			 *  0x85	bulk		=> digital TS2
 			 * (*: audio should always be isoc)
 			 * (**: analog, if ep 0x82 is isoc, otherwise digital)
 			 *
@@ -3499,6 +3546,10 @@ static int em28xx_usb_probe(struct usb_i
 	dev->has_video = has_video;
 	dev->ifnum = ifnum;
 
+	dev->ts = PRIMARY_TS;
+	snprintf(dev->name, 28, "em28xx");
+	dev->dev_next = NULL;
+
 	if (has_vendor_audio) {
 		printk(KERN_INFO DRIVER_NAME ": Audio interface %i found %s\n",
 		       ifnum, "(Vendor Class)");
@@ -3568,6 +3619,65 @@ static int em28xx_usb_probe(struct usb_i
 			    dev->dvb_xfer_bulk ? "bulk" : "isoc");
 	}
 
+	if (dev->board.has_dual_ts && em28xx_duplicate_dev(dev) == 0) {
+		dev->dev_next->ts = SECONDARY_TS;
+		dev->dev_next->alt   = -1;
+		dev->dev_next->is_audio_only = has_vendor_audio &&
+						!(has_video || has_dvb);
+		dev->dev_next->has_video = false;
+		dev->dev_next->ifnum = ifnum;
+		dev->dev_next->model = id->driver_info;
+
+		mutex_init(&dev->dev_next->lock);
+		retval = em28xx_init_dev(dev->dev_next, udev, interface,
+					dev->dev_next->devno);
+		if (retval)
+			goto err_free;
+
+		dev->dev_next->board.ir_codes = NULL; /* No IR for 2nd tuner */
+		dev->dev_next->board.has_ir_i2c = 0; /* No IR for 2nd tuner */
+
+		if (usb_xfer_mode < 0) {
+			if (dev->dev_next->board.is_webcam)
+				try_bulk = 1;
+			else
+				try_bulk = 0;
+		} else {
+			try_bulk = usb_xfer_mode > 0;
+		}
+
+		/* Select USB transfer types to use */
+		if (has_dvb) {
+			if (!dev->dvb_ep_isoc_ts2 ||
+			   (try_bulk && dev->dvb_ep_bulk_ts2))
+				dev->dev_next->dvb_xfer_bulk = 1;
+			printk(KERN_INFO DRIVER_NAME ": dvb ts2 set to %s mode.\n",
+				dev->dev_next->dvb_xfer_bulk ? "bulk" : "isoc");
+		}
+
+		dev->dev_next->dvb_ep_isoc = dev->dvb_ep_isoc_ts2;
+		dev->dev_next->dvb_ep_bulk = dev->dvb_ep_bulk_ts2;
+		dev->dev_next->dvb_max_pkt_size_isoc = dev->dvb_max_pkt_size_isoc_ts2;
+		dev->dev_next->dvb_alt_isoc = dev->dvb_alt_isoc;
+
+		/* Configuare hardware to support TS2*/
+		if (dev->dvb_xfer_bulk) {
+			/* The ep4 and ep5 are configuared for BULK */
+			em28xx_write_reg(dev, 0x0b, 0x96);
+			mdelay(100);
+			em28xx_write_reg(dev, 0x0b, 0x80);
+			mdelay(100);
+		} else {
+			/* The ep4 and ep5 are configuared for ISO */
+			em28xx_write_reg(dev, 0x0b, 0x96);
+			mdelay(100);
+			em28xx_write_reg(dev, 0x0b, 0x82);
+			mdelay(100);
+		}
+
+		kref_init(&dev->dev_next->ref);
+	}
+
 	kref_init(&dev->ref);
 
 	request_modules(dev);
@@ -3605,6 +3715,13 @@ static void em28xx_usb_disconnect(struct
 	if (!dev)
 		return;
 
+	if (dev->dev_next != NULL) {
+		dev->dev_next->disconnected = 1;
+		printk(KERN_INFO DRIVER_NAME ": Disconnecting %s\n",
+			dev->dev_next->name);
+		flush_request_modules(dev->dev_next);
+	}
+
 	dev->disconnected = 1;
 
 	em28xx_info("Disconnecting %s\n", dev->name);
@@ -3613,7 +3730,14 @@ static void em28xx_usb_disconnect(struct
 
 	em28xx_close_extension(dev);
 
+	if (dev->dev_next != NULL)
+		em28xx_release_resources(dev->dev_next);
 	em28xx_release_resources(dev);
+	
+	if (dev->dev_next != NULL) {
+		kref_put(&dev->dev_next->ref, em28xx_free_device);
+		dev->dev_next = NULL;
+	}
 	kref_put(&dev->ref, em28xx_free_device);
 }
 
diff -uprN linux-4.4.x-orig/drivers/media/usb/em28xx/em28xx-core.c linux-4.4.x-new/drivers/media/usb/em28xx/em28xx-core.c
--- linux-4.4.x-orig/drivers/media/usb/em28xx/em28xx-core.c	2017-10-30 17:46:43.000000000 -0400
+++ linux-4.4.x-new/drivers/media/usb/em28xx/em28xx-core.c	2019-05-06 20:55:37.642304096 -0400
@@ -636,10 +636,18 @@ int em28xx_capture_start(struct em28xx *
 	    dev->chip_id == CHIP_ID_EM28174 ||
 	    dev->chip_id == CHIP_ID_EM28178) {
 		/* The Transport Stream Enable Register moved in em2874 */
-		rc = em28xx_write_reg_bits(dev, EM2874_R5F_TS_ENABLE,
-					   start ?
-					       EM2874_TS1_CAPTURE_ENABLE : 0x00,
-					   EM2874_TS1_CAPTURE_ENABLE);
+		if (dev->ts == PRIMARY_TS)
+			rc = em28xx_write_reg_bits(dev,
+				EM2874_R5F_TS_ENABLE,
+				start ?
+				EM2874_TS1_CAPTURE_ENABLE : 0x00,
+				EM2874_TS1_CAPTURE_ENABLE);
+		else
+			rc = em28xx_write_reg_bits(dev,
+				EM2874_R5F_TS_ENABLE,
+				start ?
+				EM2874_TS2_CAPTURE_ENABLE : 0x00,
+				EM2874_TS2_CAPTURE_ENABLE);
 	} else {
 		/* FIXME: which is the best order? */
 		/* video registers are sampled by VREF */
@@ -1073,7 +1081,11 @@ int em28xx_register_extension(struct em2
 	mutex_lock(&em28xx_devlist_mutex);
 	list_add_tail(&ops->next, &em28xx_extension_devlist);
 	list_for_each_entry(dev, &em28xx_devlist, devlist) {
-		ops->init(dev);
+		if (ops->init) {
+			ops->init(dev);
+			if (dev->dev_next != NULL)
+				ops->init(dev->dev_next);
+		}
 	}
 	mutex_unlock(&em28xx_devlist_mutex);
 	printk(KERN_INFO "em28xx: Registered (%s) extension\n", ops->name);
@@ -1087,7 +1099,11 @@ void em28xx_unregister_extension(struct
 
 	mutex_lock(&em28xx_devlist_mutex);
 	list_for_each_entry(dev, &em28xx_devlist, devlist) {
-		ops->fini(dev);
+		if (ops->fini) {
+			if (dev->dev_next != NULL)
+				ops->fini(dev->dev_next);
+			ops->fini(dev);
+		}
 	}
 	list_del(&ops->next);
 	mutex_unlock(&em28xx_devlist_mutex);
@@ -1102,8 +1118,11 @@ void em28xx_init_extension(struct em28xx
 	mutex_lock(&em28xx_devlist_mutex);
 	list_add_tail(&dev->devlist, &em28xx_devlist);
 	list_for_each_entry(ops, &em28xx_extension_devlist, next) {
-		if (ops->init)
+		if (ops->init) {
 			ops->init(dev);
+			if (dev->dev_next != NULL)
+				ops->init(dev->dev_next);
+		}
 	}
 	mutex_unlock(&em28xx_devlist_mutex);
 }
@@ -1114,8 +1133,11 @@ void em28xx_close_extension(struct em28x
 
 	mutex_lock(&em28xx_devlist_mutex);
 	list_for_each_entry(ops, &em28xx_extension_devlist, next) {
-		if (ops->fini)
+		if (ops->fini) {
+			if (dev->dev_next != NULL)
+				ops->fini(dev->dev_next);
 			ops->fini(dev);
+		}
 	}
 	list_del(&dev->devlist);
 	mutex_unlock(&em28xx_devlist_mutex);
@@ -1130,6 +1152,8 @@ int em28xx_suspend_extension(struct em28
 	list_for_each_entry(ops, &em28xx_extension_devlist, next) {
 		if (ops->suspend)
 			ops->suspend(dev);
+		if (dev->dev_next != NULL)
+			ops->suspend(dev->dev_next);
 	}
 	mutex_unlock(&em28xx_devlist_mutex);
 	return 0;
@@ -1144,6 +1168,8 @@ int em28xx_resume_extension(struct em28x
 	list_for_each_entry(ops, &em28xx_extension_devlist, next) {
 		if (ops->resume)
 			ops->resume(dev);
+		if (dev->dev_next != NULL)
+			ops->resume(dev->dev_next);
 	}
 	mutex_unlock(&em28xx_devlist_mutex);
 	return 0;
diff -uprN linux-4.4.x-orig/drivers/media/usb/em28xx/em28xx-dvb.c linux-4.4.x-new/drivers/media/usb/em28xx/em28xx-dvb.c
--- linux-4.4.x-orig/drivers/media/usb/em28xx/em28xx-dvb.c	2019-05-06 20:53:45.600707980 -0400
+++ linux-4.4.x-new/drivers/media/usb/em28xx/em28xx-dvb.c	2019-05-06 20:55:37.646304153 -0400
@@ -212,7 +212,6 @@ static int em28xx_start_streaming(struct
 		dvb_alt = dev->dvb_alt_isoc;
 	}
 
-	usb_set_interface(dev->udev, dev->ifnum, dvb_alt);
 	rc = em28xx_set_mode(dev, EM28XX_DIGITAL_MODE);
 	if (rc < 0)
 		return rc;
@@ -1042,8 +1041,9 @@ static void em28xx_unregister_dvb(struct
 
 static int em28xx_dvb_init(struct em28xx *dev)
 {
-	int result = 0;
+	int result = 0, dvb_alt = 0;
 	struct em28xx_dvb *dvb;
+	/* struct usb_device *udev; */
 
 	if (dev->is_audio_only) {
 		/* Shouldn't initialize IR for this interface */
@@ -1631,7 +1631,10 @@ static int em28xx_dvb_init(struct em28xx
 			si2168_config.ts_mode = SI2168_TS_PARALLEL;
 			memset(&info, 0, sizeof(struct i2c_board_info));
 			strlcpy(info.type, "si2168", I2C_NAME_SIZE);
-			info.addr = 0x64;
+			if (dev->ts == PRIMARY_TS)
+				info.addr = 0x64;
+			else
+				info.addr = 0x67;
 			info.platform_data = &si2168_config;
 			request_module(info.type);
 			client = i2c_new_device(&dev->i2c_adap[dev->def_i2c_bus], &info);
@@ -1654,7 +1657,10 @@ static int em28xx_dvb_init(struct em28xx
 			si2157_config.if_port = 1;
 			memset(&info, 0, sizeof(struct i2c_board_info));
 			strlcpy(info.type, "si2157", I2C_NAME_SIZE);
-			info.addr = 0x60;
+			if (dev->ts == PRIMARY_TS)
+				info.addr = 0x60;
+			else
+				info.addr = 0x63;
 			info.platform_data = &si2157_config;
 			request_module(info.type);
 			client = i2c_new_device(adapter, &info);
@@ -1814,7 +1820,10 @@ static int em28xx_dvb_init(struct em28xx
 			lgdt3306a_config.fe = &dvb->fe[0];
 			lgdt3306a_config.i2c_adapter = &adapter;
 			strlcpy(info.type, "lgdt3306a", sizeof(info.type));
-			info.addr = 0x59;
+			if (dev->ts == PRIMARY_TS)
+				info.addr = 0x59;
+			else
+				info.addr = 0x0e;
 			info.platform_data = &lgdt3306a_config;
 			request_module(info.type);
 			client = i2c_new_device(&dev->i2c_adap[dev->def_i2c_bus],
@@ -1841,7 +1850,10 @@ static int em28xx_dvb_init(struct em28xx
 #endif
 			memset(&info, 0, sizeof(struct i2c_board_info));
 			strlcpy(info.type, "si2157", sizeof(info.type));
-			info.addr = 0x60;
+			if (dev->ts == PRIMARY_TS)
+				info.addr = 0x60;
+			else
+				info.addr = 0x62;
 			info.platform_data = &si2157_config;
 			request_module(info.type);
 
@@ -1884,6 +1896,15 @@ static int em28xx_dvb_init(struct em28xx
 	if (result < 0)
 		goto out_free;
 
+	if (dev->dvb_xfer_bulk) {
+		dvb_alt = 0;
+	} else { /* isoc */
+		dvb_alt = dev->dvb_alt_isoc;
+	}
+
+	/* udev = interface_to_usbdev(dev->intf); */
+	usb_set_interface(dev->udev, dev->ifnum, dvb_alt);
+	/* usb_set_interface(udev, dev->ifnum, dvb_alt); */
 	em28xx_info("DVB extension successfully initialized\n");
 
 	kref_get(&dev->ref);
diff -uprN linux-4.4.x-orig/drivers/media/usb/em28xx/em28xx.h linux-4.4.x-new/drivers/media/usb/em28xx/em28xx.h
--- linux-4.4.x-orig/drivers/media/usb/em28xx/em28xx.h	2019-05-06 20:53:45.600707980 -0400
+++ linux-4.4.x-new/drivers/media/usb/em28xx/em28xx.h	2019-05-06 20:55:37.646304153 -0400
@@ -216,6 +216,9 @@
 /* max. number of button state polling addresses */
 #define EM28XX_NUM_BUTTON_ADDRESSES_MAX		5
 
+#define PRIMARY_TS	0
+#define SECONDARY_TS	1
+
 enum em28xx_mode {
 	EM28XX_SUSPEND,
 	EM28XX_ANALOG_MODE,
@@ -462,6 +465,7 @@ struct em28xx_board {
 	unsigned int mts_firmware:1;
 	unsigned int max_range_640_480:1;
 	unsigned int has_dvb:1;
+	unsigned int has_dual_ts:1;
 	unsigned int is_webcam:1;
 	unsigned int valid:1;
 	unsigned int has_ir_i2c:1;
@@ -612,7 +616,6 @@ struct em28xx {
 	struct em28xx_IR *ir;
 
 	/* generic device properties */
-	char name[30];		/* name (including minor) of the device */
 	int model;		/* index in the device_data struct */
 	int devno;		/* marks the number of this device */
 	enum em28xx_chip_id chip_id;
@@ -623,6 +626,7 @@ struct em28xx {
 	unsigned int is_audio_only:1;
 	enum em28xx_int_audio_type int_audio_type;
 	enum em28xx_usb_audio_type usb_audio_type;
+	unsigned char name[32];
 
 	struct em28xx_board board;
 
@@ -684,6 +688,8 @@ struct em28xx {
 	u8 ifnum;		/* number of the assigned usb interface */
 	u8 analog_ep_isoc;	/* address of isoc endpoint for analog */
 	u8 analog_ep_bulk;	/* address of bulk endpoint for analog */
+	u8 dvb_ep_isoc_ts2;	/* address of isoc endpoint for DVB TS2*/
+	u8 dvb_ep_bulk_ts2;	/* address of bulk endpoint for DVB TS2*/
 	u8 dvb_ep_isoc;		/* address of isoc endpoint for DVB */
 	u8 dvb_ep_bulk;		/* address of bulk endpoint for DVB */
 	int alt;		/* alternate setting */
@@ -697,6 +703,8 @@ struct em28xx {
 	int dvb_alt_isoc;	/* alternate setting for DVB isoc transfers */
 	unsigned int dvb_max_pkt_size_isoc;	/* isoc max packet size of the
 						   selected DVB ep at dvb_alt */
+	unsigned int dvb_max_pkt_size_isoc_ts2;	/* isoc max packet size of the
+						   selected DVB ep at dvb_alt */
 	unsigned int dvb_xfer_bulk:1;		/* use bulk instead of isoc
 						   transfers for DVB          */
 	char urb_buf[URB_MAX_CTRL_SIZE];	/* urb control msg buffer */
@@ -722,6 +730,9 @@ struct em28xx {
 	/* Snapshot button input device */
 	char snapshot_button_path[30];	/* path of the input dev */
 	struct input_dev *sbutton_input_dev;
+
+	struct em28xx   *dev_next;
+	int ts;
 };
 
 #define kref_to_dev(d) container_of(d, struct em28xx, ref)


================================================
FILE: hauppauge.sh
================================================
#!/bin/sh

#-------------------------------------------
# hauppauge.sh
#
# by: Vincent Fortier
# email: th0ma7_AT_gmail.com
#
# Simplify the status, start, stop, reset
# of tvheadend service along with loading
# and unloading of all necessary modules
#-------------------------------------------

SERVICE="pkgctl-tvheadend"
MODULE_PATH=/usr/local/lib/modules/$(uname -r)
MODULES="em28xx_dvb em28xx lgdt3306a si2157 tveeprom v4l2_common dvb_usb rc_core dvb_core videobuf2_vmalloc videobuf2_memops videobuf2_v4l2 videobuf2_common videodev media"
RESET=em28xx_dvb
#
declare -a SYSCTL_VAR=('vm.dirty_expire_centisecs' 'vm.swappiness')
declare -a SYSCTL_VALUE=('300' '1')
#
NOAUTOSUSPEND=em28xx
declare -a AUTOSUSPEND_VAR=('autosuspend_delay_ms' 'autosuspend')
declare -a AUTOSUSPEND_VALUE=('-1000' '-1')

USBAutoSuspend() {
   usbID=$(lsusb -i | grep $NOAUTOSUSPEND | awk '{print $1}' | cut -f1 -d:)
   if [ ! "$usbID" ]; then
      echo "kernel USB (none) autosuspend values  N/A"
   else
      echo "kernel USB ($usbID) autosuspend values..."
      for index in "${!AUTOSUSPEND_VAR[@]}"
      do
         declare sys=/sys/bus/usb/devices/$usbID/power/${AUTOSUSPEND_VAR[$index]}
         declare -i current=$(cat $sys)
         declare -i new=${AUTOSUSPEND_VALUE[$index]}

         printf '\t(%s)%-25s' $usbID ${AUTOSUSPEND_VAR[$index]}

         if [ $current -eq $new ]; then
            printf '[%5s] -> OK\n' "$current"
         else
            [ "$1" = "check" ] || echo ${AUTOSUSPEND_VALUE[$index]} | sudo tee $sys 1>/dev/null
            printf '[%5s] -> [%5s]\n' "$current" "$new"
         fi
     done
   fi
}

Sysctl() {
   echo "kernel sysctl values... "
   for index in "${!SYSCTL_VAR[@]}"
   do
      declare -i current=$(sysctl -n ${SYSCTL_VAR[$index]})
      declare -i new=${SYSCTL_VALUE[$index]}

      printf '\t%-30s' ${SYSCTL_VAR[$index]}
      if [ $current -eq $new ]; then
         printf '[%5s] -> OK\n' "$current"
      else
         [ "$1" = "check" ] || sysctl -w ${SYSCTL_VAR[$index]}=$new 2>/dev/null 1>&2
         printf '[%5s] -> [%5s]\n' "$current" "$new"
      fi
   done
}

ModuleLOAD() {
   echo "Loading kernel modules... "
   for item in $MODULES; do echo $item; done | tac | while read module
   do
      module_load=$(echo "${module}.ko" | sed 's/_/-/g')
      printf '\t%-30s' $module_load
      status=$(lsmod | grep "^$module ")

      if [ $? -eq 0 -a "status" ]; then
         echo "Loaded"
      else
         insmod $MODULE_PATH/$module_load
         [ $? -eq 0 ] && echo "OK" || echo "ERROR"
      fi
   done
}

ModuleUNLOAD() {
   # Unload Hauppauge updated drivers
   echo "Unloading kernel modules... "
   for module in $MODULES
   do
      printf '\t%-30s' $module

      status=$(lsmod | grep "^$module ")
      if [ $? -eq 0 -a "status" ]; then
         rmmod $module
         echo -ne "OK\n"
      else
         echo -ne "N/A\n"
      fi
   done
}

ModuleSTATUS() {
   # Unload Hauppauge updated drivers
   echo "kernel module status... "
   for module in $MODULES
   do
      printf '\t%-30s' $module

      status=$(lsmod | grep "^$module ")
      if [ $? -eq 0 -a "status" ]; then
         echo -ne "OK\n"
      else
         echo -ne "N/A\n"
      fi
   done
}

ModuleRESET() {
   reset=$1
   module=$(echo "${reset}.ko" | sed 's/_/-/g')
   rmmod $reset
   sleep 1
   insmod $MODULE_PATH/$module
}

ServiceSTATUS() {
   status=$(synoservice --status $SERVICE | grep "\[$SERVICE\] status" | awk -F"status=" '{print $2}' | sed -e 's/\[//g' -e 's/\]//g')
   running=$(synoservice --status $SERVICE | grep "\[$SERVICE\] is" | awk -F"is " '{print $2}' | sed -e 's/\.//g' -e 's/ //g')
   pid=$(pidof tvheadend)
   [ ! "$pid" ] && pid="---"

   case "$1" in
      "full" ) printf '%-38s' "Status $SERVICE..."
               echo $status,$running,$pid
               ;;
           * ) echo $running;;
   esac
}

ServiceSTART() {
   status=$(ServiceSTATUS)
   #echo $status 1>&2
   printf '%-38s' "Starting $SERVICE..."

   module=$(lsmod | grep "^$RESET ")
   if [ $? -ne 0 -a ! "module" ]; then
      echo "ERROR module $RESET not found!"
      return
   fi

   if [ "$status" = "stop" ]; then
      synoservice --start $SERVICE
   echo "OK"
   elif [ "$status" = "start" ]; then
      # Check if sevice isn't already started!
      if [ "$(pidof tvheadend)" ]; then
         echo "Started"
      else
         synoservice --restart $SERVICE 2>/dev/null 1>&2
         echo "Restart"
      fi
   else
      echo "N/A"
   fi
}

ServiceSTOP() {
   status=$(ServiceSTATUS)
   #echo $status 1>&2
   printf '%-38s' "Stopping $SERVICE..."
   if [ "$status" = "start" ]; then
      #synoservice --disable $SERVICE
      synoservice --stop $SERVICE 2>/dev/null 1>&2
      echo "OK"
   else
      echo "N/A"
   fi

   # Is it really off?
   if [ "`pidof tvheadend`" ]; then
      printf '%-33s' "Killing $SERVICE..."
      kill -9 $(pidof tvheadend)
      echo "killed"
   fi
}

Usage() {
      echo "$0 - Usage: start, stop, status, reset, restart, load"
}

case $1 in
   start ) ModuleLOAD
           USBAutoSuspend
           Sysctl
           ServiceSTART
           ;;
    stop ) ServiceSTOP
           ModuleUNLOAD
           ;;
  status ) ServiceSTATUS full
           ModuleSTATUS
           USBAutoSuspend check
           Sysctl check
           ;;
 restart ) ServiceSTOP
           ModuleUNLOAD
           sleep 1
           ModuleLOAD
           USBAutoSuspend
           Sysctl
           ServiceSTART
           ;;
   reset ) ServiceSTOP
           ModuleRESET $RESET
           ServiceSTART
           ServiceSTATUS full
           ModuleSTATUS
           ;;
    load ) ModuleLOAD
           USBAutoSuspend
           Sysctl
           ;;
       * ) Usage
           ;;
esac

exit 0


================================================
FILE: kernel/synocli-kernelmodule.sh
================================================
#!/bin/bash

#########################################################################
# Written by: th0ma7@gmail.com
# Part of SynoCommunity Developpers
#
# 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 3 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, see <https://www.gnu.org/licenses/>.
#########################################################################

#------------------------------------------------
# Make sure an argument was passed 
#------------------------------------------------
usage() {
   echo
   printf '%10s %s\n' "Usage :" "$0 [-s|--spk <package>] [<insmod,start|rmmod,stop|reload,restart|status>] module1.ko module2.ko ..."
   printf '%20s %s\n' "Optional :" "[-c|--config <file>:<option1>,<option2>,...]"
   printf '%20s %s\n' "" "[-u|--udev <file>]"
   echo
   printf '%40s %s\n' "[-s|--spk <package>] : " "SynoCommunity package name containing kernel modules"
   printf '%40s %s\n' "[<insmod|rmmod|reload|status>] : " "Action to be performed"
   printf '%40s %s\n' "[-h|--help] : " "Print this help"
   printf '%40s %s\n' "[-v|--verbose] : " "Verbose mode"
   echo
   printf '%10s %s\n' "" "Examples :"
   printf '%20s %s\n' "" "$0 --spk synokernel-cdrom --verbose cdrom sr_mod status"
   printf '%20s %s\n' "" "$0 --spk synokernel-cdrom --config synokernel-cdrom.cfg:default status"
   printf '%20s %s\n' "" "$0 --spk synokernel-usbserial --udev 60-synokernel-usbserial.rules usbserial ch341 cp210x status"
   printf '%20s %s\n' "" "$0 --spk synokernel-usbserial --config synokernel-usbserial.cfg:ch341,cp210x status"
   echo
}

#------------------------------------------------
# Print detailed information for debugging
#------------------------------------------------
verbose() {
   printf '%20s %s\n' "" "$0 (verbose)"
   printf '%60s %s\n' "SynoCommunity kernel driver package name (SPK)" "[${SPK}]"
   printf '%60s %s\n' "SynoCommunity configuration file (SPK_CFG)" "[${SPK_CFG}]"
   printf '%60s %s\n' "SynoCommunity configuration path (SPK_CFG_PATH)" "[${SPK_CFG_PATH}]"
   printf '%60s %s\n' "SynoCommunity configuration option (SPK_CFG_OPT)" "[${SPK_CFG_OPT}]"
   printf '%60s %s\n' "Synology NAS arch (ARCH)" "[${ARCH}]"
   printf '%60s %s\n' "Synology DSM version (DSM_VERSION)" "[${DSM_VERSION}]"
   printf '%60s %s\n' "Running kernel version (KVER)" "[${KVER}]"
   printf '%60s %s\n' "Module action insmod|rmmod|reload|status (ACTION)" "[${ACTION}]"
   printf '%60s %s\n' "Kernel modules path (MPATH)" "[${MPATH}]"
   printf '%60s %s\n' "Full kernel modules path (KPATH)" "[${KPATH}]"
   printf '%60s %s\n' "Device firmware path (FPATH)" "[${FPATH}]"
   printf '%60s %s\n' "udev rules.d path (UPATH)" "[${UPATH}]"
   printf '%60s %s\n' "udev rules.d file (URULE)" "[${URULE}]"
   printf '%60s %s\n' "Kernel objects list (KO_LIST)" "[${KO_LIST}]"
   printf '%60s %s\n' "Kernel objects found (KO_PATH)" "[${KO_PATH}]"
}


#------------------------------------------------
# Get all kernel modules from configuration file
# based on passed configuration option
#------------------------------------------------
get_ko_list() {
   ko_list_cfg=""

   if [ "${SPK_CFG}" ]; then
      # Check that configuration exists (if requested)
      if [ ! -f ${SPK_CFG_PATH}/${SPK_CFG} ]; then
         usage
         echo -ne "\nERROR: Configuration file [${SPK_CFG_PATH}/${SPK_CFG}] does not exist or inaccessible...\n\n"
         exit 1
      fi

      # Always include default first
      ko_list_cfg=$(sed -n "s/^default:\(.*\)/\1/p" ${SPK_CFG_PATH}/${SPK_CFG})
      if [ ! "${ko_list_cfg}" ]; then
         usage
         echo -ne "\nERROR: Configuration option [default] not found in file [${SPK_CFG_PATH}/${SPK_CFG}]...\n\n"
         exit 1
      fi

      IFS=","
      for config in ${SPK_CFG_OPT}
      do
         ko_list_cfg_tmp=$(sed -n "s/^${config}:\(.*\)/\1/p" ${SPK_CFG_PATH}/${SPK_CFG})
         if [ ! "${ko_list_cfg}" ]; then
            usage
            echo -ne "\nERROR: Configuration option [${config}:] not found in file [${SPK_CFG_PATH}/${SPK_CFG}]...\n\n"
            exit 1
         fi
         ko_list_cfg+="${ko_list_cfg_tmp} "
      done
      IFS=" "
   fi

   # Return merged module list from config file and
   # parameters passed as arguments but keep its order,
   # starting with the config file followed by args
   echo ${ko_list_cfg} ${KO_LIST_ARG} | awk '{for (i=1;i<=NF;i++) if (!a[$i]++) printf("%s%s",$i,FS)}{printf("\n")}' | xargs
}


#------------------------------------------------
# Modules can be passed as:
#   <module>
#   <module>.ko
#   /<path>/<module>*
# The following find the right module as needed
#------------------------------------------------
ko_path_match() {
   ko_find=""
   ko_path=""
   ko_missing=""

   for ko in $KO_LIST
   do
      # first check if the name
      # matches to a path
      if [ ! "$(echo $ko | grep '/')" ]; then
         # Ensure to add .ko if needed
         [ ! "$(echo $ko | grep '.ko$')" ] && ko=$ko.ko
         # Replace any '-' or '_' by '[-_]
         ko=$(echo ${ko} | sed -r 's/[-_]/[-_]/g')
         # Find full module kernel object path
         ko_find=$(find $KPATH -name $ko)
         [ ! "$ko_find" ] && ko_missing+="$ko "
      fi
      ko_path+="${ko_find##*${KVER}} "
   done

   # Return missing modules in case of error
   # else return the list of found modules
   [ ! "${ko_missing}" ] \
      && echo "$ko_path" | xargs \
      || echo "missing: ${ko_missing}" | xargs
}

# exit if no parameters passed
[ $# -eq 0 ] && usage && exit 1

# must be root to load/unload kernel modules
if [ ! "$(id -un)" = "root" ]; then
   verbose && usage
   echo
   echo "ERROR: Must have root or sudo priviledges..."
   echo
   exit 1
fi


###
### Global variables
###
SPK=""                                                                 # SynoCommunity kernel driver package name
SPK_CFG=""                                                             # SynoCommunity configuration file
SPK_CFG_OPT=""                                                         # SynoCommunity configuration option
SPK_CFG_PATH=""                                                        # SynoCommunity configuration path
ACTION=""                                                              # Module action insmod|rmmod|reload|status
VERBOSE="FALSE"                                                        # Set verbose mode
HELP="FALSE"                                                           # Print help
URULE="FALSE"                                                          # Set udev rules to false by default

while [ $# -gt 0 ] 
do
   case $1 in
                                        -s|--spk ) shift 1
                                                   SPK=$1;;
                                     -c|--config ) shift 1
                                                   SPK_CFG=$(echo $1 | cut -f1 -d:)
                                                   SPK_CFG_OPT=$(echo $1 | cut -f2 -d:);;
                                       -u|--udev ) shift 1
                                                   URULE=$(echo $1 | cut -f1 -d:);;
                                       -h|--help ) HELP="TRUE";;
   insmod|rmmod|reload|start|stop|restart|status ) ACTION=$1;;
                                    -v|--verbose ) VERBOSE="TRUE";;
                                               * ) KO_LIST_ARG+="$1 ";;
   esac
   shift 1;
done

###
### Other global variables
###
ARCH=$(uname -a | awk '{print $NF}' | cut -f2 -d_)                     # Synology NAS arch
DSM_VERSION=$(sed -n 's/^productversion="\(.*\)"/\1/p' /etc/VERSION)   # Synology DSM version
FPATH_SYS="/sys/module/firmware_class/parameters/path"                 # System module firmware path file index
KVER=$(uname -r | awk -F. '{print $1 "." $2 "." $3}')                  # Running kernel version
FPATH=""                                                               # Device firmware path
KPATH=""                                                               # Full kernel modules path
MPATH=""                                                               # Kernel modules path
KO_LIST=""                                                             # List of kernel objects to enable|disable (includes config+args)
KO_LIST_ARG=$(echo ${KO_LIST_ARG} | xargs)                             # List of kernel objects passed in argument
KO_PATH=""                                                             # List of found kernel objects (*.ko) with path (includes config+args)
SYNOLOG_PATH=/var/log/packages                                         # Default log output file
SYNOLOG=${SYNOLOG_PATH}/synocli-kernelmodule.log                       # Default log output file

# If SPK is set reassign variables
if [ -n "${SPK}" ]; then
   SPK_CFG_PATH="/var/packages/${SPK}/target/etc"
   FPATH="/var/packages/${SPK}/target/lib/firmware"
   MPATH="/var/packages/${SPK}/target/lib/modules"
   UPATH="/var/packages/${SPK}/target/rules.d"
   KPATH="${MPATH}/${ARCH}-${DSM_VERSION}/${KVER}"
   SYNOLOG="${SYNOLOG_PATH}/synocli-kernelmodule-${SPK}.log"
fi

# All output to SYNOLOG, STDOUT to the screen
exec > >(tee -a ${SYNOLOG}) 2> >(tee -a ${SYNOLOG} >/dev/null)


# Get list of kernel objects from
# both the configuration file and
# arguments passed on cmd line
KO_LIST=$(get_ko_list)

# Find resulting kernel object (*.ko) full path
KO_PATH=$(ko_path_match)

# exit if modules are missing/not found
if [ "$(echo $KO_PATH | cut -f1 -d:)" = "missing" ]; then
   verbose && usage
   echo
   echo "ERROR: Missing kernel modules: [$(echo $KO_PATH | cut -f2 -d: | xargs)]"
   echo
   exit 1
fi

# If verbose is set print default arguments
[ ${VERBOSE} = "TRUE" ] && verbose

[ ${HELP} = "TRUE" ] && usage && exit 0

# If module path does not exists, exit
if [ ! -d ${MPATH} ]; then
   usage
   echo -ne "\nERROR: Module path [${MPATH}] does not exist or inaccessible...\n\n"
   exit 1
fi

# Set kernel module .ko object base path
# If does not exists, exit
if [ ! -d ${KPATH} ]; then
   usage
   echo -ne "\nERROR: Kernel modules base path [${KPATH}] does not exist or inaccessible...\n\n"
   exit 1
fi

# Check that udev rules file exist
if [ ! ${URULE} "FALSE" -a ! -f ${UPATH}/${URULE} ]; then
   usage
   echo -ne "\nERROR: udev rules.d file [${UPATH}/${URULE}] does not exist or inaccessible...\n\n"
   exit 1
fi

# load the requested modules
load ()
{
   error=0

   if [ "{URULE}" ]; then
      echo -ne "\t[enable] optional udev rules...\n"
      printf '%40s %-34s' "" "[$URULE]"
      ln -s ${UPATH}/${URULE} /lib/udev/rules.d/${URULE}
      udevadm control --reload-rules
      if [ $? -eq 0 ]; then
         echo -ne " OK\n"
      else
         error=1
         echo -ne " N/A\n"
      fi
   fi

   # Add firmware path to running kernel
   if [ -d "${FPATH}" ]; then
      echo -ne "\t[loading] optional firmware path...\n"
      printf '%65s' "[${FPATH}]"
      echo "${FPATH}" > ${FPATH_SYS}
      if [ $? -eq 0 ]; then
         echo -ne " OK\n"
      else
         error=1
         echo -ne " N/A\n"
      fi
   fi

   echo -ne "\t[insmod] kernel modules...\n"
   for ko in $KO_PATH
   do
      module=$(echo "${ko}" | sed -e 's/.*\///' -e 's/-/_/' -e 's/\.ko//')
      printf '%40s %-35s' $ko "[$module]"

      status=$(lsmod | grep "^$module ")
      if [ $? -eq 0 -a "status" ]; then
         echo "Already Loaded"
      else
         if [ -f $KPATH/$ko ]; then
            insmod $KPATH/$ko
            [ $? -eq 0 ] && echo "OK" || echo "ERROR"
         else
            echo "ERROR: Module $KPATH/$ko not found!"
            error=1
         fi
      fi
   done

   return $error
}


# unload the requested modules in a reversed order
unload ()
{
   error=0

   # Unload drivers in reverse order
   echo -ne "\t[rmmod] kernel modules...\n"
   for item in $KO_PATH; do echo $item; done | tac | while read ko
   do
      module=$(echo "${ko}" | sed -e 's/.*\///' -e 's/-/_/g' -e 's/\.ko//')
      printf '%40s %-35s' $ko "[$module]"
      status=$(lsmod | grep "^$module ")
      if [ $? -eq 0 -a "status" ]; then
         rmmod $module
         echo -ne "N/A\n"
      else
         echo -ne "ERROR\n"
         error=1
      fi
   done

   # Remove firmware path to running kernel
   if [ -d "${FPATH}" ]; then
      echo -ne "\t[unloading] optional firmware path...\n"
      echo "" > ${FPATH_SYS}
      error=$?
   fi

   # Remove udev rules
   if [ "{URULE}" ]; then
      echo -ne "\t[remove] optional udev rules...\n"
      printf '%40s %-34s' "" "[$URULE]"
      rm -f /lib/udev/rules.d/${URULE}
      udevadm control --reload-rules
      if [ $? -eq 0 ]; then
         echo -ne " N/A\n"
      else
         error=1
         echo -ne " ERROR\n"
      fi
   fi

   return $error
}


# Provide a status of the loaded modules
status ()
{
   error=0

   echo -ne "\t[status] kernel modules...\n"
   for ko in $KO_PATH
   do
      module=$(echo "${ko}" | sed -e 's/.*\///' -e 's/-/_/g' -e 's/\.ko//')
      printf '%40s %-35s' $ko "[$module]"

      status=$(lsmod | grep "^$module ")
      if [ $? -eq 0 -a "status" ]; then
         echo -ne "OK\n"
      else
         error=1
         echo -ne "N/A\n"
      fi
   done

   # Validate option firmware path
   if [ -d "${FPATH}" ]; then
      echo -ne "\t[status] of optional firmware path...\n"
	  printf '%65s' "[${FPATH}]"

      grep -q ${FPATH} ${FPATH_SYS}
      if [ $? -eq 0 ]; then
         echo -ne " OK\n"
      else
         error=1
         echo -ne " N/A\n"
      fi
   fi

   # Validate udev rules (not much can be done)
   if [ "{URULE}" ]; then
      echo -ne "\t[status] of optional udev rules...\n"
      printf '%40s %-34s' "" "[$URULE]"
      if [ -h /lib/udev/rules.d/${URULE} ]; then
         echo -ne " OK\n"
      else
         error=1
         echo -ne " N/A\n"
      fi
   fi

   return $error
}


case $ACTION in
    insmod|start)
       if status; then
           echo ${SPK} is already running
           exit 0
       else
           echo Starting ${SPK} ...
           load
           exit $?
       fi
       ;;
    rmmod|stop)
       if status; then
           echo Stopping ${SPK} ...
           unload
           exit $?
       else
           echo ${SPK} is not running
           exit 0
       fi
       ;;
    reload|restart)
       echo -ne "---\nReloading ${SPK} package...\n"
       unload
       load
       exit $?
       ;;
    status)
       echo -ne "---\nStatus of ${SPK} package...\n"
       if status; then
           echo ${SPK} is running
           exit 0
       else
           echo ${SPK} is not running
           exit 1
       fi
       ;;
    *) usage
       exit 1
       ;;
esac


================================================
FILE: tvheadend-backup.sh
================================================
#!/bin/bash

DEST=/volume1/backup/tvheadend
DATE=`date +%Y%m%d-%H%M`

tar -C /var/packages/tvheadend/target -jcvf - \
    --exclude='.lock' \
    --exclude='tvheadend.pid' \
    cache var \
    bin/tv_grab_file bin/zap2xml.pl bin/zap2xml.sh perl5 .cpan .xmltv \
    share/tvheadend/data/dvb-scan/atsc/ca-QC-saint-jean-sur-richelieu \
    > $DEST/tvheadend-backup-$DATE.tar.bz2
Download .txt
gitextract_iqeu8csl/

├── README-old.md
├── README.md
├── hauppauge/
│   ├── 001-Hauppauge955D-lgdt3306a-v3.patch
│   ├── 002-Hauppauge955D-em28xx-Tuner1.patch
│   ├── 003-Hauppauge955D-em28xx-Tuner2-v6.patch
│   └── hauppauge955D-SYNOApollolake-DSM622_24922-Kernel_4.4.59-20190520.tar.bz2
├── hauppauge.sh
├── kernel/
│   └── synocli-kernelmodule.sh
└── tvheadend-backup.sh
Condensed preview — 9 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (93K chars).
[
  {
    "path": "README-old.md",
    "chars": 12333,
    "preview": "# synology - th0ma7\nSynology personnal hack, info, tools &amp; source code\n\nDonnations welcomed at: `0x522d164549E68681d"
  },
  {
    "path": "README.md",
    "chars": 13221,
    "preview": "# synology - th0ma7\nSynology personnal hack, info, tools &amp; source code\n\nDonnations welcomed at: `0x522d164549E68681d"
  },
  {
    "path": "hauppauge/001-Hauppauge955D-lgdt3306a-v3.patch",
    "chars": 13518,
    "preview": "diff -uprN linux-4.4.x-orig/drivers/media/dvb-frontends/lgdt3306a.c linux-4.4.x-new/drivers/media/dvb-frontends/lgdt3306"
  },
  {
    "path": "hauppauge/002-Hauppauge955D-em28xx-Tuner1.patch",
    "chars": 11858,
    "preview": "diff -uprN linux-4.4.x-orig/Documentation/video4linux/CARDLIST.em28xx linux-4.4.x-new/Documentation/video4linux/CARDLIST"
  },
  {
    "path": "hauppauge/003-Hauppauge955D-em28xx-Tuner2-v6.patch",
    "chars": 14967,
    "preview": "diff -uprN linux-4.4.x-orig/drivers/media/usb/em28xx/em28xx-cards.c linux-4.4.x-new/drivers/media/usb/em28xx/em28xx-card"
  },
  {
    "path": "hauppauge.sh",
    "chars": 5737,
    "preview": "#!/bin/sh\n\n#-------------------------------------------\n# hauppauge.sh\n#\n# by: Vincent Fortier\n# email: th0ma7_AT_gmail."
  },
  {
    "path": "kernel/synocli-kernelmodule.sh",
    "chars": 15088,
    "preview": "#!/bin/bash\n\n#########################################################################\n# Written by: th0ma7@gmail.com\n# "
  },
  {
    "path": "tvheadend-backup.sh",
    "chars": 377,
    "preview": "#!/bin/bash\n\nDEST=/volume1/backup/tvheadend\nDATE=`date +%Y%m%d-%H%M`\n\ntar -C /var/packages/tvheadend/target -jcvf - \\\n  "
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the th0ma7/synology GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 9 files (85.1 KB), approximately 30.1k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!