diff options
author | 2023-02-21 18:24:12 -0800 | |
---|---|---|
committer | 2023-02-21 18:24:12 -0800 | |
commit | 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch) | |
tree | cc5c2d0a898769fd59549594fedb3ee6f84e59a0 /drivers/media/usb/cx231xx/cx231xx-dvb.c | |
download | linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.tar.gz linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.zip |
Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextgrafted
Pull networking updates from Jakub Kicinski:
"Core:
- Add dedicated kmem_cache for typical/small skb->head, avoid having
to access struct page at kfree time, and improve memory use.
- Introduce sysctl to set default RPS configuration for new netdevs.
- Define Netlink protocol specification format which can be used to
describe messages used by each family and auto-generate parsers.
Add tools for generating kernel data structures and uAPI headers.
- Expose all net/core sysctls inside netns.
- Remove 4s sleep in netpoll if carrier is instantly detected on
boot.
- Add configurable limit of MDB entries per port, and port-vlan.
- Continue populating drop reasons throughout the stack.
- Retire a handful of legacy Qdiscs and classifiers.
Protocols:
- Support IPv4 big TCP (TSO frames larger than 64kB).
- Add IP_LOCAL_PORT_RANGE socket option, to control local port range
on socket by socket basis.
- Track and report in procfs number of MPTCP sockets used.
- Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path
manager.
- IPv6: don't check net.ipv6.route.max_size and rely on garbage
collection to free memory (similarly to IPv4).
- Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986).
- ICMP: add per-rate limit counters.
- Add support for user scanning requests in ieee802154.
- Remove static WEP support.
- Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate
reporting.
- WiFi 7 EHT channel puncturing support (client & AP).
BPF:
- Add a rbtree data structure following the "next-gen data structure"
precedent set by recently added linked list, that is, by using
kfunc + kptr instead of adding a new BPF map type.
- Expose XDP hints via kfuncs with initial support for RX hash and
timestamp metadata.
- Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to
better support decap on GRE tunnel devices not operating in collect
metadata.
- Improve x86 JIT's codegen for PROBE_MEM runtime error checks.
- Remove the need for trace_printk_lock for bpf_trace_printk and
bpf_trace_vprintk helpers.
- Extend libbpf's bpf_tracing.h support for tracing arguments of
kprobes/uprobes and syscall as a special case.
- Significantly reduce the search time for module symbols by
livepatch and BPF.
- Enable cpumasks to be used as kptrs, which is useful for tracing
programs tracking which tasks end up running on which CPUs in
different time intervals.
- Add support for BPF trampoline on s390x and riscv64.
- Add capability to export the XDP features supported by the NIC.
- Add __bpf_kfunc tag for marking kernel functions as kfuncs.
- Add cgroup.memory=nobpf kernel parameter option to disable BPF
memory accounting for container environments.
Netfilter:
- Remove the CLUSTERIP target. It has been marked as obsolete for
years, and we still have WARN splats wrt races of the out-of-band
/proc interface installed by this target.
- Add 'destroy' commands to nf_tables. They are identical to the
existing 'delete' commands, but do not return an error if the
referenced object (set, chain, rule...) did not exist.
Driver API:
- Improve cpumask_local_spread() locality to help NICs set the right
IRQ affinity on AMD platforms.
- Separate C22 and C45 MDIO bus transactions more clearly.
- Introduce new DCB table to control DSCP rewrite on egress.
- Support configuration of Physical Layer Collision Avoidance (PLCA)
Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of
shared medium Ethernet.
- Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing
preemption of low priority frames by high priority frames.
- Add support for controlling MACSec offload using netlink SET.
- Rework devlink instance refcounts to allow registration and
de-registration under the instance lock. Split the code into
multiple files, drop some of the unnecessarily granular locks and
factor out common parts of netlink operation handling.
- Add TX frame aggregation parameters (for USB drivers).
- Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning
messages with notifications for debug.
- Allow offloading of UDP NEW connections via act_ct.
- Add support for per action HW stats in TC.
- Support hardware miss to TC action (continue processing in SW from
a specific point in the action chain).
- Warn if old Wireless Extension user space interface is used with
modern cfg80211/mac80211 drivers. Do not support Wireless
Extensions for Wi-Fi 7 devices at all. Everyone should switch to
using nl80211 interface instead.
- Improve the CAN bit timing configuration. Use extack to return
error messages directly to user space, update the SJW handling,
including the definition of a new default value that will benefit
CAN-FD controllers, by increasing their oscillator tolerance.
New hardware / drivers:
- Ethernet:
- nVidia BlueField-3 support (control traffic driver)
- Ethernet support for imx93 SoCs
- Motorcomm yt8531 gigabit Ethernet PHY
- onsemi NCN26000 10BASE-T1S PHY (with support for PLCA)
- Microchip LAN8841 PHY (incl. cable diagnostics and PTP)
- Amlogic gxl MDIO mux
- WiFi:
- RealTek RTL8188EU (rtl8xxxu)
- Qualcomm Wi-Fi 7 devices (ath12k)
- CAN:
- Renesas R-Car V4H
Drivers:
- Bluetooth:
- Set Per Platform Antenna Gain (PPAG) for Intel controllers.
- Ethernet NICs:
- Intel (1G, igc):
- support TSN / Qbv / packet scheduling features of i226 model
- Intel (100G, ice):
- use GNSS subsystem instead of TTY
- multi-buffer XDP support
- extend support for GPIO pins to E823 devices
- nVidia/Mellanox:
- update the shared buffer configuration on PFC commands
- implement PTP adjphase function for HW offset control
- TC support for Geneve and GRE with VF tunnel offload
- more efficient crypto key management method
- multi-port eswitch support
- Netronome/Corigine:
- add DCB IEEE support
- support IPsec offloading for NFP3800
- Freescale/NXP (enetc):
- support XDP_REDIRECT for XDP non-linear buffers
- improve reconfig, avoid link flap and waiting for idle
- support MAC Merge layer
- Other NICs:
- sfc/ef100: add basic devlink support for ef100
- ionic: rx_push mode operation (writing descriptors via MMIO)
- bnxt: use the auxiliary bus abstraction for RDMA
- r8169: disable ASPM and reset bus in case of tx timeout
- cpsw: support QSGMII mode for J721e CPSW9G
- cpts: support pulse-per-second output
- ngbe: add an mdio bus driver
- usbnet: optimize usbnet_bh() by avoiding unnecessary queuing
- r8152: handle devices with FW with NCM support
- amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation
- virtio-net: support multi buffer XDP
- virtio/vsock: replace virtio_vsock_pkt with sk_buff
- tsnep: XDP support
- Ethernet high-speed switches:
- nVidia/Mellanox (mlxsw):
- add support for latency TLV (in FW control messages)
- Microchip (sparx5):
- separate explicit and implicit traffic forwarding rules, make
the implicit rules always active
- add support for egress DSCP rewrite
- IS0 VCAP support (Ingress Classification)
- IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS
etc.)
- ES2 VCAP support (Egress Access Control)
- support for Per-Stream Filtering and Policing (802.1Q,
8.6.5.1)
- Ethernet embedded switches:
- Marvell (mv88e6xxx):
- add MAB (port auth) offload support
- enable PTP receive for mv88e6390
- NXP (ocelot):
- support MAC Merge layer
- support for the the vsc7512 internal copper phys
- Microchip:
- lan9303: convert to PHYLINK
- lan966x: support TC flower filter statistics
- lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x
- lan937x: support Credit Based Shaper configuration
- ksz9477: support Energy Efficient Ethernet
- other:
- qca8k: convert to regmap read/write API, use bulk operations
- rswitch: Improve TX timestamp accuracy
- Intel WiFi (iwlwifi):
- EHT (Wi-Fi 7) rate reporting
- STEP equalizer support: transfer some STEP (connection to radio
on platforms with integrated wifi) related parameters from the
BIOS to the firmware.
- Qualcomm 802.11ax WiFi (ath11k):
- IPQ5018 support
- Fine Timing Measurement (FTM) responder role support
- channel 177 support
- MediaTek WiFi (mt76):
- per-PHY LED support
- mt7996: EHT (Wi-Fi 7) support
- Wireless Ethernet Dispatch (WED) reset support
- switch to using page pool allocator
- RealTek WiFi (rtw89):
- support new version of Bluetooth co-existance
- Mobile:
- rmnet: support TX aggregation"
* tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits)
page_pool: add a comment explaining the fragment counter usage
net: ethtool: fix __ethtool_dev_mm_supported() implementation
ethtool: pse-pd: Fix double word in comments
xsk: add linux/vmalloc.h to xsk.c
sefltests: netdevsim: wait for devlink instance after netns removal
selftest: fib_tests: Always cleanup before exit
net/mlx5e: Align IPsec ASO result memory to be as required by hardware
net/mlx5e: TC, Set CT miss to the specific ct action instance
net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG
net/mlx5: Refactor tc miss handling to a single function
net/mlx5: Kconfig: Make tc offload depend on tc skb extension
net/sched: flower: Support hardware miss to tc action
net/sched: flower: Move filter handle initialization earlier
net/sched: cls_api: Support hardware miss to tc action
net/sched: Rename user cookie and act cookie
sfc: fix builds without CONFIG_RTC_LIB
sfc: clean up some inconsistent indentings
net/mlx4_en: Introduce flexible array to silence overflow warning
net: lan966x: Fix possible deadlock inside PTP
net/ulp: Remove redundant ->clone() test in inet_clone_ulp().
...
Diffstat (limited to 'drivers/media/usb/cx231xx/cx231xx-dvb.c')
-rw-r--r-- | drivers/media/usb/cx231xx/cx231xx-dvb.c | 1175 |
1 files changed, 1175 insertions, 0 deletions
diff --git a/drivers/media/usb/cx231xx/cx231xx-dvb.c b/drivers/media/usb/cx231xx/cx231xx-dvb.c new file mode 100644 index 000000000..0037b4b13 --- /dev/null +++ b/drivers/media/usb/cx231xx/cx231xx-dvb.c @@ -0,0 +1,1175 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + DVB device driver for cx231xx + + Copyright (C) 2008 <srinivasa.deevi at conexant dot com> + Based on em28xx driver + + */ + +#include "cx231xx.h" +#include <linux/kernel.h> +#include <linux/slab.h> + +#include <media/dvbdev.h> +#include <media/dmxdev.h> +#include <media/dvb_demux.h> +#include <media/dvb_net.h> +#include <media/dvb_frontend.h> +#include <media/v4l2-common.h> +#include <media/tuner.h> + +#include "xc5000.h" +#include "s5h1432.h" +#include "tda18271.h" +#include "s5h1411.h" +#include "lgdt3305.h" +#include "si2165.h" +#include "si2168.h" +#include "mb86a20s.h" +#include "si2157.h" +#include "lgdt3306a.h" +#include "r820t.h" +#include "mn88473.h" + +MODULE_DESCRIPTION("driver for cx231xx based DVB cards"); +MODULE_AUTHOR("Srinivasa Deevi <srinivasa.deevi@conexant.com>"); +MODULE_LICENSE("GPL"); + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "enable debug messages [dvb]"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +#define CX231XX_DVB_NUM_BUFS 5 +#define CX231XX_DVB_MAX_PACKETSIZE 564 +#define CX231XX_DVB_MAX_PACKETS 64 +#define CX231XX_DVB_MAX_FRONTENDS 2 + +struct cx231xx_dvb { + struct dvb_frontend *frontend[CX231XX_DVB_MAX_FRONTENDS]; + + /* feed count management */ + struct mutex lock; + int nfeeds; + + /* general boilerplate stuff */ + struct dvb_adapter adapter; + struct dvb_demux demux; + struct dmxdev dmxdev; + struct dmx_frontend fe_hw; + struct dmx_frontend fe_mem; + struct dvb_net net; + struct i2c_client *i2c_client_demod[2]; + struct i2c_client *i2c_client_tuner; +}; + +static struct s5h1432_config dvico_s5h1432_config = { + .output_mode = S5H1432_SERIAL_OUTPUT, + .gpio = S5H1432_GPIO_ON, + .qam_if = S5H1432_IF_4000, + .vsb_if = S5H1432_IF_4000, + .inversion = S5H1432_INVERSION_OFF, + .status_mode = S5H1432_DEMODLOCKING, + .mpeg_timing = S5H1432_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, +}; + +static struct tda18271_std_map cnxt_rde253s_tda18271_std_map = { + .dvbt_6 = { .if_freq = 4000, .agc_mode = 3, .std = 4, + .if_lvl = 1, .rfagc_top = 0x37, }, + .dvbt_7 = { .if_freq = 4000, .agc_mode = 3, .std = 5, + .if_lvl = 1, .rfagc_top = 0x37, }, + .dvbt_8 = { .if_freq = 4000, .agc_mode = 3, .std = 6, + .if_lvl = 1, .rfagc_top = 0x37, }, +}; + +static struct tda18271_std_map mb86a20s_tda18271_config = { + .dvbt_6 = { .if_freq = 4000, .agc_mode = 3, .std = 4, + .if_lvl = 0, .rfagc_top = 0x37, }, +}; + +static struct tda18271_config cnxt_rde253s_tunerconfig = { + .std_map = &cnxt_rde253s_tda18271_std_map, + .gate = TDA18271_GATE_ANALOG, +}; + +static struct s5h1411_config tda18271_s5h1411_config = { + .output_mode = S5H1411_SERIAL_OUTPUT, + .gpio = S5H1411_GPIO_OFF, + .vsb_if = S5H1411_IF_3250, + .qam_if = S5H1411_IF_4000, + .inversion = S5H1411_INVERSION_ON, + .status_mode = S5H1411_DEMODLOCKING, + .mpeg_timing = S5H1411_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, +}; +static struct s5h1411_config xc5000_s5h1411_config = { + .output_mode = S5H1411_SERIAL_OUTPUT, + .gpio = S5H1411_GPIO_OFF, + .vsb_if = S5H1411_IF_3250, + .qam_if = S5H1411_IF_3250, + .inversion = S5H1411_INVERSION_OFF, + .status_mode = S5H1411_DEMODLOCKING, + .mpeg_timing = S5H1411_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, +}; + +static struct lgdt3305_config hcw_lgdt3305_config = { + .i2c_addr = 0x0e, + .mpeg_mode = LGDT3305_MPEG_SERIAL, + .tpclk_edge = LGDT3305_TPCLK_FALLING_EDGE, + .tpvalid_polarity = LGDT3305_TP_VALID_HIGH, + .deny_i2c_rptr = 1, + .spectral_inversion = 1, + .qam_if_khz = 4000, + .vsb_if_khz = 3250, +}; + +static struct tda18271_std_map hauppauge_tda18271_std_map = { + .atsc_6 = { .if_freq = 3250, .agc_mode = 3, .std = 4, + .if_lvl = 1, .rfagc_top = 0x58, }, + .qam_6 = { .if_freq = 4000, .agc_mode = 3, .std = 5, + .if_lvl = 1, .rfagc_top = 0x58, }, +}; + +static struct tda18271_config hcw_tda18271_config = { + .std_map = &hauppauge_tda18271_std_map, + .gate = TDA18271_GATE_DIGITAL, +}; + +static const struct mb86a20s_config pv_mb86a20s_config = { + .demod_address = 0x10, + .is_serial = true, +}; + +static struct tda18271_config pv_tda18271_config = { + .std_map = &mb86a20s_tda18271_config, + .gate = TDA18271_GATE_DIGITAL, + .small_i2c = TDA18271_03_BYTE_CHUNK_INIT, +}; + +static const struct lgdt3306a_config hauppauge_955q_lgdt3306a_config = { + .qam_if_khz = 4000, + .vsb_if_khz = 3250, + .spectral_inversion = 1, + .mpeg_mode = LGDT3306A_MPEG_SERIAL, + .tpclk_edge = LGDT3306A_TPCLK_RISING_EDGE, + .tpvalid_polarity = LGDT3306A_TP_VALID_HIGH, + .xtalMHz = 25, +}; + +static struct r820t_config astrometa_t2hybrid_r820t_config = { + .i2c_addr = 0x3a, /* 0x74 >> 1 */ + .xtal = 16000000, + .rafael_chip = CHIP_R828D, + .max_i2c_msg_len = 2, +}; + +static inline void print_err_status(struct cx231xx *dev, int packet, int status) +{ + char *errmsg = "Unknown"; + + switch (status) { + case -ENOENT: + errmsg = "unlinked synchronously"; + break; + case -ECONNRESET: + errmsg = "unlinked asynchronously"; + break; + case -ENOSR: + errmsg = "Buffer error (overrun)"; + break; + case -EPIPE: + errmsg = "Stalled (device not responding)"; + break; + case -EOVERFLOW: + errmsg = "Babble (bad cable?)"; + break; + case -EPROTO: + errmsg = "Bit-stuff error (bad cable?)"; + break; + case -EILSEQ: + errmsg = "CRC/Timeout (could be anything)"; + break; + case -ETIME: + errmsg = "Device does not respond"; + break; + } + if (packet < 0) { + dev_dbg(dev->dev, + "URB status %d [%s].\n", status, errmsg); + } else { + dev_dbg(dev->dev, + "URB packet %d, status %d [%s].\n", + packet, status, errmsg); + } +} + +static inline int dvb_isoc_copy(struct cx231xx *dev, struct urb *urb) +{ + int i; + + if (!dev) + return 0; + + if (dev->state & DEV_DISCONNECTED) + return 0; + + if (urb->status < 0) { + print_err_status(dev, -1, urb->status); + if (urb->status == -ENOENT) + return 0; + } + + for (i = 0; i < urb->number_of_packets; i++) { + int status = urb->iso_frame_desc[i].status; + + if (status < 0) { + print_err_status(dev, i, status); + if (urb->iso_frame_desc[i].status != -EPROTO) + continue; + } + + dvb_dmx_swfilter(&dev->dvb->demux, + urb->transfer_buffer + + urb->iso_frame_desc[i].offset, + urb->iso_frame_desc[i].actual_length); + } + + return 0; +} + +static inline int dvb_bulk_copy(struct cx231xx *dev, struct urb *urb) +{ + if (!dev) + return 0; + + if (dev->state & DEV_DISCONNECTED) + return 0; + + if (urb->status < 0) { + print_err_status(dev, -1, urb->status); + if (urb->status == -ENOENT) + return 0; + } + + /* Feed the transport payload into the kernel demux */ + dvb_dmx_swfilter(&dev->dvb->demux, + urb->transfer_buffer, urb->actual_length); + + return 0; +} + +static int start_streaming(struct cx231xx_dvb *dvb) +{ + int rc; + struct cx231xx *dev = dvb->adapter.priv; + + if (dev->USE_ISO) { + dev_dbg(dev->dev, "DVB transfer mode is ISO.\n"); + cx231xx_set_alt_setting(dev, INDEX_TS1, 5); + rc = cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE); + if (rc < 0) + return rc; + dev->mode_tv = 1; + return cx231xx_init_isoc(dev, CX231XX_DVB_MAX_PACKETS, + CX231XX_DVB_NUM_BUFS, + dev->ts1_mode.max_pkt_size, + dvb_isoc_copy); + } else { + dev_dbg(dev->dev, "DVB transfer mode is BULK.\n"); + cx231xx_set_alt_setting(dev, INDEX_TS1, 0); + rc = cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE); + if (rc < 0) + return rc; + dev->mode_tv = 1; + return cx231xx_init_bulk(dev, CX231XX_DVB_MAX_PACKETS, + CX231XX_DVB_NUM_BUFS, + dev->ts1_mode.max_pkt_size, + dvb_bulk_copy); + } + +} + +static int stop_streaming(struct cx231xx_dvb *dvb) +{ + struct cx231xx *dev = dvb->adapter.priv; + + if (dev->USE_ISO) + cx231xx_uninit_isoc(dev); + else + cx231xx_uninit_bulk(dev); + + cx231xx_set_mode(dev, CX231XX_SUSPEND); + + return 0; +} + +static int start_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct cx231xx_dvb *dvb = demux->priv; + int rc, ret; + + if (!demux->dmx.frontend) + return -EINVAL; + + mutex_lock(&dvb->lock); + dvb->nfeeds++; + rc = dvb->nfeeds; + + if (dvb->nfeeds == 1) { + ret = start_streaming(dvb); + if (ret < 0) + rc = ret; + } + + mutex_unlock(&dvb->lock); + return rc; +} + +static int stop_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct cx231xx_dvb *dvb = demux->priv; + int err = 0; + + mutex_lock(&dvb->lock); + dvb->nfeeds--; + + if (0 == dvb->nfeeds) + err = stop_streaming(dvb); + + mutex_unlock(&dvb->lock); + return err; +} + +/* ------------------------------------------------------------------ */ +static int cx231xx_dvb_bus_ctrl(struct dvb_frontend *fe, int acquire) +{ + struct cx231xx *dev = fe->dvb->priv; + + if (acquire) + return cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE); + else + return cx231xx_set_mode(dev, CX231XX_SUSPEND); +} + +/* ------------------------------------------------------------------ */ + +static struct xc5000_config cnxt_rde250_tunerconfig = { + .i2c_address = 0x61, + .if_khz = 4000, +}; +static struct xc5000_config cnxt_rdu250_tunerconfig = { + .i2c_address = 0x61, + .if_khz = 3250, +}; + +/* ------------------------------------------------------------------ */ +#if 0 +static int attach_xc5000(u8 addr, struct cx231xx *dev) +{ + + struct dvb_frontend *fe; + struct xc5000_config cfg; + + memset(&cfg, 0, sizeof(cfg)); + cfg.i2c_adap = cx231xx_get_i2c_adap(dev, dev->board.tuner_i2c_master); + cfg.i2c_addr = addr; + + if (!dev->dvb->frontend[0]) { + dev_err(dev->dev, "%s/2: dvb frontend not attached. Can't attach xc5000\n", + dev->name); + return -EINVAL; + } + + fe = dvb_attach(xc5000_attach, dev->dvb->frontend[0], &cfg); + if (!fe) { + dev_err(dev->dev, "%s/2: xc5000 attach failed\n", dev->name); + dvb_frontend_detach(dev->dvb->frontend[0]); + dev->dvb->frontend[0] = NULL; + return -EINVAL; + } + + dev_info(dev->dev, "%s/2: xc5000 attached\n", dev->name); + + return 0; +} +#endif + +int cx231xx_set_analog_freq(struct cx231xx *dev, u32 freq) +{ + if (dev->dvb && dev->dvb->frontend[0]) { + + struct dvb_tuner_ops *dops = &dev->dvb->frontend[0]->ops.tuner_ops; + + if (dops->set_analog_params != NULL) { + struct analog_parameters params; + + params.frequency = freq; + params.std = dev->norm; + params.mode = 0; /* 0- Air; 1 - cable */ + /*params.audmode = ; */ + + /* Set the analog parameters to set the frequency */ + dops->set_analog_params(dev->dvb->frontend[0], ¶ms); + } + + } + + return 0; +} + +int cx231xx_reset_analog_tuner(struct cx231xx *dev) +{ + int status = 0; + + if (dev->dvb && dev->dvb->frontend[0]) { + + struct dvb_tuner_ops *dops = &dev->dvb->frontend[0]->ops.tuner_ops; + + if (dops->init != NULL && !dev->xc_fw_load_done) { + + dev_dbg(dev->dev, + "Reloading firmware for XC5000\n"); + status = dops->init(dev->dvb->frontend[0]); + if (status == 0) { + dev->xc_fw_load_done = 1; + dev_dbg(dev->dev, + "XC5000 firmware download completed\n"); + } else { + dev->xc_fw_load_done = 0; + dev_dbg(dev->dev, + "XC5000 firmware download failed !!!\n"); + } + } + + } + + return status; +} + +/* ------------------------------------------------------------------ */ + +static int register_dvb(struct cx231xx_dvb *dvb, + struct module *module, + struct cx231xx *dev, struct device *device) +{ + int result; + + mutex_init(&dvb->lock); + + + /* register adapter */ + result = dvb_register_adapter(&dvb->adapter, dev->name, module, device, + adapter_nr); + if (result < 0) { + dev_warn(dev->dev, + "%s: dvb_register_adapter failed (errno = %d)\n", + dev->name, result); + goto fail_adapter; + } + dvb_register_media_controller(&dvb->adapter, dev->media_dev); + + /* Ensure all frontends negotiate bus access */ + dvb->frontend[0]->ops.ts_bus_ctrl = cx231xx_dvb_bus_ctrl; + if (dvb->frontend[1]) + dvb->frontend[1]->ops.ts_bus_ctrl = cx231xx_dvb_bus_ctrl; + + dvb->adapter.priv = dev; + + /* register frontend */ + result = dvb_register_frontend(&dvb->adapter, dvb->frontend[0]); + if (result < 0) { + dev_warn(dev->dev, + "%s: dvb_register_frontend failed (errno = %d)\n", + dev->name, result); + goto fail_frontend0; + } + + if (dvb->frontend[1]) { + result = dvb_register_frontend(&dvb->adapter, dvb->frontend[1]); + if (result < 0) { + dev_warn(dev->dev, + "%s: 2nd dvb_register_frontend failed (errno = %d)\n", + dev->name, result); + goto fail_frontend1; + } + + /* MFE lock */ + dvb->adapter.mfe_shared = 1; + } + + /* register demux stuff */ + dvb->demux.dmx.capabilities = + DMX_TS_FILTERING | DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING; + dvb->demux.priv = dvb; + dvb->demux.filternum = 256; + dvb->demux.feednum = 256; + dvb->demux.start_feed = start_feed; + dvb->demux.stop_feed = stop_feed; + + result = dvb_dmx_init(&dvb->demux); + if (result < 0) { + dev_warn(dev->dev, + "%s: dvb_dmx_init failed (errno = %d)\n", + dev->name, result); + goto fail_dmx; + } + + dvb->dmxdev.filternum = 256; + dvb->dmxdev.demux = &dvb->demux.dmx; + dvb->dmxdev.capabilities = 0; + result = dvb_dmxdev_init(&dvb->dmxdev, &dvb->adapter); + if (result < 0) { + dev_warn(dev->dev, + "%s: dvb_dmxdev_init failed (errno = %d)\n", + dev->name, result); + goto fail_dmxdev; + } + + dvb->fe_hw.source = DMX_FRONTEND_0; + result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_hw); + if (result < 0) { + dev_warn(dev->dev, + "%s: add_frontend failed (DMX_FRONTEND_0, errno = %d)\n", + dev->name, result); + goto fail_fe_hw; + } + + dvb->fe_mem.source = DMX_MEMORY_FE; + result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_mem); + if (result < 0) { + dev_warn(dev->dev, + "%s: add_frontend failed (DMX_MEMORY_FE, errno = %d)\n", + dev->name, result); + goto fail_fe_mem; + } + + result = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx, &dvb->fe_hw); + if (result < 0) { + dev_warn(dev->dev, + "%s: connect_frontend failed (errno = %d)\n", + dev->name, result); + goto fail_fe_conn; + } + + /* register network adapter */ + dvb_net_init(&dvb->adapter, &dvb->net, &dvb->demux.dmx); + result = dvb_create_media_graph(&dvb->adapter, + dev->tuner_type == TUNER_ABSENT); + if (result < 0) + goto fail_create_graph; + + return 0; + +fail_create_graph: + dvb_net_release(&dvb->net); +fail_fe_conn: + dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem); +fail_fe_mem: + dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw); +fail_fe_hw: + dvb_dmxdev_release(&dvb->dmxdev); +fail_dmxdev: + dvb_dmx_release(&dvb->demux); +fail_dmx: + if (dvb->frontend[1]) + dvb_unregister_frontend(dvb->frontend[1]); + dvb_unregister_frontend(dvb->frontend[0]); +fail_frontend1: + if (dvb->frontend[1]) + dvb_frontend_detach(dvb->frontend[1]); +fail_frontend0: + dvb_frontend_detach(dvb->frontend[0]); + dvb_unregister_adapter(&dvb->adapter); +fail_adapter: + return result; +} + +static void unregister_dvb(struct cx231xx_dvb *dvb) +{ + dvb_net_release(&dvb->net); + dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem); + dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw); + dvb_dmxdev_release(&dvb->dmxdev); + dvb_dmx_release(&dvb->demux); + if (dvb->frontend[1]) + dvb_unregister_frontend(dvb->frontend[1]); + dvb_unregister_frontend(dvb->frontend[0]); + if (dvb->frontend[1]) + dvb_frontend_detach(dvb->frontend[1]); + dvb_frontend_detach(dvb->frontend[0]); + dvb_unregister_adapter(&dvb->adapter); + + /* remove I2C tuner */ + dvb_module_release(dvb->i2c_client_tuner); + dvb->i2c_client_tuner = NULL; + /* remove I2C demod(s) */ + dvb_module_release(dvb->i2c_client_demod[1]); + dvb->i2c_client_demod[1] = NULL; + dvb_module_release(dvb->i2c_client_demod[0]); + dvb->i2c_client_demod[0] = NULL; +} + +static int dvb_init(struct cx231xx *dev) +{ + int result; + struct cx231xx_dvb *dvb; + struct i2c_adapter *tuner_i2c; + struct i2c_adapter *demod_i2c; + struct i2c_client *client; + struct i2c_adapter *adapter; + + if (!dev->board.has_dvb) { + /* This device does not support the extension */ + return 0; + } + + dvb = kzalloc(sizeof(struct cx231xx_dvb), GFP_KERNEL); + + if (dvb == NULL) { + dev_info(dev->dev, + "cx231xx_dvb: memory allocation failed\n"); + return -ENOMEM; + } + dev->dvb = dvb; + dev->cx231xx_set_analog_freq = cx231xx_set_analog_freq; + dev->cx231xx_reset_analog_tuner = cx231xx_reset_analog_tuner; + + tuner_i2c = cx231xx_get_i2c_adap(dev, dev->board.tuner_i2c_master); + demod_i2c = cx231xx_get_i2c_adap(dev, dev->board.demod_i2c_master); + mutex_lock(&dev->lock); + cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE); + cx231xx_demod_reset(dev); + /* init frontend */ + switch (dev->model) { + case CX231XX_BOARD_CNXT_CARRAERA: + case CX231XX_BOARD_CNXT_RDE_250: + + dev->dvb->frontend[0] = dvb_attach(s5h1432_attach, + &dvico_s5h1432_config, + demod_i2c); + + if (!dev->dvb->frontend[0]) { + dev_err(dev->dev, + "Failed to attach s5h1432 front end\n"); + result = -EINVAL; + goto out_free; + } + + /* define general-purpose callback pointer */ + dvb->frontend[0]->callback = cx231xx_tuner_callback; + + if (!dvb_attach(xc5000_attach, dev->dvb->frontend[0], + tuner_i2c, + &cnxt_rde250_tunerconfig)) { + result = -EINVAL; + goto out_free; + } + + break; + case CX231XX_BOARD_CNXT_SHELBY: + case CX231XX_BOARD_CNXT_RDU_250: + + dev->dvb->frontend[0] = dvb_attach(s5h1411_attach, + &xc5000_s5h1411_config, + demod_i2c); + + if (!dev->dvb->frontend[0]) { + dev_err(dev->dev, + "Failed to attach s5h1411 front end\n"); + result = -EINVAL; + goto out_free; + } + + /* define general-purpose callback pointer */ + dvb->frontend[0]->callback = cx231xx_tuner_callback; + + if (!dvb_attach(xc5000_attach, dev->dvb->frontend[0], + tuner_i2c, + &cnxt_rdu250_tunerconfig)) { + result = -EINVAL; + goto out_free; + } + break; + case CX231XX_BOARD_CNXT_RDE_253S: + + dev->dvb->frontend[0] = dvb_attach(s5h1432_attach, + &dvico_s5h1432_config, + demod_i2c); + + if (!dev->dvb->frontend[0]) { + dev_err(dev->dev, + "Failed to attach s5h1432 front end\n"); + result = -EINVAL; + goto out_free; + } + + /* define general-purpose callback pointer */ + dvb->frontend[0]->callback = cx231xx_tuner_callback; + + if (!dvb_attach(tda18271_attach, dev->dvb->frontend[0], + dev->board.tuner_addr, tuner_i2c, + &cnxt_rde253s_tunerconfig)) { + result = -EINVAL; + goto out_free; + } + break; + case CX231XX_BOARD_CNXT_RDU_253S: + case CX231XX_BOARD_KWORLD_UB445_USB_HYBRID: + + dev->dvb->frontend[0] = dvb_attach(s5h1411_attach, + &tda18271_s5h1411_config, + demod_i2c); + + if (!dev->dvb->frontend[0]) { + dev_err(dev->dev, + "Failed to attach s5h1411 front end\n"); + result = -EINVAL; + goto out_free; + } + + /* define general-purpose callback pointer */ + dvb->frontend[0]->callback = cx231xx_tuner_callback; + + if (!dvb_attach(tda18271_attach, dev->dvb->frontend[0], + dev->board.tuner_addr, tuner_i2c, + &cnxt_rde253s_tunerconfig)) { + result = -EINVAL; + goto out_free; + } + break; + case CX231XX_BOARD_HAUPPAUGE_EXETER: + + dev_info(dev->dev, + "%s: looking for tuner / demod on i2c bus: %d\n", + __func__, i2c_adapter_id(tuner_i2c)); + + dev->dvb->frontend[0] = dvb_attach(lgdt3305_attach, + &hcw_lgdt3305_config, + demod_i2c); + + if (!dev->dvb->frontend[0]) { + dev_err(dev->dev, + "Failed to attach LG3305 front end\n"); + result = -EINVAL; + goto out_free; + } + + /* define general-purpose callback pointer */ + dvb->frontend[0]->callback = cx231xx_tuner_callback; + + dvb_attach(tda18271_attach, dev->dvb->frontend[0], + dev->board.tuner_addr, tuner_i2c, + &hcw_tda18271_config); + break; + + case CX231XX_BOARD_HAUPPAUGE_930C_HD_1113xx: + { + struct si2165_platform_data si2165_pdata = {}; + + /* attach demod */ + si2165_pdata.fe = &dev->dvb->frontend[0]; + si2165_pdata.chip_mode = SI2165_MODE_PLL_XTAL; + si2165_pdata.ref_freq_hz = 16000000; + + /* perform probe/init/attach */ + client = dvb_module_probe("si2165", NULL, demod_i2c, + dev->board.demod_addr, + &si2165_pdata); + if (!client) { + result = -ENODEV; + goto out_free; + } + dvb->i2c_client_demod[0] = client; + + dev->dvb->frontend[0]->ops.i2c_gate_ctrl = NULL; + + /* define general-purpose callback pointer */ + dvb->frontend[0]->callback = cx231xx_tuner_callback; + + dvb_attach(tda18271_attach, dev->dvb->frontend[0], + dev->board.tuner_addr, tuner_i2c, + &hcw_tda18271_config); + + dev->cx231xx_reset_analog_tuner = NULL; + break; + } + case CX231XX_BOARD_HAUPPAUGE_930C_HD_1114xx: + { + struct si2165_platform_data si2165_pdata = {}; + struct si2157_config si2157_config = {}; + + /* attach demod */ + si2165_pdata.fe = &dev->dvb->frontend[0]; + si2165_pdata.chip_mode = SI2165_MODE_PLL_EXT; + si2165_pdata.ref_freq_hz = 24000000; + + /* perform probe/init/attach */ + client = dvb_module_probe("si2165", NULL, demod_i2c, + dev->board.demod_addr, + &si2165_pdata); + if (!client) { + result = -ENODEV; + goto out_free; + } + dvb->i2c_client_demod[0] = client; + + dev->dvb->frontend[0]->ops.i2c_gate_ctrl = NULL; + + /* define general-purpose callback pointer */ + dvb->frontend[0]->callback = cx231xx_tuner_callback; + + /* attach tuner */ + si2157_config.fe = dev->dvb->frontend[0]; +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + si2157_config.mdev = dev->media_dev; +#endif + si2157_config.if_port = 1; + si2157_config.inversion = true; + + /* perform probe/init/attach */ + client = dvb_module_probe("si2157", NULL, tuner_i2c, + dev->board.tuner_addr, + &si2157_config); + if (!client) { + result = -ENODEV; + goto out_free; + } + dev->cx231xx_reset_analog_tuner = NULL; + + dev->dvb->i2c_client_tuner = client; + break; + } + case CX231XX_BOARD_HAUPPAUGE_955Q: + { + struct si2157_config si2157_config = {}; + struct lgdt3306a_config lgdt3306a_config = {}; + + lgdt3306a_config = hauppauge_955q_lgdt3306a_config; + lgdt3306a_config.fe = &dev->dvb->frontend[0]; + lgdt3306a_config.i2c_adapter = &adapter; + + /* perform probe/init/attach */ + client = dvb_module_probe("lgdt3306a", NULL, demod_i2c, + dev->board.demod_addr, + &lgdt3306a_config); + if (!client) { + result = -ENODEV; + goto out_free; + } + dvb->i2c_client_demod[0] = client; + + dev->dvb->frontend[0]->ops.i2c_gate_ctrl = NULL; + + /* define general-purpose callback pointer */ + dvb->frontend[0]->callback = cx231xx_tuner_callback; + + /* attach tuner */ + si2157_config.fe = dev->dvb->frontend[0]; +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + si2157_config.mdev = dev->media_dev; +#endif + si2157_config.if_port = 1; + si2157_config.inversion = true; + + /* perform probe/init/attach */ + client = dvb_module_probe("si2157", NULL, tuner_i2c, + dev->board.tuner_addr, + &si2157_config); + if (!client) { + result = -ENODEV; + goto out_free; + } + dev->cx231xx_reset_analog_tuner = NULL; + + dev->dvb->i2c_client_tuner = client; + break; + } + case CX231XX_BOARD_PV_PLAYTV_USB_HYBRID: + case CX231XX_BOARD_KWORLD_UB430_USB_HYBRID: + + dev_info(dev->dev, + "%s: looking for demod on i2c bus: %d\n", + __func__, i2c_adapter_id(tuner_i2c)); + + dev->dvb->frontend[0] = dvb_attach(mb86a20s_attach, + &pv_mb86a20s_config, + demod_i2c); + + if (!dev->dvb->frontend[0]) { + dev_err(dev->dev, + "Failed to attach mb86a20s demod\n"); + result = -EINVAL; + goto out_free; + } + + /* define general-purpose callback pointer */ + dvb->frontend[0]->callback = cx231xx_tuner_callback; + + dvb_attach(tda18271_attach, dev->dvb->frontend[0], + dev->board.tuner_addr, tuner_i2c, + &pv_tda18271_config); + break; + + case CX231XX_BOARD_EVROMEDIA_FULL_HYBRID_FULLHD: + { + struct si2157_config si2157_config = {}; + struct si2168_config si2168_config = {}; + + /* attach demodulator chip */ + si2168_config.ts_mode = SI2168_TS_SERIAL; /* from *.inf file */ + si2168_config.fe = &dev->dvb->frontend[0]; + si2168_config.i2c_adapter = &adapter; + si2168_config.ts_clock_inv = true; + + /* perform probe/init/attach */ + client = dvb_module_probe("si2168", NULL, demod_i2c, + dev->board.demod_addr, + &si2168_config); + if (!client) { + result = -ENODEV; + goto out_free; + } + dvb->i2c_client_demod[0] = client; + + /* attach tuner chip */ + si2157_config.fe = dev->dvb->frontend[0]; +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + si2157_config.mdev = dev->media_dev; +#endif + si2157_config.if_port = 1; + si2157_config.inversion = false; + + /* perform probe/init/attach */ + client = dvb_module_probe("si2157", NULL, tuner_i2c, + dev->board.tuner_addr, + &si2157_config); + if (!client) { + result = -ENODEV; + goto out_free; + } + dev->cx231xx_reset_analog_tuner = NULL; + dev->dvb->i2c_client_tuner = client; + break; + } + case CX231XX_BOARD_ASTROMETA_T2HYBRID: + { + struct mn88473_config mn88473_config = {}; + + /* attach demodulator chip */ + mn88473_config.i2c_wr_max = 16; + mn88473_config.xtal = 25000000; + mn88473_config.fe = &dev->dvb->frontend[0]; + + /* perform probe/init/attach */ + client = dvb_module_probe("mn88473", NULL, demod_i2c, + dev->board.demod_addr, + &mn88473_config); + if (!client) { + result = -ENODEV; + goto out_free; + } + dvb->i2c_client_demod[0] = client; + + /* define general-purpose callback pointer */ + dvb->frontend[0]->callback = cx231xx_tuner_callback; + + /* attach tuner chip */ + dvb_attach(r820t_attach, dev->dvb->frontend[0], + tuner_i2c, + &astrometa_t2hybrid_r820t_config); + break; + } + case CX231XX_BOARD_HAUPPAUGE_935C: + { + struct si2157_config si2157_config = {}; + struct si2168_config si2168_config = {}; + + /* attach demodulator chip */ + si2168_config.ts_mode = SI2168_TS_SERIAL; + si2168_config.fe = &dev->dvb->frontend[0]; + si2168_config.i2c_adapter = &adapter; + si2168_config.ts_clock_inv = true; + + /* perform probe/init/attach */ + client = dvb_module_probe("si2168", NULL, demod_i2c, + dev->board.demod_addr, + &si2168_config); + if (!client) { + result = -ENODEV; + goto out_free; + } + dvb->i2c_client_demod[0] = client; + dev->dvb->frontend[0]->ops.i2c_gate_ctrl = NULL; + + /* define general-purpose callback pointer */ + dvb->frontend[0]->callback = cx231xx_tuner_callback; + + /* attach tuner */ + si2157_config.fe = dev->dvb->frontend[0]; +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + si2157_config.mdev = dev->media_dev; +#endif + si2157_config.if_port = 1; + si2157_config.inversion = true; + + /* perform probe/init/attach */ + client = dvb_module_probe("si2157", NULL, tuner_i2c, + dev->board.tuner_addr, + &si2157_config); + if (!client) { + result = -ENODEV; + goto out_free; + } + dev->cx231xx_reset_analog_tuner = NULL; + dev->dvb->i2c_client_tuner = client; + break; + } + case CX231XX_BOARD_HAUPPAUGE_975: + { + struct i2c_adapter *adapter2; + struct si2157_config si2157_config = {}; + struct lgdt3306a_config lgdt3306a_config = {}; + struct si2168_config si2168_config = {}; + + /* attach first demodulator chip */ + lgdt3306a_config = hauppauge_955q_lgdt3306a_config; + lgdt3306a_config.fe = &dev->dvb->frontend[0]; + lgdt3306a_config.i2c_adapter = &adapter; + + /* perform probe/init/attach */ + client = dvb_module_probe("lgdt3306a", NULL, demod_i2c, + dev->board.demod_addr, + &lgdt3306a_config); + if (!client) { + result = -ENODEV; + goto out_free; + } + dvb->i2c_client_demod[0] = client; + + /* attach second demodulator chip */ + si2168_config.ts_mode = SI2168_TS_SERIAL; + si2168_config.fe = &dev->dvb->frontend[1]; + si2168_config.i2c_adapter = &adapter2; + si2168_config.ts_clock_inv = true; + + /* perform probe/init/attach */ + client = dvb_module_probe("si2168", NULL, adapter, + dev->board.demod_addr2, + &si2168_config); + if (!client) { + result = -ENODEV; + goto out_free; + } + dvb->i2c_client_demod[1] = client; + dvb->frontend[1]->id = 1; + + /* define general-purpose callback pointer */ + dvb->frontend[0]->callback = cx231xx_tuner_callback; + dvb->frontend[1]->callback = cx231xx_tuner_callback; + + /* attach tuner */ + si2157_config.fe = dev->dvb->frontend[0]; +#ifdef CONFIG_MEDIA_CONTROLLER_DVB + si2157_config.mdev = dev->media_dev; +#endif + si2157_config.if_port = 1; + si2157_config.inversion = true; + + /* perform probe/init/attach */ + client = dvb_module_probe("si2157", NULL, adapter, + dev->board.tuner_addr, + &si2157_config); + if (!client) { + result = -ENODEV; + goto out_free; + } + dev->cx231xx_reset_analog_tuner = NULL; + dvb->i2c_client_tuner = client; + + dvb->frontend[1]->tuner_priv = dvb->frontend[0]->tuner_priv; + + memcpy(&dvb->frontend[1]->ops.tuner_ops, + &dvb->frontend[0]->ops.tuner_ops, + sizeof(struct dvb_tuner_ops)); + break; + } + default: + dev_err(dev->dev, + "%s/2: The frontend of your DVB/ATSC card isn't supported yet\n", + dev->name); + break; + } + if (!dvb->frontend[0]) { + dev_err(dev->dev, + "%s/2: frontend initialization failed\n", dev->name); + result = -EINVAL; + goto out_free; + } + + /* register everything */ + result = register_dvb(dvb, THIS_MODULE, dev, dev->dev); + + if (result < 0) + goto out_free; + + + dev_info(dev->dev, "Successfully loaded cx231xx-dvb\n"); + +ret: + cx231xx_set_mode(dev, CX231XX_SUSPEND); + mutex_unlock(&dev->lock); + return result; + +out_free: + /* remove I2C tuner */ + dvb_module_release(dvb->i2c_client_tuner); + dvb->i2c_client_tuner = NULL; + /* remove I2C demod(s) */ + dvb_module_release(dvb->i2c_client_demod[1]); + dvb->i2c_client_demod[1] = NULL; + dvb_module_release(dvb->i2c_client_demod[0]); + dvb->i2c_client_demod[0] = NULL; + kfree(dvb); + dev->dvb = NULL; + goto ret; +} + +static int dvb_fini(struct cx231xx *dev) +{ + if (!dev->board.has_dvb) { + /* This device does not support the extension */ + return 0; + } + + if (dev->dvb) { + unregister_dvb(dev->dvb); + kfree(dev->dvb); + dev->dvb = NULL; + } + + return 0; +} + +static struct cx231xx_ops dvb_ops = { + .id = CX231XX_DVB, + .name = "Cx231xx dvb Extension", + .init = dvb_init, + .fini = dvb_fini, +}; + +static int __init cx231xx_dvb_register(void) +{ + return cx231xx_register_extension(&dvb_ops); +} + +static void __exit cx231xx_dvb_unregister(void) +{ + cx231xx_unregister_extension(&dvb_ops); +} + +module_init(cx231xx_dvb_register); +module_exit(cx231xx_dvb_unregister); |