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 ---> 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 & 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@ ``` 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@:~/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 +#include #include #include "dvb_math.h" #include "lgdt3306a.h" +#include 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 "); 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 #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 . ######################################################################### #------------------------------------------------ # Make sure an argument was passed #------------------------------------------------ usage() { echo printf '%10s %s\n' "Usage :" "$0 [-s|--spk ] [] module1.ko module2.ko ..." printf '%20s %s\n' "Optional :" "[-c|--config :,,...]" printf '%20s %s\n' "" "[-u|--udev ]" echo printf '%40s %s\n' "[-s|--spk ] : " "SynoCommunity package name containing kernel modules" printf '%40s %s\n' "[] : " "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: # # .ko # //* # 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