Alternative Linux driver for the Conexant CX2388x series video decoder/encoder chips, configured to capture raw 8-bit or 16-bit unsigned samples (28β54 MHz) from video input ports. Enables low-cost ADC use for SDR and similar applications.
Warning
- Secure Boot must be disabled before driver installation
- Do not load both
cxadcand the regularcx88driver simultaneously - Kernel updates require driver reinstallation unless DKMS is configured
[!NOTE]
- Compatible: CX2388x, CX25800
- Incompatible: CX23885-xx, CX23888-xx
- See the upstream reference wiki for hardware variants, modifications, and cabling
- CX25800 (White cards): Lowest self-noise; recommended for best results
- Stock crystal: 28.6 MHz (28.636 MSPS); outputs ~14 MHz bandwidth
- Upgrades:
ABLS2-40.000MHZ-D4YF-Tcrystal replacement for 40 MHz capability; see upstream crystal upgrade list (reference) - Cooling: Important for 40β54 MHz crystals; keep β€10Β°C above ambient
- MSPS terminology: When 28 MHz is specified, effective bandwidth is 14 MHz (Nyquist: 2:1 ratio)
- System resources: Lower-end systems (Pentium 4 era) may drop samples; SoX/FLAC encoding in real-time requires adequate resources
- PCIe bridge: Asmedia chips may have issues on older Intel PCH (3rd gen); ITE chips are more reliable
- Purchasing: White CX25800 cards (~16β30 USD) from upstream hardware/purchasing notes (reference)
- Repository: Primary source for this project
- Upstream Wiki (reference): Card variants, modifications, and cabling
- Utils README: Scripted tools for configuration and monitoring
- Clockgen README: Synchronized multi-card clock generator and audio ADC overview
- Clockgen BUILDING: Firmware build and flashing instructions
For current GDH capture rigs, CX cards and clockgen devices are primarily used through:
- GDH-Technologies/digitization-toolkit: capture orchestration GUI and operator workflow tooling
- GDH-Technologies/capture-node: hardware-owning capture service and session lifecycle control
Integration surfaces are configuration-first:
Standalone driver usage remains supported for direct sysfs/CLI workflows.
This repository also contains the clockgen firmware and helper scripts in clockgen/. Use this when you run synchronized multi-card capture rigs and want shared sample clocks plus auxiliary linear audio capture.
- Firmware sources: clockgen/firmware/
- Build and flashing guide: clockgen/BUILD.md
Hardware design and mechanical files are maintained upstream:
src/kernel/ kernel module sources (cxadc.c, cx88-reg.h, Kbuild)
src/tools/common/ shared userland library (sysfs helpers, analyser, clockgen)
src/tools/leveladj/, levelmon/, cx-capture/, cxadc-status/
src/scripts/ convenience shell scripts (cxvalues, cxfreq, ...)
config/ modprobe / udev / systemd files
clockgen/ clock generator firmware and scripts
build/ all userland build output (git-ignored)
Kernel build artifacts are produced under src/kernel/ (git-ignored); the
resulting cxadc.ko is copied to build/ for convenience.
Disable Secure Boot before proceeding. Check status:
mokutil --sb-stateInstall dependencies:
sudo dnf install -y dkms gcc make kernel-devel-$(uname -r) kernel-headers rsyncOptional:
sudo dnf install -y ffmpeg sox pv flacIf you prefer a one-time build without DKMS:
cd cxadc
make # builds the kernel module and userland tools
sudo make install-module # runs modules_install + depmod
sudo make install # installs userland tools + helper scripts
sudo cp config/cxadc.conf /etc/modprobe.d/
sudo cp config/cxadc.rules /etc/udev/rules.d/Note: Manual builds require reinstallation after every kernel update.
To change module parameters without sudo, add your user to the video group:
sudo usermod -a -G video $USERLog out and back in for the change to take effect.
If you prefer a dedicated cxadc group instead, use the commented
alternative rules in /etc/udev/rules.d/cxadc.rules, then:
sudo groupadd -f cxadc
sudo usermod -a -G cxadc $USERAfter changing udev rules, reload and trigger:
sudo udevadm control --reload-rules
sudo udevadm trigger -c add -s cxadcModule parameters control driver behavior. Most can be changed at runtime via sysfs without reloading the driver.
Change a parameter:
echo <value> >/sys/class/cxadc/cxadc0/device/parameters/<param>Multi-card usage: Replace cxadc0 with cxadc1, cxadc2, etc.
Persistent configuration: Edit /etc/modprobe.d/cxadc.conf to set parameters at boot, or edit /etc/udev/rules.d/cxadc.rules for device-specific settings.
Note
Use cxvalues (from src/scripts) to display current configuration at any time.
Most cxadc userland tools and helper scripts now accept all of the following device inputs:
- canonical class names:
cxadc0,cxadc1, ... - numeric shorthand:
0,1, ... - absolute device nodes:
/dev/cxadc0 - absolute aliases:
/dev/cx/vcr0-video - bare aliases discovered under
/dev/cx:vcr0-video
Inputs are resolved back to the canonical class device (cxadcN) before sysfs
parameter writes. Use cxresolve to inspect how an input resolves.
Selects which video input to capture. Most TV cards have:
- 0: Tuner
- 1: Composite (BNC)
- 2: S-Video
- 3: Alternative input (varies by card)
Find the correct input:
# Attach a live signal and change vmux until you see a response
for i in 0 1 2 3; do
echo "Testing vmux=$i..."
echo $i >/sys/class/cxadc/cxadc0/device/parameters/vmux
sudo ffplay -hide_banner -async 1 -f rawvideo -pixel_format gray8 \
-video_size 1832x625 -i /dev/cxadc0 -vf scale=1135x625,eq=gamma=0.5:contrast=1.5 &
sleep 2
pkill ffplay
doneSee upstream Card Types Wiki (reference) for optimal connections.
For TV cards with external audio selection (e.g., PixelView PlayTV Pro Ultra):
- 0: Tuner TV audio
- 1: Silence
- 2: FM stereo tuner
- 3: Audio input to output
1: Enabled (default; +6 dB gain)0: Disabled (cleaner signal; may require external amplifier)
Use case: Disable for raw CVBS or with external AD4857 (upstream reference) amplifiers.
Applies fixed gain (0β31) to the input signal. Adjust to minimize clipping.
Use the leveladj tool to auto-adjust:
leveladj # For device 0
leveladj -d cxadc1 # For device 1
leveladj -d vcr0-videoManually adjust DC centering of the RF signal.
Check centering: If highest and lowest values are equidistant from 0 and 255, the signal is centered.
leveladjExample output:
low 121 high 133 clipped 0 nsamp 2097152
Analysis: 121 + 133 = 254 β centered.
Sets the ADC sampling rate based on crystal frequency.
Stock 28.6 MHz crystal:
0: 28.6 MHz 8-bit (default)1: 35.8 MHz 8-bit (1.25Γ upsampled)2β99: Treated as MHz (e.g.,20= 20 MSPS)- Custom: Enter exact frequency (e.g.,
14318181)
Constraints:
- Minimum:
(HW crystal / 40) Γ 14 - Maximum:
(HW crystal / 8) Γ 10
For 40 MHz crystal:
- Minimum: ~14 MSPS
- Maximum: ~50 MSPS
Tip
For 40 MHz 8-bit or 20 MHz 16-bit reliable operation, use upstream crystal replacement notes (reference) or upstream clock gen modification notes (reference).
0: 8-bit (8Γ Fsc, raw unsigned data)1: 16-bit (4Γ Fsc, filtered VBI data; downsampled 50%)
Practical rates (stock 28.6 MHz crystal):
- 8-bit: 28.6 MHz
- 16-bit: 14.3 MHz
Specifies the XTAL crystal frequency in Hz on your CX card. Used only to compute custom tenxfsc rates. Common values:
- Stock: 28636363 (28.6 MHz)
- Upgraded: 40000000 (40 MHz)
- High-end: 54000000 (54 MHz, requires cooling)
PCI latency timer. Rarely needs adjustment; use default unless experiencing buffer underruns.
Create a video preview to confirm signal reception. Depending on the RF signal type, expect either unstable video or a white flash.
PAL (28.6 MHz, 8-bit):
sudo ffplay -hide_banner -async 1 -f rawvideo -pixel_format gray8 \
-video_size 1832x625 -i /dev/cxadc0 \
-vf scale=1135x625,eq=gamma=0.5:contrast=1.5NTSC (28.6 MHz, 8-bit):
sudo ffplay -hide_banner -async 1 -f rawvideo -pixel_format gray8 \
-video_size 1820x525 -i /dev/cxadc0 \
-vf scale=910x525,eq=gamma=0.5:contrast=1.5cxadc-status prints a summary of the driver, every connected card and its
current sysfs parameters, which process (if any) is using each card, and any
detected clock generator. It is strictly read-only β it never opens
/dev/cxadcN β so it is safe to run during an active capture.
cxadc-status # rich table summary
cxadc-status --plain # ASCII fallback (no rich dependency required)
cxadc-status --json # machine-readable output
cxadc-status --watch 1 # refresh every secondTip
Rich output uses the optional rich Python package if installed; otherwise a
plain coloured/ASCII layout is used automatically.
leveladj # Device 0
leveladj -d cxadc1 # Device 1, etc.
leveladj -d /dev/cx/vcr0-videolevelmon -d cxadc0Output is printed every 0.25 seconds. Example:
lo |0| [ 3.906%] ( 33.429%) center -0.54% hi ( 65.764%) [ 95.312%] |0| nsamp 10000000 rate 44.58
^ clipped% neg avg offset pos avg clipped% MSPSFor device 1: levelmon -d cxadc1
For aliases: levelmon -d vcr0-video
Set internal and digital gain without sudo (after adding user to video group):
# Internal gain (0β31)
echo 10 >/sys/class/cxadc/cxadc0/device/parameters/level
# Digital gain boost (1=on, 0=off)
echo 0 >/sys/class/cxadc/cxadc0/device/parameters/sixdbcx-capture wraps device configuration and reading in one command, with optional
live fan-out to several destinations at once and optional continuous gain/DC
auto-adjustment. It reads the device with large sequential reads; the file and
--stdout sinks are lossless, while --tcp, --listen and --fifo are
best-effort monitor sinks that drop data under backpressure so a slow monitor can
never stall (or overrun) the capture.
# Capture 10 seconds to a file
cx-capture -d cxadc0 -t 10 -o capture.u8
# Same capture via alias path or bare alias
cx-capture -d /dev/cx/vcr0-video -t 10 -o capture.u8
cx-capture -d vcr0-video -t 10 -o capture.u8
# Capture to a file AND serve a live monitor over TCP (e.g. a waveform viewer)
cx-capture -d cxadc0 -o capture.u8 --listen 9977
# then, elsewhere: nc localhost 9977 | your-visualiser
# Pipe losslessly into a decoder while also writing a file
cx-capture -d cxadc0 -o capture.u8 --stdout | your-decoder
# Continuously auto-adjust gain and DC offset during capture (opt-in)
cx-capture -d cxadc0 -t 30 --auto_adjust -o capture.u8
# Set a clockgen frequency for this capture
cx-capture -d cxadc0 --clock 40MHz -t 10 -o capture.u8Run cx-capture --help for the full option list (device parameters such as
--vmux, --level, --sixdb, --tenbit, --center-offset can be applied
inline).
Note
--auto_adjust changes the gain during the capture, which introduces a level
step in the recorded stream. It is ideal for live monitoring and CAV
LaserDisc captures, but for archival masters prefer a one-time leveladj
before capture. It is disabled by default.
The driver counts ring overruns: events where the 64 MB DMA ring lapped
a reader that fell more than a full ring (~2.2 s at 28.6 MSPS) behind, which
silently overwrites unread samples. The per-device counter is exposed at
/sys/class/cxadc/cxadcN/device/parameters/overrun_count (read-only, reset
each time the device is opened). cx-capture reads it after every capture,
prints it in the final summary, and exits with code 3 when it is
non-zero β a capture that exits 0 is provably sample-continuous.
timeout 10s cat /dev/cxadc0 | pv > capture.u8Options:
- Duration: Replace
10swith5s,1m,2h, etc. - Ctrl+C: Stop capture manually
- pv: Shows data rate and elapsed time (optional; remove if not installed)
- Extensions: Use
.u8(8-bit) or.u16(16-bit) for proper codec detection
cat /dev/cxadc0 | pv > capture.u8cat /dev/cxadc0 | sox -r 28636363 -t u8 -c 1 - -r 44100 -t u8 output.u8Note
Requires FLAC 1.5.0+ for real-time multi-threaded encoding 40β60% file size reduction vs. raw 8-bit or 16-bit
8-bit (stock 28.6 MSPS):
cat /dev/cxadc0 | flac --threads=64 -6 \
--sample-rate=28636 --sign=unsigned --channels=1 \
--endian=little --bps=8 --blocksize=65535 --lax -f - \
-o capture-28msps-8bit.flac16-bit (stock 17.8 MSPS):
cat /dev/cxadc0 | flac --threads=64 -6 \
--sample-rate=17898 --sign=unsigned --channels=1 \
--endian=little --bps=16 --blocksize=65535 --lax -f - \
-o capture-17.8msps-16bit.flacFor multiple cards, use /dev/cxadc0, /dev/cxadc1, etc., or host-defined
aliases under /dev/cx, and adjust parameters per card:
# Configure card 1
echo 2 >/sys/class/cxadc/cxadc1/device/parameters/vmux
echo 20 >/sys/class/cxadc/cxadc1/device/parameters/level
# Capture from both simultaneously
cat /dev/cxadc0 | pv > card0.u8 &
cat /dev/cxadc1 | pv > card1.u8 &
waitNote
Each card has a separate set of entries in /etc/udev/rules.d/cxadc.rules for persistent settings.
Warning
Secure Boot is the most common cause of driver load failures, especially with PCIe devices.
Check status:
mokutil --sb-stateDisable Secure Boot:
- Reboot into BIOS/UEFI setup (usually F2, F10, or Del during boot)
- Find Secure Boot setting (usually under "Security" or "Boot")
- Set to Disabled
- Save and exit
Note
If using DKMS, the driver automatically rebuilds on kernel update. Manual installs require reinstallation.
Current DKMS package version: 1.2
If driver stops loading after kernel update:
-
Check if DKMS is active:
dkms status | grep cxadcYou want the current version, and only the current version, listed as
installedfor every kernel you can boot. Two things to look for:- An older version still listed. Superseded versions are not removed
automatically by DKMS, and with
AUTOINSTALL="YES"every new kernel builds each registered version. The deploy pipeline prunes these (PRUNE_OLD_DKMS_VERSIONS, default on); see INSTALL.md Β§3 to do it by hand. - A kernel missing the current version. Autoinstall covers newly
installed kernels, but never back-fills kernels that predate a version
bump β so a fallback kernel can silently keep an older driver, or none at
all.
DKMS_INSTALL_ALL_KERNELS(default on) back-fills them.
- An older version still listed. Superseded versions are not removed
automatically by DKMS, and with
-
If DKMS is installed: Rebuild automatically or manually:
dkms_name="$(awk -F= '/^PACKAGE_NAME=/{gsub(/"/,"",$2); print $2}' dkms.conf)" dkms_version="$(awk -F= '/^PACKAGE_VERSION=/{gsub(/"/,"",$2); print $2}' dkms.conf)" sudo rsync -a --delete --exclude '.git' --exclude 'build' ./ "/usr/src/${dkms_name}-${dkms_version}/" sudo dkms remove -m "${dkms_name}" -v "${dkms_version}" --all || true sudo dkms add "/usr/src/${dkms_name}-${dkms_version}" sudo dkms build -m "${dkms_name}" -v "${dkms_version}" -k "$(uname -r)" sudo dkms install -m "${dkms_name}" -v "${dkms_version}" -k "$(uname -r)" --force sudo depmod -a
-
If manual installation: Reinstall the driver:
cd cxadc make clean make sudo make modules_install sudo depmod -a
Expand: "CONFIG_X86_X32 enabled but no binutils support" error
This error appears during make or module signing and indicates a kernel/toolchain mismatch:
arch/x86/Makefile:142: CONFIG_X86_X32 enabled but no binutils support
SIGN /lib/modules/.../extra/cxadc.ko
DEPMOD ...
Warning: modules_install: missing 'System.map' file. Skipping depmod.Solution 1: Install binutils
sudo apt install binutils # Debian/Ubuntu
sudo dnf install binutils # FedoraSolution 2: Use Xanmod kernel (verified working)
Xanmod kernels have been tested as a reliable workaround:
# Fedora
sudo dnf copr enable rmnscnce/xanmod
sudo dnf install kernel-xanmod kernel-xanmod-devel
# Then reboot into Xanmod kernel and rebuildThe warning about System.map is non-fatal; the module still loads successfully.
If /dev/cxadc0 doesn't exist after loading the driver:
-
Verify the module loaded:
lsmod | grep cxadc -
Check kernel logs:
dmesg | grep cxadc -
Verify card is detected:
lspci | grep -i "cx"
-
Reload udev rules:
sudo udevadm control --reload-rules sudo modprobe -r cxadc sudo modprobe cxadc ls -l /dev/cxadc*
Since 1.1 the driver verifies a card answers MMIO before attaching to it, and
refuses one that does not:
cxadc 0000:03:00.0: card is not answering mmio reads (all-ones); refusing to attach -- the chip is faulted or dead
The card still appears in lspci and still gets a BAR β its PCIe-to-PCI bridge
is fine β but the CX chip behind it has stopped answering memory reads, so no
/dev/cxadcN is created for it. Other healthy cards in the same machine are
unaffected and keep their device nodes.
Important
This refusal is deliberate and you should not try to work around it. Arming the DMA engine against such a card raises a fatal PCIe AER error that resets the root port; at boot that hangs the machine during udev coldplug, before anything can be logged, which looks exactly like a POST failure.
Confirming the diagnosis. A healthy card exposes two PCI functions; a faulted one is usually missing the IR port function:
lspci -nn | grep -i conexant
# healthy: 03:00.0 ... [14f1:8800] and 03:00.4 ... [14f1:8804]
# faulted: 03:00.0 ... [14f1:8800] with no .4 entryCheck for the fatal error signature from an earlier boot, if the card had attached before:
journalctl -k -b -1 | grep -Ei "aer|cxadc"A card in this state is not recoverable in software β replace it. Common causes are power-rail damage from a miswired clockgen mod, or ESD.
Since 1.2 a card can be unbound while the module stays loaded β via
echo 1 > /sys/bus/pci/devices/<addr>/remove, PCIe hot-remove, or a
dkms-driven rebind β without leaving the driver holding freed memory:
- If nothing has the device open, the node disappears immediately.
- If a capture is running, the unbind blocks until the reader closes. The
reader receives whatever was already copied as a short read, then
EIOon the nextread();cx-capturereports the read error and exits1. Nothing the driver owns is freed while a file is open. - After
echo 1 > /sys/bus/pci/rescanthe card returns as the samecxadcN(minors are handed out lowest-free-first), so udev aliases keyed on the node name keep working. - Opening a node that belongs to a removed card returns
ENXIO(orENODEVduring the brief unbind window).
modprobe -r cxadc is unchanged: it refuses while any /dev/cxadcN is open.
If you see "Permission denied" or cannot read /dev/cxadc0:
-
Add user to
videogroup:sudo usermod -a -G video $USER -
Log out and back in, then retry:
cat /dev/cxadc0 | pv | head -c 100K > /dev/null
-
Check device permissions:
ls -l /dev/cxadc* # Should show: crw-rw---- root video
Note
Module parameters reset on reboot unless configured persistently.
Method 1: Edit /etc/modprobe.d/cxadc.conf
# Example persistent configuration
options cxadc vmux=2 level=16 sixdb=1 crystal=28636363Method 2: Device-specific settings via udev
Edit /etc/udev/rules.d/cxadc.rules to apply settings per card on plugin. See file for examples.
Method 3: Apply at runtime (resets on reboot)
echo 2 >/sys/class/cxadc/cxadc0/device/parameters/vmux
echo 16 >/sys/class/cxadc/cxadc0/device/parameters/levelVersion history and development timeline:
Originally written by Hew How Chee (how_chee@yahoo.com). See SDR using a CX2388x TV+FM card for original project.
- I2C support for tuning via
i2c.c - Optimized register settings for reduced gain and centered signal (β128)
- Added
vmuxandaudselruntime parameters
- Kernel 2.6.18 (Fedora Core 6) support
- Code cleanup and simplification
Retargeted for Ubuntu 13.10 / Linux 3.11 by Chad Page
- Standard
read()semantics for data capture (no external capture program needed) - 64-bit Linux support
- SMP (multi-processor) compatibility improvements
- Linux 5.1+ compatibility
- Code style improvements per kernel
checkpatchstandards - Optional
audselparameter - Single-open enforcement (prevent multiple
/dev/cxadcopens) - AGC register reset on unload (enables cx88/cxadc switching without reboot)
By Harry Munday
- Corrected sample format documentation: 8-bit and 16-bit (not 10-bit)
- Module parameter examples and real-time monitoring
ABLS2-40.000MHZ-D4YF-Tcrystal replacement documentationsixdbmode (gain boost) configuration guide- Updated hardware purchasing links
- Crystal upgrade compatibility list
- Fixed
leveladjparameter reset bug - Command-line utility scripts
- Additional level adjustment tools
- Dedicated utils README
cxlevel: Real-time level monitoringcxfreq: Frequency control utilitycxvalues: Display current driver configurationfortycryst: 40 MHz crystal mode control- High/low gain warning messages
- Documentation cleanup
By Adam R
- Support for up to 256 cards per system
- Per-card configuration (vmux, level, etc.)
- Updated scripts for multi-card operations
Clockgen Mod (upstream reference) established
- Software-defined sample rates: 20/28.6/40/50 MSPS modes
- Shared synchronized clock for multi-card setups
- Raspberry Pi 4 & 5 support by Alistair Buxton
Windows Port established by JuniorIsAJitterbug
- Multi-threaded FLAC encoding support
- Enables real-time compressed RF archival capture
levelmon: Real-time level and clipping monitor- Linux 6.12+ compatibility