From 4617721c502b2ddaa4e324e86da4997edf738fa5 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Wed, 2 Sep 2026 09:55:01 -0400 Subject: ftrace: Synchronize the initialization of ftrace_ops There's some internal state that ftrace_ops needs to have set, but since it can be declared outside of the ftrace.c code, it calls ftrace_ops_init() on the ops in every global function. The issue is that if two tasks call it on the same ops at the same time it is possible to have the initialization of one corrupt the initialization of the other call. Create a ops_mutex to use to synchronize every initialization of the ftrace_ops. The mutex is taken within checking the ftrace_ops flag that states it was initializied but the flag is checked again after the mutex has been taken. Checking first outside the mutex allows it to shortcut having to take the mutex. But then the check needs to be done again after the mute is taken in case of races. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock") Reported-by: sashiko-bot@kernel.org Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/ Signed-off-by: Steven Rostedt --- drivers/media/pci/Kconfig | 80 + drivers/media/pci/Makefile | 41 + drivers/media/pci/b2c2/Kconfig | 16 + drivers/media/pci/b2c2/Makefile | 9 + drivers/media/pci/b2c2/flexcop-dma.c | 158 + drivers/media/pci/b2c2/flexcop-pci.c | 440 ++ drivers/media/pci/bt8xx/Kconfig | 46 + drivers/media/pci/bt8xx/Makefile | 10 + drivers/media/pci/bt8xx/bt848.h | 365 + drivers/media/pci/bt8xx/bt878.c | 567 ++ drivers/media/pci/bt8xx/bt878.h | 137 + drivers/media/pci/bt8xx/btcx-risc.c | 77 + drivers/media/pci/bt8xx/btcx-risc.h | 18 + drivers/media/pci/bt8xx/bttv-audio-hook.c | 481 ++ drivers/media/pci/bt8xx/bttv-audio-hook.h | 25 + drivers/media/pci/bt8xx/bttv-cards.c | 4933 +++++++++++++ drivers/media/pci/bt8xx/bttv-driver.c | 3625 ++++++++++ drivers/media/pci/bt8xx/bttv-gpio.c | 170 + drivers/media/pci/bt8xx/bttv-i2c.c | 390 + drivers/media/pci/bt8xx/bttv-if.c | 103 + drivers/media/pci/bt8xx/bttv-input.c | 580 ++ drivers/media/pci/bt8xx/bttv-risc.c | 804 +++ drivers/media/pci/bt8xx/bttv-vbi.c | 377 + drivers/media/pci/bt8xx/bttv.h | 379 + drivers/media/pci/bt8xx/bttvp.h | 498 ++ drivers/media/pci/bt8xx/dst.c | 1839 +++++ drivers/media/pci/bt8xx/dst_ca.c | 675 ++ drivers/media/pci/bt8xx/dst_ca.h | 46 + drivers/media/pci/bt8xx/dst_common.h | 170 + drivers/media/pci/bt8xx/dst_priv.h | 36 + drivers/media/pci/bt8xx/dvb-bt8xx.c | 968 +++ drivers/media/pci/bt8xx/dvb-bt8xx.h | 49 + drivers/media/pci/cobalt/Kconfig | 25 + drivers/media/pci/cobalt/Makefile | 6 + drivers/media/pci/cobalt/cobalt-alsa-main.c | 149 + drivers/media/pci/cobalt/cobalt-alsa-pcm.c | 530 ++ drivers/media/pci/cobalt/cobalt-alsa-pcm.h | 10 + drivers/media/pci/cobalt/cobalt-alsa.h | 29 + drivers/media/pci/cobalt/cobalt-cpld.c | 324 + drivers/media/pci/cobalt/cobalt-cpld.h | 17 + drivers/media/pci/cobalt/cobalt-driver.c | 797 +++ drivers/media/pci/cobalt/cobalt-driver.h | 373 + drivers/media/pci/cobalt/cobalt-flash.c | 116 + drivers/media/pci/cobalt/cobalt-flash.h | 17 + drivers/media/pci/cobalt/cobalt-i2c.c | 384 + drivers/media/pci/cobalt/cobalt-i2c.h | 13 + drivers/media/pci/cobalt/cobalt-irq.c | 247 + drivers/media/pci/cobalt/cobalt-irq.h | 13 + drivers/media/pci/cobalt/cobalt-omnitek.c | 329 + drivers/media/pci/cobalt/cobalt-omnitek.h | 50 + drivers/media/pci/cobalt/cobalt-v4l2.c | 1317 ++++ drivers/media/pci/cobalt/cobalt-v4l2.h | 10 + .../cobalt/m00233_video_measure_memmap_package.h | 103 + .../pci/cobalt/m00235_fdma_packer_memmap_package.h | 32 + .../media/pci/cobalt/m00389_cvi_memmap_package.h | 47 + .../media/pci/cobalt/m00460_evcnt_memmap_package.h | 32 + .../pci/cobalt/m00473_freewheel_memmap_package.h | 45 + .../m00479_clk_loss_detector_memmap_package.h | 41 + .../m00514_syncgen_flow_evcnt_memmap_package.h | 76 + drivers/media/pci/cx18/Kconfig | 36 + drivers/media/pci/cx18/Makefile | 13 + drivers/media/pci/cx18/cx18-alsa-main.c | 274 + drivers/media/pci/cx18/cx18-alsa-pcm.c | 272 + drivers/media/pci/cx18/cx18-alsa-pcm.h | 13 + drivers/media/pci/cx18/cx18-alsa.h | 60 + drivers/media/pci/cx18/cx18-audio.c | 78 + drivers/media/pci/cx18/cx18-audio.h | 10 + drivers/media/pci/cx18/cx18-av-audio.c | 457 ++ drivers/media/pci/cx18/cx18-av-core.c | 1358 ++++ drivers/media/pci/cx18/cx18-av-core.h | 376 + drivers/media/pci/cx18/cx18-av-firmware.c | 210 + drivers/media/pci/cx18/cx18-av-vbi.c | 291 + drivers/media/pci/cx18/cx18-cards.c | 624 ++ drivers/media/pci/cx18/cx18-cards.h | 139 + drivers/media/pci/cx18/cx18-controls.c | 117 + drivers/media/pci/cx18/cx18-controls.h | 11 + drivers/media/pci/cx18/cx18-driver.c | 1339 ++++ drivers/media/pci/cx18/cx18-driver.h | 697 ++ drivers/media/pci/cx18/cx18-dvb.c | 593 ++ drivers/media/pci/cx18/cx18-dvb.h | 11 + drivers/media/pci/cx18/cx18-fileops.c | 820 +++ drivers/media/pci/cx18/cx18-fileops.h | 28 + drivers/media/pci/cx18/cx18-firmware.c | 445 ++ drivers/media/pci/cx18/cx18-firmware.h | 11 + drivers/media/pci/cx18/cx18-gpio.c | 322 + drivers/media/pci/cx18/cx18-gpio.h | 20 + drivers/media/pci/cx18/cx18-i2c.c | 309 + drivers/media/pci/cx18/cx18-i2c.h | 15 + drivers/media/pci/cx18/cx18-io.c | 83 + drivers/media/pci/cx18/cx18-io.h | 177 + drivers/media/pci/cx18/cx18-ioctl.c | 1057 +++ drivers/media/pci/cx18/cx18-ioctl.h | 19 + drivers/media/pci/cx18/cx18-irq.c | 67 + drivers/media/pci/cx18/cx18-irq.h | 21 + drivers/media/pci/cx18/cx18-mailbox.c | 846 +++ drivers/media/pci/cx18/cx18-mailbox.h | 81 + drivers/media/pci/cx18/cx18-queue.c | 434 ++ drivers/media/pci/cx18/cx18-queue.h | 84 + drivers/media/pci/cx18/cx18-scb.c | 108 + drivers/media/pci/cx18/cx18-scb.h | 266 + drivers/media/pci/cx18/cx18-streams.c | 1051 +++ drivers/media/pci/cx18/cx18-streams.h | 48 + drivers/media/pci/cx18/cx18-vbi.c | 263 + drivers/media/pci/cx18/cx18-vbi.h | 12 + drivers/media/pci/cx18/cx18-version.h | 14 + drivers/media/pci/cx18/cx18-video.c | 18 + drivers/media/pci/cx18/cx18-video.h | 8 + drivers/media/pci/cx18/cx23418.h | 478 ++ drivers/media/pci/cx23885/Kconfig | 62 + drivers/media/pci/cx23885/Makefile | 14 + drivers/media/pci/cx23885/altera-ci.c | 838 +++ drivers/media/pci/cx23885/altera-ci.h | 85 + drivers/media/pci/cx23885/cimax2.c | 534 ++ drivers/media/pci/cx23885/cimax2.h | 33 + drivers/media/pci/cx23885/cx23885-417.c | 1564 ++++ drivers/media/pci/cx23885/cx23885-alsa.c | 596 ++ drivers/media/pci/cx23885/cx23885-av.c | 35 + drivers/media/pci/cx23885/cx23885-av.h | 13 + drivers/media/pci/cx23885/cx23885-cards.c | 2526 +++++++ drivers/media/pci/cx23885/cx23885-core.c | 2315 ++++++ drivers/media/pci/cx23885/cx23885-dvb.c | 2741 +++++++ drivers/media/pci/cx23885/cx23885-f300.c | 164 + drivers/media/pci/cx23885/cx23885-f300.h | 3 + drivers/media/pci/cx23885/cx23885-i2c.c | 374 + drivers/media/pci/cx23885/cx23885-input.c | 410 ++ drivers/media/pci/cx23885/cx23885-input.h | 16 + drivers/media/pci/cx23885/cx23885-ioctl.c | 98 + drivers/media/pci/cx23885/cx23885-ioctl.h | 25 + drivers/media/pci/cx23885/cx23885-ir.c | 104 + drivers/media/pci/cx23885/cx23885-ir.h | 17 + drivers/media/pci/cx23885/cx23885-reg.h | 452 ++ drivers/media/pci/cx23885/cx23885-vbi.c | 254 + drivers/media/pci/cx23885/cx23885-video.c | 1424 ++++ drivers/media/pci/cx23885/cx23885-video.h | 12 + drivers/media/pci/cx23885/cx23885.h | 636 ++ drivers/media/pci/cx23885/cx23888-ir.c | 1205 ++++ drivers/media/pci/cx23885/cx23888-ir.h | 14 + drivers/media/pci/cx23885/netup-eeprom.c | 64 + drivers/media/pci/cx23885/netup-eeprom.h | 27 + drivers/media/pci/cx23885/netup-init.c | 112 + drivers/media/pci/cx23885/netup-init.h | 11 + drivers/media/pci/cx25821/Kconfig | 29 + drivers/media/pci/cx25821/Makefile | 7 + drivers/media/pci/cx25821/cx25821-alsa.c | 820 +++ drivers/media/pci/cx25821/cx25821-audio.h | 48 + drivers/media/pci/cx25821/cx25821-biffuncs.h | 31 + drivers/media/pci/cx25821/cx25821-cards.c | 33 + drivers/media/pci/cx25821/cx25821-core.c | 1380 ++++ drivers/media/pci/cx25821/cx25821-gpio.c | 85 + drivers/media/pci/cx25821/cx25821-i2c.c | 405 ++ drivers/media/pci/cx25821/cx25821-medusa-defines.h | 28 + drivers/media/pci/cx25821/cx25821-medusa-reg.h | 441 ++ drivers/media/pci/cx25821/cx25821-medusa-video.c | 731 ++ drivers/media/pci/cx25821/cx25821-medusa-video.h | 29 + drivers/media/pci/cx25821/cx25821-reg.h | 1578 ++++ drivers/media/pci/cx25821/cx25821-sram.h | 247 + drivers/media/pci/cx25821/cx25821-video.c | 774 ++ drivers/media/pci/cx25821/cx25821-video.h | 48 + drivers/media/pci/cx25821/cx25821.h | 426 ++ drivers/media/pci/cx88/Kconfig | 93 + drivers/media/pci/cx88/Makefile | 14 + drivers/media/pci/cx88/cx88-alsa.c | 1009 +++ drivers/media/pci/cx88/cx88-blackbird.c | 1251 ++++ drivers/media/pci/cx88/cx88-cards.c | 3856 ++++++++++ drivers/media/pci/cx88/cx88-core.c | 1099 +++ drivers/media/pci/cx88/cx88-dsp.c | 323 + drivers/media/pci/cx88/cx88-dvb.c | 1842 +++++ drivers/media/pci/cx88/cx88-i2c.c | 173 + drivers/media/pci/cx88/cx88-input.c | 651 ++ drivers/media/pci/cx88/cx88-mpeg.c | 805 +++ drivers/media/pci/cx88/cx88-reg.h | 816 +++ drivers/media/pci/cx88/cx88-tvaudio.c | 1046 +++ drivers/media/pci/cx88/cx88-vbi.c | 233 + drivers/media/pci/cx88/cx88-video.c | 1630 +++++ drivers/media/pci/cx88/cx88-vp3054-i2c.c | 141 + drivers/media/pci/cx88/cx88-vp3054-i2c.h | 26 + drivers/media/pci/cx88/cx88.h | 731 ++ drivers/media/pci/ddbridge/Kconfig | 46 + drivers/media/pci/ddbridge/Makefile | 13 + drivers/media/pci/ddbridge/ddbridge-ci.c | 377 + drivers/media/pci/ddbridge/ddbridge-ci.h | 20 + drivers/media/pci/ddbridge/ddbridge-core.c | 3438 +++++++++ drivers/media/pci/ddbridge/ddbridge-dummy-fe.c | 153 + drivers/media/pci/ddbridge/ddbridge-dummy-fe.h | 16 + drivers/media/pci/ddbridge/ddbridge-hw.c | 379 + drivers/media/pci/ddbridge/ddbridge-hw.h | 34 + drivers/media/pci/ddbridge/ddbridge-i2c.c | 225 + drivers/media/pci/ddbridge/ddbridge-i2c.h | 103 + drivers/media/pci/ddbridge/ddbridge-io.h | 62 + drivers/media/pci/ddbridge/ddbridge-main.c | 313 + drivers/media/pci/ddbridge/ddbridge-max.c | 495 ++ drivers/media/pci/ddbridge/ddbridge-max.h | 21 + drivers/media/pci/ddbridge/ddbridge-mci.c | 169 + drivers/media/pci/ddbridge/ddbridge-mci.h | 253 + drivers/media/pci/ddbridge/ddbridge-regs.h | 140 + drivers/media/pci/ddbridge/ddbridge-sx8.c | 477 ++ drivers/media/pci/ddbridge/ddbridge.h | 373 + drivers/media/pci/dm1105/Kconfig | 22 + drivers/media/pci/dm1105/Makefile | 4 + drivers/media/pci/dm1105/dm1105.c | 1233 ++++ drivers/media/pci/dt3155/Kconfig | 12 + drivers/media/pci/dt3155/Makefile | 2 + drivers/media/pci/dt3155/dt3155.c | 597 ++ drivers/media/pci/dt3155/dt3155.h | 186 + drivers/media/pci/hws/Kconfig | 11 + drivers/media/pci/hws/Makefile | 4 + drivers/media/pci/hws/hws.h | 173 + drivers/media/pci/hws/hws_irq.c | 269 + drivers/media/pci/hws/hws_irq.h | 10 + drivers/media/pci/hws/hws_pci.c | 865 +++ drivers/media/pci/hws/hws_reg.h | 136 + drivers/media/pci/hws/hws_v4l2_ioctl.c | 919 +++ drivers/media/pci/hws/hws_v4l2_ioctl.h | 32 + drivers/media/pci/hws/hws_video.c | 1490 ++++ drivers/media/pci/hws/hws_video.h | 29 + drivers/media/pci/intel/Kconfig | 21 + drivers/media/pci/intel/Makefile | 8 + drivers/media/pci/intel/ipu-bridge.c | 995 +++ drivers/media/pci/intel/ipu3/Kconfig | 20 + drivers/media/pci/intel/ipu3/Makefile | 2 + drivers/media/pci/intel/ipu3/ipu3-cio2.c | 1989 +++++ drivers/media/pci/intel/ipu3/ipu3-cio2.h | 456 ++ drivers/media/pci/intel/ipu6/Kconfig | 18 + drivers/media/pci/intel/ipu6/Makefile | 23 + drivers/media/pci/intel/ipu6/ipu6-bus.c | 159 + drivers/media/pci/intel/ipu6/ipu6-bus.h | 55 + drivers/media/pci/intel/ipu6/ipu6-buttress.c | 910 +++ drivers/media/pci/intel/ipu6/ipu6-buttress.h | 85 + drivers/media/pci/intel/ipu6/ipu6-cpd.c | 362 + drivers/media/pci/intel/ipu6/ipu6-cpd.h | 105 + drivers/media/pci/intel/ipu6/ipu6-dma.c | 459 ++ drivers/media/pci/intel/ipu6/ipu6-dma.h | 43 + drivers/media/pci/intel/ipu6/ipu6-fw-com.c | 413 ++ drivers/media/pci/intel/ipu6/ipu6-fw-com.h | 47 + drivers/media/pci/intel/ipu6/ipu6-fw-isys.c | 487 ++ drivers/media/pci/intel/ipu6/ipu6-fw-isys.h | 596 ++ drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c | 647 ++ drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h | 78 + drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c | 548 ++ drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c | 242 + drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c | 720 ++ drivers/media/pci/intel/ipu6/ipu6-isys-queue.c | 857 +++ drivers/media/pci/intel/ipu6/ipu6-isys-queue.h | 70 + drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c | 389 + drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h | 51 + drivers/media/pci/intel/ipu6/ipu6-isys-video.c | 1368 ++++ drivers/media/pci/intel/ipu6/ipu6-isys-video.h | 135 + drivers/media/pci/intel/ipu6/ipu6-isys.c | 1366 ++++ drivers/media/pci/intel/ipu6/ipu6-isys.h | 200 + drivers/media/pci/intel/ipu6/ipu6-mmu.c | 806 +++ drivers/media/pci/intel/ipu6/ipu6-mmu.h | 73 + .../pci/intel/ipu6/ipu6-platform-buttress-regs.h | 224 + .../pci/intel/ipu6/ipu6-platform-isys-csi2-reg.h | 172 + drivers/media/pci/intel/ipu6/ipu6-platform-regs.h | 179 + drivers/media/pci/intel/ipu6/ipu6.c | 842 +++ drivers/media/pci/intel/ipu6/ipu6.h | 342 + drivers/media/pci/intel/ivsc/Kconfig | 22 + drivers/media/pci/intel/ivsc/Makefile | 9 + drivers/media/pci/intel/ivsc/mei_ace.c | 582 ++ drivers/media/pci/intel/ivsc/mei_csi.c | 791 ++ drivers/media/pci/ivtv/Kconfig | 78 + drivers/media/pci/ivtv/Makefile | 15 + drivers/media/pci/ivtv/ivtv-alsa-main.c | 274 + drivers/media/pci/ivtv/ivtv-alsa-pcm.c | 274 + drivers/media/pci/ivtv/ivtv-alsa-pcm.h | 9 + drivers/media/pci/ivtv/ivtv-alsa.h | 61 + drivers/media/pci/ivtv/ivtv-cards.c | 1358 ++++ drivers/media/pci/ivtv/ivtv-cards.h | 314 + drivers/media/pci/ivtv/ivtv-controls.c | 153 + drivers/media/pci/ivtv/ivtv-controls.h | 16 + drivers/media/pci/ivtv/ivtv-driver.c | 1453 ++++ drivers/media/pci/ivtv/ivtv-driver.h | 829 +++ drivers/media/pci/ivtv/ivtv-fileops.c | 1071 +++ drivers/media/pci/ivtv/ivtv-fileops.h | 32 + drivers/media/pci/ivtv/ivtv-firmware.c | 390 + drivers/media/pci/ivtv/ivtv-firmware.h | 19 + drivers/media/pci/ivtv/ivtv-gpio.c | 355 + drivers/media/pci/ivtv/ivtv-gpio.h | 17 + drivers/media/pci/ivtv/ivtv-i2c.c | 739 ++ drivers/media/pci/ivtv/ivtv-i2c.h | 20 + drivers/media/pci/ivtv/ivtv-ioctl.c | 1751 +++++ drivers/media/pci/ivtv/ivtv-ioctl.h | 25 + drivers/media/pci/ivtv/ivtv-irq.c | 1078 +++ drivers/media/pci/ivtv/ivtv-irq.h | 41 + drivers/media/pci/ivtv/ivtv-mailbox.c | 373 + drivers/media/pci/ivtv/ivtv-mailbox.h | 23 + drivers/media/pci/ivtv/ivtv-queue.c | 287 + drivers/media/pci/ivtv/ivtv-queue.h | 87 + drivers/media/pci/ivtv/ivtv-routing.c | 107 + drivers/media/pci/ivtv/ivtv-routing.h | 15 + drivers/media/pci/ivtv/ivtv-streams.c | 1026 +++ drivers/media/pci/ivtv/ivtv-streams.h | 25 + drivers/media/pci/ivtv/ivtv-udma.c | 226 + drivers/media/pci/ivtv/ivtv-udma.h | 36 + drivers/media/pci/ivtv/ivtv-vbi.c | 537 ++ drivers/media/pci/ivtv/ivtv-vbi.h | 22 + drivers/media/pci/ivtv/ivtv-version.h | 14 + drivers/media/pci/ivtv/ivtv-yuv.c | 1293 ++++ drivers/media/pci/ivtv/ivtv-yuv.h | 32 + drivers/media/pci/ivtv/ivtvfb.c | 1303 ++++ drivers/media/pci/mantis/Kconfig | 39 + drivers/media/pci/mantis/Makefile | 29 + drivers/media/pci/mantis/hopper_cards.c | 262 + drivers/media/pci/mantis/hopper_vp3028.c | 76 + drivers/media/pci/mantis/hopper_vp3028.h | 18 + drivers/media/pci/mantis/mantis_ca.c | 197 + drivers/media/pci/mantis/mantis_ca.h | 15 + drivers/media/pci/mantis/mantis_cards.c | 306 + drivers/media/pci/mantis/mantis_common.h | 190 + drivers/media/pci/mantis/mantis_dma.c | 216 + drivers/media/pci/mantis/mantis_dma.h | 18 + drivers/media/pci/mantis/mantis_dvb.c | 290 + drivers/media/pci/mantis/mantis_dvb.h | 23 + drivers/media/pci/mantis/mantis_evm.c | 105 + drivers/media/pci/mantis/mantis_hif.c | 227 + drivers/media/pci/mantis/mantis_hif.h | 17 + drivers/media/pci/mantis/mantis_i2c.c | 252 + drivers/media/pci/mantis/mantis_i2c.h | 18 + drivers/media/pci/mantis/mantis_input.c | 77 + drivers/media/pci/mantis/mantis_input.h | 16 + drivers/media/pci/mantis/mantis_ioc.c | 112 + drivers/media/pci/mantis/mantis_ioc.h | 39 + drivers/media/pci/mantis/mantis_link.h | 71 + drivers/media/pci/mantis/mantis_pci.c | 156 + drivers/media/pci/mantis/mantis_pci.h | 15 + drivers/media/pci/mantis/mantis_pcmcia.c | 109 + drivers/media/pci/mantis/mantis_reg.h | 185 + drivers/media/pci/mantis/mantis_uart.c | 186 + drivers/media/pci/mantis/mantis_uart.h | 46 + drivers/media/pci/mantis/mantis_vp1033.c | 200 + drivers/media/pci/mantis/mantis_vp1033.h | 18 + drivers/media/pci/mantis/mantis_vp1034.c | 108 + drivers/media/pci/mantis/mantis_vp1034.h | 22 + drivers/media/pci/mantis/mantis_vp1041.c | 345 + drivers/media/pci/mantis/mantis_vp1041.h | 21 + drivers/media/pci/mantis/mantis_vp2033.c | 176 + drivers/media/pci/mantis/mantis_vp2033.h | 18 + drivers/media/pci/mantis/mantis_vp2040.c | 175 + drivers/media/pci/mantis/mantis_vp2040.h | 20 + drivers/media/pci/mantis/mantis_vp3030.c | 93 + drivers/media/pci/mantis/mantis_vp3030.h | 18 + drivers/media/pci/mgb4/Kconfig | 18 + drivers/media/pci/mgb4/Makefile | 6 + drivers/media/pci/mgb4/mgb4_cmt.c | 248 + drivers/media/pci/mgb4/mgb4_cmt.h | 17 + drivers/media/pci/mgb4/mgb4_core.c | 715 ++ drivers/media/pci/mgb4/mgb4_core.h | 81 + drivers/media/pci/mgb4/mgb4_dma.c | 123 + drivers/media/pci/mgb4/mgb4_dma.h | 18 + drivers/media/pci/mgb4/mgb4_i2c.c | 140 + drivers/media/pci/mgb4/mgb4_i2c.h | 35 + drivers/media/pci/mgb4/mgb4_io.h | 54 + drivers/media/pci/mgb4/mgb4_regs.c | 31 + drivers/media/pci/mgb4/mgb4_regs.h | 35 + drivers/media/pci/mgb4/mgb4_sysfs.h | 20 + drivers/media/pci/mgb4/mgb4_sysfs_in.c | 813 +++ drivers/media/pci/mgb4/mgb4_sysfs_out.c | 825 +++ drivers/media/pci/mgb4/mgb4_sysfs_pci.c | 71 + drivers/media/pci/mgb4/mgb4_trigger.c | 209 + drivers/media/pci/mgb4/mgb4_trigger.h | 8 + drivers/media/pci/mgb4/mgb4_vin.c | 1078 +++ drivers/media/pci/mgb4/mgb4_vin.h | 69 + drivers/media/pci/mgb4/mgb4_vout.c | 873 +++ drivers/media/pci/mgb4/mgb4_vout.h | 65 + drivers/media/pci/netup_unidvb/Kconfig | 18 + drivers/media/pci/netup_unidvb/Makefile | 9 + drivers/media/pci/netup_unidvb/netup_unidvb.h | 134 + drivers/media/pci/netup_unidvb/netup_unidvb_ci.c | 239 + drivers/media/pci/netup_unidvb/netup_unidvb_core.c | 1028 +++ drivers/media/pci/netup_unidvb/netup_unidvb_i2c.c | 364 + drivers/media/pci/netup_unidvb/netup_unidvb_spi.c | 240 + drivers/media/pci/ngene/Kconfig | 21 + drivers/media/pci/ngene/Makefile | 11 + drivers/media/pci/ngene/ngene-cards.c | 1238 ++++ drivers/media/pci/ngene/ngene-core.c | 1713 +++++ drivers/media/pci/ngene/ngene-dvb.c | 340 + drivers/media/pci/ngene/ngene-i2c.c | 159 + drivers/media/pci/ngene/ngene.h | 853 +++ drivers/media/pci/pluto2/Kconfig | 16 + drivers/media/pci/pluto2/Makefile | 4 + drivers/media/pci/pluto2/pluto2.c | 784 ++ drivers/media/pci/pt1/Kconfig | 16 + drivers/media/pci/pt1/Makefile | 7 + drivers/media/pci/pt1/pt1.c | 1482 ++++ drivers/media/pci/pt3/Kconfig | 11 + drivers/media/pci/pt3/Makefile | 8 + drivers/media/pci/pt3/pt3.c | 807 +++ drivers/media/pci/pt3/pt3.h | 177 + drivers/media/pci/pt3/pt3_dma.c | 216 + drivers/media/pci/pt3/pt3_i2c.c | 230 + drivers/media/pci/saa7134/Kconfig | 74 + drivers/media/pci/saa7134/Makefile | 17 + drivers/media/pci/saa7134/saa7134-alsa.c | 1259 ++++ drivers/media/pci/saa7134/saa7134-cards.c | 7567 ++++++++++++++++++++ drivers/media/pci/saa7134/saa7134-core.c | 1524 ++++ drivers/media/pci/saa7134/saa7134-dvb.c | 2009 ++++++ drivers/media/pci/saa7134/saa7134-empress.c | 346 + drivers/media/pci/saa7134/saa7134-go7007.c | 520 ++ drivers/media/pci/saa7134/saa7134-i2c.c | 456 ++ drivers/media/pci/saa7134/saa7134-input.c | 1001 +++ drivers/media/pci/saa7134/saa7134-reg.h | 377 + drivers/media/pci/saa7134/saa7134-ts.c | 326 + drivers/media/pci/saa7134/saa7134-tvaudio.c | 1068 +++ drivers/media/pci/saa7134/saa7134-vbi.c | 208 + drivers/media/pci/saa7134/saa7134-video.c | 1877 +++++ drivers/media/pci/saa7134/saa7134.h | 907 +++ drivers/media/pci/saa7146/Kconfig | 39 + drivers/media/pci/saa7146/Makefile | 6 + drivers/media/pci/saa7146/hexium_gemini.c | 418 ++ drivers/media/pci/saa7146/hexium_orion.c | 485 ++ drivers/media/pci/saa7146/mxb.c | 874 +++ drivers/media/pci/saa7164/Kconfig | 18 + drivers/media/pci/saa7164/Makefile | 9 + drivers/media/pci/saa7164/saa7164-api.c | 1512 ++++ drivers/media/pci/saa7164/saa7164-buffer.c | 285 + drivers/media/pci/saa7164/saa7164-bus.c | 467 ++ drivers/media/pci/saa7164/saa7164-cards.c | 943 +++ drivers/media/pci/saa7164/saa7164-cmd.c | 540 ++ drivers/media/pci/saa7164/saa7164-core.c | 1575 ++++ drivers/media/pci/saa7164/saa7164-dvb.c | 740 ++ drivers/media/pci/saa7164/saa7164-encoder.c | 1144 +++ drivers/media/pci/saa7164/saa7164-fw.c | 597 ++ drivers/media/pci/saa7164/saa7164-i2c.c | 112 + drivers/media/pci/saa7164/saa7164-reg.h | 205 + drivers/media/pci/saa7164/saa7164-types.h | 428 ++ drivers/media/pci/saa7164/saa7164-vbi.c | 765 ++ drivers/media/pci/saa7164/saa7164.h | 629 ++ drivers/media/pci/smipcie/Kconfig | 19 + drivers/media/pci/smipcie/Makefile | 8 + drivers/media/pci/smipcie/smipcie-ir.c | 186 + drivers/media/pci/smipcie/smipcie-main.c | 1123 +++ drivers/media/pci/smipcie/smipcie.h | 310 + drivers/media/pci/solo6x10/Kconfig | 19 + drivers/media/pci/solo6x10/Makefile | 6 + drivers/media/pci/solo6x10/solo6x10-core.c | 671 ++ drivers/media/pci/solo6x10/solo6x10-disp.c | 313 + drivers/media/pci/solo6x10/solo6x10-eeprom.c | 141 + drivers/media/pci/solo6x10/solo6x10-enc.c | 331 + drivers/media/pci/solo6x10/solo6x10-g723.c | 393 + drivers/media/pci/solo6x10/solo6x10-gpio.c | 183 + drivers/media/pci/solo6x10/solo6x10-i2c.c | 323 + drivers/media/pci/solo6x10/solo6x10-jpeg.h | 180 + drivers/media/pci/solo6x10/solo6x10-offsets.h | 74 + drivers/media/pci/solo6x10/solo6x10-p2m.c | 317 + drivers/media/pci/solo6x10/solo6x10-regs.h | 628 ++ drivers/media/pci/solo6x10/solo6x10-tw28.c | 869 +++ drivers/media/pci/solo6x10/solo6x10-tw28.h | 56 + drivers/media/pci/solo6x10/solo6x10-v4l2-enc.c | 1392 ++++ drivers/media/pci/solo6x10/solo6x10-v4l2.c | 717 ++ drivers/media/pci/solo6x10/solo6x10.h | 380 + drivers/media/pci/ttpci/Kconfig | 86 + drivers/media/pci/ttpci/Makefile | 13 + drivers/media/pci/ttpci/budget-av.c | 1625 +++++ drivers/media/pci/ttpci/budget-ci.c | 1573 ++++ drivers/media/pci/ttpci/budget-core.c | 606 ++ drivers/media/pci/ttpci/budget.c | 894 +++ drivers/media/pci/ttpci/budget.h | 135 + drivers/media/pci/tw5864/Kconfig | 12 + drivers/media/pci/tw5864/Makefile | 4 + drivers/media/pci/tw5864/tw5864-core.c | 336 + drivers/media/pci/tw5864/tw5864-h264.c | 250 + drivers/media/pci/tw5864/tw5864-reg.h | 2132 ++++++ drivers/media/pci/tw5864/tw5864-util.c | 38 + drivers/media/pci/tw5864/tw5864-video.c | 1513 ++++ drivers/media/pci/tw5864/tw5864.h | 197 + drivers/media/pci/tw68/Kconfig | 10 + drivers/media/pci/tw68/Makefile | 4 + drivers/media/pci/tw68/tw68-core.c | 418 ++ drivers/media/pci/tw68/tw68-reg.h | 186 + drivers/media/pci/tw68/tw68-risc.c | 223 + drivers/media/pci/tw68/tw68-video.c | 1017 +++ drivers/media/pci/tw68/tw68.h | 205 + drivers/media/pci/tw686x/Kconfig | 20 + drivers/media/pci/tw686x/Makefile | 4 + drivers/media/pci/tw686x/tw686x-audio.c | 413 ++ drivers/media/pci/tw686x/tw686x-core.c | 451 ++ drivers/media/pci/tw686x/tw686x-regs.h | 132 + drivers/media/pci/tw686x/tw686x-video.c | 1307 ++++ drivers/media/pci/tw686x/tw686x.h | 179 + drivers/media/pci/zoran/Kconfig | 74 + drivers/media/pci/zoran/Makefile | 7 + drivers/media/pci/zoran/videocodec.c | 278 + drivers/media/pci/zoran/videocodec.h | 325 + drivers/media/pci/zoran/zoran.h | 322 + drivers/media/pci/zoran/zoran_card.c | 1437 ++++ drivers/media/pci/zoran/zoran_card.h | 27 + drivers/media/pci/zoran/zoran_device.c | 956 +++ drivers/media/pci/zoran/zoran_device.h | 56 + drivers/media/pci/zoran/zoran_driver.c | 983 +++ drivers/media/pci/zoran/zr36016.c | 406 ++ drivers/media/pci/zoran/zr36016.h | 94 + drivers/media/pci/zoran/zr36050.c | 817 +++ drivers/media/pci/zoran/zr36050.h | 165 + drivers/media/pci/zoran/zr36057.h | 154 + drivers/media/pci/zoran/zr36060.c | 870 +++ drivers/media/pci/zoran/zr36060.h | 203 + 496 files changed, 205154 insertions(+) create mode 100644 drivers/media/pci/Kconfig create mode 100644 drivers/media/pci/Makefile create mode 100644 drivers/media/pci/b2c2/Kconfig create mode 100644 drivers/media/pci/b2c2/Makefile create mode 100644 drivers/media/pci/b2c2/flexcop-dma.c create mode 100644 drivers/media/pci/b2c2/flexcop-pci.c create mode 100644 drivers/media/pci/bt8xx/Kconfig create mode 100644 drivers/media/pci/bt8xx/Makefile create mode 100644 drivers/media/pci/bt8xx/bt848.h create mode 100644 drivers/media/pci/bt8xx/bt878.c create mode 100644 drivers/media/pci/bt8xx/bt878.h create mode 100644 drivers/media/pci/bt8xx/btcx-risc.c create mode 100644 drivers/media/pci/bt8xx/btcx-risc.h create mode 100644 drivers/media/pci/bt8xx/bttv-audio-hook.c create mode 100644 drivers/media/pci/bt8xx/bttv-audio-hook.h create mode 100644 drivers/media/pci/bt8xx/bttv-cards.c create mode 100644 drivers/media/pci/bt8xx/bttv-driver.c create mode 100644 drivers/media/pci/bt8xx/bttv-gpio.c create mode 100644 drivers/media/pci/bt8xx/bttv-i2c.c create mode 100644 drivers/media/pci/bt8xx/bttv-if.c create mode 100644 drivers/media/pci/bt8xx/bttv-input.c create mode 100644 drivers/media/pci/bt8xx/bttv-risc.c create mode 100644 drivers/media/pci/bt8xx/bttv-vbi.c create mode 100644 drivers/media/pci/bt8xx/bttv.h create mode 100644 drivers/media/pci/bt8xx/bttvp.h create mode 100644 drivers/media/pci/bt8xx/dst.c create mode 100644 drivers/media/pci/bt8xx/dst_ca.c create mode 100644 drivers/media/pci/bt8xx/dst_ca.h create mode 100644 drivers/media/pci/bt8xx/dst_common.h create mode 100644 drivers/media/pci/bt8xx/dst_priv.h create mode 100644 drivers/media/pci/bt8xx/dvb-bt8xx.c create mode 100644 drivers/media/pci/bt8xx/dvb-bt8xx.h create mode 100644 drivers/media/pci/cobalt/Kconfig create mode 100644 drivers/media/pci/cobalt/Makefile create mode 100644 drivers/media/pci/cobalt/cobalt-alsa-main.c create mode 100644 drivers/media/pci/cobalt/cobalt-alsa-pcm.c create mode 100644 drivers/media/pci/cobalt/cobalt-alsa-pcm.h create mode 100644 drivers/media/pci/cobalt/cobalt-alsa.h create mode 100644 drivers/media/pci/cobalt/cobalt-cpld.c create mode 100644 drivers/media/pci/cobalt/cobalt-cpld.h create mode 100644 drivers/media/pci/cobalt/cobalt-driver.c create mode 100644 drivers/media/pci/cobalt/cobalt-driver.h create mode 100644 drivers/media/pci/cobalt/cobalt-flash.c create mode 100644 drivers/media/pci/cobalt/cobalt-flash.h create mode 100644 drivers/media/pci/cobalt/cobalt-i2c.c create mode 100644 drivers/media/pci/cobalt/cobalt-i2c.h create mode 100644 drivers/media/pci/cobalt/cobalt-irq.c create mode 100644 drivers/media/pci/cobalt/cobalt-irq.h create mode 100644 drivers/media/pci/cobalt/cobalt-omnitek.c create mode 100644 drivers/media/pci/cobalt/cobalt-omnitek.h create mode 100644 drivers/media/pci/cobalt/cobalt-v4l2.c create mode 100644 drivers/media/pci/cobalt/cobalt-v4l2.h create mode 100644 drivers/media/pci/cobalt/m00233_video_measure_memmap_package.h create mode 100644 drivers/media/pci/cobalt/m00235_fdma_packer_memmap_package.h create mode 100644 drivers/media/pci/cobalt/m00389_cvi_memmap_package.h create mode 100644 drivers/media/pci/cobalt/m00460_evcnt_memmap_package.h create mode 100644 drivers/media/pci/cobalt/m00473_freewheel_memmap_package.h create mode 100644 drivers/media/pci/cobalt/m00479_clk_loss_detector_memmap_package.h create mode 100644 drivers/media/pci/cobalt/m00514_syncgen_flow_evcnt_memmap_package.h create mode 100644 drivers/media/pci/cx18/Kconfig create mode 100644 drivers/media/pci/cx18/Makefile create mode 100644 drivers/media/pci/cx18/cx18-alsa-main.c create mode 100644 drivers/media/pci/cx18/cx18-alsa-pcm.c create mode 100644 drivers/media/pci/cx18/cx18-alsa-pcm.h create mode 100644 drivers/media/pci/cx18/cx18-alsa.h create mode 100644 drivers/media/pci/cx18/cx18-audio.c create mode 100644 drivers/media/pci/cx18/cx18-audio.h create mode 100644 drivers/media/pci/cx18/cx18-av-audio.c create mode 100644 drivers/media/pci/cx18/cx18-av-core.c create mode 100644 drivers/media/pci/cx18/cx18-av-core.h create mode 100644 drivers/media/pci/cx18/cx18-av-firmware.c create mode 100644 drivers/media/pci/cx18/cx18-av-vbi.c create mode 100644 drivers/media/pci/cx18/cx18-cards.c create mode 100644 drivers/media/pci/cx18/cx18-cards.h create mode 100644 drivers/media/pci/cx18/cx18-controls.c create mode 100644 drivers/media/pci/cx18/cx18-controls.h create mode 100644 drivers/media/pci/cx18/cx18-driver.c create mode 100644 drivers/media/pci/cx18/cx18-driver.h create mode 100644 drivers/media/pci/cx18/cx18-dvb.c create mode 100644 drivers/media/pci/cx18/cx18-dvb.h create mode 100644 drivers/media/pci/cx18/cx18-fileops.c create mode 100644 drivers/media/pci/cx18/cx18-fileops.h create mode 100644 drivers/media/pci/cx18/cx18-firmware.c create mode 100644 drivers/media/pci/cx18/cx18-firmware.h create mode 100644 drivers/media/pci/cx18/cx18-gpio.c create mode 100644 drivers/media/pci/cx18/cx18-gpio.h create mode 100644 drivers/media/pci/cx18/cx18-i2c.c create mode 100644 drivers/media/pci/cx18/cx18-i2c.h create mode 100644 drivers/media/pci/cx18/cx18-io.c create mode 100644 drivers/media/pci/cx18/cx18-io.h create mode 100644 drivers/media/pci/cx18/cx18-ioctl.c create mode 100644 drivers/media/pci/cx18/cx18-ioctl.h create mode 100644 drivers/media/pci/cx18/cx18-irq.c create mode 100644 drivers/media/pci/cx18/cx18-irq.h create mode 100644 drivers/media/pci/cx18/cx18-mailbox.c create mode 100644 drivers/media/pci/cx18/cx18-mailbox.h create mode 100644 drivers/media/pci/cx18/cx18-queue.c create mode 100644 drivers/media/pci/cx18/cx18-queue.h create mode 100644 drivers/media/pci/cx18/cx18-scb.c create mode 100644 drivers/media/pci/cx18/cx18-scb.h create mode 100644 drivers/media/pci/cx18/cx18-streams.c create mode 100644 drivers/media/pci/cx18/cx18-streams.h create mode 100644 drivers/media/pci/cx18/cx18-vbi.c create mode 100644 drivers/media/pci/cx18/cx18-vbi.h create mode 100644 drivers/media/pci/cx18/cx18-version.h create mode 100644 drivers/media/pci/cx18/cx18-video.c create mode 100644 drivers/media/pci/cx18/cx18-video.h create mode 100644 drivers/media/pci/cx18/cx23418.h create mode 100644 drivers/media/pci/cx23885/Kconfig create mode 100644 drivers/media/pci/cx23885/Makefile create mode 100644 drivers/media/pci/cx23885/altera-ci.c create mode 100644 drivers/media/pci/cx23885/altera-ci.h create mode 100644 drivers/media/pci/cx23885/cimax2.c create mode 100644 drivers/media/pci/cx23885/cimax2.h create mode 100644 drivers/media/pci/cx23885/cx23885-417.c create mode 100644 drivers/media/pci/cx23885/cx23885-alsa.c create mode 100644 drivers/media/pci/cx23885/cx23885-av.c create mode 100644 drivers/media/pci/cx23885/cx23885-av.h create mode 100644 drivers/media/pci/cx23885/cx23885-cards.c create mode 100644 drivers/media/pci/cx23885/cx23885-core.c create mode 100644 drivers/media/pci/cx23885/cx23885-dvb.c create mode 100644 drivers/media/pci/cx23885/cx23885-f300.c create mode 100644 drivers/media/pci/cx23885/cx23885-f300.h create mode 100644 drivers/media/pci/cx23885/cx23885-i2c.c create mode 100644 drivers/media/pci/cx23885/cx23885-input.c create mode 100644 drivers/media/pci/cx23885/cx23885-input.h create mode 100644 drivers/media/pci/cx23885/cx23885-ioctl.c create mode 100644 drivers/media/pci/cx23885/cx23885-ioctl.h create mode 100644 drivers/media/pci/cx23885/cx23885-ir.c create mode 100644 drivers/media/pci/cx23885/cx23885-ir.h create mode 100644 drivers/media/pci/cx23885/cx23885-reg.h create mode 100644 drivers/media/pci/cx23885/cx23885-vbi.c create mode 100644 drivers/media/pci/cx23885/cx23885-video.c create mode 100644 drivers/media/pci/cx23885/cx23885-video.h create mode 100644 drivers/media/pci/cx23885/cx23885.h create mode 100644 drivers/media/pci/cx23885/cx23888-ir.c create mode 100644 drivers/media/pci/cx23885/cx23888-ir.h create mode 100644 drivers/media/pci/cx23885/netup-eeprom.c create mode 100644 drivers/media/pci/cx23885/netup-eeprom.h create mode 100644 drivers/media/pci/cx23885/netup-init.c create mode 100644 drivers/media/pci/cx23885/netup-init.h create mode 100644 drivers/media/pci/cx25821/Kconfig create mode 100644 drivers/media/pci/cx25821/Makefile create mode 100644 drivers/media/pci/cx25821/cx25821-alsa.c create mode 100644 drivers/media/pci/cx25821/cx25821-audio.h create mode 100644 drivers/media/pci/cx25821/cx25821-biffuncs.h create mode 100644 drivers/media/pci/cx25821/cx25821-cards.c create mode 100644 drivers/media/pci/cx25821/cx25821-core.c create mode 100644 drivers/media/pci/cx25821/cx25821-gpio.c create mode 100644 drivers/media/pci/cx25821/cx25821-i2c.c create mode 100644 drivers/media/pci/cx25821/cx25821-medusa-defines.h create mode 100644 drivers/media/pci/cx25821/cx25821-medusa-reg.h create mode 100644 drivers/media/pci/cx25821/cx25821-medusa-video.c create mode 100644 drivers/media/pci/cx25821/cx25821-medusa-video.h create mode 100644 drivers/media/pci/cx25821/cx25821-reg.h create mode 100644 drivers/media/pci/cx25821/cx25821-sram.h create mode 100644 drivers/media/pci/cx25821/cx25821-video.c create mode 100644 drivers/media/pci/cx25821/cx25821-video.h create mode 100644 drivers/media/pci/cx25821/cx25821.h create mode 100644 drivers/media/pci/cx88/Kconfig create mode 100644 drivers/media/pci/cx88/Makefile create mode 100644 drivers/media/pci/cx88/cx88-alsa.c create mode 100644 drivers/media/pci/cx88/cx88-blackbird.c create mode 100644 drivers/media/pci/cx88/cx88-cards.c create mode 100644 drivers/media/pci/cx88/cx88-core.c create mode 100644 drivers/media/pci/cx88/cx88-dsp.c create mode 100644 drivers/media/pci/cx88/cx88-dvb.c create mode 100644 drivers/media/pci/cx88/cx88-i2c.c create mode 100644 drivers/media/pci/cx88/cx88-input.c create mode 100644 drivers/media/pci/cx88/cx88-mpeg.c create mode 100644 drivers/media/pci/cx88/cx88-reg.h create mode 100644 drivers/media/pci/cx88/cx88-tvaudio.c create mode 100644 drivers/media/pci/cx88/cx88-vbi.c create mode 100644 drivers/media/pci/cx88/cx88-video.c create mode 100644 drivers/media/pci/cx88/cx88-vp3054-i2c.c create mode 100644 drivers/media/pci/cx88/cx88-vp3054-i2c.h create mode 100644 drivers/media/pci/cx88/cx88.h create mode 100644 drivers/media/pci/ddbridge/Kconfig create mode 100644 drivers/media/pci/ddbridge/Makefile create mode 100644 drivers/media/pci/ddbridge/ddbridge-ci.c create mode 100644 drivers/media/pci/ddbridge/ddbridge-ci.h create mode 100644 drivers/media/pci/ddbridge/ddbridge-core.c create mode 100644 drivers/media/pci/ddbridge/ddbridge-dummy-fe.c create mode 100644 drivers/media/pci/ddbridge/ddbridge-dummy-fe.h create mode 100644 drivers/media/pci/ddbridge/ddbridge-hw.c create mode 100644 drivers/media/pci/ddbridge/ddbridge-hw.h create mode 100644 drivers/media/pci/ddbridge/ddbridge-i2c.c create mode 100644 drivers/media/pci/ddbridge/ddbridge-i2c.h create mode 100644 drivers/media/pci/ddbridge/ddbridge-io.h create mode 100644 drivers/media/pci/ddbridge/ddbridge-main.c create mode 100644 drivers/media/pci/ddbridge/ddbridge-max.c create mode 100644 drivers/media/pci/ddbridge/ddbridge-max.h create mode 100644 drivers/media/pci/ddbridge/ddbridge-mci.c create mode 100644 drivers/media/pci/ddbridge/ddbridge-mci.h create mode 100644 drivers/media/pci/ddbridge/ddbridge-regs.h create mode 100644 drivers/media/pci/ddbridge/ddbridge-sx8.c create mode 100644 drivers/media/pci/ddbridge/ddbridge.h create mode 100644 drivers/media/pci/dm1105/Kconfig create mode 100644 drivers/media/pci/dm1105/Makefile create mode 100644 drivers/media/pci/dm1105/dm1105.c create mode 100644 drivers/media/pci/dt3155/Kconfig create mode 100644 drivers/media/pci/dt3155/Makefile create mode 100644 drivers/media/pci/dt3155/dt3155.c create mode 100644 drivers/media/pci/dt3155/dt3155.h create mode 100644 drivers/media/pci/hws/Kconfig create mode 100644 drivers/media/pci/hws/Makefile create mode 100644 drivers/media/pci/hws/hws.h create mode 100644 drivers/media/pci/hws/hws_irq.c create mode 100644 drivers/media/pci/hws/hws_irq.h create mode 100644 drivers/media/pci/hws/hws_pci.c create mode 100644 drivers/media/pci/hws/hws_reg.h create mode 100644 drivers/media/pci/hws/hws_v4l2_ioctl.c create mode 100644 drivers/media/pci/hws/hws_v4l2_ioctl.h create mode 100644 drivers/media/pci/hws/hws_video.c create mode 100644 drivers/media/pci/hws/hws_video.h create mode 100644 drivers/media/pci/intel/Kconfig create mode 100644 drivers/media/pci/intel/Makefile create mode 100644 drivers/media/pci/intel/ipu-bridge.c create mode 100644 drivers/media/pci/intel/ipu3/Kconfig create mode 100644 drivers/media/pci/intel/ipu3/Makefile create mode 100644 drivers/media/pci/intel/ipu3/ipu3-cio2.c create mode 100644 drivers/media/pci/intel/ipu3/ipu3-cio2.h create mode 100644 drivers/media/pci/intel/ipu6/Kconfig create mode 100644 drivers/media/pci/intel/ipu6/Makefile create mode 100644 drivers/media/pci/intel/ipu6/ipu6-bus.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-bus.h create mode 100644 drivers/media/pci/intel/ipu6/ipu6-buttress.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-buttress.h create mode 100644 drivers/media/pci/intel/ipu6/ipu6-cpd.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-cpd.h create mode 100644 drivers/media/pci/intel/ipu6/ipu6-dma.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-dma.h create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-com.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-com.h create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-isys.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-isys.h create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-queue.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-queue.h create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-video.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-video.h create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys.h create mode 100644 drivers/media/pci/intel/ipu6/ipu6-mmu.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-mmu.h create mode 100644 drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h create mode 100644 drivers/media/pci/intel/ipu6/ipu6-platform-isys-csi2-reg.h create mode 100644 drivers/media/pci/intel/ipu6/ipu6-platform-regs.h create mode 100644 drivers/media/pci/intel/ipu6/ipu6.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6.h create mode 100644 drivers/media/pci/intel/ivsc/Kconfig create mode 100644 drivers/media/pci/intel/ivsc/Makefile create mode 100644 drivers/media/pci/intel/ivsc/mei_ace.c create mode 100644 drivers/media/pci/intel/ivsc/mei_csi.c create mode 100644 drivers/media/pci/ivtv/Kconfig create mode 100644 drivers/media/pci/ivtv/Makefile create mode 100644 drivers/media/pci/ivtv/ivtv-alsa-main.c create mode 100644 drivers/media/pci/ivtv/ivtv-alsa-pcm.c create mode 100644 drivers/media/pci/ivtv/ivtv-alsa-pcm.h create mode 100644 drivers/media/pci/ivtv/ivtv-alsa.h create mode 100644 drivers/media/pci/ivtv/ivtv-cards.c create mode 100644 drivers/media/pci/ivtv/ivtv-cards.h create mode 100644 drivers/media/pci/ivtv/ivtv-controls.c create mode 100644 drivers/media/pci/ivtv/ivtv-controls.h create mode 100644 drivers/media/pci/ivtv/ivtv-driver.c create mode 100644 drivers/media/pci/ivtv/ivtv-driver.h create mode 100644 drivers/media/pci/ivtv/ivtv-fileops.c create mode 100644 drivers/media/pci/ivtv/ivtv-fileops.h create mode 100644 drivers/media/pci/ivtv/ivtv-firmware.c create mode 100644 drivers/media/pci/ivtv/ivtv-firmware.h create mode 100644 drivers/media/pci/ivtv/ivtv-gpio.c create mode 100644 drivers/media/pci/ivtv/ivtv-gpio.h create mode 100644 drivers/media/pci/ivtv/ivtv-i2c.c create mode 100644 drivers/media/pci/ivtv/ivtv-i2c.h create mode 100644 drivers/media/pci/ivtv/ivtv-ioctl.c create mode 100644 drivers/media/pci/ivtv/ivtv-ioctl.h create mode 100644 drivers/media/pci/ivtv/ivtv-irq.c create mode 100644 drivers/media/pci/ivtv/ivtv-irq.h create mode 100644 drivers/media/pci/ivtv/ivtv-mailbox.c create mode 100644 drivers/media/pci/ivtv/ivtv-mailbox.h create mode 100644 drivers/media/pci/ivtv/ivtv-queue.c create mode 100644 drivers/media/pci/ivtv/ivtv-queue.h create mode 100644 drivers/media/pci/ivtv/ivtv-routing.c create mode 100644 drivers/media/pci/ivtv/ivtv-routing.h create mode 100644 drivers/media/pci/ivtv/ivtv-streams.c create mode 100644 drivers/media/pci/ivtv/ivtv-streams.h create mode 100644 drivers/media/pci/ivtv/ivtv-udma.c create mode 100644 drivers/media/pci/ivtv/ivtv-udma.h create mode 100644 drivers/media/pci/ivtv/ivtv-vbi.c create mode 100644 drivers/media/pci/ivtv/ivtv-vbi.h create mode 100644 drivers/media/pci/ivtv/ivtv-version.h create mode 100644 drivers/media/pci/ivtv/ivtv-yuv.c create mode 100644 drivers/media/pci/ivtv/ivtv-yuv.h create mode 100644 drivers/media/pci/ivtv/ivtvfb.c create mode 100644 drivers/media/pci/mantis/Kconfig create mode 100644 drivers/media/pci/mantis/Makefile create mode 100644 drivers/media/pci/mantis/hopper_cards.c create mode 100644 drivers/media/pci/mantis/hopper_vp3028.c create mode 100644 drivers/media/pci/mantis/hopper_vp3028.h create mode 100644 drivers/media/pci/mantis/mantis_ca.c create mode 100644 drivers/media/pci/mantis/mantis_ca.h create mode 100644 drivers/media/pci/mantis/mantis_cards.c create mode 100644 drivers/media/pci/mantis/mantis_common.h create mode 100644 drivers/media/pci/mantis/mantis_dma.c create mode 100644 drivers/media/pci/mantis/mantis_dma.h create mode 100644 drivers/media/pci/mantis/mantis_dvb.c create mode 100644 drivers/media/pci/mantis/mantis_dvb.h create mode 100644 drivers/media/pci/mantis/mantis_evm.c create mode 100644 drivers/media/pci/mantis/mantis_hif.c create mode 100644 drivers/media/pci/mantis/mantis_hif.h create mode 100644 drivers/media/pci/mantis/mantis_i2c.c create mode 100644 drivers/media/pci/mantis/mantis_i2c.h create mode 100644 drivers/media/pci/mantis/mantis_input.c create mode 100644 drivers/media/pci/mantis/mantis_input.h create mode 100644 drivers/media/pci/mantis/mantis_ioc.c create mode 100644 drivers/media/pci/mantis/mantis_ioc.h create mode 100644 drivers/media/pci/mantis/mantis_link.h create mode 100644 drivers/media/pci/mantis/mantis_pci.c create mode 100644 drivers/media/pci/mantis/mantis_pci.h create mode 100644 drivers/media/pci/mantis/mantis_pcmcia.c create mode 100644 drivers/media/pci/mantis/mantis_reg.h create mode 100644 drivers/media/pci/mantis/mantis_uart.c create mode 100644 drivers/media/pci/mantis/mantis_uart.h create mode 100644 drivers/media/pci/mantis/mantis_vp1033.c create mode 100644 drivers/media/pci/mantis/mantis_vp1033.h create mode 100644 drivers/media/pci/mantis/mantis_vp1034.c create mode 100644 drivers/media/pci/mantis/mantis_vp1034.h create mode 100644 drivers/media/pci/mantis/mantis_vp1041.c create mode 100644 drivers/media/pci/mantis/mantis_vp1041.h create mode 100644 drivers/media/pci/mantis/mantis_vp2033.c create mode 100644 drivers/media/pci/mantis/mantis_vp2033.h create mode 100644 drivers/media/pci/mantis/mantis_vp2040.c create mode 100644 drivers/media/pci/mantis/mantis_vp2040.h create mode 100644 drivers/media/pci/mantis/mantis_vp3030.c create mode 100644 drivers/media/pci/mantis/mantis_vp3030.h create mode 100644 drivers/media/pci/mgb4/Kconfig create mode 100644 drivers/media/pci/mgb4/Makefile create mode 100644 drivers/media/pci/mgb4/mgb4_cmt.c create mode 100644 drivers/media/pci/mgb4/mgb4_cmt.h create mode 100644 drivers/media/pci/mgb4/mgb4_core.c create mode 100644 drivers/media/pci/mgb4/mgb4_core.h create mode 100644 drivers/media/pci/mgb4/mgb4_dma.c create mode 100644 drivers/media/pci/mgb4/mgb4_dma.h create mode 100644 drivers/media/pci/mgb4/mgb4_i2c.c create mode 100644 drivers/media/pci/mgb4/mgb4_i2c.h create mode 100644 drivers/media/pci/mgb4/mgb4_io.h create mode 100644 drivers/media/pci/mgb4/mgb4_regs.c create mode 100644 drivers/media/pci/mgb4/mgb4_regs.h create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs.h create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs_in.c create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs_out.c create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs_pci.c create mode 100644 drivers/media/pci/mgb4/mgb4_trigger.c create mode 100644 drivers/media/pci/mgb4/mgb4_trigger.h create mode 100644 drivers/media/pci/mgb4/mgb4_vin.c create mode 100644 drivers/media/pci/mgb4/mgb4_vin.h create mode 100644 drivers/media/pci/mgb4/mgb4_vout.c create mode 100644 drivers/media/pci/mgb4/mgb4_vout.h create mode 100644 drivers/media/pci/netup_unidvb/Kconfig create mode 100644 drivers/media/pci/netup_unidvb/Makefile create mode 100644 drivers/media/pci/netup_unidvb/netup_unidvb.h create mode 100644 drivers/media/pci/netup_unidvb/netup_unidvb_ci.c create mode 100644 drivers/media/pci/netup_unidvb/netup_unidvb_core.c create mode 100644 drivers/media/pci/netup_unidvb/netup_unidvb_i2c.c create mode 100644 drivers/media/pci/netup_unidvb/netup_unidvb_spi.c create mode 100644 drivers/media/pci/ngene/Kconfig create mode 100644 drivers/media/pci/ngene/Makefile create mode 100644 drivers/media/pci/ngene/ngene-cards.c create mode 100644 drivers/media/pci/ngene/ngene-core.c create mode 100644 drivers/media/pci/ngene/ngene-dvb.c create mode 100644 drivers/media/pci/ngene/ngene-i2c.c create mode 100644 drivers/media/pci/ngene/ngene.h create mode 100644 drivers/media/pci/pluto2/Kconfig create mode 100644 drivers/media/pci/pluto2/Makefile create mode 100644 drivers/media/pci/pluto2/pluto2.c create mode 100644 drivers/media/pci/pt1/Kconfig create mode 100644 drivers/media/pci/pt1/Makefile create mode 100644 drivers/media/pci/pt1/pt1.c create mode 100644 drivers/media/pci/pt3/Kconfig create mode 100644 drivers/media/pci/pt3/Makefile create mode 100644 drivers/media/pci/pt3/pt3.c create mode 100644 drivers/media/pci/pt3/pt3.h create mode 100644 drivers/media/pci/pt3/pt3_dma.c create mode 100644 drivers/media/pci/pt3/pt3_i2c.c create mode 100644 drivers/media/pci/saa7134/Kconfig create mode 100644 drivers/media/pci/saa7134/Makefile create mode 100644 drivers/media/pci/saa7134/saa7134-alsa.c create mode 100644 drivers/media/pci/saa7134/saa7134-cards.c create mode 100644 drivers/media/pci/saa7134/saa7134-core.c create mode 100644 drivers/media/pci/saa7134/saa7134-dvb.c create mode 100644 drivers/media/pci/saa7134/saa7134-empress.c create mode 100644 drivers/media/pci/saa7134/saa7134-go7007.c create mode 100644 drivers/media/pci/saa7134/saa7134-i2c.c create mode 100644 drivers/media/pci/saa7134/saa7134-input.c create mode 100644 drivers/media/pci/saa7134/saa7134-reg.h create mode 100644 drivers/media/pci/saa7134/saa7134-ts.c create mode 100644 drivers/media/pci/saa7134/saa7134-tvaudio.c create mode 100644 drivers/media/pci/saa7134/saa7134-vbi.c create mode 100644 drivers/media/pci/saa7134/saa7134-video.c create mode 100644 drivers/media/pci/saa7134/saa7134.h create mode 100644 drivers/media/pci/saa7146/Kconfig create mode 100644 drivers/media/pci/saa7146/Makefile create mode 100644 drivers/media/pci/saa7146/hexium_gemini.c create mode 100644 drivers/media/pci/saa7146/hexium_orion.c create mode 100644 drivers/media/pci/saa7146/mxb.c create mode 100644 drivers/media/pci/saa7164/Kconfig create mode 100644 drivers/media/pci/saa7164/Makefile create mode 100644 drivers/media/pci/saa7164/saa7164-api.c create mode 100644 drivers/media/pci/saa7164/saa7164-buffer.c create mode 100644 drivers/media/pci/saa7164/saa7164-bus.c create mode 100644 drivers/media/pci/saa7164/saa7164-cards.c create mode 100644 drivers/media/pci/saa7164/saa7164-cmd.c create mode 100644 drivers/media/pci/saa7164/saa7164-core.c create mode 100644 drivers/media/pci/saa7164/saa7164-dvb.c create mode 100644 drivers/media/pci/saa7164/saa7164-encoder.c create mode 100644 drivers/media/pci/saa7164/saa7164-fw.c create mode 100644 drivers/media/pci/saa7164/saa7164-i2c.c create mode 100644 drivers/media/pci/saa7164/saa7164-reg.h create mode 100644 drivers/media/pci/saa7164/saa7164-types.h create mode 100644 drivers/media/pci/saa7164/saa7164-vbi.c create mode 100644 drivers/media/pci/saa7164/saa7164.h create mode 100644 drivers/media/pci/smipcie/Kconfig create mode 100644 drivers/media/pci/smipcie/Makefile create mode 100644 drivers/media/pci/smipcie/smipcie-ir.c create mode 100644 drivers/media/pci/smipcie/smipcie-main.c create mode 100644 drivers/media/pci/smipcie/smipcie.h create mode 100644 drivers/media/pci/solo6x10/Kconfig create mode 100644 drivers/media/pci/solo6x10/Makefile create mode 100644 drivers/media/pci/solo6x10/solo6x10-core.c create mode 100644 drivers/media/pci/solo6x10/solo6x10-disp.c create mode 100644 drivers/media/pci/solo6x10/solo6x10-eeprom.c create mode 100644 drivers/media/pci/solo6x10/solo6x10-enc.c create mode 100644 drivers/media/pci/solo6x10/solo6x10-g723.c create mode 100644 drivers/media/pci/solo6x10/solo6x10-gpio.c create mode 100644 drivers/media/pci/solo6x10/solo6x10-i2c.c create mode 100644 drivers/media/pci/solo6x10/solo6x10-jpeg.h create mode 100644 drivers/media/pci/solo6x10/solo6x10-offsets.h create mode 100644 drivers/media/pci/solo6x10/solo6x10-p2m.c create mode 100644 drivers/media/pci/solo6x10/solo6x10-regs.h create mode 100644 drivers/media/pci/solo6x10/solo6x10-tw28.c create mode 100644 drivers/media/pci/solo6x10/solo6x10-tw28.h create mode 100644 drivers/media/pci/solo6x10/solo6x10-v4l2-enc.c create mode 100644 drivers/media/pci/solo6x10/solo6x10-v4l2.c create mode 100644 drivers/media/pci/solo6x10/solo6x10.h create mode 100644 drivers/media/pci/ttpci/Kconfig create mode 100644 drivers/media/pci/ttpci/Makefile create mode 100644 drivers/media/pci/ttpci/budget-av.c create mode 100644 drivers/media/pci/ttpci/budget-ci.c create mode 100644 drivers/media/pci/ttpci/budget-core.c create mode 100644 drivers/media/pci/ttpci/budget.c create mode 100644 drivers/media/pci/ttpci/budget.h create mode 100644 drivers/media/pci/tw5864/Kconfig create mode 100644 drivers/media/pci/tw5864/Makefile create mode 100644 drivers/media/pci/tw5864/tw5864-core.c create mode 100644 drivers/media/pci/tw5864/tw5864-h264.c create mode 100644 drivers/media/pci/tw5864/tw5864-reg.h create mode 100644 drivers/media/pci/tw5864/tw5864-util.c create mode 100644 drivers/media/pci/tw5864/tw5864-video.c create mode 100644 drivers/media/pci/tw5864/tw5864.h create mode 100644 drivers/media/pci/tw68/Kconfig create mode 100644 drivers/media/pci/tw68/Makefile create mode 100644 drivers/media/pci/tw68/tw68-core.c create mode 100644 drivers/media/pci/tw68/tw68-reg.h create mode 100644 drivers/media/pci/tw68/tw68-risc.c create mode 100644 drivers/media/pci/tw68/tw68-video.c create mode 100644 drivers/media/pci/tw68/tw68.h create mode 100644 drivers/media/pci/tw686x/Kconfig create mode 100644 drivers/media/pci/tw686x/Makefile create mode 100644 drivers/media/pci/tw686x/tw686x-audio.c create mode 100644 drivers/media/pci/tw686x/tw686x-core.c create mode 100644 drivers/media/pci/tw686x/tw686x-regs.h create mode 100644 drivers/media/pci/tw686x/tw686x-video.c create mode 100644 drivers/media/pci/tw686x/tw686x.h create mode 100644 drivers/media/pci/zoran/Kconfig create mode 100644 drivers/media/pci/zoran/Makefile create mode 100644 drivers/media/pci/zoran/videocodec.c create mode 100644 drivers/media/pci/zoran/videocodec.h create mode 100644 drivers/media/pci/zoran/zoran.h create mode 100644 drivers/media/pci/zoran/zoran_card.c create mode 100644 drivers/media/pci/zoran/zoran_card.h create mode 100644 drivers/media/pci/zoran/zoran_device.c create mode 100644 drivers/media/pci/zoran/zoran_device.h create mode 100644 drivers/media/pci/zoran/zoran_driver.c create mode 100644 drivers/media/pci/zoran/zr36016.c create mode 100644 drivers/media/pci/zoran/zr36016.h create mode 100644 drivers/media/pci/zoran/zr36050.c create mode 100644 drivers/media/pci/zoran/zr36050.h create mode 100644 drivers/media/pci/zoran/zr36057.h create mode 100644 drivers/media/pci/zoran/zr36060.c create mode 100644 drivers/media/pci/zoran/zr36060.h (limited to 'drivers/media/pci') diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig new file mode 100644 index 000000000..bfdb200f8 --- /dev/null +++ b/drivers/media/pci/Kconfig @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: GPL-2.0-only + +if PCI + +menuconfig MEDIA_PCI_SUPPORT + bool "Media PCI Adapters" + help + Enable media drivers for PCI/PCIe bus. + If you have such devices, say Y. + +if MEDIA_PCI_SUPPORT + +if MEDIA_CAMERA_SUPPORT + comment "Media capture support" + +source "drivers/media/pci/hws/Kconfig" +source "drivers/media/pci/mgb4/Kconfig" +source "drivers/media/pci/solo6x10/Kconfig" +source "drivers/media/pci/tw5864/Kconfig" +source "drivers/media/pci/tw68/Kconfig" +source "drivers/media/pci/tw686x/Kconfig" +source "drivers/media/pci/zoran/Kconfig" + +endif + +if MEDIA_ANALOG_TV_SUPPORT + comment "Media capture/analog TV support" + +source "drivers/media/pci/dt3155/Kconfig" +source "drivers/media/pci/ivtv/Kconfig" +source "drivers/media/pci/saa7146/Kconfig" + +endif + +if MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT + comment "Media capture/analog/hybrid TV support" + +source "drivers/media/pci/bt8xx/Kconfig" +source "drivers/media/pci/cobalt/Kconfig" +source "drivers/media/pci/cx18/Kconfig" +source "drivers/media/pci/cx23885/Kconfig" +source "drivers/media/pci/cx25821/Kconfig" +source "drivers/media/pci/cx88/Kconfig" +source "drivers/media/pci/saa7134/Kconfig" +source "drivers/media/pci/saa7164/Kconfig" + +endif + +if MEDIA_DIGITAL_TV_SUPPORT + comment "Media digital TV PCI Adapters" + +source "drivers/media/pci/b2c2/Kconfig" +source "drivers/media/pci/ddbridge/Kconfig" +source "drivers/media/pci/dm1105/Kconfig" +source "drivers/media/pci/mantis/Kconfig" +source "drivers/media/pci/netup_unidvb/Kconfig" +source "drivers/media/pci/ngene/Kconfig" +source "drivers/media/pci/pluto2/Kconfig" +source "drivers/media/pci/pt1/Kconfig" +source "drivers/media/pci/pt3/Kconfig" +source "drivers/media/pci/smipcie/Kconfig" +source "drivers/media/pci/ttpci/Kconfig" + +endif + +config VIDEO_PCI_SKELETON + tristate "Skeleton PCI V4L2 driver" + depends on SAMPLES + depends on MEDIA_TEST_SUPPORT + depends on PCI && VIDEO_DEV + select VIDEOBUF2_MEMOPS + select VIDEOBUF2_DMA_CONTIG + help + Enable build of the skeleton PCI driver, used as a reference + when developing new drivers. + +source "drivers/media/pci/intel/Kconfig" + +endif #MEDIA_PCI_SUPPORT +endif #PCI diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile new file mode 100644 index 000000000..c4508b672 --- /dev/null +++ b/drivers/media/pci/Makefile @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the kernel multimedia device drivers. +# + +# Please keep it alphabetically sorted by directory +# (e. g. LC_ALL=C sort Makefile) +obj-y += ttpci/ \ + b2c2/ \ + pluto2/ \ + dm1105/ \ + pt1/ \ + pt3/ \ + mantis/ \ + ngene/ \ + ddbridge/ \ + saa7146/ \ + smipcie/ \ + netup_unidvb/ \ + intel/ + +# Please keep it alphabetically sorted by Kconfig name +# (e. g. LC_ALL=C sort Makefile) + +obj-$(CONFIG_VIDEO_BT848) += bt8xx/ +obj-$(CONFIG_VIDEO_COBALT) += cobalt/ +obj-$(CONFIG_VIDEO_CX18) += cx18/ +obj-$(CONFIG_VIDEO_CX23885) += cx23885/ +obj-$(CONFIG_VIDEO_CX25821) += cx25821/ +obj-$(CONFIG_VIDEO_CX88) += cx88/ +obj-$(CONFIG_VIDEO_DT3155) += dt3155/ +obj-$(CONFIG_VIDEO_HWS) += hws/ +obj-$(CONFIG_VIDEO_IVTV) += ivtv/ +obj-$(CONFIG_VIDEO_MGB4) += mgb4/ +obj-$(CONFIG_VIDEO_SAA7134) += saa7134/ +obj-$(CONFIG_VIDEO_SAA7164) += saa7164/ +obj-$(CONFIG_VIDEO_SOLO6X10) += solo6x10/ +obj-$(CONFIG_VIDEO_TW5864) += tw5864/ +obj-$(CONFIG_VIDEO_TW686X) += tw686x/ +obj-$(CONFIG_VIDEO_TW68) += tw68/ +obj-$(CONFIG_VIDEO_ZORAN) += zoran/ diff --git a/drivers/media/pci/b2c2/Kconfig b/drivers/media/pci/b2c2/Kconfig new file mode 100644 index 000000000..0a7d1e178 --- /dev/null +++ b/drivers/media/pci/b2c2/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DVB_B2C2_FLEXCOP_PCI + tristate "Technisat/B2C2 Air/Sky/Cable2PC PCI" + depends on DVB_CORE && I2C + help + Support for the Air/Sky/CableStar2 PCI card (DVB/ATSC) by Technisat/B2C2. + + Say Y if you own such a device and want to use it. + +config DVB_B2C2_FLEXCOP_PCI_DEBUG + bool "Enable debug for the B2C2 FlexCop drivers" + depends on DVB_B2C2_FLEXCOP_PCI + select DVB_B2C2_FLEXCOP_DEBUG + help + Say Y if you want to enable the module option to control debug messages + of all B2C2 FlexCop drivers. diff --git a/drivers/media/pci/b2c2/Makefile b/drivers/media/pci/b2c2/Makefile new file mode 100644 index 000000000..14ed6e441 --- /dev/null +++ b/drivers/media/pci/b2c2/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +ifneq ($(CONFIG_DVB_B2C2_FLEXCOP_PCI),) +b2c2-flexcop-pci-objs += flexcop-dma.o +endif + +b2c2-flexcop-pci-objs += flexcop-pci.o +obj-$(CONFIG_DVB_B2C2_FLEXCOP_PCI) += b2c2-flexcop-pci.o + +ccflags-y += -I $(srctree)/drivers/media/common/b2c2/ diff --git a/drivers/media/pci/b2c2/flexcop-dma.c b/drivers/media/pci/b2c2/flexcop-dma.c new file mode 100644 index 000000000..2ef97be4d --- /dev/null +++ b/drivers/media/pci/b2c2/flexcop-dma.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III + * flexcop-dma.c - configuring and controlling the DMA of the FlexCop + * see flexcop.c for copyright information + */ +#include "flexcop.h" + +int flexcop_dma_allocate(struct pci_dev *pdev, + struct flexcop_dma *dma, u32 size) +{ + u8 *tcpu; + dma_addr_t tdma = 0; + + if (size % 2) { + err("dma buffersize has to be even."); + return -EINVAL; + } + + tcpu = dma_alloc_coherent(&pdev->dev, size, &tdma, GFP_KERNEL); + if (tcpu != NULL) { + dma->pdev = pdev; + dma->cpu_addr0 = tcpu; + dma->dma_addr0 = tdma; + dma->cpu_addr1 = tcpu + size/2; + dma->dma_addr1 = tdma + size/2; + dma->size = size/2; + return 0; + } + return -ENOMEM; +} +EXPORT_SYMBOL(flexcop_dma_allocate); + +void flexcop_dma_free(struct flexcop_dma *dma) +{ + dma_free_coherent(&dma->pdev->dev, dma->size * 2, dma->cpu_addr0, + dma->dma_addr0); + memset(dma, 0, sizeof(struct flexcop_dma)); +} +EXPORT_SYMBOL(flexcop_dma_free); + +int flexcop_dma_config(struct flexcop_device *fc, + struct flexcop_dma *dma, + flexcop_dma_index_t dma_idx) +{ + flexcop_ibi_value v0x0, v0x4, v0xc; + + v0x0.raw = v0x4.raw = v0xc.raw = 0; + v0x0.dma_0x0.dma_address0 = dma->dma_addr0 >> 2; + v0xc.dma_0xc.dma_address1 = dma->dma_addr1 >> 2; + v0x4.dma_0x4_write.dma_addr_size = dma->size / 4; + + if ((dma_idx & FC_DMA_1) == dma_idx) { + fc->write_ibi_reg(fc, dma1_000, v0x0); + fc->write_ibi_reg(fc, dma1_004, v0x4); + fc->write_ibi_reg(fc, dma1_00c, v0xc); + } else if ((dma_idx & FC_DMA_2) == dma_idx) { + fc->write_ibi_reg(fc, dma2_010, v0x0); + fc->write_ibi_reg(fc, dma2_014, v0x4); + fc->write_ibi_reg(fc, dma2_01c, v0xc); + } else { + err("either DMA1 or DMA2 can be configured within one %s call.", + __func__); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(flexcop_dma_config); + +/* start the DMA transfers, but not the DMA IRQs */ +int flexcop_dma_xfer_control(struct flexcop_device *fc, + flexcop_dma_index_t dma_idx, + flexcop_dma_addr_index_t index, + int onoff) +{ + flexcop_ibi_value v0x0, v0xc; + flexcop_ibi_register r0x0, r0xc; + + if ((dma_idx & FC_DMA_1) == dma_idx) { + r0x0 = dma1_000; + r0xc = dma1_00c; + } else if ((dma_idx & FC_DMA_2) == dma_idx) { + r0x0 = dma2_010; + r0xc = dma2_01c; + } else { + err("transfer DMA1 or DMA2 can be started within one %s call.", + __func__); + return -EINVAL; + } + + v0x0 = fc->read_ibi_reg(fc, r0x0); + v0xc = fc->read_ibi_reg(fc, r0xc); + + deb_rdump("reg: %03x: %x\n", r0x0, v0x0.raw); + deb_rdump("reg: %03x: %x\n", r0xc, v0xc.raw); + + if (index & FC_DMA_SUBADDR_0) + v0x0.dma_0x0.dma_0start = onoff; + + if (index & FC_DMA_SUBADDR_1) + v0xc.dma_0xc.dma_1start = onoff; + + fc->write_ibi_reg(fc, r0x0, v0x0); + fc->write_ibi_reg(fc, r0xc, v0xc); + + deb_rdump("reg: %03x: %x\n", r0x0, v0x0.raw); + deb_rdump("reg: %03x: %x\n", r0xc, v0xc.raw); + return 0; +} +EXPORT_SYMBOL(flexcop_dma_xfer_control); + +static int flexcop_dma_remap(struct flexcop_device *fc, + flexcop_dma_index_t dma_idx, + int onoff) +{ + flexcop_ibi_register r = (dma_idx & FC_DMA_1) ? dma1_00c : dma2_01c; + flexcop_ibi_value v = fc->read_ibi_reg(fc, r); + + deb_info("%s\n", __func__); + v.dma_0xc.remap_enable = onoff; + fc->write_ibi_reg(fc, r, v); + return 0; +} + +int flexcop_dma_control_timer_irq(struct flexcop_device *fc, + flexcop_dma_index_t no, + int onoff) +{ + flexcop_ibi_value v = fc->read_ibi_reg(fc, ctrl_208); + + if (no & FC_DMA_1) + v.ctrl_208.DMA1_Timer_Enable_sig = onoff; + + if (no & FC_DMA_2) + v.ctrl_208.DMA2_Timer_Enable_sig = onoff; + + fc->write_ibi_reg(fc, ctrl_208, v); + return 0; +} +EXPORT_SYMBOL(flexcop_dma_control_timer_irq); + +/* 1 cycles = 1.97 msec */ +int flexcop_dma_config_timer(struct flexcop_device *fc, + flexcop_dma_index_t dma_idx, u8 cycles) +{ + flexcop_ibi_register r = (dma_idx & FC_DMA_1) ? dma1_004 : dma2_014; + flexcop_ibi_value v = fc->read_ibi_reg(fc, r); + + flexcop_dma_remap(fc, dma_idx, 0); + + deb_info("%s\n", __func__); + v.dma_0x4_write.dmatimer = cycles; + fc->write_ibi_reg(fc, r, v); + return 0; +} +EXPORT_SYMBOL(flexcop_dma_config_timer); + diff --git a/drivers/media/pci/b2c2/flexcop-pci.c b/drivers/media/pci/b2c2/flexcop-pci.c new file mode 100644 index 000000000..ab53c5b02 --- /dev/null +++ b/drivers/media/pci/b2c2/flexcop-pci.c @@ -0,0 +1,440 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Linux driver the digital TV devices equipped with B2C2 FlexcopII(b)/III + * flexcop-pci.c - covers the PCI part including DMA transfers + * see flexcop.c for copyright information + */ + +#define FC_LOG_PREFIX "flexcop-pci" +#include "flexcop-common.h" + +static int enable_pid_filtering = 1; +module_param(enable_pid_filtering, int, 0444); +MODULE_PARM_DESC(enable_pid_filtering, + "enable hardware pid filtering: supported values: 0 (fullts), 1"); + +static int irq_chk_intv = 100; +module_param(irq_chk_intv, int, 0644); +MODULE_PARM_DESC(irq_chk_intv, "set the interval for IRQ streaming watchdog."); + +#ifdef CONFIG_DVB_B2C2_FLEXCOP_DEBUG +#define dprintk(level, args...) \ + do { if ((debug & (level))) printk(args); } while (0) +#define DEBSTATUS "" +#else +#define dprintk(level, args...) no_printk(args) +#define DEBSTATUS " (debugging is not enabled)" +#endif + +#define deb_info(args...) dprintk(0x01, args) +#define deb_reg(args...) dprintk(0x02, args) +#define deb_ts(args...) dprintk(0x04, args) +#define deb_irq(args...) dprintk(0x08, args) +#define deb_chk(args...) dprintk(0x10, args) + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, + "set debug level (1=info,2=regs,4=TS,8=irqdma,16=check (|-able))." + DEBSTATUS); + +#define DRIVER_VERSION "0.1" +#define DRIVER_NAME "flexcop-pci" +#define DRIVER_AUTHOR "Patrick Boettcher " + +struct flexcop_pci { + struct pci_dev *pdev; + +#define FC_PCI_INIT 0x01 +#define FC_PCI_DMA_INIT 0x02 + int init_state; + + void __iomem *io_mem; + u32 irq; + /* buffersize (at least for DMA1, need to be % 188 == 0, + * this logic is required */ +#define FC_DEFAULT_DMA1_BUFSIZE (1280 * 188) +#define FC_DEFAULT_DMA2_BUFSIZE (10 * 188) + struct flexcop_dma dma[2]; + + int active_dma1_addr; /* 0 = addr0 of dma1; 1 = addr1 of dma1 */ + u32 last_dma1_cur_pos; + /* position of the pointer last time the timer/packet irq occurred */ + int count; + int count_prev; + int stream_problem; + + spinlock_t irq_lock; + unsigned long last_irq; + + struct delayed_work irq_check_work; + struct flexcop_device *fc_dev; +}; + +static int lastwreg, lastwval, lastrreg, lastrval; + +static flexcop_ibi_value flexcop_pci_read_ibi_reg(struct flexcop_device *fc, + flexcop_ibi_register r) +{ + struct flexcop_pci *fc_pci = fc->bus_specific; + flexcop_ibi_value v; + v.raw = readl(fc_pci->io_mem + r); + + if (lastrreg != r || lastrval != v.raw) { + lastrreg = r; lastrval = v.raw; + deb_reg("new rd: %3x: %08x\n", r, v.raw); + } + + return v; +} + +static int flexcop_pci_write_ibi_reg(struct flexcop_device *fc, + flexcop_ibi_register r, flexcop_ibi_value v) +{ + struct flexcop_pci *fc_pci = fc->bus_specific; + + if (lastwreg != r || lastwval != v.raw) { + lastwreg = r; lastwval = v.raw; + deb_reg("new wr: %3x: %08x\n", r, v.raw); + } + + writel(v.raw, fc_pci->io_mem + r); + return 0; +} + +static void flexcop_pci_irq_check_work(struct work_struct *work) +{ + struct flexcop_pci *fc_pci = + container_of(work, struct flexcop_pci, irq_check_work.work); + struct flexcop_device *fc = fc_pci->fc_dev; + + if (fc->feedcount) { + + if (fc_pci->count == fc_pci->count_prev) { + deb_chk("no IRQ since the last check\n"); + if (fc_pci->stream_problem++ == 3) { + struct dvb_demux_feed *feed; + deb_info("flexcop-pci: stream problem, resetting pid filter\n"); + + spin_lock_irq(&fc->demux.lock); + list_for_each_entry(feed, &fc->demux.feed_list, + list_head) { + flexcop_pid_feed_control(fc, feed, 0); + } + + list_for_each_entry(feed, &fc->demux.feed_list, + list_head) { + flexcop_pid_feed_control(fc, feed, 1); + } + spin_unlock_irq(&fc->demux.lock); + + fc_pci->stream_problem = 0; + } + } else { + fc_pci->stream_problem = 0; + fc_pci->count_prev = fc_pci->count; + } + } + + schedule_delayed_work(&fc_pci->irq_check_work, + msecs_to_jiffies(irq_chk_intv < 100 ? 100 : irq_chk_intv)); +} + +/* When PID filtering is turned on, we use the timer IRQ, because small amounts + * of data need to be passed to the user space instantly as well. When PID + * filtering is turned off, we use the page-change-IRQ */ +static irqreturn_t flexcop_pci_isr(int irq, void *dev_id) +{ + struct flexcop_pci *fc_pci = dev_id; + struct flexcop_device *fc = fc_pci->fc_dev; + unsigned long flags; + flexcop_ibi_value v; + irqreturn_t ret = IRQ_HANDLED; + + spin_lock_irqsave(&fc_pci->irq_lock, flags); + v = fc->read_ibi_reg(fc, irq_20c); + + /* errors */ + if (v.irq_20c.Data_receiver_error) + deb_chk("data receiver error\n"); + if (v.irq_20c.Continuity_error_flag) + deb_chk("Continuity error flag is set\n"); + if (v.irq_20c.LLC_SNAP_FLAG_set) + deb_chk("LLC_SNAP_FLAG_set is set\n"); + if (v.irq_20c.Transport_Error) + deb_chk("Transport error\n"); + + if ((fc_pci->count % 1000) == 0) + deb_chk("%d valid irq took place so far\n", fc_pci->count); + + if (v.irq_20c.DMA1_IRQ_Status == 1) { + if (fc_pci->active_dma1_addr == 0) + flexcop_pass_dmx_packets(fc_pci->fc_dev, + fc_pci->dma[0].cpu_addr0, + fc_pci->dma[0].size / 188); + else + flexcop_pass_dmx_packets(fc_pci->fc_dev, + fc_pci->dma[0].cpu_addr1, + fc_pci->dma[0].size / 188); + + deb_irq("page change to page: %d\n",!fc_pci->active_dma1_addr); + fc_pci->active_dma1_addr = !fc_pci->active_dma1_addr; + /* for the timer IRQ we only can use buffer dmx feeding, because we don't have + * complete TS packets when reading from the DMA memory */ + } else if (v.irq_20c.DMA1_Timer_Status == 1) { + dma_addr_t cur_addr = + fc->read_ibi_reg(fc,dma1_008).dma_0x8.dma_cur_addr << 2; + u32 cur_pos = cur_addr - fc_pci->dma[0].dma_addr0; + if (cur_pos > fc_pci->dma[0].size * 2) + goto error; + + deb_irq("%u irq: %08x cur_addr: %llx: cur_pos: %08x, last_cur_pos: %08x ", + jiffies_to_usecs(jiffies - fc_pci->last_irq), + v.raw, (unsigned long long)cur_addr, cur_pos, + fc_pci->last_dma1_cur_pos); + fc_pci->last_irq = jiffies; + + /* buffer end was reached, restarted from the beginning + * pass the data from last_cur_pos to the buffer end to the demux + */ + if (cur_pos < fc_pci->last_dma1_cur_pos) { + deb_irq(" end was reached: passing %d bytes ", + (fc_pci->dma[0].size*2 - 1) - + fc_pci->last_dma1_cur_pos); + flexcop_pass_dmx_data(fc_pci->fc_dev, + fc_pci->dma[0].cpu_addr0 + + fc_pci->last_dma1_cur_pos, + (fc_pci->dma[0].size*2) - + fc_pci->last_dma1_cur_pos); + fc_pci->last_dma1_cur_pos = 0; + } + + if (cur_pos > fc_pci->last_dma1_cur_pos) { + deb_irq(" passing %d bytes ", + cur_pos - fc_pci->last_dma1_cur_pos); + flexcop_pass_dmx_data(fc_pci->fc_dev, + fc_pci->dma[0].cpu_addr0 + + fc_pci->last_dma1_cur_pos, + cur_pos - fc_pci->last_dma1_cur_pos); + } + deb_irq("\n"); + + fc_pci->last_dma1_cur_pos = cur_pos; + fc_pci->count++; + } else { + deb_irq("isr for flexcop called, apparently without reason (%08x)\n", + v.raw); + ret = IRQ_NONE; + } + +error: + spin_unlock_irqrestore(&fc_pci->irq_lock, flags); + return ret; +} + +static int flexcop_pci_stream_control(struct flexcop_device *fc, int onoff) +{ + struct flexcop_pci *fc_pci = fc->bus_specific; + if (onoff) { + flexcop_dma_config(fc, &fc_pci->dma[0], FC_DMA_1); + flexcop_dma_config(fc, &fc_pci->dma[1], FC_DMA_2); + flexcop_dma_config_timer(fc, FC_DMA_1, 0); + flexcop_dma_xfer_control(fc, FC_DMA_1, + FC_DMA_SUBADDR_0 | FC_DMA_SUBADDR_1, 1); + deb_irq("DMA xfer enabled\n"); + + fc_pci->last_dma1_cur_pos = 0; + flexcop_dma_control_timer_irq(fc, FC_DMA_1, 1); + deb_irq("IRQ enabled\n"); + fc_pci->count_prev = fc_pci->count; + } else { + flexcop_dma_control_timer_irq(fc, FC_DMA_1, 0); + deb_irq("IRQ disabled\n"); + + flexcop_dma_xfer_control(fc, FC_DMA_1, + FC_DMA_SUBADDR_0 | FC_DMA_SUBADDR_1, 0); + deb_irq("DMA xfer disabled\n"); + } + return 0; +} + +static int flexcop_pci_dma_init(struct flexcop_pci *fc_pci) +{ + int ret; + ret = flexcop_dma_allocate(fc_pci->pdev, &fc_pci->dma[0], + FC_DEFAULT_DMA1_BUFSIZE); + if (ret != 0) + return ret; + + ret = flexcop_dma_allocate(fc_pci->pdev, &fc_pci->dma[1], + FC_DEFAULT_DMA2_BUFSIZE); + if (ret != 0) { + flexcop_dma_free(&fc_pci->dma[0]); + return ret; + } + + flexcop_sram_set_dest(fc_pci->fc_dev, FC_SRAM_DEST_MEDIA | + FC_SRAM_DEST_NET, FC_SRAM_DEST_TARGET_DMA1); + flexcop_sram_set_dest(fc_pci->fc_dev, FC_SRAM_DEST_CAO | + FC_SRAM_DEST_CAI, FC_SRAM_DEST_TARGET_DMA2); + fc_pci->init_state |= FC_PCI_DMA_INIT; + return ret; +} + +static void flexcop_pci_dma_exit(struct flexcop_pci *fc_pci) +{ + if (fc_pci->init_state & FC_PCI_DMA_INIT) { + flexcop_dma_free(&fc_pci->dma[0]); + flexcop_dma_free(&fc_pci->dma[1]); + } + fc_pci->init_state &= ~FC_PCI_DMA_INIT; +} + +static int flexcop_pci_init(struct flexcop_pci *fc_pci) +{ + int ret; + + info("card revision %x", fc_pci->pdev->revision); + + if ((ret = pci_enable_device(fc_pci->pdev)) != 0) + return ret; + pci_set_master(fc_pci->pdev); + + if ((ret = pci_request_regions(fc_pci->pdev, DRIVER_NAME)) != 0) + goto err_pci_disable_device; + + fc_pci->io_mem = pci_iomap(fc_pci->pdev, 0, 0x800); + + if (!fc_pci->io_mem) { + err("cannot map io memory\n"); + ret = -EIO; + goto err_pci_release_regions; + } + + pci_set_drvdata(fc_pci->pdev, fc_pci); + spin_lock_init(&fc_pci->irq_lock); + if ((ret = request_irq(fc_pci->pdev->irq, flexcop_pci_isr, + IRQF_SHARED, DRIVER_NAME, fc_pci)) != 0) + goto err_pci_iounmap; + + fc_pci->init_state |= FC_PCI_INIT; + return ret; + +err_pci_iounmap: + pci_iounmap(fc_pci->pdev, fc_pci->io_mem); +err_pci_release_regions: + pci_release_regions(fc_pci->pdev); +err_pci_disable_device: + pci_disable_device(fc_pci->pdev); + return ret; +} + +static void flexcop_pci_exit(struct flexcop_pci *fc_pci) +{ + if (fc_pci->init_state & FC_PCI_INIT) { + free_irq(fc_pci->pdev->irq, fc_pci); + pci_iounmap(fc_pci->pdev, fc_pci->io_mem); + pci_release_regions(fc_pci->pdev); + pci_disable_device(fc_pci->pdev); + } + fc_pci->init_state &= ~FC_PCI_INIT; +} + +static int flexcop_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct flexcop_device *fc; + struct flexcop_pci *fc_pci; + int ret = -ENOMEM; + + if ((fc = flexcop_device_kmalloc(sizeof(struct flexcop_pci))) == NULL) { + err("out of memory\n"); + return -ENOMEM; + } + + /* general flexcop init */ + fc_pci = fc->bus_specific; + fc_pci->fc_dev = fc; + + fc->read_ibi_reg = flexcop_pci_read_ibi_reg; + fc->write_ibi_reg = flexcop_pci_write_ibi_reg; + fc->i2c_request = flexcop_i2c_request; + fc->get_mac_addr = flexcop_eeprom_check_mac_addr; + fc->stream_control = flexcop_pci_stream_control; + + if (enable_pid_filtering) + info("will use the HW PID filter."); + else + info("will pass the complete TS to the demuxer."); + + fc->pid_filtering = enable_pid_filtering; + fc->bus_type = FC_PCI; + fc->dev = &pdev->dev; + fc->owner = THIS_MODULE; + + /* bus specific part */ + fc_pci->pdev = pdev; + if ((ret = flexcop_pci_init(fc_pci)) != 0) + goto err_kfree; + + /* init flexcop */ + if ((ret = flexcop_device_initialize(fc)) != 0) + goto err_pci_exit; + + /* init dma */ + if ((ret = flexcop_pci_dma_init(fc_pci)) != 0) + goto err_fc_exit; + + INIT_DELAYED_WORK(&fc_pci->irq_check_work, flexcop_pci_irq_check_work); + + if (irq_chk_intv > 0) + schedule_delayed_work(&fc_pci->irq_check_work, + msecs_to_jiffies(irq_chk_intv < 100 ? + 100 : + irq_chk_intv)); + return ret; + +err_fc_exit: + flexcop_device_exit(fc); +err_pci_exit: + flexcop_pci_exit(fc_pci); +err_kfree: + flexcop_device_kfree(fc); + return ret; +} + +/* in theory every _exit function should be called exactly two times, + * here and in the bail-out-part of the _init-function + */ +static void flexcop_pci_remove(struct pci_dev *pdev) +{ + struct flexcop_pci *fc_pci = pci_get_drvdata(pdev); + + if (irq_chk_intv > 0) + cancel_delayed_work_sync(&fc_pci->irq_check_work); + + flexcop_pci_dma_exit(fc_pci); + flexcop_device_exit(fc_pci->fc_dev); + flexcop_pci_exit(fc_pci); + flexcop_device_kfree(fc_pci->fc_dev); +} + +static const struct pci_device_id flexcop_pci_tbl[] = { + { PCI_DEVICE(0x13d0, 0x2103) }, + { }, +}; + +MODULE_DEVICE_TABLE(pci, flexcop_pci_tbl); + +static struct pci_driver flexcop_pci_driver = { + .name = "b2c2_flexcop_pci", + .id_table = flexcop_pci_tbl, + .probe = flexcop_pci_probe, + .remove = flexcop_pci_remove, +}; + +module_pci_driver(flexcop_pci_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_NAME); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/bt8xx/Kconfig b/drivers/media/pci/bt8xx/Kconfig new file mode 100644 index 000000000..2f7762824 --- /dev/null +++ b/drivers/media/pci/bt8xx/Kconfig @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_BT848 + tristate "BT848 Video For Linux" + depends on PCI && I2C && VIDEO_DEV + select I2C_ALGOBIT + select VIDEOBUF2_DMA_SG + depends on RC_CORE + depends on MEDIA_RADIO_SUPPORT + select VIDEO_TUNER + select VIDEO_TVEEPROM + select VIDEO_MSP3400 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_TVAUDIO if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_TDA7432 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_SAA6588 if MEDIA_SUBDRV_AUTOSELECT + select RADIO_ADAPTERS + select RADIO_TEA575X + help + Support for BT848 based frame grabber boards. This includes + the Miro, Hauppauge and STB boards. Please read the material in + for more information. + + To compile this driver as a module, choose M here: the + module will be called bttv. + +config DVB_BT8XX + tristate "DVB/ATSC Support for bt878 based TV cards" + depends on DVB_CORE && PCI && I2C && VIDEO_BT848 + select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT + select DVB_SP887X if MEDIA_SUBDRV_AUTOSELECT + select DVB_NXT6000 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CX24110 if MEDIA_SUBDRV_AUTOSELECT + select DVB_OR51211 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT + select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT + help + Support for PCI cards based on the Bt8xx PCI bridge. Examples are + the Nebula cards, the Pinnacle PCTV cards, the Twinhan DST cards, + the pcHDTV HD2000 cards, the DViCO FusionHDTV Lite cards, and + some AVerMedia cards. + + Since these cards have no MPEG decoder onboard, they transmit + only compressed MPEG data over the PCI bus, so you need + an external software decoder to watch TV on your computer. + + Say Y if you own such a device and want to use it. diff --git a/drivers/media/pci/bt8xx/Makefile b/drivers/media/pci/bt8xx/Makefile new file mode 100644 index 000000000..69bc0d9c4 --- /dev/null +++ b/drivers/media/pci/bt8xx/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +bttv-objs := bttv-driver.o bttv-cards.o bttv-if.o \ + bttv-risc.o bttv-vbi.o bttv-i2c.o bttv-gpio.o \ + bttv-input.o bttv-audio-hook.o btcx-risc.o + +obj-$(CONFIG_VIDEO_BT848) += bttv.o +obj-$(CONFIG_DVB_BT8XX) += bt878.o dvb-bt8xx.o dst.o dst_ca.o + +ccflags-y += -I $(srctree)/drivers/media/dvb-frontends +ccflags-y += -I $(srctree)/drivers/media/tuners diff --git a/drivers/media/pci/bt8xx/bt848.h b/drivers/media/pci/bt8xx/bt848.h new file mode 100644 index 000000000..c8a0e1ab0 --- /dev/null +++ b/drivers/media/pci/bt8xx/bt848.h @@ -0,0 +1,365 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + bt848.h - Bt848 register offsets + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + +*/ + +#ifndef _BT848_H_ +#define _BT848_H_ + +#ifndef PCI_VENDOR_ID_BROOKTREE +#define PCI_VENDOR_ID_BROOKTREE 0x109e +#endif +#ifndef PCI_DEVICE_ID_BT848 +#define PCI_DEVICE_ID_BT848 0x350 +#endif +#ifndef PCI_DEVICE_ID_BT849 +#define PCI_DEVICE_ID_BT849 0x351 +#endif +#ifndef PCI_DEVICE_ID_FUSION879 +#define PCI_DEVICE_ID_FUSION879 0x36c +#endif + +#ifndef PCI_DEVICE_ID_BT878 +#define PCI_DEVICE_ID_BT878 0x36e +#endif +#ifndef PCI_DEVICE_ID_BT879 +#define PCI_DEVICE_ID_BT879 0x36f +#endif + +/* Brooktree 848 registers */ + +#define BT848_DSTATUS 0x000 +#define BT848_DSTATUS_PRES (1<<7) +#define BT848_DSTATUS_HLOC (1<<6) +#define BT848_DSTATUS_FIELD (1<<5) +#define BT848_DSTATUS_NUML (1<<4) +#define BT848_DSTATUS_CSEL (1<<3) +#define BT848_DSTATUS_PLOCK (1<<2) +#define BT848_DSTATUS_LOF (1<<1) +#define BT848_DSTATUS_COF (1<<0) + +#define BT848_IFORM 0x004 +#define BT848_IFORM_HACTIVE (1<<7) +#define BT848_IFORM_MUXSEL (3<<5) +#define BT848_IFORM_MUX0 (2<<5) +#define BT848_IFORM_MUX1 (3<<5) +#define BT848_IFORM_MUX2 (1<<5) +#define BT848_IFORM_XTSEL (3<<3) +#define BT848_IFORM_XT0 (1<<3) +#define BT848_IFORM_XT1 (2<<3) +#define BT848_IFORM_XTAUTO (3<<3) +#define BT848_IFORM_XTBOTH (3<<3) +#define BT848_IFORM_NTSC 1 +#define BT848_IFORM_NTSC_J 2 +#define BT848_IFORM_PAL_BDGHI 3 +#define BT848_IFORM_PAL_M 4 +#define BT848_IFORM_PAL_N 5 +#define BT848_IFORM_SECAM 6 +#define BT848_IFORM_PAL_NC 7 +#define BT848_IFORM_AUTO 0 +#define BT848_IFORM_NORM 7 + +#define BT848_TDEC 0x008 +#define BT848_TDEC_DEC_FIELD (1<<7) +#define BT848_TDEC_FLDALIGN (1<<6) +#define BT848_TDEC_DEC_RAT (0x1f) + +#define BT848_E_CROP 0x00C +#define BT848_O_CROP 0x08C + +#define BT848_E_VDELAY_LO 0x010 +#define BT848_O_VDELAY_LO 0x090 + +#define BT848_E_VACTIVE_LO 0x014 +#define BT848_O_VACTIVE_LO 0x094 + +#define BT848_E_HDELAY_LO 0x018 +#define BT848_O_HDELAY_LO 0x098 + +#define BT848_E_HACTIVE_LO 0x01C +#define BT848_O_HACTIVE_LO 0x09C + +#define BT848_E_HSCALE_HI 0x020 +#define BT848_O_HSCALE_HI 0x0A0 + +#define BT848_E_HSCALE_LO 0x024 +#define BT848_O_HSCALE_LO 0x0A4 + +#define BT848_BRIGHT 0x028 + +#define BT848_E_CONTROL 0x02C +#define BT848_O_CONTROL 0x0AC +#define BT848_CONTROL_LNOTCH (1<<7) +#define BT848_CONTROL_COMP (1<<6) +#define BT848_CONTROL_LDEC (1<<5) +#define BT848_CONTROL_CBSENSE (1<<4) +#define BT848_CONTROL_CON_MSB (1<<2) +#define BT848_CONTROL_SAT_U_MSB (1<<1) +#define BT848_CONTROL_SAT_V_MSB (1<<0) + +#define BT848_CONTRAST_LO 0x030 +#define BT848_SAT_U_LO 0x034 +#define BT848_SAT_V_LO 0x038 +#define BT848_HUE 0x03C + +#define BT848_E_SCLOOP 0x040 +#define BT848_O_SCLOOP 0x0C0 +#define BT848_SCLOOP_CAGC (1<<6) +#define BT848_SCLOOP_CKILL (1<<5) +#define BT848_SCLOOP_HFILT_AUTO (0<<3) +#define BT848_SCLOOP_HFILT_CIF (1<<3) +#define BT848_SCLOOP_HFILT_QCIF (2<<3) +#define BT848_SCLOOP_HFILT_ICON (3<<3) + +#define BT848_SCLOOP_PEAK (1<<7) +#define BT848_SCLOOP_HFILT_MINP (1<<3) +#define BT848_SCLOOP_HFILT_MEDP (2<<3) +#define BT848_SCLOOP_HFILT_MAXP (3<<3) + + +#define BT848_OFORM 0x048 +#define BT848_OFORM_RANGE (1<<7) +#define BT848_OFORM_CORE0 (0<<5) +#define BT848_OFORM_CORE8 (1<<5) +#define BT848_OFORM_CORE16 (2<<5) +#define BT848_OFORM_CORE32 (3<<5) + +#define BT848_E_VSCALE_HI 0x04C +#define BT848_O_VSCALE_HI 0x0CC +#define BT848_VSCALE_YCOMB (1<<7) +#define BT848_VSCALE_COMB (1<<6) +#define BT848_VSCALE_INT (1<<5) +#define BT848_VSCALE_HI 15 + +#define BT848_E_VSCALE_LO 0x050 +#define BT848_O_VSCALE_LO 0x0D0 +#define BT848_TEST 0x054 +#define BT848_ADELAY 0x060 +#define BT848_BDELAY 0x064 + +#define BT848_ADC 0x068 +#define BT848_ADC_RESERVED (2<<6) +#define BT848_ADC_SYNC_T (1<<5) +#define BT848_ADC_AGC_EN (1<<4) +#define BT848_ADC_CLK_SLEEP (1<<3) +#define BT848_ADC_Y_SLEEP (1<<2) +#define BT848_ADC_C_SLEEP (1<<1) +#define BT848_ADC_CRUSH (1<<0) + +#define BT848_WC_UP 0x044 +#define BT848_WC_DOWN 0x078 + +#define BT848_E_VTC 0x06C +#define BT848_O_VTC 0x0EC +#define BT848_VTC_HSFMT (1<<7) +#define BT848_VTC_VFILT_2TAP 0 +#define BT848_VTC_VFILT_3TAP 1 +#define BT848_VTC_VFILT_4TAP 2 +#define BT848_VTC_VFILT_5TAP 3 + +#define BT848_SRESET 0x07C + +#define BT848_COLOR_FMT 0x0D4 +#define BT848_COLOR_FMT_O_RGB32 (0<<4) +#define BT848_COLOR_FMT_O_RGB24 (1<<4) +#define BT848_COLOR_FMT_O_RGB16 (2<<4) +#define BT848_COLOR_FMT_O_RGB15 (3<<4) +#define BT848_COLOR_FMT_O_YUY2 (4<<4) +#define BT848_COLOR_FMT_O_BtYUV (5<<4) +#define BT848_COLOR_FMT_O_Y8 (6<<4) +#define BT848_COLOR_FMT_O_RGB8 (7<<4) +#define BT848_COLOR_FMT_O_YCrCb422 (8<<4) +#define BT848_COLOR_FMT_O_YCrCb411 (9<<4) +#define BT848_COLOR_FMT_O_RAW (14<<4) +#define BT848_COLOR_FMT_E_RGB32 0 +#define BT848_COLOR_FMT_E_RGB24 1 +#define BT848_COLOR_FMT_E_RGB16 2 +#define BT848_COLOR_FMT_E_RGB15 3 +#define BT848_COLOR_FMT_E_YUY2 4 +#define BT848_COLOR_FMT_E_BtYUV 5 +#define BT848_COLOR_FMT_E_Y8 6 +#define BT848_COLOR_FMT_E_RGB8 7 +#define BT848_COLOR_FMT_E_YCrCb422 8 +#define BT848_COLOR_FMT_E_YCrCb411 9 +#define BT848_COLOR_FMT_E_RAW 14 + +#define BT848_COLOR_FMT_RGB32 0x00 +#define BT848_COLOR_FMT_RGB24 0x11 +#define BT848_COLOR_FMT_RGB16 0x22 +#define BT848_COLOR_FMT_RGB15 0x33 +#define BT848_COLOR_FMT_YUY2 0x44 +#define BT848_COLOR_FMT_BtYUV 0x55 +#define BT848_COLOR_FMT_Y8 0x66 +#define BT848_COLOR_FMT_RGB8 0x77 +#define BT848_COLOR_FMT_YCrCb422 0x88 +#define BT848_COLOR_FMT_YCrCb411 0x99 +#define BT848_COLOR_FMT_RAW 0xee + +#define BT848_VTOTAL_LO 0xB0 +#define BT848_VTOTAL_HI 0xB4 + +#define BT848_COLOR_CTL 0x0D8 +#define BT848_COLOR_CTL_EXT_FRMRATE (1<<7) +#define BT848_COLOR_CTL_COLOR_BARS (1<<6) +#define BT848_COLOR_CTL_RGB_DED (1<<5) +#define BT848_COLOR_CTL_GAMMA (1<<4) +#define BT848_COLOR_CTL_WSWAP_ODD (1<<3) +#define BT848_COLOR_CTL_WSWAP_EVEN (1<<2) +#define BT848_COLOR_CTL_BSWAP_ODD (1<<1) +#define BT848_COLOR_CTL_BSWAP_EVEN (1<<0) + +#define BT848_CAP_CTL 0x0DC +#define BT848_CAP_CTL_DITH_FRAME (1<<4) +#define BT848_CAP_CTL_CAPTURE_VBI_ODD (1<<3) +#define BT848_CAP_CTL_CAPTURE_VBI_EVEN (1<<2) +#define BT848_CAP_CTL_CAPTURE_ODD (1<<1) +#define BT848_CAP_CTL_CAPTURE_EVEN (1<<0) + +#define BT848_VBI_PACK_SIZE 0x0E0 + +#define BT848_VBI_PACK_DEL 0x0E4 +#define BT848_VBI_PACK_DEL_VBI_HDELAY 0xfc +#define BT848_VBI_PACK_DEL_EXT_FRAME 2 +#define BT848_VBI_PACK_DEL_VBI_PKT_HI 1 + + +#define BT848_INT_STAT 0x100 +#define BT848_INT_MASK 0x104 + +#define BT848_INT_ETBF (1<<23) + +#define BT848_RISC_VIDEO 1 +#define BT848_RISC_TOP 2 +#define BT848_RISC_VBI 4 + +#define BT848_INT_RISCS (0xf<<28) +#define BT848_INT_RISCS_VIDEO (BT848_RISC_VIDEO << 28) +#define BT848_INT_RISCS_TOP (BT848_RISC_TOP << 28) +#define BT848_INT_RISCS_VBI (BT848_RISC_VBI << 28) + +#define BT848_INT_RISC_EN (1<<27) +#define BT848_INT_RACK (1<<25) +#define BT848_INT_FIELD (1<<24) +#define BT848_INT_SCERR (1<<19) +#define BT848_INT_OCERR (1<<18) +#define BT848_INT_PABORT (1<<17) +#define BT848_INT_RIPERR (1<<16) +#define BT848_INT_PPERR (1<<15) +#define BT848_INT_FDSR (1<<14) +#define BT848_INT_FTRGT (1<<13) +#define BT848_INT_FBUS (1<<12) +#define BT848_INT_RISCI (1<<11) +#define BT848_INT_GPINT (1<<9) +#define BT848_INT_I2CDONE (1<<8) +#define BT848_INT_VPRES (1<<5) +#define BT848_INT_HLOCK (1<<4) +#define BT848_INT_OFLOW (1<<3) +#define BT848_INT_HSYNC (1<<2) +#define BT848_INT_VSYNC (1<<1) +#define BT848_INT_FMTCHG (1<<0) + + +#define BT848_GPIO_DMA_CTL 0x10C +#define BT848_GPIO_DMA_CTL_GPINTC (1<<15) +#define BT848_GPIO_DMA_CTL_GPINTI (1<<14) +#define BT848_GPIO_DMA_CTL_GPWEC (1<<13) +#define BT848_GPIO_DMA_CTL_GPIOMODE (3<<11) +#define BT848_GPIO_DMA_CTL_GPCLKMODE (1<<10) +#define BT848_GPIO_DMA_CTL_PLTP23_4 (0<<6) +#define BT848_GPIO_DMA_CTL_PLTP23_8 (1<<6) +#define BT848_GPIO_DMA_CTL_PLTP23_16 (2<<6) +#define BT848_GPIO_DMA_CTL_PLTP23_32 (3<<6) +#define BT848_GPIO_DMA_CTL_PLTP1_4 (0<<4) +#define BT848_GPIO_DMA_CTL_PLTP1_8 (1<<4) +#define BT848_GPIO_DMA_CTL_PLTP1_16 (2<<4) +#define BT848_GPIO_DMA_CTL_PLTP1_32 (3<<4) +#define BT848_GPIO_DMA_CTL_PKTP_4 (0<<2) +#define BT848_GPIO_DMA_CTL_PKTP_8 (1<<2) +#define BT848_GPIO_DMA_CTL_PKTP_16 (2<<2) +#define BT848_GPIO_DMA_CTL_PKTP_32 (3<<2) +#define BT848_GPIO_DMA_CTL_RISC_ENABLE (1<<1) +#define BT848_GPIO_DMA_CTL_FIFO_ENABLE (1<<0) + +#define BT848_I2C 0x110 +#define BT878_I2C_MODE (1<<7) +#define BT878_I2C_RATE (1<<6) +#define BT878_I2C_NOSTOP (1<<5) +#define BT878_I2C_NOSTART (1<<4) +#define BT848_I2C_DIV (0xf<<4) +#define BT848_I2C_SYNC (1<<3) +#define BT848_I2C_W3B (1<<2) +#define BT848_I2C_SCL (1<<1) +#define BT848_I2C_SDA (1<<0) + +#define BT848_RISC_STRT_ADD 0x114 +#define BT848_GPIO_OUT_EN 0x118 +#define BT848_GPIO_REG_INP 0x11C +#define BT848_RISC_COUNT 0x120 +#define BT848_GPIO_DATA 0x200 + + +/* Bt848 RISC commands */ + +/* only for the SYNC RISC command */ +#define BT848_FIFO_STATUS_FM1 0x06 +#define BT848_FIFO_STATUS_FM3 0x0e +#define BT848_FIFO_STATUS_SOL 0x02 +#define BT848_FIFO_STATUS_EOL4 0x01 +#define BT848_FIFO_STATUS_EOL3 0x0d +#define BT848_FIFO_STATUS_EOL2 0x09 +#define BT848_FIFO_STATUS_EOL1 0x05 +#define BT848_FIFO_STATUS_VRE 0x04 +#define BT848_FIFO_STATUS_VRO 0x0c +#define BT848_FIFO_STATUS_PXV 0x00 + +#define BT848_RISC_RESYNC (1<<15) + +/* WRITE and SKIP */ +/* disable which bytes of each DWORD */ +#define BT848_RISC_BYTE0 (1U<<12) +#define BT848_RISC_BYTE1 (1U<<13) +#define BT848_RISC_BYTE2 (1U<<14) +#define BT848_RISC_BYTE3 (1U<<15) +#define BT848_RISC_BYTE_ALL (0x0fU<<12) +#define BT848_RISC_BYTE_NONE 0 +/* cause RISCI */ +#define BT848_RISC_IRQ (1U<<24) +/* RISC command is last one in this line */ +#define BT848_RISC_EOL (1U<<26) +/* RISC command is first one in this line */ +#define BT848_RISC_SOL (1U<<27) + +#define BT848_RISC_WRITE (0x01U<<28) +#define BT848_RISC_SKIP (0x02U<<28) +#define BT848_RISC_WRITEC (0x05U<<28) +#define BT848_RISC_JUMP (0x07U<<28) +#define BT848_RISC_SYNC (0x08U<<28) + +#define BT848_RISC_WRITE123 (0x09U<<28) +#define BT848_RISC_SKIP123 (0x0aU<<28) +#define BT848_RISC_WRITE1S23 (0x0bU<<28) + + +/* Bt848A and higher only !! */ +#define BT848_TGLB 0x080 +#define BT848_TGCTRL 0x084 +#define BT848_FCAP 0x0E8 +#define BT848_PLL_F_LO 0x0F0 +#define BT848_PLL_F_HI 0x0F4 + +#define BT848_PLL_XCI 0x0F8 +#define BT848_PLL_X (1<<7) +#define BT848_PLL_C (1<<6) + +#define BT848_DVSIF 0x0FC + +/* Bt878 register */ + +#define BT878_DEVCTRL 0x40 +#define BT878_EN_TBFX 0x02 +#define BT878_EN_VSFX 0x04 + +#endif diff --git a/drivers/media/pci/bt8xx/bt878.c b/drivers/media/pci/bt8xx/bt878.c new file mode 100644 index 000000000..62a6c4a80 --- /dev/null +++ b/drivers/media/pci/bt8xx/bt878.c @@ -0,0 +1,567 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * bt878.c: part of the driver for the Pinnacle PCTV Sat DVB PCI card + * + * Copyright (C) 2002 Peter Hettkamp + * + * large parts based on the bttv driver + * Copyright (C) 1996,97,98 Ralph Metzler (rjkm@metzlerbros.de) + * & Marcus Metzler (mocm@metzlerbros.de) + * (c) 1999,2000 Gerd Knorr + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "bt878.h" +#include "dst_priv.h" + + +/**************************************/ +/* Miscellaneous utility definitions */ +/**************************************/ + +static unsigned int bt878_verbose = 1; +static unsigned int bt878_debug; + +module_param_named(verbose, bt878_verbose, int, 0444); +MODULE_PARM_DESC(verbose, + "verbose startup messages, default is 1 (yes)"); +module_param_named(debug, bt878_debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off debugging, default is 0 (off)."); + +int bt878_num; +struct bt878 bt878[BT878_MAX]; + +EXPORT_SYMBOL(bt878_num); +EXPORT_SYMBOL(bt878); + +#define btwrite(dat,adr) bmtwrite((dat), (bt->bt878_mem+(adr))) +#define btread(adr) bmtread(bt->bt878_mem+(adr)) + +#define btand(dat,adr) btwrite((dat) & btread(adr), adr) +#define btor(dat,adr) btwrite((dat) | btread(adr), adr) +#define btaor(dat,mask,adr) btwrite((dat) | ((mask) & btread(adr)), adr) + +#if defined(dprintk) +#undef dprintk +#endif +#define dprintk(fmt, arg...) \ + do { \ + if (bt878_debug) \ + printk(KERN_DEBUG fmt, ##arg); \ + } while (0) + +static void bt878_mem_free(struct bt878 *bt) +{ + if (bt->buf_cpu) { + dma_free_coherent(&bt->dev->dev, bt->buf_size, bt->buf_cpu, + bt->buf_dma); + bt->buf_cpu = NULL; + } + + if (bt->risc_cpu) { + dma_free_coherent(&bt->dev->dev, bt->risc_size, bt->risc_cpu, + bt->risc_dma); + bt->risc_cpu = NULL; + } +} + +static int bt878_mem_alloc(struct bt878 *bt) +{ + if (!bt->buf_cpu) { + bt->buf_size = 128 * 1024; + + bt->buf_cpu = dma_alloc_coherent(&bt->dev->dev, bt->buf_size, + &bt->buf_dma, GFP_KERNEL); + if (!bt->buf_cpu) + return -ENOMEM; + } + + if (!bt->risc_cpu) { + bt->risc_size = PAGE_SIZE; + bt->risc_cpu = dma_alloc_coherent(&bt->dev->dev, bt->risc_size, + &bt->risc_dma, GFP_KERNEL); + if (!bt->risc_cpu) { + bt878_mem_free(bt); + return -ENOMEM; + } + } + + return 0; +} + +/* RISC instructions */ +#define RISC_WRITE (0x01 << 28) +#define RISC_JUMP (0x07 << 28) +#define RISC_SYNC (0x08 << 28) + +/* RISC bits */ +#define RISC_WR_SOL (1 << 27) +#define RISC_WR_EOL (1 << 26) +#define RISC_IRQ (1 << 24) +#define RISC_STATUS(status) ((((~status) & 0x0F) << 20) | ((status & 0x0F) << 16)) +#define RISC_SYNC_RESYNC (1 << 15) +#define RISC_SYNC_FM1 0x06 +#define RISC_SYNC_VRO 0x0C + +#define RISC_FLUSH() bt->risc_pos = 0 +#define RISC_INSTR(instr) bt->risc_cpu[bt->risc_pos++] = cpu_to_le32(instr) + +static int bt878_make_risc(struct bt878 *bt) +{ + bt->block_bytes = bt->buf_size >> 4; + bt->block_count = 1 << 4; + bt->line_bytes = bt->block_bytes; + bt->line_count = bt->block_count; + + while (bt->line_bytes > 4095) { + bt->line_bytes >>= 1; + bt->line_count <<= 1; + } + + if (bt->line_count > 255) { + printk(KERN_ERR "bt878: buffer size error!\n"); + return -EINVAL; + } + return 0; +} + + +static void bt878_risc_program(struct bt878 *bt, u32 op_sync_orin) +{ + u32 buf_pos = 0; + u32 line; + + RISC_FLUSH(); + RISC_INSTR(RISC_SYNC | RISC_SYNC_FM1 | op_sync_orin); + RISC_INSTR(0); + + dprintk("bt878: risc len lines %u, bytes per line %u\n", + bt->line_count, bt->line_bytes); + for (line = 0; line < bt->line_count; line++) { + // At the beginning of every block we issue an IRQ with previous (finished) block number set + if (!(buf_pos % bt->block_bytes)) + RISC_INSTR(RISC_WRITE | RISC_WR_SOL | RISC_WR_EOL | + RISC_IRQ | + RISC_STATUS(((buf_pos / + bt->block_bytes) + + (bt->block_count - + 1)) % + bt->block_count) | bt-> + line_bytes); + else + RISC_INSTR(RISC_WRITE | RISC_WR_SOL | RISC_WR_EOL | + bt->line_bytes); + RISC_INSTR(bt->buf_dma + buf_pos); + buf_pos += bt->line_bytes; + } + + RISC_INSTR(RISC_SYNC | op_sync_orin | RISC_SYNC_VRO); + RISC_INSTR(0); + + RISC_INSTR(RISC_JUMP); + RISC_INSTR(bt->risc_dma); + + btwrite((bt->line_count << 16) | bt->line_bytes, BT878_APACK_LEN); +} + +/*****************************/ +/* Start/Stop grabbing funcs */ +/*****************************/ + +void bt878_start(struct bt878 *bt, u32 controlreg, u32 op_sync_orin, + u32 irq_err_ignore) +{ + u32 int_mask; + + dprintk("bt878 debug: bt878_start (ctl=%8.8x)\n", controlreg); + /* complete the writing of the risc dma program now we have + * the card specifics + */ + bt878_risc_program(bt, op_sync_orin); + controlreg &= ~0x1f; + controlreg |= 0x1b; + + btwrite(bt->risc_dma, BT878_ARISC_START); + + /* original int mask had : + * 6 2 8 4 0 + * 1111 1111 1000 0000 0000 + * SCERR|OCERR|PABORT|RIPERR|FDSR|FTRGT|FBUS|RISCI + * Hacked for DST to: + * SCERR | OCERR | FDSR | FTRGT | FBUS | RISCI + */ + int_mask = BT878_ASCERR | BT878_AOCERR | BT878_APABORT | + BT878_ARIPERR | BT878_APPERR | BT878_AFDSR | BT878_AFTRGT | + BT878_AFBUS | BT878_ARISCI; + + + /* ignore pesky bits */ + int_mask &= ~irq_err_ignore; + + btwrite(int_mask, BT878_AINT_MASK); + btwrite(controlreg, BT878_AGPIO_DMA_CTL); +} + +void bt878_stop(struct bt878 *bt) +{ + u32 stat; + int i = 0; + + dprintk("bt878 debug: bt878_stop\n"); + + btwrite(0, BT878_AINT_MASK); + btand(~0x13, BT878_AGPIO_DMA_CTL); + + do { + stat = btread(BT878_AINT_STAT); + if (!(stat & BT878_ARISC_EN)) + break; + i++; + } while (i < 500); + + dprintk("bt878(%d) debug: bt878_stop, i=%d, stat=0x%8.8x\n", + bt->nr, i, stat); +} + +EXPORT_SYMBOL(bt878_start); +EXPORT_SYMBOL(bt878_stop); + +/*****************************/ +/* Interrupt service routine */ +/*****************************/ + +static irqreturn_t bt878_irq(int irq, void *dev_id) +{ + u32 stat, astat, mask; + int count; + struct bt878 *bt; + + bt = (struct bt878 *) dev_id; + + count = 0; + while (1) { + stat = btread(BT878_AINT_STAT); + mask = btread(BT878_AINT_MASK); + if (!(astat = (stat & mask))) + return IRQ_NONE; /* this interrupt is not for me */ +/* dprintk("bt878(%d) debug: irq count %d, stat 0x%8.8x, mask 0x%8.8x\n",bt->nr,count,stat,mask); */ + btwrite(astat, BT878_AINT_STAT); /* try to clear interrupt condition */ + + + if (astat & (BT878_ASCERR | BT878_AOCERR)) { + if (bt878_verbose) { + printk(KERN_INFO + "bt878(%d): irq%s%s risc_pc=%08x\n", + bt->nr, + (astat & BT878_ASCERR) ? " SCERR" : + "", + (astat & BT878_AOCERR) ? " OCERR" : + "", btread(BT878_ARISC_PC)); + } + } + if (astat & (BT878_APABORT | BT878_ARIPERR | BT878_APPERR)) { + if (bt878_verbose) { + printk(KERN_INFO + "bt878(%d): irq%s%s%s risc_pc=%08x\n", + bt->nr, + (astat & BT878_APABORT) ? " PABORT" : + "", + (astat & BT878_ARIPERR) ? " RIPERR" : + "", + (astat & BT878_APPERR) ? " PPERR" : + "", btread(BT878_ARISC_PC)); + } + } + if (astat & (BT878_AFDSR | BT878_AFTRGT | BT878_AFBUS)) { + if (bt878_verbose) { + printk(KERN_INFO + "bt878(%d): irq%s%s%s risc_pc=%08x\n", + bt->nr, + (astat & BT878_AFDSR) ? " FDSR" : "", + (astat & BT878_AFTRGT) ? " FTRGT" : + "", + (astat & BT878_AFBUS) ? " FBUS" : "", + btread(BT878_ARISC_PC)); + } + } + if (astat & BT878_ARISCI) { + bt->finished_block = (stat & BT878_ARISCS) >> 28; + if (bt->bh_work.func) + queue_work(system_bh_wq, &bt->bh_work); + break; + } + count++; + if (count > 20) { + btwrite(0, BT878_AINT_MASK); + printk(KERN_ERR + "bt878(%d): IRQ lockup, cleared int mask\n", + bt->nr); + break; + } + } + return IRQ_HANDLED; +} + +int +bt878_device_control(struct bt878 *bt, unsigned int cmd, union dst_gpio_packet *mp) +{ + int retval; + + retval = 0; + if (mutex_lock_interruptible(&bt->gpio_lock)) + return -ERESTARTSYS; + /* special gpio signal */ + switch (cmd) { + case DST_IG_ENABLE: + // dprintk("dvb_bt8xx: dst enable mask 0x%02x enb 0x%02x \n", mp->dstg.enb.mask, mp->dstg.enb.enable); + retval = bttv_gpio_enable(bt->bttv_nr, + mp->enb.mask, + mp->enb.enable); + break; + case DST_IG_WRITE: + // dprintk("dvb_bt8xx: dst write gpio mask 0x%02x out 0x%02x\n", mp->dstg.outp.mask, mp->dstg.outp.highvals); + retval = bttv_write_gpio(bt->bttv_nr, + mp->outp.mask, + mp->outp.highvals); + + break; + case DST_IG_READ: + /* read */ + retval = bttv_read_gpio(bt->bttv_nr, &mp->rd.value); + // dprintk("dvb_bt8xx: dst read gpio 0x%02x\n", (unsigned)mp->dstg.rd.value); + break; + case DST_IG_TS: + /* Set packet size */ + bt->TS_Size = mp->psize; + break; + + default: + retval = -EINVAL; + break; + } + mutex_unlock(&bt->gpio_lock); + return retval; +} + +EXPORT_SYMBOL(bt878_device_control); + +#define BROOKTREE_878_DEVICE(vend, dev, name) \ + { \ + .vendor = PCI_VENDOR_ID_BROOKTREE, \ + .device = PCI_DEVICE_ID_BROOKTREE_878, \ + .subvendor = (vend), .subdevice = (dev), \ + .driver_data = (unsigned long) name \ + } + +static const struct pci_device_id bt878_pci_tbl[] = { + BROOKTREE_878_DEVICE(0x0071, 0x0101, "Nebula Electronics DigiTV"), + BROOKTREE_878_DEVICE(0x1461, 0x0761, "AverMedia AverTV DVB-T 761"), + BROOKTREE_878_DEVICE(0x11bd, 0x001c, "Pinnacle PCTV Sat"), + BROOKTREE_878_DEVICE(0x11bd, 0x0026, "Pinnacle PCTV SAT CI"), + BROOKTREE_878_DEVICE(0x1822, 0x0001, "Twinhan VisionPlus DVB"), + BROOKTREE_878_DEVICE(0x270f, 0xfc00, + "ChainTech digitop DST-1000 DVB-S"), + BROOKTREE_878_DEVICE(0x1461, 0x0771, "AVermedia AverTV DVB-T 771"), + BROOKTREE_878_DEVICE(0x18ac, 0xdb10, "DViCO FusionHDTV DVB-T Lite"), + BROOKTREE_878_DEVICE(0x18ac, 0xdb11, "Ultraview DVB-T Lite"), + BROOKTREE_878_DEVICE(0x18ac, 0xd500, "DViCO FusionHDTV 5 Lite"), + BROOKTREE_878_DEVICE(0x7063, 0x2000, "pcHDTV HD-2000 TV"), + BROOKTREE_878_DEVICE(0x1822, 0x0026, "DNTV Live! Mini"), + { } +}; + +MODULE_DEVICE_TABLE(pci, bt878_pci_tbl); + +static const char * card_name(const struct pci_device_id *id) +{ + return id->driver_data ? (const char *)id->driver_data : "Unknown"; +} + +/***********************/ +/* PCI device handling */ +/***********************/ + +static int bt878_probe(struct pci_dev *dev, const struct pci_device_id *pci_id) +{ + int result = 0; + unsigned char lat; + struct bt878 *bt; + unsigned int cardid; + + printk(KERN_INFO "bt878: Bt878 AUDIO function found (%d).\n", + bt878_num); + if (bt878_num >= BT878_MAX) { + printk(KERN_ERR "bt878: Too many devices inserted\n"); + return -ENOMEM; + } + if (pci_enable_device(dev)) + return -EIO; + + cardid = dev->subsystem_device << 16; + cardid |= dev->subsystem_vendor; + + printk(KERN_INFO "%s: card id=[0x%x],[ %s ] has DVB functions.\n", + __func__, cardid, card_name(pci_id)); + + bt = &bt878[bt878_num]; + bt->dev = dev; + bt->nr = bt878_num; + bt->shutdown = 0; + + bt->id = dev->device; + bt->irq = dev->irq; + bt->bt878_adr = pci_resource_start(dev, 0); + if (!request_mem_region(pci_resource_start(dev, 0), + pci_resource_len(dev, 0), "bt878")) { + result = -EBUSY; + goto fail0; + } + + bt->revision = dev->revision; + pci_read_config_byte(dev, PCI_LATENCY_TIMER, &lat); + + + printk(KERN_INFO "bt878(%d): Bt%x (rev %d) at %02x:%02x.%x, ", + bt878_num, bt->id, bt->revision, dev->bus->number, + PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn)); + printk("irq: %d, latency: %d, memory: 0x%lx\n", + bt->irq, lat, bt->bt878_adr); + +#ifdef __sparc__ + bt->bt878_mem = (unsigned char *) bt->bt878_adr; +#else + bt->bt878_mem = ioremap(bt->bt878_adr, 0x1000); +#endif + + /* clear interrupt mask */ + btwrite(0, BT848_INT_MASK); + + result = request_irq(bt->irq, bt878_irq, + IRQF_SHARED, "bt878", (void *) bt); + if (result == -EINVAL) { + printk(KERN_ERR "bt878(%d): Bad irq number or handler\n", + bt878_num); + goto fail1; + } + if (result == -EBUSY) { + printk(KERN_ERR + "bt878(%d): IRQ %d busy, change your PnP config in BIOS\n", + bt878_num, bt->irq); + goto fail1; + } + if (result < 0) + goto fail1; + + pci_set_master(dev); + pci_set_drvdata(dev, bt); + + if ((result = bt878_mem_alloc(bt))) { + printk(KERN_ERR "bt878: failed to allocate memory!\n"); + goto fail2; + } + + bt878_make_risc(bt); + btwrite(0, BT878_AINT_MASK); + bt878_num++; + + if (!bt->bh_work.func) + disable_work_sync(&bt->bh_work); + + return 0; + + fail2: + free_irq(bt->irq, bt); + fail1: + release_mem_region(pci_resource_start(bt->dev, 0), + pci_resource_len(bt->dev, 0)); + fail0: + pci_disable_device(dev); + return result; +} + +static void bt878_remove(struct pci_dev *pci_dev) +{ + u8 command; + struct bt878 *bt = pci_get_drvdata(pci_dev); + + if (bt878_verbose) + printk(KERN_INFO "bt878(%d): unloading\n", bt->nr); + + /* turn off all capturing, DMA and IRQs */ + btand(~0x13, BT878_AGPIO_DMA_CTL); + + /* first disable interrupts before unmapping the memory! */ + btwrite(0, BT878_AINT_MASK); + btwrite(~0U, BT878_AINT_STAT); + + /* disable PCI bus-mastering */ + pci_read_config_byte(bt->dev, PCI_COMMAND, &command); + /* Should this be &=~ ?? */ + command &= ~PCI_COMMAND_MASTER; + pci_write_config_byte(bt->dev, PCI_COMMAND, command); + + free_irq(bt->irq, bt); + printk(KERN_DEBUG "bt878_mem: 0x%p.\n", bt->bt878_mem); + if (bt->bt878_mem) + iounmap(bt->bt878_mem); + + release_mem_region(pci_resource_start(bt->dev, 0), + pci_resource_len(bt->dev, 0)); + /* wake up any waiting processes + because shutdown flag is set, no new processes (in this queue) + are expected + */ + bt->shutdown = 1; + bt878_mem_free(bt); + + pci_disable_device(pci_dev); + return; +} + +static struct pci_driver bt878_pci_driver = { + .name = "bt878", + .id_table = bt878_pci_tbl, + .probe = bt878_probe, + .remove = bt878_remove, +}; + +/*******************************/ +/* Module management functions */ +/*******************************/ + +static int __init bt878_init_module(void) +{ + bt878_num = 0; + + printk(KERN_INFO "bt878: AUDIO driver version %d.%d.%d loaded\n", + (BT878_VERSION_CODE >> 16) & 0xff, + (BT878_VERSION_CODE >> 8) & 0xff, + BT878_VERSION_CODE & 0xff); + + return pci_register_driver(&bt878_pci_driver); +} + +static void __exit bt878_cleanup_module(void) +{ + pci_unregister_driver(&bt878_pci_driver); +} + +module_init(bt878_init_module); +module_exit(bt878_cleanup_module); + +MODULE_DESCRIPTION("DVB/ATSC Support for bt878 based TV cards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/bt8xx/bt878.h b/drivers/media/pci/bt8xx/bt878.h new file mode 100644 index 000000000..5b1c7f56e --- /dev/null +++ b/drivers/media/pci/bt8xx/bt878.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + bt878.h - Bt878 audio module (register offsets) + + Copyright (C) 2002 Peter Hettkamp + +*/ + +#ifndef _BT878_H_ +#define _BT878_H_ + +#include +#include +#include +#include +#include +#include + +#include "bt848.h" +#include "bttv.h" + +#define BT878_VERSION_CODE 0x000000 + +#define BT878_AINT_STAT 0x100 +#define BT878_ARISCS (0xf<<28) +#define BT878_ARISC_EN (1<<27) +#define BT878_ASCERR (1<<19) +#define BT878_AOCERR (1<<18) +#define BT878_APABORT (1<<17) +#define BT878_ARIPERR (1<<16) +#define BT878_APPERR (1<<15) +#define BT878_AFDSR (1<<14) +#define BT878_AFTRGT (1<<13) +#define BT878_AFBUS (1<<12) +#define BT878_ARISCI (1<<11) +#define BT878_AOFLOW (1<<3) + +#define BT878_AINT_MASK 0x104 + +#define BT878_AGPIO_DMA_CTL 0x10c +#define BT878_A_GAIN (0xf<<28) +#define BT878_A_G2X (1<<27) +#define BT878_A_PWRDN (1<<26) +#define BT878_A_SEL (3<<24) +#define BT878_DA_SCE (1<<23) +#define BT878_DA_LRI (1<<22) +#define BT878_DA_MLB (1<<21) +#define BT878_DA_LRD (0x1f<<16) +#define BT878_DA_DPM (1<<15) +#define BT878_DA_SBR (1<<14) +#define BT878_DA_ES2 (1<<13) +#define BT878_DA_LMT (1<<12) +#define BT878_DA_SDR (0xf<<8) +#define BT878_DA_IOM (3<<6) +#define BT878_DA_APP (1<<5) +#define BT878_ACAP_EN (1<<4) +#define BT878_PKTP (3<<2) +#define BT878_RISC_EN (1<<1) +#define BT878_FIFO_EN 1 + +#define BT878_APACK_LEN 0x110 +#define BT878_AFP_LEN (0xff<<16) +#define BT878_ALP_LEN 0xfff + +#define BT878_ARISC_START 0x114 + +#define BT878_ARISC_PC 0x120 + +/* BT878 FUNCTION 0 REGISTERS */ +#define BT878_GPIO_DMA_CTL 0x10c + +/* Interrupt register */ +#define BT878_INT_STAT 0x100 +#define BT878_INT_MASK 0x104 +#define BT878_I2CRACK (1<<25) +#define BT878_I2CDONE (1<<8) + +#define BT878_MAX 4 + +#define BT878_RISC_SYNC_MASK (1 << 15) + + +#define BTTV_BOARD_UNKNOWN 0x00 +#define BTTV_BOARD_PINNACLESAT 0x5e +#define BTTV_BOARD_NEBULA_DIGITV 0x68 +#define BTTV_BOARD_PC_HDTV 0x70 +#define BTTV_BOARD_TWINHAN_DST 0x71 +#define BTTV_BOARD_AVDVBT_771 0x7b +#define BTTV_BOARD_AVDVBT_761 0x7c +#define BTTV_BOARD_DVICO_DVBT_LITE 0x80 +#define BTTV_BOARD_DVICO_FUSIONHDTV_5_LITE 0x87 + +extern int bt878_num; + +struct bt878 { + struct mutex gpio_lock; + unsigned int nr; + unsigned int bttv_nr; + struct i2c_adapter *adapter; + struct pci_dev *dev; + unsigned int id; + unsigned int TS_Size; + unsigned char revision; + unsigned int irq; + unsigned long bt878_adr; + volatile void __iomem *bt878_mem; /* function 1 */ + + volatile u32 finished_block; + volatile u32 last_block; + u32 block_count; + u32 block_bytes; + u32 line_bytes; + u32 line_count; + + u32 buf_size; + u8 *buf_cpu; + dma_addr_t buf_dma; + + u32 risc_size; + __le32 *risc_cpu; + dma_addr_t risc_dma; + u32 risc_pos; + + struct work_struct bh_work; + int shutdown; +}; + +extern struct bt878 bt878[BT878_MAX]; + +void bt878_start(struct bt878 *bt, u32 controlreg, u32 op_sync_orin, + u32 irq_err_ignore); +void bt878_stop(struct bt878 *bt); + +#define bmtwrite(dat,adr) writel((dat), (adr)) +#define bmtread(adr) readl(adr) + +#endif diff --git a/drivers/media/pci/bt8xx/btcx-risc.c b/drivers/media/pci/bt8xx/btcx-risc.c new file mode 100644 index 000000000..0adbf8233 --- /dev/null +++ b/drivers/media/pci/bt8xx/btcx-risc.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + + btcx-risc.c + + bt848/bt878/cx2388x risc code generator. + + (c) 2000-03 Gerd Knorr [SuSE Labs] + + +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include + +#include "btcx-risc.h" + +static unsigned int btcx_debug; +module_param(btcx_debug, int, 0644); +MODULE_PARM_DESC(btcx_debug,"debug messages, default is 0 (no)"); + +#define dprintk(fmt, arg...) do { \ + if (btcx_debug) \ + printk(KERN_DEBUG pr_fmt("%s: " fmt), \ + __func__, ##arg); \ +} while (0) + + +/* ---------------------------------------------------------- */ +/* allocate/free risc memory */ + +static int memcnt; + +void btcx_riscmem_free(struct pci_dev *pci, + struct btcx_riscmem *risc) +{ + if (NULL == risc->cpu) + return; + + memcnt--; + dprintk("btcx: riscmem free [%d] dma=%lx\n", + memcnt, (unsigned long)risc->dma); + + dma_free_coherent(&pci->dev, risc->size, risc->cpu, risc->dma); + memset(risc,0,sizeof(*risc)); +} + +int btcx_riscmem_alloc(struct pci_dev *pci, + struct btcx_riscmem *risc, + unsigned int size) +{ + __le32 *cpu; + dma_addr_t dma = 0; + + if (NULL != risc->cpu && risc->size < size) + btcx_riscmem_free(pci,risc); + if (NULL == risc->cpu) { + cpu = dma_alloc_coherent(&pci->dev, size, &dma, GFP_KERNEL); + if (NULL == cpu) + return -ENOMEM; + risc->cpu = cpu; + risc->dma = dma; + risc->size = size; + + memcnt++; + dprintk("btcx: riscmem alloc [%d] dma=%lx cpu=%p size=%d\n", + memcnt, (unsigned long)dma, cpu, size); + } + return 0; +} diff --git a/drivers/media/pci/bt8xx/btcx-risc.h b/drivers/media/pci/bt8xx/btcx-risc.h new file mode 100644 index 000000000..6ac79a157 --- /dev/null +++ b/drivers/media/pci/bt8xx/btcx-risc.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +struct btcx_riscmem { + unsigned int size; + __le32 *cpu; + __le32 *jmp; + dma_addr_t dma; +}; + +struct btcx_skiplist { + int start; + int end; +}; + +int btcx_riscmem_alloc(struct pci_dev *pci, + struct btcx_riscmem *risc, + unsigned int size); +void btcx_riscmem_free(struct pci_dev *pci, + struct btcx_riscmem *risc); diff --git a/drivers/media/pci/bt8xx/bttv-audio-hook.c b/drivers/media/pci/bt8xx/bttv-audio-hook.c new file mode 100644 index 000000000..b5d071835 --- /dev/null +++ b/drivers/media/pci/bt8xx/bttv-audio-hook.c @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Handlers for board audio hooks, split from bttv-cards + * + * Copyright (c) 2006 Mauro Carvalho Chehab + */ + +#include "bttv-audio-hook.h" + +#include + +/* ----------------------------------------------------------------------- */ +/* winview */ + +void winview_volume(struct bttv *btv, __u16 volume) +{ + /* PT2254A programming Jon Tombs, jon@gte.esi.us.es */ + int bits_out, loops, vol, data; + + /* 32 levels logarithmic */ + vol = 32 - ((volume>>11)); + /* units */ + bits_out = (PT2254_DBS_IN_2>>(vol%5)); + /* tens */ + bits_out |= (PT2254_DBS_IN_10>>(vol/5)); + bits_out |= PT2254_L_CHANNEL | PT2254_R_CHANNEL; + data = gpio_read(); + data &= ~(WINVIEW_PT2254_CLK| WINVIEW_PT2254_DATA| + WINVIEW_PT2254_STROBE); + for (loops = 17; loops >= 0 ; loops--) { + if (bits_out & (1<audmode = V4L2_TUNER_MODE_LANG1; + t->rxsubchans = V4L2_TUNER_SUB_MONO | + V4L2_TUNER_SUB_STEREO | + V4L2_TUNER_SUB_LANG1 | + V4L2_TUNER_SUB_LANG2; + + return; + } + + gpio_inout(0x300, 0x300); + switch (t->audmode) { + case V4L2_TUNER_MODE_LANG1: + default: + con = 0x000; + break; + case V4L2_TUNER_MODE_LANG2: + con = 0x300; + break; + case V4L2_TUNER_MODE_STEREO: + con = 0x200; + break; + } + gpio_bits(0x300, con); +} + +void gvbctv5pci_audio(struct bttv *btv, struct v4l2_tuner *t, int set) +{ + unsigned int val, con; + + if (btv->radio_user) + return; + + val = gpio_read(); + if (set) { + switch (t->audmode) { + case V4L2_TUNER_MODE_LANG2: + con = 0x300; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + con = 0x100; + break; + default: + con = 0x000; + break; + } + if (con != (val & 0x300)) { + gpio_bits(0x300, con); + if (bttv_gpio) + bttv_gpio_tracking(btv, "gvbctv5pci"); + } + } else { + switch (val & 0x70) { + case 0x10: + t->rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + t->audmode = V4L2_TUNER_MODE_LANG1_LANG2; + break; + case 0x30: + t->rxsubchans = V4L2_TUNER_SUB_LANG2; + t->audmode = V4L2_TUNER_MODE_LANG1_LANG2; + break; + case 0x50: + t->rxsubchans = V4L2_TUNER_SUB_LANG1; + t->audmode = V4L2_TUNER_MODE_LANG1_LANG2; + break; + case 0x60: + t->rxsubchans = V4L2_TUNER_SUB_STEREO; + t->audmode = V4L2_TUNER_MODE_STEREO; + break; + case 0x70: + t->rxsubchans = V4L2_TUNER_SUB_MONO; + t->audmode = V4L2_TUNER_MODE_MONO; + break; + default: + t->rxsubchans = V4L2_TUNER_SUB_MONO | + V4L2_TUNER_SUB_STEREO | + V4L2_TUNER_SUB_LANG1 | + V4L2_TUNER_SUB_LANG2; + t->audmode = V4L2_TUNER_MODE_LANG1; + } + } +} + +/* + * Mario Medina Nussbaum + * I discover that on BT848_GPIO_DATA address a byte 0xcce enable stereo, + * 0xdde enables mono and 0xccd enables sap + * + * Petr Vandrovec + * P.S.: At least mask in line above is wrong - GPIO pins 3,2 select + * input/output sound connection, so both must be set for output mode. + * + * Looks like it's needed only for the "tvphone", the "tvphone 98" + * handles this with a tda9840 + * + */ + +void avermedia_tvphone_audio(struct bttv *btv, struct v4l2_tuner *t, int set) +{ + int val; + + if (!set) { + /* Not much to do here */ + t->audmode = V4L2_TUNER_MODE_LANG1; + t->rxsubchans = V4L2_TUNER_SUB_MONO | + V4L2_TUNER_SUB_STEREO | + V4L2_TUNER_SUB_LANG1 | + V4L2_TUNER_SUB_LANG2; + + return; + } + + switch (t->audmode) { + case V4L2_TUNER_MODE_LANG2: /* SAP */ + val = 0x02; + break; + case V4L2_TUNER_MODE_STEREO: + val = 0x01; + break; + default: + return; + } + gpio_bits(0x03, val); + if (bttv_gpio) + bttv_gpio_tracking(btv, "avermedia"); +} + + +void avermedia_tv_stereo_audio(struct bttv *btv, struct v4l2_tuner *t, int set) +{ + int val = 0; + + if (!set) { + /* Not much to do here */ + t->audmode = V4L2_TUNER_MODE_LANG1; + t->rxsubchans = V4L2_TUNER_SUB_MONO | + V4L2_TUNER_SUB_STEREO | + V4L2_TUNER_SUB_LANG1 | + V4L2_TUNER_SUB_LANG2; + + return; + } + + switch (t->audmode) { + case V4L2_TUNER_MODE_LANG2: /* SAP */ + val = 0x01; + break; + case V4L2_TUNER_MODE_STEREO: + val = 0x02; + break; + default: + val = 0; + break; + } + btaor(val, ~0x03, BT848_GPIO_DATA); + if (bttv_gpio) + bttv_gpio_tracking(btv, "avermedia"); +} + +/* Lifetec 9415 handling */ + +void lt9415_audio(struct bttv *btv, struct v4l2_tuner *t, int set) +{ + int val = 0; + + if (gpio_read() & 0x4000) { + t->audmode = V4L2_TUNER_MODE_MONO; + return; + } + + if (!set) { + /* Not much to do here */ + t->audmode = V4L2_TUNER_MODE_LANG1; + t->rxsubchans = V4L2_TUNER_SUB_MONO | + V4L2_TUNER_SUB_STEREO | + V4L2_TUNER_SUB_LANG1 | + V4L2_TUNER_SUB_LANG2; + + return; + } + + switch (t->audmode) { + case V4L2_TUNER_MODE_LANG2: /* A2 SAP */ + val = 0x0080; + break; + case V4L2_TUNER_MODE_STEREO: /* A2 stereo */ + val = 0x0880; + break; + default: + val = 0; + break; + } + + gpio_bits(0x0880, val); + if (bttv_gpio) + bttv_gpio_tracking(btv, "lt9415"); +} + +/* TDA9821 on TerraTV+ Bt848, Bt878 */ +void terratv_audio(struct bttv *btv, struct v4l2_tuner *t, int set) +{ + unsigned int con = 0; + + if (!set) { + /* Not much to do here */ + t->audmode = V4L2_TUNER_MODE_LANG1; + t->rxsubchans = V4L2_TUNER_SUB_MONO | + V4L2_TUNER_SUB_STEREO | + V4L2_TUNER_SUB_LANG1 | + V4L2_TUNER_SUB_LANG2; + + return; + } + + gpio_inout(0x180000, 0x180000); + switch (t->audmode) { + case V4L2_TUNER_MODE_LANG2: + con = 0x080000; + break; + case V4L2_TUNER_MODE_STEREO: + con = 0x180000; + break; + default: + con = 0; + break; + } + gpio_bits(0x180000, con); + if (bttv_gpio) + bttv_gpio_tracking(btv, "terratv"); +} + + +void winfast2000_audio(struct bttv *btv, struct v4l2_tuner *t, int set) +{ + unsigned long val; + + if (!set) + return; + + /*btor (0xc32000, BT848_GPIO_OUT_EN);*/ + switch (t->audmode) { + case V4L2_TUNER_MODE_MONO: + case V4L2_TUNER_MODE_LANG1: + val = 0x420000; + break; + case V4L2_TUNER_MODE_LANG2: /* SAP */ + val = 0x410000; + break; + case V4L2_TUNER_MODE_STEREO: + val = 0x020000; + break; + default: + return; + } + + gpio_bits(0x430000, val); + if (bttv_gpio) + bttv_gpio_tracking(btv, "winfast2000"); +} + +/* + * Dariusz Kowalewski + * sound control for Prolink PV-BT878P+9B (PixelView PlayTV Pro FM+NICAM + * revision 9B has on-board TDA9874A sound decoder). + * + * Note: There are card variants without tda9874a. Forcing the "stereo sound route" + * will mute this cards. + */ +void pvbt878p9b_audio(struct bttv *btv, struct v4l2_tuner *t, int set) +{ + unsigned int val = 0; + + if (btv->radio_user) + return; + + if (!set) { + /* Not much to do here */ + t->audmode = V4L2_TUNER_MODE_LANG1; + t->rxsubchans = V4L2_TUNER_SUB_MONO | + V4L2_TUNER_SUB_STEREO | + V4L2_TUNER_SUB_LANG1 | + V4L2_TUNER_SUB_LANG2; + + return; + } + + switch (t->audmode) { + case V4L2_TUNER_MODE_MONO: + val = 0x01; + break; + case V4L2_TUNER_MODE_LANG1: + case V4L2_TUNER_MODE_LANG2: + case V4L2_TUNER_MODE_STEREO: + val = 0x02; + break; + default: + return; + } + + gpio_bits(0x03, val); + if (bttv_gpio) + bttv_gpio_tracking(btv, "pvbt878p9b"); +} + +/* + * Dariusz Kowalewski + * sound control for FlyVideo 2000S (with tda9874 decoder) + * based on pvbt878p9b_audio() - this is not tested, please fix!!! + */ +void fv2000s_audio(struct bttv *btv, struct v4l2_tuner *t, int set) +{ + unsigned int val; + + if (btv->radio_user) + return; + + if (!set) { + /* Not much to do here */ + t->audmode = V4L2_TUNER_MODE_LANG1; + t->rxsubchans = V4L2_TUNER_SUB_MONO | + V4L2_TUNER_SUB_STEREO | + V4L2_TUNER_SUB_LANG1 | + V4L2_TUNER_SUB_LANG2; + + return; + } + + switch (t->audmode) { + case V4L2_TUNER_MODE_MONO: + val = 0x0000; + break; + case V4L2_TUNER_MODE_LANG1: + case V4L2_TUNER_MODE_LANG2: + case V4L2_TUNER_MODE_STEREO: + val = 0x1080; /*-dk-???: 0x0880, 0x0080, 0x1800 ... */ + break; + default: + return; + } + gpio_bits(0x1800, val); + if (bttv_gpio) + bttv_gpio_tracking(btv, "fv2000s"); +} + +/* + * sound control for Canopus WinDVR PCI + * Masaki Suzuki + */ +void windvr_audio(struct bttv *btv, struct v4l2_tuner *t, int set) +{ + unsigned long val; + + if (!set) { + /* Not much to do here */ + t->audmode = V4L2_TUNER_MODE_LANG1; + t->rxsubchans = V4L2_TUNER_SUB_MONO | + V4L2_TUNER_SUB_STEREO | + V4L2_TUNER_SUB_LANG1 | + V4L2_TUNER_SUB_LANG2; + + return; + } + + switch (t->audmode) { + case V4L2_TUNER_MODE_MONO: + val = 0x040000; + break; + case V4L2_TUNER_MODE_LANG2: + val = 0x100000; + break; + default: + return; + } + + gpio_bits(0x140000, val); + if (bttv_gpio) + bttv_gpio_tracking(btv, "windvr"); +} + +/* + * sound control for AD-TVK503 + * Hiroshi Takekawa + */ +void adtvk503_audio(struct bttv *btv, struct v4l2_tuner *t, int set) +{ + unsigned int con = 0xffffff; + + /* btaor(0x1e0000, ~0x1e0000, BT848_GPIO_OUT_EN); */ + + if (!set) { + /* Not much to do here */ + t->audmode = V4L2_TUNER_MODE_LANG1; + t->rxsubchans = V4L2_TUNER_SUB_MONO | + V4L2_TUNER_SUB_STEREO | + V4L2_TUNER_SUB_LANG1 | + V4L2_TUNER_SUB_LANG2; + + return; + } + + /* btor(***, BT848_GPIO_OUT_EN); */ + switch (t->audmode) { + case V4L2_TUNER_MODE_LANG1: + con = 0x00000000; + break; + case V4L2_TUNER_MODE_LANG2: + con = 0x00180000; + break; + case V4L2_TUNER_MODE_STEREO: + con = 0x00000000; + break; + case V4L2_TUNER_MODE_MONO: + con = 0x00060000; + break; + default: + return; + } + + gpio_bits(0x1e0000, con); + if (bttv_gpio) + bttv_gpio_tracking(btv, "adtvk503"); +} diff --git a/drivers/media/pci/bt8xx/bttv-audio-hook.h b/drivers/media/pci/bt8xx/bttv-audio-hook.h new file mode 100644 index 000000000..d6a1a5a60 --- /dev/null +++ b/drivers/media/pci/bt8xx/bttv-audio-hook.h @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: GPL-2.0 + * + * Handlers for board audio hooks, split from bttv-cards + * + * Copyright (c) 2006 Mauro Carvalho Chehab + * This code is placed under the terms of the GNU General Public License + */ + +#include "bttvp.h" + +void winview_volume (struct bttv *btv, __u16 volume); + +void lt9415_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set); +void avermedia_tvphone_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set); +void avermedia_tv_stereo_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set); +void terratv_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set); +void gvbctv3pci_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set); +void gvbctv5pci_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set); +void winfast2000_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set); +void pvbt878p9b_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set); +void fv2000s_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set); +void windvr_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set); +void adtvk503_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set); + diff --git a/drivers/media/pci/bt8xx/bttv-cards.c b/drivers/media/pci/bt8xx/bttv-cards.c new file mode 100644 index 000000000..365b04e5a --- /dev/null +++ b/drivers/media/pci/bt8xx/bttv-cards.c @@ -0,0 +1,4933 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + + bttv-cards.c + + this file has configuration information - card-specific stuff + like the big tvcards array for the most part + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 1999-2001 Gerd Knorr + + +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "bttvp.h" +#include +#include +#include "bttv-audio-hook.h" + +/* fwd decl */ +static void boot_msp34xx(struct bttv *btv, int pin); +static void hauppauge_eeprom(struct bttv *btv); +static void avermedia_eeprom(struct bttv *btv); +static void osprey_eeprom(struct bttv *btv, const u8 ee[256]); +static void modtec_eeprom(struct bttv *btv); +static void init_PXC200(struct bttv *btv); +static void init_RTV24(struct bttv *btv); +static void init_PCI8604PW(struct bttv *btv); + +static void rv605_muxsel(struct bttv *btv, unsigned int input); +static void eagle_muxsel(struct bttv *btv, unsigned int input); +static void xguard_muxsel(struct bttv *btv, unsigned int input); +static void ivc120_muxsel(struct bttv *btv, unsigned int input); +static void gvc1100_muxsel(struct bttv *btv, unsigned int input); + +static void PXC200_muxsel(struct bttv *btv, unsigned int input); + +static void picolo_tetra_muxsel(struct bttv *btv, unsigned int input); +static void picolo_tetra_init(struct bttv *btv); + +static void tibetCS16_muxsel(struct bttv *btv, unsigned int input); +static void tibetCS16_init(struct bttv *btv); + +static void kodicom4400r_muxsel(struct bttv *btv, unsigned int input); +static void kodicom4400r_init(struct bttv *btv); + +static void sigmaSLC_muxsel(struct bttv *btv, unsigned int input); +static void sigmaSQ_muxsel(struct bttv *btv, unsigned int input); + +static void geovision_muxsel(struct bttv *btv, unsigned int input); + +static void phytec_muxsel(struct bttv *btv, unsigned int input); + +static void gv800s_muxsel(struct bttv *btv, unsigned int input); +static void gv800s_init(struct bttv *btv); + +static void td3116_muxsel(struct bttv *btv, unsigned int input); + +static int terratec_active_radio_upgrade(struct bttv *btv); +static int tea575x_init(struct bttv *btv); +static void identify_by_eeprom(struct bttv *btv, + unsigned char eeprom_data[256]); +static int pvr_boot(struct bttv *btv); + +/* config variables */ +static unsigned int triton1; +static unsigned int vsfx; +static unsigned int latency = UNSET; + +static unsigned int card[BTTV_MAX] = { [ 0 ... (BTTV_MAX-1) ] = UNSET }; +static unsigned int pll[BTTV_MAX] = { [ 0 ... (BTTV_MAX-1) ] = UNSET }; +static unsigned int tuner[BTTV_MAX] = { [ 0 ... (BTTV_MAX-1) ] = UNSET }; +static unsigned int svhs[BTTV_MAX] = { [ 0 ... (BTTV_MAX-1) ] = UNSET }; +static unsigned int remote[BTTV_MAX] = { [ 0 ... (BTTV_MAX-1) ] = UNSET }; +static unsigned int audiodev[BTTV_MAX]; +static unsigned int saa6588[BTTV_MAX]; +static struct bttv *master[BTTV_MAX] = { [ 0 ... (BTTV_MAX-1) ] = NULL }; +static unsigned int autoload = UNSET; +static unsigned int gpiomask = UNSET; +static unsigned int audioall = UNSET; +static unsigned int audiomux[5] = { [ 0 ... 4 ] = UNSET }; + +/* insmod options */ +module_param(triton1, int, 0444); +module_param(vsfx, int, 0444); +module_param(latency, int, 0444); +module_param(gpiomask, int, 0444); +module_param(audioall, int, 0444); +module_param(autoload, int, 0444); + +module_param_array(card, int, NULL, 0444); +module_param_array(pll, int, NULL, 0444); +module_param_array(tuner, int, NULL, 0444); +module_param_array(svhs, int, NULL, 0444); +module_param_array(remote, int, NULL, 0444); +module_param_array(audiodev, int, NULL, 0444); +module_param_array(audiomux, int, NULL, 0444); + +MODULE_PARM_DESC(triton1, "set ETBF pci config bit [enable bug compatibility for triton1 + others]"); +MODULE_PARM_DESC(vsfx, "set VSFX pci config bit [yet another chipset flaw workaround]"); +MODULE_PARM_DESC(latency,"pci latency timer"); +MODULE_PARM_DESC(card,"specify TV/grabber card model, see CARDLIST file for a list"); +MODULE_PARM_DESC(pll, "specify installed crystal (0=none, 28=28 MHz, 35=35 MHz, 14=14 MHz)"); +MODULE_PARM_DESC(tuner,"specify installed tuner type"); +MODULE_PARM_DESC(autoload, "obsolete option, please do not use anymore"); +MODULE_PARM_DESC(audiodev, "specify audio device:\n" + "\t\t-1 = no audio\n" + "\t\t 0 = autodetect (default)\n" + "\t\t 1 = msp3400\n" + "\t\t 2 = tda7432\n" + "\t\t 3 = tvaudio"); +MODULE_PARM_DESC(saa6588, "if 1, then load the saa6588 RDS module, default (0) is to use the card definition."); + +MODULE_FIRMWARE("hcwamc.rbf"); + +/* I2C addresses list */ +#define I2C_ADDR_TDA7432 0x8a +#define I2C_ADDR_MSP3400 0x80 +#define I2C_ADDR_MSP3400_ALT 0x88 + + +/* ----------------------------------------------------------------------- */ +/* list of card IDs for bt878+ cards */ + +static struct CARD { + unsigned id; + int cardnr; + char *name; +} cards[] = { + { 0x13eb0070, BTTV_BOARD_HAUPPAUGE878, "Hauppauge WinTV" }, + { 0x39000070, BTTV_BOARD_HAUPPAUGE878, "Hauppauge WinTV-D" }, + { 0x45000070, BTTV_BOARD_HAUPPAUGEPVR, "Hauppauge WinTV/PVR" }, + { 0xff000070, BTTV_BOARD_OSPREY1x0, "Osprey-100" }, + { 0xff010070, BTTV_BOARD_OSPREY2x0_SVID,"Osprey-200" }, + { 0xff020070, BTTV_BOARD_OSPREY500, "Osprey-500" }, + { 0xff030070, BTTV_BOARD_OSPREY2000, "Osprey-2000" }, + { 0xff040070, BTTV_BOARD_OSPREY540, "Osprey-540" }, + { 0xff070070, BTTV_BOARD_OSPREY440, "Osprey-440" }, + + { 0x00011002, BTTV_BOARD_ATI_TVWONDER, "ATI TV Wonder" }, + { 0x00031002, BTTV_BOARD_ATI_TVWONDERVE,"ATI TV Wonder/VE" }, + + { 0x6606107d, BTTV_BOARD_WINFAST2000, "Leadtek WinFast TV 2000" }, + { 0x6607107d, BTTV_BOARD_WINFASTVC100, "Leadtek WinFast VC 100" }, + { 0x6609107d, BTTV_BOARD_WINFAST2000, "Leadtek TV 2000 XP" }, + { 0x263610b4, BTTV_BOARD_STB2, "STB TV PCI FM, Gateway P/N 6000704" }, + { 0x264510b4, BTTV_BOARD_STB2, "STB TV PCI FM, Gateway P/N 6000704" }, + { 0x402010fc, BTTV_BOARD_GVBCTV3PCI, "I-O Data Co. GV-BCTV3/PCI" }, + { 0x405010fc, BTTV_BOARD_GVBCTV4PCI, "I-O Data Co. GV-BCTV4/PCI" }, + { 0x407010fc, BTTV_BOARD_GVBCTV5PCI, "I-O Data Co. GV-BCTV5/PCI" }, + { 0xd01810fc, BTTV_BOARD_GVBCTV5PCI, "I-O Data Co. GV-BCTV5/PCI" }, + + { 0x001211bd, BTTV_BOARD_PINNACLE, "Pinnacle PCTV" }, + /* some cards ship with byteswapped IDs ... */ + { 0x1200bd11, BTTV_BOARD_PINNACLE, "Pinnacle PCTV [bswap]" }, + { 0xff00bd11, BTTV_BOARD_PINNACLE, "Pinnacle PCTV [bswap]" }, + /* this seems to happen as well ... */ + { 0xff1211bd, BTTV_BOARD_PINNACLE, "Pinnacle PCTV" }, + + { 0x3000121a, BTTV_BOARD_VOODOOTV_200, "3Dfx VoodooTV 200" }, + { 0x263710b4, BTTV_BOARD_VOODOOTV_FM, "3Dfx VoodooTV FM" }, + { 0x3060121a, BTTV_BOARD_STB2, "3Dfx VoodooTV 100/ STB OEM" }, + + { 0x3000144f, BTTV_BOARD_MAGICTVIEW063, "(Askey Magic/others) TView99 CPH06x" }, + { 0xa005144f, BTTV_BOARD_MAGICTVIEW063, "CPH06X TView99-Card" }, + { 0x3002144f, BTTV_BOARD_MAGICTVIEW061, "(Askey Magic/others) TView99 CPH05x" }, + { 0x3005144f, BTTV_BOARD_MAGICTVIEW061, "(Askey Magic/others) TView99 CPH061/06L (T1/LC)" }, + { 0x5000144f, BTTV_BOARD_MAGICTVIEW061, "Askey CPH050" }, + { 0x300014ff, BTTV_BOARD_MAGICTVIEW061, "TView 99 (CPH061)" }, + { 0x300214ff, BTTV_BOARD_PHOEBE_TVMAS, "Phoebe TV Master (CPH060)" }, + + { 0x00011461, BTTV_BOARD_AVPHONE98, "AVerMedia TVPhone98" }, + { 0x00021461, BTTV_BOARD_AVERMEDIA98, "AVermedia TVCapture 98" }, + { 0x00031461, BTTV_BOARD_AVPHONE98, "AVerMedia TVPhone98" }, + { 0x00041461, BTTV_BOARD_AVERMEDIA98, "AVerMedia TVCapture 98" }, + { 0x03001461, BTTV_BOARD_AVERMEDIA98, "VDOMATE TV TUNER CARD" }, + + { 0x1117153b, BTTV_BOARD_TERRATVALUE, "Terratec TValue (Philips PAL B/G)" }, + { 0x1118153b, BTTV_BOARD_TERRATVALUE, "Terratec TValue (Temic PAL B/G)" }, + { 0x1119153b, BTTV_BOARD_TERRATVALUE, "Terratec TValue (Philips PAL I)" }, + { 0x111a153b, BTTV_BOARD_TERRATVALUE, "Terratec TValue (Temic PAL I)" }, + + { 0x1123153b, BTTV_BOARD_TERRATVRADIO, "Terratec TV Radio+" }, + { 0x1127153b, BTTV_BOARD_TERRATV, "Terratec TV+ (V1.05)" }, + /* clashes with FlyVideo + *{ 0x18521852, BTTV_BOARD_TERRATV, "Terratec TV+ (V1.10)" }, */ + { 0x1134153b, BTTV_BOARD_TERRATVALUE, "Terratec TValue (LR102)" }, + { 0x1135153b, BTTV_BOARD_TERRATVALUER, "Terratec TValue Radio" }, /* LR102 */ + { 0x5018153b, BTTV_BOARD_TERRATVALUE, "Terratec TValue" }, /* ?? */ + { 0xff3b153b, BTTV_BOARD_TERRATVALUER, "Terratec TValue Radio" }, /* ?? */ + + { 0x400015b0, BTTV_BOARD_ZOLTRIX_GENIE, "Zoltrix Genie TV" }, + { 0x400a15b0, BTTV_BOARD_ZOLTRIX_GENIE, "Zoltrix Genie TV" }, + { 0x400d15b0, BTTV_BOARD_ZOLTRIX_GENIE, "Zoltrix Genie TV / Radio" }, + { 0x401015b0, BTTV_BOARD_ZOLTRIX_GENIE, "Zoltrix Genie TV / Radio" }, + { 0x401615b0, BTTV_BOARD_ZOLTRIX_GENIE, "Zoltrix Genie TV / Radio" }, + + { 0x1430aa00, BTTV_BOARD_PV143, "Provideo PV143A" }, + { 0x1431aa00, BTTV_BOARD_PV143, "Provideo PV143B" }, + { 0x1432aa00, BTTV_BOARD_PV143, "Provideo PV143C" }, + { 0x1433aa00, BTTV_BOARD_PV143, "Provideo PV143D" }, + { 0x1433aa03, BTTV_BOARD_PV143, "Security Eyes" }, + + { 0x1460aa00, BTTV_BOARD_PV150, "Provideo PV150A-1" }, + { 0x1461aa01, BTTV_BOARD_PV150, "Provideo PV150A-2" }, + { 0x1462aa02, BTTV_BOARD_PV150, "Provideo PV150A-3" }, + { 0x1463aa03, BTTV_BOARD_PV150, "Provideo PV150A-4" }, + + { 0x1464aa04, BTTV_BOARD_PV150, "Provideo PV150B-1" }, + { 0x1465aa05, BTTV_BOARD_PV150, "Provideo PV150B-2" }, + { 0x1466aa06, BTTV_BOARD_PV150, "Provideo PV150B-3" }, + { 0x1467aa07, BTTV_BOARD_PV150, "Provideo PV150B-4" }, + + { 0xa132ff00, BTTV_BOARD_IVC100, "IVC-100" }, + { 0xa1550000, BTTV_BOARD_IVC200, "IVC-200" }, + { 0xa1550001, BTTV_BOARD_IVC200, "IVC-200" }, + { 0xa1550002, BTTV_BOARD_IVC200, "IVC-200" }, + { 0xa1550003, BTTV_BOARD_IVC200, "IVC-200" }, + { 0xa1550100, BTTV_BOARD_IVC200, "IVC-200G" }, + { 0xa1550101, BTTV_BOARD_IVC200, "IVC-200G" }, + { 0xa1550102, BTTV_BOARD_IVC200, "IVC-200G" }, + { 0xa1550103, BTTV_BOARD_IVC200, "IVC-200G" }, + { 0xa1550800, BTTV_BOARD_IVC200, "IVC-200" }, + { 0xa1550801, BTTV_BOARD_IVC200, "IVC-200" }, + { 0xa1550802, BTTV_BOARD_IVC200, "IVC-200" }, + { 0xa1550803, BTTV_BOARD_IVC200, "IVC-200" }, + { 0xa182ff00, BTTV_BOARD_IVC120, "IVC-120G" }, + { 0xa182ff01, BTTV_BOARD_IVC120, "IVC-120G" }, + { 0xa182ff02, BTTV_BOARD_IVC120, "IVC-120G" }, + { 0xa182ff03, BTTV_BOARD_IVC120, "IVC-120G" }, + { 0xa182ff04, BTTV_BOARD_IVC120, "IVC-120G" }, + { 0xa182ff05, BTTV_BOARD_IVC120, "IVC-120G" }, + { 0xa182ff06, BTTV_BOARD_IVC120, "IVC-120G" }, + { 0xa182ff07, BTTV_BOARD_IVC120, "IVC-120G" }, + { 0xa182ff08, BTTV_BOARD_IVC120, "IVC-120G" }, + { 0xa182ff09, BTTV_BOARD_IVC120, "IVC-120G" }, + { 0xa182ff0a, BTTV_BOARD_IVC120, "IVC-120G" }, + { 0xa182ff0b, BTTV_BOARD_IVC120, "IVC-120G" }, + { 0xa182ff0c, BTTV_BOARD_IVC120, "IVC-120G" }, + { 0xa182ff0d, BTTV_BOARD_IVC120, "IVC-120G" }, + { 0xa182ff0e, BTTV_BOARD_IVC120, "IVC-120G" }, + { 0xa182ff0f, BTTV_BOARD_IVC120, "IVC-120G" }, + { 0xf0500000, BTTV_BOARD_IVCE8784, "IVCE-8784" }, + { 0xf0500001, BTTV_BOARD_IVCE8784, "IVCE-8784" }, + { 0xf0500002, BTTV_BOARD_IVCE8784, "IVCE-8784" }, + { 0xf0500003, BTTV_BOARD_IVCE8784, "IVCE-8784" }, + + { 0x41424344, BTTV_BOARD_GRANDTEC, "GrandTec Multi Capture" }, + { 0x01020304, BTTV_BOARD_XGUARD, "Grandtec Grand X-Guard" }, + + { 0x18501851, BTTV_BOARD_CHRONOS_VS2, "FlyVideo 98 (LR50)/ Chronos Video Shuttle II" }, + { 0xa0501851, BTTV_BOARD_CHRONOS_VS2, "FlyVideo 98 (LR50)/ Chronos Video Shuttle II" }, + { 0x18511851, BTTV_BOARD_FLYVIDEO98EZ, "FlyVideo 98EZ (LR51)/ CyberMail AV" }, + { 0x18521852, BTTV_BOARD_TYPHOON_TVIEW, "FlyVideo 98FM (LR50)/ Typhoon TView TV/FM Tuner" }, + { 0x41a0a051, BTTV_BOARD_FLYVIDEO_98FM, "Lifeview FlyVideo 98 LR50 Rev Q" }, + { 0x18501f7f, BTTV_BOARD_FLYVIDEO_98, "Lifeview Flyvideo 98" }, + + { 0x010115cb, BTTV_BOARD_GMV1, "AG GMV1" }, + { 0x010114c7, BTTV_BOARD_MODTEC_205, "Modular Technology MM201/MM202/MM205/MM210/MM215 PCTV" }, + + { 0x10b42636, BTTV_BOARD_HAUPPAUGE878, "STB ???" }, + { 0x217d6606, BTTV_BOARD_WINFAST2000, "Leadtek WinFast TV 2000" }, + { 0xfff6f6ff, BTTV_BOARD_WINFAST2000, "Leadtek WinFast TV 2000" }, + { 0x03116000, BTTV_BOARD_SENSORAY311_611, "Sensoray 311" }, + { 0x06116000, BTTV_BOARD_SENSORAY311_611, "Sensoray 611" }, + { 0x00790e11, BTTV_BOARD_WINDVR, "Canopus WinDVR PCI" }, + { 0xa0fca1a0, BTTV_BOARD_ZOLTRIX, "Face to Face Tvmax" }, + { 0x82b2aa6a, BTTV_BOARD_SIMUS_GVC1100, "SIMUS GVC1100" }, + { 0x146caa0c, BTTV_BOARD_PV951, "ituner spectra8" }, + { 0x200a1295, BTTV_BOARD_PXC200, "ImageNation PXC200A" }, + + { 0x40111554, BTTV_BOARD_PV_BT878P_9B, "Prolink Pixelview PV-BT" }, + { 0x17de0a01, BTTV_BOARD_KWORLD, "Mecer TV/FM/Video Tuner" }, + + { 0x01051805, BTTV_BOARD_PICOLO_TETRA_CHIP, "Picolo Tetra Chip #1" }, + { 0x01061805, BTTV_BOARD_PICOLO_TETRA_CHIP, "Picolo Tetra Chip #2" }, + { 0x01071805, BTTV_BOARD_PICOLO_TETRA_CHIP, "Picolo Tetra Chip #3" }, + { 0x01081805, BTTV_BOARD_PICOLO_TETRA_CHIP, "Picolo Tetra Chip #4" }, + + { 0x15409511, BTTV_BOARD_ACORP_Y878F, "Acorp Y878F" }, + + { 0x53534149, BTTV_BOARD_SSAI_SECURITY, "SSAI Security Video Interface" }, + { 0x5353414a, BTTV_BOARD_SSAI_ULTRASOUND, "SSAI Ultrasound Video Interface" }, + + /* likely broken, vendor id doesn't match the other magic views ... + * { 0xa0fca04f, BTTV_BOARD_MAGICTVIEW063, "Guillemot Maxi TV Video 3" }, */ + + /* Duplicate PCI ID, reconfigure for this board during the eeprom read. + * { 0x13eb0070, BTTV_BOARD_HAUPPAUGE_IMPACTVCB, "Hauppauge ImpactVCB" }, */ + + { 0x109e036e, BTTV_BOARD_CONCEPTRONIC_CTVFMI2, "Conceptronic CTVFMi v2"}, + + /* DVB cards (using pci function .1 for mpeg data xfer) */ + { 0x001c11bd, BTTV_BOARD_PINNACLESAT, "Pinnacle PCTV Sat" }, + { 0x01010071, BTTV_BOARD_NEBULA_DIGITV, "Nebula Electronics DigiTV" }, + { 0x20007063, BTTV_BOARD_PC_HDTV, "pcHDTV HD-2000 TV"}, + { 0x002611bd, BTTV_BOARD_TWINHAN_DST, "Pinnacle PCTV SAT CI" }, + { 0x00011822, BTTV_BOARD_TWINHAN_DST, "Twinhan VisionPlus DVB" }, + { 0xfc00270f, BTTV_BOARD_TWINHAN_DST, "ChainTech digitop DST-1000 DVB-S" }, + { 0x07711461, BTTV_BOARD_AVDVBT_771, "AVermedia AverTV DVB-T 771" }, + { 0x07611461, BTTV_BOARD_AVDVBT_761, "AverMedia AverTV DVB-T 761" }, + { 0xdb1018ac, BTTV_BOARD_DVICO_DVBT_LITE, "DViCO FusionHDTV DVB-T Lite" }, + { 0xdb1118ac, BTTV_BOARD_DVICO_DVBT_LITE, "Ultraview DVB-T Lite" }, + { 0xd50018ac, BTTV_BOARD_DVICO_FUSIONHDTV_5_LITE, "DViCO FusionHDTV 5 Lite" }, + { 0x00261822, BTTV_BOARD_TWINHAN_DST, "DNTV Live! Mini "}, + { 0xd200dbc0, BTTV_BOARD_DVICO_FUSIONHDTV_2, "DViCO FusionHDTV 2" }, + { 0x763c008a, BTTV_BOARD_GEOVISION_GV600, "GeoVision GV-600" }, + { 0x18011000, BTTV_BOARD_ENLTV_FM_2, "Encore ENL TV-FM-2" }, + { 0x763d800a, BTTV_BOARD_GEOVISION_GV800S, "GeoVision GV-800(S) (master)" }, + { 0x763d800b, BTTV_BOARD_GEOVISION_GV800S_SL, "GeoVision GV-800(S) (slave)" }, + { 0x763d800c, BTTV_BOARD_GEOVISION_GV800S_SL, "GeoVision GV-800(S) (slave)" }, + { 0x763d800d, BTTV_BOARD_GEOVISION_GV800S_SL, "GeoVision GV-800(S) (slave)" }, + + { 0x15401830, BTTV_BOARD_PV183, "Provideo PV183-1" }, + { 0x15401831, BTTV_BOARD_PV183, "Provideo PV183-2" }, + { 0x15401832, BTTV_BOARD_PV183, "Provideo PV183-3" }, + { 0x15401833, BTTV_BOARD_PV183, "Provideo PV183-4" }, + { 0x15401834, BTTV_BOARD_PV183, "Provideo PV183-5" }, + { 0x15401835, BTTV_BOARD_PV183, "Provideo PV183-6" }, + { 0x15401836, BTTV_BOARD_PV183, "Provideo PV183-7" }, + { 0x15401837, BTTV_BOARD_PV183, "Provideo PV183-8" }, + { 0x3116f200, BTTV_BOARD_TVT_TD3116, "Tongwei Video Technology TD-3116" }, + { 0x02280279, BTTV_BOARD_APOSONIC_WDVR, "Aposonic W-DVR" }, + { 0, -1, NULL } +}; + +/* ----------------------------------------------------------------------- */ +/* array with description for bt848 / bt878 tv/grabber cards */ + +struct tvcard bttv_tvcards[] = { + /* ---- card 0x00 ---------------------------------- */ + [BTTV_BOARD_UNKNOWN] = { + .name = " *** UNKNOWN/GENERIC *** ", + .video_inputs = 4, + .svhs = 2, + .muxsel = MUXSEL(2, 3, 1, 0), + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_MIRO] = { + .name = "MIRO PCTV", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 15, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 2, 0, 0, 0 }, + .gpiomute = 10, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_HAUPPAUGE] = { + .name = "Hauppauge (bt848)", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 7, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 1, 2, 3 }, + .gpiomute = 4, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_STB] = { + .name = "STB, Gateway P/N 6000699 (bt848)", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 7, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 4, 0, 2, 3 }, + .gpiomute = 1, + .no_msp34xx = 1, + .tuner_type = TUNER_PHILIPS_NTSC, + .tuner_addr = ADDR_UNSET, + .pll = PLL_28, + .has_radio = 1, + }, + + /* ---- card 0x04 ---------------------------------- */ + [BTTV_BOARD_INTEL] = { + .name = "Intel Create and Share PCI/ Smart Video Recorder III", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .svhs = 2, + .gpiomask = 0, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0 }, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_DIAMOND] = { + .name = "Diamond DTV2000", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 3, + .muxsel = MUXSEL(2, 3, 1, 0), + .gpiomux = { 0, 1, 0, 1 }, + .gpiomute = 3, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_AVERMEDIA] = { + .name = "AVerMedia TVPhone", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 3, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomask = 0x0f, + .gpiomux = { 0x0c, 0x04, 0x08, 0x04 }, + /* 0x04 for some cards ?? */ + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + .audio_mode_gpio= avermedia_tvphone_audio, + .has_remote = 1, + }, + [BTTV_BOARD_MATRIX_VISION] = { + .name = "MATRIX-Vision MV-Delta", + .video_inputs = 5, + /* .audio_inputs= 1, */ + .svhs = 3, + .gpiomask = 0, + .muxsel = MUXSEL(2, 3, 1, 0, 0), + .gpiomux = { 0 }, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + + /* ---- card 0x08 ---------------------------------- */ + [BTTV_BOARD_FLYVIDEO] = { + .name = "Lifeview FlyVideo II (Bt848) LR26 / MAXI TV Video PCI2 LR26", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0xc00, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 0xc00, 0x800, 0x400 }, + .gpiomute = 0xc00, + .pll = PLL_28, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_TURBOTV] = { + .name = "IMS/IXmicro TurboTV", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 3, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 1, 1, 2, 3 }, + .pll = PLL_28, + .tuner_type = TUNER_TEMIC_PAL, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_HAUPPAUGE878] = { + .name = "Hauppauge (bt878)", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x0f, /* old: 7 */ + .muxsel = MUXSEL(2, 0, 1, 1), + .gpiomux = { 0, 1, 2, 3 }, + .gpiomute = 4, + .pll = PLL_28, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_MIROPRO] = { + .name = "MIRO PCTV pro", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x3014f, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0x20001,0x10001, 0, 0 }, + .gpiomute = 10, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + + /* ---- card 0x0c ---------------------------------- */ + [BTTV_BOARD_ADSTECH_TV] = { + .name = "ADS Technologies Channel Surfer TV (bt848)", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 15, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 13, 14, 11, 7 }, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_AVERMEDIA98] = { + .name = "AVerMedia TVCapture 98", + .video_inputs = 3, + /* .audio_inputs= 4, */ + .svhs = 2, + .gpiomask = 15, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 13, 14, 11, 7 }, + .msp34xx_alt = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + .audio_mode_gpio= avermedia_tv_stereo_audio, + .no_gpioirq = 1, + }, + [BTTV_BOARD_VHX] = { + .name = "Aimslab Video Highway Xtreme (VHX)", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 7, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 2, 1, 3 }, /* old: {0, 1, 2, 3, 4} */ + .gpiomute = 4, + .pll = PLL_28, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_ZOLTRIX] = { + .name = "Zoltrix TV-Max", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 15, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 0, 1, 0 }, + .gpiomute = 10, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + + /* ---- card 0x10 ---------------------------------- */ + [BTTV_BOARD_PIXVIEWPLAYTV] = { + .name = "Prolink Pixelview PlayTV (bt878)", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x01fe00, + .muxsel = MUXSEL(2, 3, 1, 1), + /* 2003-10-20 by "Anton A. Arapov" */ + .gpiomux = { 0x001e00, 0, 0x018000, 0x014000 }, + .gpiomute = 0x002000, + .pll = PLL_28, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_WINVIEW_601] = { + .name = "Leadtek WinView 601", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x8300f8, + .muxsel = MUXSEL(2, 3, 1, 1, 0), + .gpiomux = { 0x4fa007,0xcfa007,0xcfa007,0xcfa007 }, + .gpiomute = 0xcfa007, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + .volume_gpio = winview_volume, + .has_radio = 1, + }, + [BTTV_BOARD_AVEC_INTERCAP] = { + .name = "AVEC Intercapture", + .video_inputs = 3, + /* .audio_inputs= 2, */ + .svhs = 2, + .gpiomask = 0, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 1, 0, 0, 0 }, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_LIFE_FLYKIT] = { + .name = "Lifeview FlyVideo II EZ /FlyKit LR38 Bt848 (capture only)", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = NO_SVHS, + .gpiomask = 0x8dff00, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0 }, + .no_msp34xx = 1, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + + /* ---- card 0x14 ---------------------------------- */ + [BTTV_BOARD_CEI_RAFFLES] = { + .name = "CEI Raffles Card", + .video_inputs = 3, + /* .audio_inputs= 3, */ + .svhs = 2, + .muxsel = MUXSEL(2, 3, 1, 1), + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_CONFERENCETV] = { + .name = "Lifeview FlyVideo 98/ Lucky Star Image World ConferenceTV LR50", + .video_inputs = 4, + /* .audio_inputs= 2, tuner, line in */ + .svhs = 2, + .gpiomask = 0x1800, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 0x800, 0x1000, 0x1000 }, + .gpiomute = 0x1800, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL_I, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_PHOEBE_TVMAS] = { + .name = "Askey CPH050/ Phoebe Tv Master + FM", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0xc00, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 1, 0x800, 0x400 }, + .gpiomute = 0xc00, + .pll = PLL_28, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_MODTEC_205] = { + .name = "Modular Technology MM201/MM202/MM205/MM210/MM215 PCTV, bt878", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = NO_SVHS, + .has_dig_in = 1, + .gpiomask = 7, + .muxsel = MUXSEL(2, 3, 0), /* input 2 is digital */ + /* .digital_mode= DIGITAL_MODE_CAMERA, */ + .gpiomux = { 0, 0, 0, 0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_ALPS_TSBB5_PAL_I, + .tuner_addr = ADDR_UNSET, + }, + + /* ---- card 0x18 ---------------------------------- */ + [BTTV_BOARD_MAGICTVIEW061] = { + .name = "Askey CPH05X/06X (bt878) [many vendors]", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0xe00, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = {0x400, 0x400, 0x400, 0x400 }, + .gpiomute = 0xc00, + .pll = PLL_28, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + .has_remote = 1, + .has_radio = 1, /* not every card has radio */ + }, + [BTTV_BOARD_VOBIS_BOOSTAR] = { + .name = "Terratec TerraTV+ Version 1.0 (Bt848)/ Terra TValue Version 1.0/ Vobis TV-Boostar", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x1f0fff, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0x20000, 0x30000, 0x10000, 0 }, + .gpiomute = 0x40000, + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + .audio_mode_gpio= terratv_audio, + }, + [BTTV_BOARD_HAUPPAUG_WCAM] = { + .name = "Hauppauge WinCam newer (bt878)", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 3, + .gpiomask = 7, + .muxsel = MUXSEL(2, 0, 1, 1), + .gpiomux = { 0, 1, 2, 3 }, + .gpiomute = 4, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_MAXI] = { + .name = "Lifeview FlyVideo 98/ MAXI TV Video PCI2 LR50", + .video_inputs = 4, + /* .audio_inputs= 2, */ + .svhs = 2, + .gpiomask = 0x1800, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 0x800, 0x1000, 0x1000 }, + .gpiomute = 0x1800, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_SECAM, + .tuner_addr = ADDR_UNSET, + }, + + /* ---- card 0x1c ---------------------------------- */ + [BTTV_BOARD_TERRATV] = { + .name = "Terratec TerraTV+ Version 1.1 (bt878)", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x1f0fff, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0x20000, 0x30000, 0x10000, 0x00000 }, + .gpiomute = 0x40000, + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + .audio_mode_gpio= terratv_audio, + /* GPIO wiring: + External 20 pin connector (for Active Radio Upgrade board) + gpio00: i2c-sda + gpio01: i2c-scl + gpio02: om5610-data + gpio03: om5610-clk + gpio04: om5610-wre + gpio05: om5610-stereo + gpio06: rds6588-davn + gpio07: Pin 7 n.c. + gpio08: nIOW + gpio09+10: nIOR, nSEL ?? (bt878) + gpio09: nIOR (bt848) + gpio10: nSEL (bt848) + Sound Routing: + gpio16: u2-A0 (1st 4052bt) + gpio17: u2-A1 + gpio18: u2-nEN + gpio19: u4-A0 (2nd 4052) + gpio20: u4-A1 + u4-nEN - GND + Btspy: + 00000 : Cdrom (internal audio input) + 10000 : ext. Video audio input + 20000 : TV Mono + a0000 : TV Mono/2 + 1a0000 : TV Stereo + 30000 : Radio + 40000 : Mute + */ + + }, + [BTTV_BOARD_PXC200] = { + /* Jannik Fritsch */ + .name = "Imagenation PXC200", + .video_inputs = 5, + /* .audio_inputs= 1, */ + .svhs = 1, /* was: 4 */ + .gpiomask = 0, + .muxsel = MUXSEL(2, 3, 1, 0, 0), + .gpiomux = { 0 }, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .muxsel_hook = PXC200_muxsel, + + }, + [BTTV_BOARD_FLYVIDEO_98] = { + .name = "Lifeview FlyVideo 98 LR50", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x1800, /* 0x8dfe00 */ + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 0x0800, 0x1000, 0x1000 }, + .gpiomute = 0x1800, + .pll = PLL_28, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_IPROTV] = { + .name = "Formac iProTV, Formac ProTV I (bt848)", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 3, + .gpiomask = 1, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 1, 0, 0, 0 }, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + }, + + /* ---- card 0x20 ---------------------------------- */ + [BTTV_BOARD_INTEL_C_S_PCI] = { + .name = "Intel Create and Share PCI/ Smart Video Recorder III", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .svhs = 2, + .gpiomask = 0, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0 }, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_TERRATVALUE] = { + .name = "Terratec TerraTValue Version Bt878", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0xffff00, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0x500, 0, 0x300, 0x900 }, + .gpiomute = 0x900, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_WINFAST2000] = { + .name = "Leadtek WinFast 2000/ WinFast 2000 XP", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + /* TV, CVid, SVid, CVid over SVid connector */ + .muxsel = MUXSEL(2, 3, 1, 1, 0), + /* Alexander Varakin [stereo version] */ + .gpiomask = 0xb33000, + .gpiomux = { 0x122000,0x1000,0x0000,0x620000 }, + .gpiomute = 0x800000, + /* Audio Routing for "WinFast 2000 XP" (no tv stereo !) + gpio23 -- hef4052:nEnable (0x800000) + gpio12 -- hef4052:A1 + gpio13 -- hef4052:A0 + 0x0000: external audio + 0x1000: FM + 0x2000: TV + 0x3000: n.c. + Note: There exists another variant "Winfast 2000" with tv stereo !? + Note: eeprom only contains FF and pci subsystem id 107d:6606 + */ + .pll = PLL_28, + .has_radio = 1, + .tuner_type = TUNER_PHILIPS_PAL, /* default for now, gpio reads BFFF06 for Pal bg+dk */ + .tuner_addr = ADDR_UNSET, + .audio_mode_gpio= winfast2000_audio, + .has_remote = 1, + }, + [BTTV_BOARD_CHRONOS_VS2] = { + .name = "Lifeview FlyVideo 98 LR50 / Chronos Video Shuttle II", + .video_inputs = 4, + /* .audio_inputs= 3, */ + .svhs = 2, + .gpiomask = 0x1800, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 0x800, 0x1000, 0x1000 }, + .gpiomute = 0x1800, + .pll = PLL_28, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + + /* ---- card 0x24 ---------------------------------- */ + [BTTV_BOARD_TYPHOON_TVIEW] = { + .name = "Lifeview FlyVideo 98FM LR50 / Typhoon TView TV/FM Tuner", + .video_inputs = 4, + /* .audio_inputs= 3, */ + .svhs = 2, + .gpiomask = 0x1800, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 0x800, 0x1000, 0x1000 }, + .gpiomute = 0x1800, + .pll = PLL_28, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + .has_radio = 1, + }, + [BTTV_BOARD_PXELVWPLTVPRO] = { + .name = "Prolink PixelView PlayTV pro", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0xff, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0x21, 0x20, 0x24, 0x2c }, + .gpiomute = 0x29, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_MAGICTVIEW063] = { + .name = "Askey CPH06X TView99", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x551e00, + .muxsel = MUXSEL(2, 3, 1, 0), + .gpiomux = { 0x551400, 0x551200, 0, 0 }, + .gpiomute = 0x551c00, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL_I, + .tuner_addr = ADDR_UNSET, + .has_remote = 1, + }, + [BTTV_BOARD_PINNACLE] = { + .name = "Pinnacle PCTV Studio/Rave", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x03000F, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 2, 0xd0001, 0, 0 }, + .gpiomute = 1, + .pll = PLL_28, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + + /* ---- card 0x28 ---------------------------------- */ + [BTTV_BOARD_STB2] = { + .name = "STB TV PCI FM, Gateway P/N 6000704 (bt878), 3Dfx VoodooTV 100", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 7, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 4, 0, 2, 3 }, + .gpiomute = 1, + .no_msp34xx = 1, + .tuner_type = TUNER_PHILIPS_NTSC, + .tuner_addr = ADDR_UNSET, + .pll = PLL_28, + .has_radio = 1, + }, + [BTTV_BOARD_AVPHONE98] = { + .name = "AVerMedia TVPhone 98", + .video_inputs = 3, + /* .audio_inputs= 4, */ + .svhs = 2, + .gpiomask = 15, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 13, 4, 11, 7 }, + .pll = PLL_28, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + .has_radio = 1, + .audio_mode_gpio= avermedia_tvphone_audio, + }, + [BTTV_BOARD_PV951] = { + .name = "ProVideo PV951", /* pic16c54 */ + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 0, 0, 0}, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL_I, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_ONAIR_TV] = { + .name = "Little OnAir TV", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0xe00b, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0xff9ff6, 0xff9ff6, 0xff1ff7, 0 }, + .gpiomute = 0xff3ffc, + .no_msp34xx = 1, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + + /* ---- card 0x2c ---------------------------------- */ + [BTTV_BOARD_SIGMA_TVII_FM] = { + .name = "Sigma TVII-FM", + .video_inputs = 2, + /* .audio_inputs= 1, */ + .svhs = NO_SVHS, + .gpiomask = 3, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 1, 1, 0, 2 }, + .gpiomute = 3, + .no_msp34xx = 1, + .pll = PLL_NONE, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_MATRIX_VISION2] = { + .name = "MATRIX-Vision MV-Delta 2", + .video_inputs = 5, + /* .audio_inputs= 1, */ + .svhs = 3, + .gpiomask = 0, + .muxsel = MUXSEL(2, 3, 1, 0, 0), + .gpiomux = { 0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_ZOLTRIX_GENIE] = { + .name = "Zoltrix Genie TV/FM", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0xbcf03f, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0xbc803f, 0xbc903f, 0xbcb03f, 0 }, + .gpiomute = 0xbcb03f, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_TEMIC_4039FR5_NTSC, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_TERRATVRADIO] = { + .name = "Terratec TV/Radio+", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x70000, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0x20000, 0x30000, 0x10000, 0 }, + .gpiomute = 0x40000, + .no_msp34xx = 1, + .pll = PLL_35, + .tuner_type = TUNER_PHILIPS_PAL_I, + .tuner_addr = ADDR_UNSET, + .has_radio = 1, + }, + + /* ---- card 0x30 ---------------------------------- */ + [BTTV_BOARD_DYNALINK] = { + .name = "Askey CPH03x/ Dynalink Magic TView", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 15, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = {2,0,0,0 }, + .gpiomute = 1, + .pll = PLL_28, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_GVBCTV3PCI] = { + .name = "IODATA GV-BCTV3/PCI", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x010f00, + .muxsel = MUXSEL(2, 3, 0, 0), + .gpiomux = {0x10000, 0, 0x10000, 0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_ALPS_TSHC6_NTSC, + .tuner_addr = ADDR_UNSET, + .audio_mode_gpio= gvbctv3pci_audio, + }, + [BTTV_BOARD_PXELVWPLTVPAK] = { + .name = "Prolink PV-BT878P+4E / PixelView PlayTV PAK / Lenco MXTV-9578 CP", + .video_inputs = 5, + /* .audio_inputs= 1, */ + .svhs = 3, + .has_dig_in = 1, + .gpiomask = 0xAA0000, + .muxsel = MUXSEL(2, 3, 1, 1, 0), /* in 4 is digital */ + /* .digital_mode= DIGITAL_MODE_CAMERA, */ + .gpiomux = { 0x20000, 0, 0x80000, 0x80000 }, + .gpiomute = 0xa8000, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL_I, + .tuner_addr = ADDR_UNSET, + .has_remote = 1, + /* GPIO wiring: (different from Rev.4C !) + GPIO17: U4.A0 (first hef4052bt) + GPIO19: U4.A1 + GPIO20: U5.A1 (second hef4052bt) + GPIO21: U4.nEN + GPIO22: BT832 Reset Line + GPIO23: A5,A0, U5,nEN + Note: At i2c=0x8a is a Bt832 chip, which changes to 0x88 after being reset via GPIO22 + */ + }, + [BTTV_BOARD_EAGLE] = { + .name = "Eagle Wireless Capricorn2 (bt878A)", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 7, + .muxsel = MUXSEL(2, 0, 1, 1), + .gpiomux = { 0, 1, 2, 3 }, + .gpiomute = 4, + .pll = PLL_28, + .tuner_type = UNSET /* TUNER_ALPS_TMDH2_NTSC */, + .tuner_addr = ADDR_UNSET, + }, + + /* ---- card 0x34 ---------------------------------- */ + [BTTV_BOARD_PINNACLEPRO] = { + /* David Härdeman */ + .name = "Pinnacle PCTV Studio Pro", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 3, + .gpiomask = 0x03000F, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 1, 0xd0001, 0, 0 }, + .gpiomute = 10, + /* sound path (5 sources): + MUX1 (mask 0x03), Enable Pin 0x08 (0=enable, 1=disable) + 0= ext. Audio IN + 1= from MUX2 + 2= Mono TV sound from Tuner + 3= not connected + MUX2 (mask 0x30000): + 0,2,3= from MSP34xx + 1= FM stereo Radio from Tuner */ + .pll = PLL_28, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_TVIEW_RDS_FM] = { + /* Claas Langbehn , + Sven Grothklags */ + .name = "Typhoon TView RDS + FM Stereo / KNC1 TV Station RDS", + .video_inputs = 4, + /* .audio_inputs= 3, */ + .svhs = 2, + .gpiomask = 0x1c, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 0, 0x10, 8 }, + .gpiomute = 4, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + .has_radio = 1, + }, + [BTTV_BOARD_LIFETEC_9415] = { + /* Tim Röstermundt + in de.comp.os.unix.linux.hardware: + options bttv card=0 pll=1 radio=1 gpiomask=0x18e0 + gpiomux =0x44c71f,0x44d71f,0,0x44d71f,0x44dfff + options tuner type=5 */ + .name = "Lifeview FlyVideo 2000 /FlyVideo A2/ Lifetec LT 9415 TV [LR90]", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x18e0, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0x0000,0x0800,0x1000,0x1000 }, + .gpiomute = 0x18e0, + /* For cards with tda9820/tda9821: + 0x0000: Tuner normal stereo + 0x0080: Tuner A2 SAP (second audio program = Zweikanalton) + 0x0880: Tuner A2 stereo */ + .pll = PLL_28, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_BESTBUY_EASYTV] = { + /* Miguel Angel Alvarez + old Easy TV BT848 version (model CPH031) */ + .name = "Askey CPH031/ BESTBUY Easy TV", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0xF, + .muxsel = MUXSEL(2, 3, 1, 0), + .gpiomux = { 2, 0, 0, 0 }, + .gpiomute = 10, + .pll = PLL_28, + .tuner_type = TUNER_TEMIC_PAL, + .tuner_addr = ADDR_UNSET, + }, + + /* ---- card 0x38 ---------------------------------- */ + [BTTV_BOARD_FLYVIDEO_98FM] = { + /* Gordon Heydon */ + [BTTV_BOARD_GRANDTEC] = { + .name = "GrandTec 'Grand Video Capture' (Bt848)", + .video_inputs = 2, + /* .audio_inputs= 0, */ + .svhs = 1, + .gpiomask = 0, + .muxsel = MUXSEL(3, 1), + .gpiomux = { 0 }, + .no_msp34xx = 1, + .pll = PLL_35, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_ASKEY_CPH060] = { + /* Daniel Herrington */ + .name = "Askey CPH060/ Phoebe TV Master Only (No FM)", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0xe00, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0x400, 0x400, 0x400, 0x400 }, + .gpiomute = 0x800, + .pll = PLL_28, + .tuner_type = TUNER_TEMIC_4036FY5_NTSC, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_ASKEY_CPH03X] = { + /* Matti Mottus */ + .name = "Askey CPH03x TV Capturer", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x03000F, + .muxsel = MUXSEL(2, 3, 1, 0), + .gpiomux = { 2, 0, 0, 0 }, + .gpiomute = 1, + .pll = PLL_28, + .tuner_type = TUNER_TEMIC_PAL, + .tuner_addr = ADDR_UNSET, + .has_remote = 1, + }, + + /* ---- card 0x3c ---------------------------------- */ + [BTTV_BOARD_MM100PCTV] = { + /* Philip Blundell */ + .name = "Modular Technology MM100PCTV", + .video_inputs = 2, + /* .audio_inputs= 2, */ + .svhs = NO_SVHS, + .gpiomask = 11, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 2, 0, 0, 1 }, + .gpiomute = 8, + .pll = PLL_35, + .tuner_type = TUNER_TEMIC_PAL, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_GMV1] = { + /* Adrian Cox + new Easy TV BT878 version (model CPH061) + special thanks to Informatica Mieres for providing the card */ + .name = "Askey CPH061/ BESTBUY Easy TV (bt878)", + .video_inputs = 3, + /* .audio_inputs= 2, */ + .svhs = 2, + .gpiomask = 0xFF, + .muxsel = MUXSEL(2, 3, 1, 0), + .gpiomux = { 1, 0, 4, 4 }, + .gpiomute = 9, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_ATI_TVWONDER] = { + /* Lukas Gebauer */ + .name = "ATI TV-Wonder", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0xf03f, + .muxsel = MUXSEL(2, 3, 1, 0), + .gpiomux = { 0xbffe, 0, 0xbfff, 0 }, + .gpiomute = 0xbffe, + .pll = PLL_28, + .tuner_type = TUNER_TEMIC_4006FN5_MULTI_PAL, + .tuner_addr = ADDR_UNSET, + }, + + /* ---- card 0x40 ---------------------------------- */ + [BTTV_BOARD_ATI_TVWONDERVE] = { + /* Lukas Gebauer */ + .name = "ATI TV-Wonder VE", + .video_inputs = 2, + /* .audio_inputs= 1, */ + .svhs = NO_SVHS, + .gpiomask = 1, + .muxsel = MUXSEL(2, 3, 0, 1), + .gpiomux = { 0, 0, 1, 0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_TEMIC_4006FN5_MULTI_PAL, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_FLYVIDEO2000] = { + /* DeeJay */ + .name = "IODATA GV-BCTV4/PCI", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x010f00, + .muxsel = MUXSEL(2, 3, 0, 0), + .gpiomux = {0x10000, 0, 0x10000, 0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_SHARP_2U5JF5540_NTSC, + .tuner_addr = ADDR_UNSET, + .audio_mode_gpio= gvbctv3pci_audio, + }, + + /* ---- card 0x44 ---------------------------------- */ + [BTTV_BOARD_VOODOOTV_FM] = { + .name = "3Dfx VoodooTV FM (Euro)", + /* try "insmod msp3400 simple=0" if you have + * sound problems with this card. */ + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = NO_SVHS, + .gpiomask = 0x4f8a00, + /* 0x100000: 1=MSP enabled (0=disable again) + * 0x010000: Connected to "S0" on tda9880 (0=Pal/BG, 1=NTSC) */ + .gpiomux = {0x947fff, 0x987fff,0x947fff,0x947fff }, + .gpiomute = 0x947fff, + /* tvtuner, radio, external,internal, mute, stereo + * tuner, Composite, SVid, Composite-on-Svid-adapter */ + .muxsel = MUXSEL(2, 3, 0, 1), + .tuner_type = TUNER_MT2032, + .tuner_addr = ADDR_UNSET, + .pll = PLL_28, + .has_radio = 1, + }, + [BTTV_BOARD_VOODOOTV_200] = { + .name = "VoodooTV 200 (USA)", + /* try "insmod msp3400 simple=0" if you have + * sound problems with this card. */ + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = NO_SVHS, + .gpiomask = 0x4f8a00, + /* 0x100000: 1=MSP enabled (0=disable again) + * 0x010000: Connected to "S0" on tda9880 (0=Pal/BG, 1=NTSC) */ + .gpiomux = {0x947fff, 0x987fff,0x947fff,0x947fff }, + .gpiomute = 0x947fff, + /* tvtuner, radio, external,internal, mute, stereo + * tuner, Composite, SVid, Composite-on-Svid-adapter */ + .muxsel = MUXSEL(2, 3, 0, 1), + .tuner_type = TUNER_MT2032, + .tuner_addr = ADDR_UNSET, + .pll = PLL_28, + .has_radio = 1, + }, + [BTTV_BOARD_AIMMS] = { + /* Philip Blundell */ + .name = "Active Imaging AIMMS", + .video_inputs = 1, + /* .audio_inputs= 0, */ + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .pll = PLL_28, + .muxsel = MUXSEL(2), + .gpiomask = 0 + }, + [BTTV_BOARD_PV_BT878P_PLUS] = { + /* Tomasz Pyra */ + .name = "Prolink Pixelview PV-BT878P+ (Rev.4C,8E)", + .video_inputs = 3, + /* .audio_inputs= 4, */ + .svhs = 2, + .gpiomask = 15, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 0, 11, 7 }, /* TV and Radio with same GPIO ! */ + .gpiomute = 13, + .pll = PLL_28, + .tuner_type = TUNER_LG_PAL_I_FM, + .tuner_addr = ADDR_UNSET, + .has_remote = 1, + /* GPIO wiring: + GPIO0: U4.A0 (hef4052bt) + GPIO1: U4.A1 + GPIO2: U4.A1 (second hef4052bt) + GPIO3: U4.nEN, U5.A0, A5.nEN + GPIO8-15: vrd866b ? + */ + }, + [BTTV_BOARD_FLYVIDEO98EZ] = { + .name = "Lifeview FlyVideo 98EZ (capture only) LR51", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .svhs = 2, + /* AV1, AV2, SVHS, CVid adapter on SVHS */ + .muxsel = MUXSEL(2, 3, 1, 1), + .pll = PLL_28, + .no_msp34xx = 1, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + + /* ---- card 0x48 ---------------------------------- */ + [BTTV_BOARD_PV_BT878P_9B] = { + /* Dariusz Kowalewski */ + .name = "Prolink Pixelview PV-BT878P+9B (PlayTV Pro rev.9B FM+NICAM)", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x3f, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0x01, 0x00, 0x03, 0x03 }, + .gpiomute = 0x09, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + .audio_mode_gpio= pvbt878p9b_audio, /* Note: not all cards have stereo */ + .has_radio = 1, /* Note: not all cards have radio */ + .has_remote = 1, + /* GPIO wiring: + GPIO0: A0 hef4052 + GPIO1: A1 hef4052 + GPIO3: nEN hef4052 + GPIO8-15: vrd866b + GPIO20,22,23: R30,R29,R28 + */ + }, + [BTTV_BOARD_SENSORAY311_611] = { + /* Clay Kunz */ + /* you must jumper JP5 for the 311 card (PC/104+) to work */ + .name = "Sensoray 311/611", + .video_inputs = 5, + /* .audio_inputs= 0, */ + .svhs = 4, + .gpiomask = 0, + .muxsel = MUXSEL(2, 3, 1, 0, 0), + .gpiomux = { 0 }, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_RV605] = { + /* Miguel Freitas */ + .name = "RemoteVision MX (RV605)", + .video_inputs = 16, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .gpiomask = 0x00, + .gpiomask2 = 0x07ff, + .muxsel = MUXSEL(3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3), + .no_msp34xx = 1, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .muxsel_hook = rv605_muxsel, + }, + [BTTV_BOARD_POWERCLR_MTV878] = { + .name = "Powercolor MTV878/ MTV878R/ MTV878F", + .video_inputs = 3, + /* .audio_inputs= 2, */ + .svhs = 2, + .gpiomask = 0x1C800F, /* Bit0-2: Audio select, 8-12:remote control 14:remote valid 15:remote reset */ + .muxsel = MUXSEL(2, 1, 1), + .gpiomux = { 0, 1, 2, 2 }, + .gpiomute = 4, + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + .pll = PLL_28, + .has_radio = 1, + }, + + /* ---- card 0x4c ---------------------------------- */ + [BTTV_BOARD_WINDVR] = { + /* Masaki Suzuki */ + .name = "Canopus WinDVR PCI (COMPAQ Presario 3524JP, 5112JP)", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x140007, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 1, 2, 3 }, + .gpiomute = 4, + .tuner_type = TUNER_PHILIPS_NTSC, + .tuner_addr = ADDR_UNSET, + .audio_mode_gpio= windvr_audio, + }, + [BTTV_BOARD_GRANDTEC_MULTI] = { + .name = "GrandTec Multi Capture Card (Bt878)", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .gpiomask = 0, + .muxsel = MUXSEL(2, 3, 1, 0), + .gpiomux = { 0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_KWORLD] = { + .name = "Jetway TV/Capture JW-TV878-FBK, Kworld KW-TV878RF", + .video_inputs = 4, + /* .audio_inputs= 3, */ + .svhs = 2, + .gpiomask = 7, + /* Tuner, SVid, SVHS, SVid to SVHS connector */ + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 0, 4, 4 },/* Yes, this tuner uses the same audio output for TV and FM radio! + * This card lacks external Audio In, so we mute it on Ext. & Int. + * The PCB can take a sbx1637/sbx1673, wiring unknown. + * This card lacks PCI subsystem ID, sigh. + * gpiomux =1: lower volume, 2+3: mute + * btwincap uses 0x80000/0x80003 + */ + .gpiomute = 4, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + /* Samsung TCPA9095PC27A (BG+DK), philips compatible, w/FM, stereo and + radio signal strength indicators work fine. */ + .has_radio = 1, + /* GPIO Info: + GPIO0,1: HEF4052 A0,A1 + GPIO2: HEF4052 nENABLE + GPIO3-7: n.c. + GPIO8-13: IRDC357 data0-5 (data6 n.c. ?) [chip not present on my card] + GPIO14,15: ?? + GPIO16-21: n.c. + GPIO22,23: ?? + ?? : mtu8b56ep microcontroller for IR (GPIO wiring unknown)*/ + }, + [BTTV_BOARD_DSP_TCVIDEO] = { + /* Arthur Tetzlaff-Deas, DSP Design Ltd */ + .name = "DSP Design TCVIDEO", + .video_inputs = 4, + .svhs = NO_SVHS, + .muxsel = MUXSEL(2, 3, 1, 0), + .pll = PLL_28, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + }, + + /* ---- card 0x50 ---------------------------------- */ + [BTTV_BOARD_HAUPPAUGEPVR] = { + .name = "Hauppauge WinTV PVR", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .muxsel = MUXSEL(2, 0, 1, 1), + .pll = PLL_28, + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + + .gpiomask = 7, + .gpiomux = {7}, + }, + [BTTV_BOARD_GVBCTV5PCI] = { + .name = "IODATA GV-BCTV5/PCI", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x0f0f80, + .muxsel = MUXSEL(2, 3, 1, 0), + .gpiomux = {0x030000, 0x010000, 0, 0 }, + .gpiomute = 0x020000, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .tuner_addr = ADDR_UNSET, + .audio_mode_gpio= gvbctv5pci_audio, + .has_radio = 1, + }, + [BTTV_BOARD_OSPREY1x0] = { + .name = "Osprey 100/150 (878)", /* 0x1(2|3)-45C6-C1 */ + .video_inputs = 4, /* id-inputs-clock */ + /* .audio_inputs= 0, */ + .svhs = 3, + .muxsel = MUXSEL(3, 2, 0, 1), + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .no_msp34xx = 1, + .no_tda7432 = 1, + }, + [BTTV_BOARD_OSPREY1x0_848] = { + .name = "Osprey 100/150 (848)", /* 0x04-54C0-C1 & older boards */ + .video_inputs = 3, + /* .audio_inputs= 0, */ + .svhs = 2, + .muxsel = MUXSEL(2, 3, 1), + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .no_msp34xx = 1, + .no_tda7432 = 1, + }, + + /* ---- card 0x54 ---------------------------------- */ + [BTTV_BOARD_OSPREY101_848] = { + .name = "Osprey 101 (848)", /* 0x05-40C0-C1 */ + .video_inputs = 2, + /* .audio_inputs= 0, */ + .svhs = 1, + .muxsel = MUXSEL(3, 1), + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .no_msp34xx = 1, + .no_tda7432 = 1, + }, + [BTTV_BOARD_OSPREY1x1] = { + .name = "Osprey 101/151", /* 0x1(4|5)-0004-C4 */ + .video_inputs = 1, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .muxsel = MUXSEL(0), + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .no_msp34xx = 1, + .no_tda7432 = 1, + }, + [BTTV_BOARD_OSPREY1x1_SVID] = { + .name = "Osprey 101/151 w/ svid", /* 0x(16|17|20)-00C4-C1 */ + .video_inputs = 2, + /* .audio_inputs= 0, */ + .svhs = 1, + .muxsel = MUXSEL(0, 1), + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .no_msp34xx = 1, + .no_tda7432 = 1, + }, + [BTTV_BOARD_OSPREY2xx] = { + .name = "Osprey 200/201/250/251", /* 0x1(8|9|E|F)-0004-C4 */ + .video_inputs = 1, + /* .audio_inputs= 1, */ + .svhs = NO_SVHS, + .muxsel = MUXSEL(0), + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .no_msp34xx = 1, + .no_tda7432 = 1, + }, + + /* ---- card 0x58 ---------------------------------- */ + [BTTV_BOARD_OSPREY2x0_SVID] = { + .name = "Osprey 200/250", /* 0x1(A|B)-00C4-C1 */ + .video_inputs = 2, + /* .audio_inputs= 1, */ + .svhs = 1, + .muxsel = MUXSEL(0, 1), + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .no_msp34xx = 1, + .no_tda7432 = 1, + }, + [BTTV_BOARD_OSPREY2x0] = { + .name = "Osprey 210/220/230", /* 0x1(A|B)-04C0-C1 */ + .video_inputs = 2, + /* .audio_inputs= 1, */ + .svhs = 1, + .muxsel = MUXSEL(2, 3), + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .no_msp34xx = 1, + .no_tda7432 = 1, + }, + [BTTV_BOARD_OSPREY500] = { + .name = "Osprey 500", /* 500 */ + .video_inputs = 2, + /* .audio_inputs= 1, */ + .svhs = 1, + .muxsel = MUXSEL(2, 3), + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .no_msp34xx = 1, + .no_tda7432 = 1, + }, + [BTTV_BOARD_OSPREY540] = { + .name = "Osprey 540", /* 540 */ + .video_inputs = 4, + /* .audio_inputs= 1, */ + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .no_msp34xx = 1, + .no_tda7432 = 1, + }, + + /* ---- card 0x5C ---------------------------------- */ + [BTTV_BOARD_OSPREY2000] = { + .name = "Osprey 2000", /* 2000 */ + .video_inputs = 2, + /* .audio_inputs= 1, */ + .svhs = 1, + .muxsel = MUXSEL(2, 3), + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .no_msp34xx = 1, + .no_tda7432 = 1, /* must avoid, conflicts with the bt860 */ + }, + [BTTV_BOARD_IDS_EAGLE] = { + /* M G Berberich */ + .name = "IDS Eagle", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .svhs = NO_SVHS, + .gpiomask = 0, + .muxsel = MUXSEL(2, 2, 2, 2), + .muxsel_hook = eagle_muxsel, + .no_msp34xx = 1, + .pll = PLL_28, + }, + [BTTV_BOARD_PINNACLESAT] = { + .name = "Pinnacle PCTV Sat", + .video_inputs = 2, + /* .audio_inputs= 0, */ + .svhs = 1, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .no_msp34xx = 1, + .no_tda7432 = 1, + .muxsel = MUXSEL(3, 1), + .pll = PLL_28, + .no_gpioirq = 1, + .has_dvb = 1, + }, + [BTTV_BOARD_FORMAC_PROTV] = { + .name = "Formac ProTV II (bt878)", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 3, + .gpiomask = 2, + /* TV, Comp1, Composite over SVID con, SVID */ + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 2, 2, 0, 0 }, + .pll = PLL_28, + .has_radio = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + /* sound routing: + GPIO=0x00,0x01,0x03: mute (?) + 0x02: both TV and radio (tuner: FM1216/I) + The card has onboard audio connectors labeled "cdrom" and "board", + not soldered here, though unknown wiring. + Card lacks: external audio in, pci subsystem id. + */ + }, + + /* ---- card 0x60 ---------------------------------- */ + [BTTV_BOARD_MACHTV] = { + .name = "MachTV", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = NO_SVHS, + .gpiomask = 7, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 1, 2, 3}, + .gpiomute = 4, + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + .pll = PLL_28, + }, + [BTTV_BOARD_EURESYS_PICOLO] = { + .name = "Euresys Picolo", + .video_inputs = 3, + /* .audio_inputs= 0, */ + .svhs = 2, + .gpiomask = 0, + .no_msp34xx = 1, + .no_tda7432 = 1, + .muxsel = MUXSEL(2, 0, 1), + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_PV150] = { + /* Luc Van Hoeylandt */ + .name = "ProVideo PV150", /* 0x4f */ + .video_inputs = 2, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .gpiomask = 0, + .muxsel = MUXSEL(2, 3), + .gpiomux = { 0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_AD_TVK503] = { + /* Hiroshi Takekawa */ + /* This card lacks subsystem ID */ + .name = "AD-TVK503", /* 0x63 */ + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x001e8007, + .muxsel = MUXSEL(2, 3, 1, 0), + /* Tuner, Radio, external, internal, off, on */ + .gpiomux = { 0x08, 0x0f, 0x0a, 0x08 }, + .gpiomute = 0x0f, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_NTSC, + .tuner_addr = ADDR_UNSET, + .audio_mode_gpio= adtvk503_audio, + }, + + /* ---- card 0x64 ---------------------------------- */ + [BTTV_BOARD_HERCULES_SM_TV] = { + .name = "Hercules Smart TV Stereo", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x00, + .muxsel = MUXSEL(2, 3, 1, 1), + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + /* Notes: + - card lacks subsystem ID + - stereo variant w/ daughter board with tda9874a @0xb0 + - Audio Routing: + always from tda9874 independent of GPIO (?) + external line in: unknown + - Other chips: em78p156elp @ 0x96 (probably IR remote control) + hef4053 (instead 4052) for unknown function + */ + }, + [BTTV_BOARD_PACETV] = { + .name = "Pace TV & Radio Card", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + /* Tuner, CVid, SVid, CVid over SVid connector */ + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomask = 0, + .no_tda7432 = 1, + .tuner_type = TUNER_PHILIPS_PAL_I, + .tuner_addr = ADDR_UNSET, + .has_radio = 1, + .pll = PLL_28, + /* Bt878, Bt832, FI1246 tuner; no pci subsystem id + only internal line out: (4pin header) RGGL + Radio must be decoded by msp3410d (not routed through)*/ + /* + .digital_mode = DIGITAL_MODE_CAMERA, todo! + */ + }, + [BTTV_BOARD_IVC200] = { + /* Chris Willing */ + .name = "IVC-200", + .video_inputs = 1, + /* .audio_inputs= 0, */ + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .svhs = NO_SVHS, + .gpiomask = 0xdf, + .muxsel = MUXSEL(2), + .pll = PLL_28, + }, + [BTTV_BOARD_IVCE8784] = { + .name = "IVCE-8784", + .video_inputs = 1, + /* .audio_inputs= 0, */ + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .svhs = NO_SVHS, + .gpiomask = 0xdf, + .muxsel = MUXSEL(2), + .pll = PLL_28, + }, + [BTTV_BOARD_XGUARD] = { + .name = "Grand X-Guard / Trust 814PCI", + .video_inputs = 16, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .gpiomask2 = 0xff, + .muxsel = MUXSEL(2,2,2,2, 3,3,3,3, 1,1,1,1, 0,0,0,0), + .muxsel_hook = xguard_muxsel, + .no_msp34xx = 1, + .no_tda7432 = 1, + .pll = PLL_28, + }, + + /* ---- card 0x68 ---------------------------------- */ + [BTTV_BOARD_NEBULA_DIGITV] = { + .name = "Nebula Electronics DigiTV", + .video_inputs = 1, + .svhs = NO_SVHS, + .muxsel = MUXSEL(2, 3, 1, 0), + .no_msp34xx = 1, + .no_tda7432 = 1, + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .has_dvb = 1, + .has_remote = 1, + .gpiomask = 0x1b, + .no_gpioirq = 1, + }, + [BTTV_BOARD_PV143] = { + /* Jorge Boncompte - DTI2 */ + .name = "ProVideo PV143", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .gpiomask = 0, + .muxsel = MUXSEL(2, 3, 1, 0), + .gpiomux = { 0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_VD009X1_VD011_MINIDIN] = { + /* M.Klahr@phytec.de */ + .name = "PHYTEC VD-009-X1 VD-011 MiniDIN (bt878)", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .svhs = 3, + .gpiomask = 0x00, + .muxsel = MUXSEL(2, 3, 1, 0), + .gpiomux = { 0, 0, 0, 0 }, /* card has no audio */ + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_VD009X1_VD011_COMBI] = { + .name = "PHYTEC VD-009-X1 VD-011 Combi (bt878)", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .svhs = 3, + .gpiomask = 0x00, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 0, 0, 0 }, /* card has no audio */ + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + + /* ---- card 0x6c ---------------------------------- */ + [BTTV_BOARD_VD009_MINIDIN] = { + .name = "PHYTEC VD-009 MiniDIN (bt878)", + .video_inputs = 10, + /* .audio_inputs= 0, */ + .svhs = 9, + .gpiomask = 0x00, + .gpiomask2 = 0x03, /* used for external video mux */ + .muxsel = MUXSEL(2, 2, 2, 2, 3, 3, 3, 3, 1, 0), + .muxsel_hook = phytec_muxsel, + .gpiomux = { 0, 0, 0, 0 }, /* card has no audio */ + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_VD009_COMBI] = { + .name = "PHYTEC VD-009 Combi (bt878)", + .video_inputs = 10, + /* .audio_inputs= 0, */ + .svhs = 9, + .gpiomask = 0x00, + .gpiomask2 = 0x03, /* used for external video mux */ + .muxsel = MUXSEL(2, 2, 2, 2, 3, 3, 3, 3, 1, 1), + .muxsel_hook = phytec_muxsel, + .gpiomux = { 0, 0, 0, 0 }, /* card has no audio */ + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_IVC100] = { + .name = "IVC-100", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .svhs = NO_SVHS, + .gpiomask = 0xdf, + .muxsel = MUXSEL(2, 3, 1, 0), + .pll = PLL_28, + }, + [BTTV_BOARD_IVC120] = { + /* IVC-120G - Alan Garfield */ + .name = "IVC-120G", + .video_inputs = 16, + /* .audio_inputs= 0, */ + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .svhs = NO_SVHS, /* card has no svhs */ + .no_msp34xx = 1, + .no_tda7432 = 1, + .gpiomask = 0x00, + .muxsel = MUXSEL(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + .muxsel_hook = ivc120_muxsel, + .pll = PLL_28, + }, + + /* ---- card 0x70 ---------------------------------- */ + [BTTV_BOARD_PC_HDTV] = { + .name = "pcHDTV HD-2000 TV", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .muxsel = MUXSEL(2, 3, 1, 0), + .tuner_type = TUNER_PHILIPS_FCV1236D, + .tuner_addr = ADDR_UNSET, + .has_dvb = 1, + }, + [BTTV_BOARD_TWINHAN_DST] = { + .name = "Twinhan DST + clones", + .no_msp34xx = 1, + .no_tda7432 = 1, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .no_video = 1, + .has_dvb = 1, + }, + [BTTV_BOARD_WINFASTVC100] = { + .name = "Winfast VC100", + .video_inputs = 3, + /* .audio_inputs= 0, */ + .svhs = 1, + /* Vid In, SVid In, Vid over SVid in connector */ + .muxsel = MUXSEL(3, 1, 1, 3), + .no_msp34xx = 1, + .no_tda7432 = 1, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .pll = PLL_28, + }, + [BTTV_BOARD_TEV560] = { + .name = "Teppro TEV-560/InterVision IV-560", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 3, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 1, 1, 1, 1 }, + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + .pll = PLL_35, + }, + + /* ---- card 0x74 ---------------------------------- */ + [BTTV_BOARD_SIMUS_GVC1100] = { + .name = "SIMUS GVC1100", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .pll = PLL_28, + .muxsel = MUXSEL(2, 2, 2, 2), + .gpiomask = 0x3F, + .muxsel_hook = gvc1100_muxsel, + }, + [BTTV_BOARD_NGSTV_PLUS] = { + /* Carlos Silva r3pek@r3pek.homelinux.org || card 0x75 */ + .name = "NGS NGSTV+", + .video_inputs = 3, + .svhs = 2, + .gpiomask = 0x008007, + .muxsel = MUXSEL(2, 3, 0, 0), + .gpiomux = { 0, 0, 0, 0 }, + .gpiomute = 0x000003, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + .has_remote = 1, + }, + [BTTV_BOARD_LMLBT4] = { + /* http://linuxmedialabs.com */ + .name = "LMLBT4", + .video_inputs = 4, /* IN1,IN2,IN3,IN4 */ + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .muxsel = MUXSEL(2, 3, 1, 0), + .no_msp34xx = 1, + .no_tda7432 = 1, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_TEKRAM_M205] = { + /* Helmroos Harri */ + .name = "Tekram M205 PRO", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + .svhs = 2, + .gpiomask = 0x68, + .muxsel = MUXSEL(2, 3, 1), + .gpiomux = { 0x68, 0x68, 0x61, 0x61 }, + .pll = PLL_28, + }, + + /* ---- card 0x78 ---------------------------------- */ + [BTTV_BOARD_CONTVFMI] = { + /* Javier Cendan Ares */ + /* bt878 TV + FM without subsystem ID */ + .name = "Conceptronic CONTVFMi", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x008007, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 1, 2, 2 }, + .gpiomute = 3, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + .has_remote = 1, + .has_radio = 1, + }, + [BTTV_BOARD_PICOLO_TETRA_CHIP] = { + /*Eric DEBIEF */ + /*EURESYS Picolo Tetra : 4 Conexant Fusion 878A, no audio, video input set with analog multiplexers GPIO controlled*/ + /*adds picolo_tetra_muxsel(), picolo_tetra_init(), the following declaration*/ + /*structure and #define BTTV_BOARD_PICOLO_TETRA_CHIP 0x79 in bttv.h*/ + .name = "Euresys Picolo Tetra", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .gpiomask = 0, + .gpiomask2 = 0x3C<<16,/*Set the GPIO[18]->GPIO[21] as output pin.==> drive the video inputs through analog multiplexers*/ + .no_msp34xx = 1, + .no_tda7432 = 1, + /*878A input is always MUX0, see above.*/ + .muxsel = MUXSEL(2, 2, 2, 2), + .gpiomux = { 0, 0, 0, 0 }, /* card has no audio */ + .pll = PLL_28, + .muxsel_hook = picolo_tetra_muxsel,/*Required as it doesn't follow the classic input selection policy*/ + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_SPIRIT_TV] = { + /* Spirit TV Tuner from http://spiritmodems.com.au */ + /* Stafford Goodsell */ + .name = "Spirit TV Tuner", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x0000000f, + .muxsel = MUXSEL(2, 1, 1), + .gpiomux = { 0x02, 0x00, 0x00, 0x00 }, + .tuner_type = TUNER_TEMIC_PAL, + .tuner_addr = ADDR_UNSET, + .no_msp34xx = 1, + }, + [BTTV_BOARD_AVDVBT_771] = { + /* Wolfram Joost */ + .name = "AVerMedia AVerTV DVB-T 771", + .video_inputs = 2, + .svhs = 1, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .muxsel = MUXSEL(3, 3), + .no_msp34xx = 1, + .no_tda7432 = 1, + .pll = PLL_28, + .has_dvb = 1, + .no_gpioirq = 1, + .has_remote = 1, + }, + /* ---- card 0x7c ---------------------------------- */ + [BTTV_BOARD_AVDVBT_761] = { + /* Matt Jesson */ + /* Based on the Nebula card data - added remote and new card number - BTTV_BOARD_AVDVBT_761, see also ir-kbd-gpio.c */ + .name = "AverMedia AverTV DVB-T 761", + .video_inputs = 2, + .svhs = 1, + .muxsel = MUXSEL(3, 1, 2, 0), /* Comp0, S-Video, ?, ? */ + .no_msp34xx = 1, + .no_tda7432 = 1, + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .has_dvb = 1, + .no_gpioirq = 1, + .has_remote = 1, + }, + [BTTV_BOARD_MATRIX_VISIONSQ] = { + /* andre.schwarz@matrix-vision.de */ + .name = "MATRIX Vision Sigma-SQ", + .video_inputs = 16, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .gpiomask = 0x0, + .muxsel = MUXSEL(2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3), + .muxsel_hook = sigmaSQ_muxsel, + .gpiomux = { 0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_MATRIX_VISIONSLC] = { + /* andre.schwarz@matrix-vision.de */ + .name = "MATRIX Vision Sigma-SLC", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .gpiomask = 0x0, + .muxsel = MUXSEL(2, 2, 2, 2), + .muxsel_hook = sigmaSLC_muxsel, + .gpiomux = { 0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + /* BTTV_BOARD_APAC_VIEWCOMP */ + [BTTV_BOARD_APAC_VIEWCOMP] = { + /* Attila Kondoros */ + /* bt878 TV + FM 0x00000000 subsystem ID */ + .name = "APAC Viewcomp 878(AMAX)", + .video_inputs = 2, + /* .audio_inputs= 1, */ + .svhs = NO_SVHS, + .gpiomask = 0xFF, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 2, 0, 0, 0 }, + .gpiomute = 10, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .tuner_addr = ADDR_UNSET, + .has_remote = 1, /* miniremote works, see ir-kbd-gpio.c */ + .has_radio = 1, /* not every card has radio */ + }, + + /* ---- card 0x80 ---------------------------------- */ + [BTTV_BOARD_DVICO_DVBT_LITE] = { + /* Chris Pascoe */ + .name = "DViCO FusionHDTV DVB-T Lite", + .no_msp34xx = 1, + .no_tda7432 = 1, + .pll = PLL_28, + .no_video = 1, + .has_dvb = 1, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_VGEAR_MYVCD] = { + /* Steven */ + .name = "V-Gear MyVCD", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x3f, + .muxsel = MUXSEL(2, 3, 1, 0), + .gpiomux = {0x31, 0x31, 0x31, 0x31 }, + .gpiomute = 0x31, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .tuner_addr = ADDR_UNSET, + .has_radio = 0, + }, + [BTTV_BOARD_SUPER_TV] = { + /* Rick C */ + .name = "Super TV Tuner", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .muxsel = MUXSEL(2, 3, 1, 0), + .tuner_type = TUNER_PHILIPS_NTSC, + .tuner_addr = ADDR_UNSET, + .gpiomask = 0x008007, + .gpiomux = { 0, 0x000001,0,0 }, + .has_radio = 1, + }, + [BTTV_BOARD_TIBET_CS16] = { + /* Chris Fanning */ + .name = "Tibet Systems 'Progress DVR' CS16", + .video_inputs = 16, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .muxsel = MUXSEL(2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2), + .pll = PLL_28, + .no_msp34xx = 1, + .no_tda7432 = 1, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .muxsel_hook = tibetCS16_muxsel, + }, + [BTTV_BOARD_KODICOM_4400R] = { + /* Bill Brack */ + /* + * Note that, because of the card's wiring, the "master" + * BT878A chip (i.e. the one which controls the analog switch + * and must use this card type) is the 2nd one detected. The + * other 3 chips should use card type 0x85, whose description + * follows this one. There is a EEPROM on the card (which is + * connected to the I2C of one of those other chips), but is + * not currently handled. There is also a facility for a + * "monitor", which is also not currently implemented. + */ + .name = "Kodicom 4400R (master)", + .video_inputs = 16, + /* .audio_inputs= 0, */ + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .svhs = NO_SVHS, + /* GPIO bits 0-9 used for analog switch: + * 00 - 03: camera selector + * 04 - 06: channel (controller) selector + * 07: data (1->on, 0->off) + * 08: strobe + * 09: reset + * bit 16 is input from sync separator for the channel + */ + .gpiomask = 0x0003ff, + .no_gpioirq = 1, + .muxsel = MUXSEL(3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3), + .pll = PLL_28, + .no_msp34xx = 1, + .no_tda7432 = 1, + .muxsel_hook = kodicom4400r_muxsel, + }, + [BTTV_BOARD_KODICOM_4400R_SL] = { + /* Bill Brack */ + /* Note that, for reasons unknown, the "master" BT878A chip (i.e. the + * one which controls the analog switch, and must use the card type) + * is the 2nd one detected. The other 3 chips should use this card + * type + */ + .name = "Kodicom 4400R (slave)", + .video_inputs = 16, + /* .audio_inputs= 0, */ + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .svhs = NO_SVHS, + .gpiomask = 0x010000, + .no_gpioirq = 1, + .muxsel = MUXSEL(3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3), + .pll = PLL_28, + .no_msp34xx = 1, + .no_tda7432 = 1, + .muxsel_hook = kodicom4400r_muxsel, + }, + /* ---- card 0x86---------------------------------- */ + [BTTV_BOARD_ADLINK_RTV24] = { + /* Michael Henson */ + /* Adlink RTV24 with special unlock codes */ + .name = "Adlink RTV24", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .svhs = 2, + .muxsel = MUXSEL(2, 3, 1, 0), + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + .pll = PLL_28, + }, + /* ---- card 0x87---------------------------------- */ + [BTTV_BOARD_DVICO_FUSIONHDTV_5_LITE] = { + /* Michael Krufky */ + .name = "DViCO FusionHDTV 5 Lite", + .tuner_type = TUNER_LG_TDVS_H06XF, /* TDVS-H064F */ + .tuner_addr = ADDR_UNSET, + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .muxsel = MUXSEL(2, 3, 1), + .gpiomask = 0x00e00007, + .gpiomux = { 0x00400005, 0, 0x00000001, 0 }, + .gpiomute = 0x00c00007, + .no_msp34xx = 1, + .no_tda7432 = 1, + .has_dvb = 1, + }, + /* ---- card 0x88---------------------------------- */ + [BTTV_BOARD_ACORP_Y878F] = { + /* Mauro Carvalho Chehab */ + .name = "Acorp Y878F", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x01fe00, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0x001e00, 0, 0x018000, 0x014000 }, + .gpiomute = 0x002000, + .pll = PLL_28, + .tuner_type = TUNER_YMEC_TVF66T5_B_DFF, + .tuner_addr = 0xc1 >>1, + .has_radio = 1, + }, + /* ---- card 0x89 ---------------------------------- */ + [BTTV_BOARD_CONCEPTRONIC_CTVFMI2] = { + .name = "Conceptronic CTVFMi v2", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x001c0007, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 1, 2, 2 }, + .gpiomute = 3, + .pll = PLL_28, + .tuner_type = TUNER_TENA_9533_DI, + .tuner_addr = ADDR_UNSET, + .has_remote = 1, + .has_radio = 1, + }, + /* ---- card 0x8a ---------------------------------- */ + [BTTV_BOARD_PV_BT878P_2E] = { + .name = "Prolink Pixelview PV-BT878P+ (Rev.2E)", + .video_inputs = 5, + /* .audio_inputs= 1, */ + .svhs = 3, + .has_dig_in = 1, + .gpiomask = 0x01fe00, + .muxsel = MUXSEL(2, 3, 1, 1, 0), /* in 4 is digital */ + /* .digital_mode= DIGITAL_MODE_CAMERA, */ + .gpiomux = { 0x00400, 0x10400, 0x04400, 0x80000 }, + .gpiomute = 0x12400, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_LG_PAL_FM, + .tuner_addr = ADDR_UNSET, + .has_remote = 1, + }, + /* ---- card 0x8b ---------------------------------- */ + [BTTV_BOARD_PV_M4900] = { + /* Sérgio Fortier */ + .name = "Prolink PixelView PlayTV MPEG2 PV-M4900", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x3f, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0x21, 0x20, 0x24, 0x2c }, + .gpiomute = 0x29, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_YMEC_TVF_5533MF, + .tuner_addr = ADDR_UNSET, + .has_radio = 1, + .has_remote = 1, + }, + /* ---- card 0x8c ---------------------------------- */ + /* Has four Bt878 chips behind a PCI bridge, each chip has: + one external BNC composite input (mux 2) + three internal composite inputs (unknown muxes) + an 18-bit stereo A/D (CS5331A), which has: + one external stereo unbalanced (RCA) audio connection + one (or 3?) internal stereo balanced (XLR) audio connection + input is selected via gpio to a 14052B mux + (mask=0x300, unbal=0x000, bal=0x100, ??=0x200,0x300) + gain is controlled via an X9221A chip on the I2C bus @0x28 + sample rate is controlled via gpio to an MK1413S + (mask=0x3, 32kHz=0x0, 44.1kHz=0x1, 48kHz=0x2, ??=0x3) + There is neither a tuner nor an svideo input. */ + [BTTV_BOARD_OSPREY440] = { + .name = "Osprey 440", + .video_inputs = 4, + /* .audio_inputs= 2, */ + .svhs = NO_SVHS, + .muxsel = MUXSEL(2, 3, 0, 1), /* 3,0,1 are guesses */ + .gpiomask = 0x303, + .gpiomute = 0x000, /* int + 32kHz */ + .gpiomux = { 0, 0, 0x000, 0x100}, + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .no_msp34xx = 1, + .no_tda7432 = 1, + }, + /* ---- card 0x8d ---------------------------------- */ + [BTTV_BOARD_ASOUND_SKYEYE] = { + .name = "Asound Skyeye PCTV", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 15, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 2, 0, 0, 0 }, + .gpiomute = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_NTSC, + .tuner_addr = ADDR_UNSET, + }, + /* ---- card 0x8e ---------------------------------- */ + [BTTV_BOARD_SABRENT_TVFM] = { + .name = "Sabrent TV-FM (bttv version)", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x108007, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 100000, 100002, 100002, 100000 }, + .no_msp34xx = 1, + .no_tda7432 = 1, + .pll = PLL_28, + .tuner_type = TUNER_TNF_5335MF, + .tuner_addr = ADDR_UNSET, + .has_radio = 1, + }, + /* ---- card 0x8f ---------------------------------- */ + [BTTV_BOARD_HAUPPAUGE_IMPACTVCB] = { + .name = "Hauppauge ImpactVCB (bt878)", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .gpiomask = 0x0f, /* old: 7 */ + .muxsel = MUXSEL(0, 1, 3, 2), /* Composite 0-3 */ + .no_msp34xx = 1, + .no_tda7432 = 1, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_MACHTV_MAGICTV] = { + /* Julian Calaby + * Slightly different from original MachTV definition (0x60) + + * FIXME: RegSpy says gpiomask should be "0x001c800f", but it + * stuffs up remote chip. Bug is a pin on the jaecs is not set + * properly (methinks) causing no keyup bits being set */ + + .name = "MagicTV", /* rebranded MachTV */ + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 7, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 1, 2, 3 }, + .gpiomute = 4, + .tuner_type = TUNER_TEMIC_4009FR5_PAL, + .tuner_addr = ADDR_UNSET, + .pll = PLL_28, + .has_radio = 1, + .has_remote = 1, + }, + [BTTV_BOARD_SSAI_SECURITY] = { + .name = "SSAI Security Video Interface", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .muxsel = MUXSEL(0, 1, 2, 3), + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_SSAI_ULTRASOUND] = { + .name = "SSAI Ultrasound Video Interface", + .video_inputs = 2, + /* .audio_inputs= 0, */ + .svhs = 1, + .muxsel = MUXSEL(2, 0, 1, 3), + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + /* ---- card 0x94---------------------------------- */ + [BTTV_BOARD_DVICO_FUSIONHDTV_2] = { + .name = "DViCO FusionHDTV 2", + .tuner_type = TUNER_PHILIPS_FCV1236D, + .tuner_addr = ADDR_UNSET, + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .muxsel = MUXSEL(2, 3, 1), + .gpiomask = 0x00e00007, + .gpiomux = { 0x00400005, 0, 0x00000001, 0 }, + .gpiomute = 0x00c00007, + .no_msp34xx = 1, + .no_tda7432 = 1, + }, + /* ---- card 0x95---------------------------------- */ + [BTTV_BOARD_TYPHOON_TVTUNERPCI] = { + .name = "Typhoon TV-Tuner PCI (50684)", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x3014f, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0x20001,0x10001, 0, 0 }, + .gpiomute = 10, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL_I, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_GEOVISION_GV600] = { + /* emhn@usb.ve */ + .name = "Geovision GV-600", + .video_inputs = 16, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .gpiomask = 0x0, + .muxsel = MUXSEL(2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2), + .muxsel_hook = geovision_muxsel, + .gpiomux = { 0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_KOZUMI_KTV_01C] = { + /* Mauro Lacy + * Based on MagicTV and Conceptronic CONTVFMi */ + + .name = "Kozumi KTV-01C", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x008007, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 1, 2, 2 }, /* CONTVFMi */ + .gpiomute = 3, /* CONTVFMi */ + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, /* TCL MK3 */ + .tuner_addr = ADDR_UNSET, + .pll = PLL_28, + .has_radio = 1, + .has_remote = 1, + }, + [BTTV_BOARD_ENLTV_FM_2] = { + /* Encore TV Tuner Pro ENL TV-FM-2 + Mauro Carvalho Chehab */ + .name = "Encore ENL TV-FM-2", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + /* bit 6 -> IR disabled + bit 18/17 = 00 -> mute + 01 -> enable external audio input + 10 -> internal audio input (mono?) + 11 -> internal audio input + */ + .gpiomask = 0x060040, + .muxsel = MUXSEL(2, 3, 3), + .gpiomux = { 0x60000, 0x60000, 0x20000, 0x20000 }, + .gpiomute = 0, + .tuner_type = TUNER_TCL_MF02GIP_5N, + .tuner_addr = ADDR_UNSET, + .pll = PLL_28, + .has_radio = 1, + .has_remote = 1, + }, + [BTTV_BOARD_VD012] = { + /* D.Heer@Phytec.de */ + .name = "PHYTEC VD-012 (bt878)", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .gpiomask = 0x00, + .muxsel = MUXSEL(0, 2, 3, 1), + .gpiomux = { 0, 0, 0, 0 }, /* card has no audio */ + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_VD012_X1] = { + /* D.Heer@Phytec.de */ + .name = "PHYTEC VD-012-X1 (bt878)", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .svhs = 3, + .gpiomask = 0x00, + .muxsel = MUXSEL(2, 3, 1), + .gpiomux = { 0, 0, 0, 0 }, /* card has no audio */ + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_VD012_X2] = { + /* D.Heer@Phytec.de */ + .name = "PHYTEC VD-012-X2 (bt878)", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .svhs = 3, + .gpiomask = 0x00, + .muxsel = MUXSEL(3, 2, 1), + .gpiomux = { 0, 0, 0, 0 }, /* card has no audio */ + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_GEOVISION_GV800S] = { + /* Bruno Christo + * + * GeoVision GV-800(S) has 4 Conexant Fusion 878A: + * 1 audio input per BT878A = 4 audio inputs + * 4 video inputs per BT878A = 16 video inputs + * This is the first BT878A chip of the GV-800(S). It's the + * "master" chip and it controls the video inputs through an + * analog multiplexer (a CD22M3494) via some GPIO pins. The + * slaves should use card type 0x9e (following this one). + * There is a EEPROM on the card which is currently not handled. + * The audio input is not working yet. + */ + .name = "Geovision GV-800(S) (master)", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .svhs = NO_SVHS, + .gpiomask = 0xf107f, + .no_gpioirq = 1, + .muxsel = MUXSEL(2, 2, 2, 2), + .pll = PLL_28, + .no_msp34xx = 1, + .no_tda7432 = 1, + .muxsel_hook = gv800s_muxsel, + }, + [BTTV_BOARD_GEOVISION_GV800S_SL] = { + /* Bruno Christo + * + * GeoVision GV-800(S) has 4 Conexant Fusion 878A: + * 1 audio input per BT878A = 4 audio inputs + * 4 video inputs per BT878A = 16 video inputs + * The 3 other BT878A chips are "slave" chips of the GV-800(S) + * and should use this card type. + * The audio input is not working yet. + */ + .name = "Geovision GV-800(S) (slave)", + .video_inputs = 4, + /* .audio_inputs= 1, */ + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + .svhs = NO_SVHS, + .gpiomask = 0x00, + .no_gpioirq = 1, + .muxsel = MUXSEL(2, 2, 2, 2), + .pll = PLL_28, + .no_msp34xx = 1, + .no_tda7432 = 1, + .muxsel_hook = gv800s_muxsel, + }, + [BTTV_BOARD_PV183] = { + .name = "ProVideo PV183", /* 0x9f */ + .video_inputs = 2, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .gpiomask = 0, + .muxsel = MUXSEL(2, 3), + .gpiomux = { 0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + /* ---- card 0xa0---------------------------------- */ + [BTTV_BOARD_TVT_TD3116] = { + .name = "Tongwei Video Technology TD-3116", + .video_inputs = 16, + .gpiomask = 0xc00ff, + .muxsel = MUXSEL(2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2), + .muxsel_hook = td3116_muxsel, + .svhs = NO_SVHS, + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + }, + [BTTV_BOARD_APOSONIC_WDVR] = { + .name = "Aposonic W-DVR", + .video_inputs = 4, + .svhs = NO_SVHS, + .muxsel = MUXSEL(2, 3, 1, 0), + .tuner_type = TUNER_ABSENT, + }, + [BTTV_BOARD_ADLINK_MPG24] = { + /* Adlink MPG24 */ + .name = "Adlink MPG24", + .video_inputs = 1, + /* .audio_inputs= 1, */ + .svhs = NO_SVHS, + .muxsel = MUXSEL(2, 2, 2, 2), + .tuner_type = UNSET, + .tuner_addr = ADDR_UNSET, + .pll = PLL_28, + }, + [BTTV_BOARD_BT848_CAP_14] = { + .name = "Bt848 Capture 14MHz", + .video_inputs = 4, + .svhs = 2, + .muxsel = MUXSEL(2, 3, 1, 0), + .pll = PLL_14, + .tuner_type = TUNER_ABSENT, + }, + [BTTV_BOARD_CYBERVISION_CV06] = { + .name = "CyberVision CV06 (SV)", + .video_inputs = 4, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + .muxsel = MUXSEL(2, 3, 1, 0), + .pll = PLL_28, + .tuner_type = TUNER_ABSENT, + .tuner_addr = ADDR_UNSET, + }, + [BTTV_BOARD_KWORLD_VSTREAM_XPERT] = { + /* Pojar George */ + .name = "Kworld V-Stream Xpert TV PVR878", + .video_inputs = 3, + /* .audio_inputs= 1, */ + .svhs = 2, + .gpiomask = 0x001c0007, + .muxsel = MUXSEL(2, 3, 1, 1), + .gpiomux = { 0, 1, 2, 2 }, + .gpiomute = 3, + .pll = PLL_28, + .tuner_type = TUNER_TENA_9533_DI, + .tuner_addr = ADDR_UNSET, + .has_remote = 1, + .has_radio = 1, + }, + /* ---- card 0xa6---------------------------------- */ + [BTTV_BOARD_PCI_8604PW] = { + /* PCI-8604PW with special unlock sequence */ + .name = "PCI-8604PW", + .video_inputs = 2, + /* .audio_inputs= 0, */ + .svhs = NO_SVHS, + /* The second input is available on CN4, if populated. + * The other 5x2 header (CN2?) connects to the same inputs + * as the on-board BNCs */ + .muxsel = MUXSEL(2, 3), + .tuner_type = TUNER_ABSENT, + .no_msp34xx = 1, + .no_tda7432 = 1, + .pll = PLL_35, + }, +}; + +static const unsigned int bttv_num_tvcards = ARRAY_SIZE(bttv_tvcards); + +/* ----------------------------------------------------------------------- */ + +static unsigned char eeprom_data[256]; + +/* + * identify card + */ +void bttv_idcard(struct bttv *btv) +{ + unsigned int gpiobits; + int i,type; + + /* read PCI subsystem ID */ + btv->cardid = btv->c.pci->subsystem_device << 16; + btv->cardid |= btv->c.pci->subsystem_vendor; + + if (0 != btv->cardid && 0xffffffff != btv->cardid) { + /* look for the card */ + for (type = -1, i = 0; cards[i].id != 0; i++) + if (cards[i].id == btv->cardid) + type = i; + + if (type != -1) { + /* found it */ + pr_info("%d: detected: %s [card=%d], PCI subsystem ID is %04x:%04x\n", + btv->c.nr, cards[type].name, cards[type].cardnr, + btv->cardid & 0xffff, + (btv->cardid >> 16) & 0xffff); + btv->c.type = cards[type].cardnr; + } else { + /* 404 */ + pr_info("%d: subsystem: %04x:%04x (UNKNOWN)\n", + btv->c.nr, btv->cardid & 0xffff, + (btv->cardid >> 16) & 0xffff); + pr_debug("please mail id, board name and the correct card= insmod option to linux-media@vger.kernel.org\n"); + } + } + + /* let the user override the autodetected type */ + if (card[btv->c.nr] < bttv_num_tvcards) + btv->c.type=card[btv->c.nr]; + + /* print which card config we are using */ + pr_info("%d: using: %s [card=%d,%s]\n", + btv->c.nr, bttv_tvcards[btv->c.type].name, btv->c.type, + card[btv->c.nr] < bttv_num_tvcards + ? "insmod option" : "autodetected"); + + /* overwrite gpio stuff ?? */ + if (UNSET == audioall && UNSET == audiomux[0]) + return; + + if (UNSET != audiomux[0]) { + gpiobits = 0; + for (i = 0; i < ARRAY_SIZE(bttv_tvcards->gpiomux); i++) { + bttv_tvcards[btv->c.type].gpiomux[i] = audiomux[i]; + gpiobits |= audiomux[i]; + } + } else { + gpiobits = audioall; + for (i = 0; i < ARRAY_SIZE(bttv_tvcards->gpiomux); i++) { + bttv_tvcards[btv->c.type].gpiomux[i] = audioall; + } + } + bttv_tvcards[btv->c.type].gpiomask = (UNSET != gpiomask) ? gpiomask : gpiobits; + pr_info("%d: gpio config override: mask=0x%x, mux=", + btv->c.nr, bttv_tvcards[btv->c.type].gpiomask); + for (i = 0; i < ARRAY_SIZE(bttv_tvcards->gpiomux); i++) { + pr_cont("%s0x%x", + i ? "," : "", bttv_tvcards[btv->c.type].gpiomux[i]); + } + pr_cont("\n"); +} + +/* + * (most) board specific initialisations goes here + */ + +/* Some Modular Technology cards have an eeprom, but no subsystem ID */ +static void identify_by_eeprom(struct bttv *btv, unsigned char eeprom_data[256]) +{ + int type = -1; + + if (0 == strncmp(eeprom_data,"GET MM20xPCTV",13)) + type = BTTV_BOARD_MODTEC_205; + else if (0 == strncmp(eeprom_data+20,"Picolo",7)) + type = BTTV_BOARD_EURESYS_PICOLO; + else if (eeprom_data[0] == 0x84 && eeprom_data[2]== 0) + type = BTTV_BOARD_HAUPPAUGE; /* old bt848 */ + + if (-1 != type) { + btv->c.type = type; + pr_info("%d: detected by eeprom: %s [card=%d]\n", + btv->c.nr, bttv_tvcards[btv->c.type].name, btv->c.type); + } +} + +static void flyvideo_gpio(struct bttv *btv) +{ + int gpio, has_remote, has_radio, is_capture_only; + int is_lr90, has_tda9820_tda9821; + int tuner_type = UNSET, ttype; + + gpio_inout(0xffffff, 0); + udelay(8); /* without this we would see the 0x1800 mask */ + gpio = gpio_read(); + /* FIXME: must restore OUR_EN ??? */ + + /* all cards provide GPIO info, some have an additional eeprom + * LR50: GPIO coding can be found lower right CP1 .. CP9 + * CP9=GPIO23 .. CP1=GPIO15; when OPEN, the corresponding GPIO reads 1. + * GPIO14-12: n.c. + * LR90: GP9=GPIO23 .. GP1=GPIO15 (right above the bt878) + + * lowest 3 bytes are remote control codes (no handshake needed) + * xxxFFF: No remote control chip soldered + * xxxF00(LR26/LR50), xxxFE0(LR90): Remote control chip (LVA001 or CF45) soldered + * Note: Some bits are Audio_Mask ! + */ + ttype = (gpio & 0x0f0000) >> 16; + switch (ttype) { + case 0x0: + tuner_type = 2; /* NTSC, e.g. TPI8NSR11P */ + break; + case 0x2: + tuner_type = 39; /* LG NTSC (newer TAPC series) TAPC-H701P */ + break; + case 0x4: + tuner_type = 5; /* Philips PAL TPI8PSB02P, TPI8PSB12P, TPI8PSB12D or FI1216, FM1216 */ + break; + case 0x6: + tuner_type = 37; /* LG PAL (newer TAPC series) TAPC-G702P */ + break; + case 0xC: + tuner_type = 3; /* Philips SECAM(+PAL) FQ1216ME or FI1216MF */ + break; + default: + pr_info("%d: FlyVideo_gpio: unknown tuner type\n", btv->c.nr); + break; + } + + has_remote = gpio & 0x800000; + has_radio = gpio & 0x400000; + /* unknown 0x200000; + * unknown2 0x100000; */ + is_capture_only = !(gpio & 0x008000); /* GPIO15 */ + has_tda9820_tda9821 = !(gpio & 0x004000); + is_lr90 = !(gpio & 0x002000); /* else LR26/LR50 (LR38/LR51 f. capture only) */ + /* + * gpio & 0x001000 output bit for audio routing */ + + if (is_capture_only) + tuner_type = TUNER_ABSENT; /* No tuner present */ + + pr_info("%d: FlyVideo Radio=%s RemoteControl=%s Tuner=%d gpio=0x%06x\n", + btv->c.nr, has_radio ? "yes" : "no", + has_remote ? "yes" : "no", tuner_type, gpio); + pr_info("%d: FlyVideo LR90=%s tda9821/tda9820=%s capture_only=%s\n", + btv->c.nr, is_lr90 ? "yes" : "no", + has_tda9820_tda9821 ? "yes" : "no", + is_capture_only ? "yes" : "no"); + + if (tuner_type != UNSET) /* only set if known tuner autodetected, else let insmod option through */ + btv->tuner_type = tuner_type; + btv->has_radio = has_radio; + + /* LR90 Audio Routing is done by 2 hef4052, so Audio_Mask has 4 bits: 0x001c80 + * LR26/LR50 only has 1 hef4052, Audio_Mask 0x000c00 + * Audio options: from tuner, from tda9821/tda9821(mono,stereo,sap), from tda9874, ext., mute */ + if (has_tda9820_tda9821) + btv->audio_mode_gpio = lt9415_audio; + /* todo: if(has_tda9874) btv->audio_mode_gpio = fv2000s_audio; */ +} + +static int miro_tunermap[] = { 0,6,2,3, 4,5,6,0, 3,0,4,5, 5,2,16,1, + 14,2,17,1, 4,1,4,3, 1,2,16,1, 4,4,4,4 }; +static int miro_fmtuner[] = { 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, + 1,1,1,1, 1,1,1,0, 0,0,0,0, 0,1,0,0 }; + +static void miro_pinnacle_gpio(struct bttv *btv) +{ + int id,msp,gpio; + char *info; + + gpio_inout(0xffffff, 0); + gpio = gpio_read(); + id = ((gpio>>10) & 63) -1; + msp = bttv_I2CRead(btv, I2C_ADDR_MSP3400, "MSP34xx"); + if (id < 32) { + btv->tuner_type = miro_tunermap[id]; + if (0 == (gpio & 0x20)) { + btv->has_radio = 1; + if (!miro_fmtuner[id]) { + btv->has_tea575x = 1; + btv->tea_gpio.wren = 6; + btv->tea_gpio.most = 7; + btv->tea_gpio.clk = 8; + btv->tea_gpio.data = 9; + tea575x_init(btv); + } + } else { + btv->has_radio = 0; + } + if (-1 != msp) { + if (btv->c.type == BTTV_BOARD_MIRO) + btv->c.type = BTTV_BOARD_MIROPRO; + if (btv->c.type == BTTV_BOARD_PINNACLE) + btv->c.type = BTTV_BOARD_PINNACLEPRO; + } + pr_info("%d: miro: id=%d tuner=%d radio=%s stereo=%s\n", + btv->c.nr, id+1, btv->tuner_type, + !btv->has_radio ? "no" : + (btv->has_tea575x ? "tea575x" : "fmtuner"), + (-1 == msp) ? "no" : "yes"); + } else { + /* new cards with microtune tuner */ + id = 63 - id; + btv->has_radio = 0; + switch (id) { + case 1: + info = "PAL / mono"; + btv->tda9887_conf = TDA9887_INTERCARRIER; + break; + case 2: + info = "PAL+SECAM / stereo"; + btv->has_radio = 1; + btv->tda9887_conf = TDA9887_QSS; + break; + case 3: + info = "NTSC / stereo"; + btv->has_radio = 1; + btv->tda9887_conf = TDA9887_QSS; + break; + case 4: + info = "PAL+SECAM / mono"; + btv->tda9887_conf = TDA9887_QSS; + break; + case 5: + info = "NTSC / mono"; + btv->tda9887_conf = TDA9887_INTERCARRIER; + break; + case 6: + info = "NTSC / stereo"; + btv->tda9887_conf = TDA9887_INTERCARRIER; + break; + case 7: + info = "PAL / stereo"; + btv->tda9887_conf = TDA9887_INTERCARRIER; + break; + default: + info = "oops: unknown card"; + break; + } + if (-1 != msp) + btv->c.type = BTTV_BOARD_PINNACLEPRO; + pr_info("%d: pinnacle/mt: id=%d info=\"%s\" radio=%s\n", + btv->c.nr, id, info, btv->has_radio ? "yes" : "no"); + btv->tuner_type = TUNER_MT2032; + } +} + +/* GPIO21 L: Buffer aktiv, H: Buffer inaktiv */ +#define LM1882_SYNC_DRIVE 0x200000L + +static void init_ids_eagle(struct bttv *btv) +{ + gpio_inout(0xffffff,0xFFFF37); + gpio_write(0x200020); + + /* flash strobe inverter ?! */ + gpio_write(0x200024); + + /* switch sync drive off */ + gpio_bits(LM1882_SYNC_DRIVE,LM1882_SYNC_DRIVE); + + /* set BT848 muxel to 2 */ + btaor((2)<<5, ~(2<<5), BT848_IFORM); +} + +/* Muxsel helper for the IDS Eagle. + * the eagles does not use the standard muxsel-bits but + * has its own multiplexer */ +static void eagle_muxsel(struct bttv *btv, unsigned int input) +{ + gpio_bits(3, input & 3); + + /* composite */ + /* set chroma ADC to sleep */ + btor(BT848_ADC_C_SLEEP, BT848_ADC); + /* set to composite video */ + btand(~BT848_CONTROL_COMP, BT848_E_CONTROL); + btand(~BT848_CONTROL_COMP, BT848_O_CONTROL); + + /* switch sync drive off */ + gpio_bits(LM1882_SYNC_DRIVE,LM1882_SYNC_DRIVE); +} + +static void gvc1100_muxsel(struct bttv *btv, unsigned int input) +{ + static const int masks[] = {0x30, 0x01, 0x12, 0x23}; + gpio_write(masks[input%4]); +} + +/* LMLBT4x initialization - to allow access to GPIO bits for sensors input and + alarms output + + GPIObit | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + assignment | TI | O3|INx| O2| O1|IN4|IN3|IN2|IN1| | | + + IN - sensor inputs, INx - sensor inputs and TI XORed together + O1,O2,O3 - alarm outputs (relays) + + OUT ENABLE 1 1 0 . 1 1 0 0 . 0 0 0 0 = 0x6C0 + +*/ + +static void init_lmlbt4x(struct bttv *btv) +{ + pr_debug("LMLBT4x init\n"); + btwrite(0x000000, BT848_GPIO_REG_INP); + gpio_inout(0xffffff, 0x0006C0); + gpio_write(0x000000); +} + +static void sigmaSQ_muxsel(struct bttv *btv, unsigned int input) +{ + unsigned int inmux = input % 8; + gpio_inout( 0xf, 0xf ); + gpio_bits( 0xf, inmux ); +} + +static void sigmaSLC_muxsel(struct bttv *btv, unsigned int input) +{ + unsigned int inmux = input % 4; + gpio_inout( 3<<9, 3<<9 ); + gpio_bits( 3<<9, inmux<<9 ); +} + +static void geovision_muxsel(struct bttv *btv, unsigned int input) +{ + unsigned int inmux = input % 16; + gpio_inout(0xf, 0xf); + gpio_bits(0xf, inmux); +} + +/* + * The TD3116 has 2 74HC4051 muxes wired to the MUX0 input of a bt878. + * The first 74HC4051 has the lower 8 inputs, the second one the higher 8. + * The muxes are controlled via a 74HC373 latch which is connected to + * GPIOs 0-7. GPIO 18 is connected to the LE signal of the latch. + * Q0 of the latch is connected to the Enable (~E) input of the first + * 74HC4051. Q1 - Q3 are connected to S0 - S2 of the same 74HC4051. + * Q4 - Q7 are connected to the second 74HC4051 in the same way. + */ + +static void td3116_latch_value(struct bttv *btv, u32 value) +{ + gpio_bits((1<<18) | 0xff, value); + gpio_bits((1<<18) | 0xff, (1<<18) | value); + udelay(1); + gpio_bits((1<<18) | 0xff, value); +} + +static void td3116_muxsel(struct bttv *btv, unsigned int input) +{ + u32 value; + u32 highbit; + + highbit = (input & 0x8) >> 3 ; + + /* Disable outputs and set value in the mux */ + value = 0x11; /* Disable outputs */ + value |= ((input & 0x7) << 1) << (4 * highbit); + td3116_latch_value(btv, value); + + /* Enable the correct output */ + value &= ~0x11; + value |= ((highbit ^ 0x1) << 4) | highbit; + td3116_latch_value(btv, value); +} + +/* ----------------------------------------------------------------------- */ + +static void bttv_reset_audio(struct bttv *btv) +{ + /* + * BT878A has a audio-reset register. + * 1. This register is an audio reset function but it is in + * function-0 (video capture) address space. + * 2. It is enough to do this once per power-up of the card. + * 3. There is a typo in the Conexant doc -- it is not at + * 0x5B, but at 0x058. (B is an odd-number, obviously a typo!). + * --//Shrikumar 030609 + */ + if (btv->id != 878) + return; + + if (bttv_debug) + pr_debug("%d: BT878A ARESET\n", btv->c.nr); + btwrite((1<<7), 0x058); + udelay(10); + btwrite( 0, 0x058); +} + +/* initialization part one -- before registering i2c bus */ +void bttv_init_card1(struct bttv *btv) +{ + switch (btv->c.type) { + case BTTV_BOARD_HAUPPAUGE: + case BTTV_BOARD_HAUPPAUGE878: + boot_msp34xx(btv,5); + break; + case BTTV_BOARD_VOODOOTV_200: + case BTTV_BOARD_VOODOOTV_FM: + boot_msp34xx(btv,20); + break; + case BTTV_BOARD_AVERMEDIA98: + boot_msp34xx(btv,11); + break; + case BTTV_BOARD_HAUPPAUGEPVR: + pvr_boot(btv); + break; + case BTTV_BOARD_TWINHAN_DST: + case BTTV_BOARD_AVDVBT_771: + case BTTV_BOARD_PINNACLESAT: + btv->use_i2c_hw = 1; + break; + case BTTV_BOARD_ADLINK_RTV24: + init_RTV24( btv ); + break; + case BTTV_BOARD_PCI_8604PW: + init_PCI8604PW(btv); + break; + + } + if (!bttv_tvcards[btv->c.type].has_dvb) + bttv_reset_audio(btv); +} + +/* initialization part two -- after registering i2c bus */ +void bttv_init_card2(struct bttv *btv) +{ + btv->tuner_type = UNSET; + + if (BTTV_BOARD_UNKNOWN == btv->c.type) { + bttv_readee(btv,eeprom_data,0xa0); + identify_by_eeprom(btv,eeprom_data); + } + + switch (btv->c.type) { + case BTTV_BOARD_MIRO: + case BTTV_BOARD_MIROPRO: + case BTTV_BOARD_PINNACLE: + case BTTV_BOARD_PINNACLEPRO: + /* miro/pinnacle */ + miro_pinnacle_gpio(btv); + break; + case BTTV_BOARD_FLYVIDEO_98: + case BTTV_BOARD_MAXI: + case BTTV_BOARD_LIFE_FLYKIT: + case BTTV_BOARD_FLYVIDEO: + case BTTV_BOARD_TYPHOON_TVIEW: + case BTTV_BOARD_CHRONOS_VS2: + case BTTV_BOARD_FLYVIDEO_98FM: + case BTTV_BOARD_FLYVIDEO2000: + case BTTV_BOARD_FLYVIDEO98EZ: + case BTTV_BOARD_CONFERENCETV: + case BTTV_BOARD_LIFETEC_9415: + flyvideo_gpio(btv); + break; + case BTTV_BOARD_HAUPPAUGE: + case BTTV_BOARD_HAUPPAUGE878: + case BTTV_BOARD_HAUPPAUGEPVR: + /* pick up some config infos from the eeprom */ + bttv_readee(btv,eeprom_data,0xa0); + hauppauge_eeprom(btv); + break; + case BTTV_BOARD_AVERMEDIA98: + case BTTV_BOARD_AVPHONE98: + bttv_readee(btv,eeprom_data,0xa0); + avermedia_eeprom(btv); + break; + case BTTV_BOARD_PXC200: + init_PXC200(btv); + break; + case BTTV_BOARD_PICOLO_TETRA_CHIP: + picolo_tetra_init(btv); + break; + case BTTV_BOARD_VHX: + btv->has_radio = 1; + btv->has_tea575x = 1; + btv->tea_gpio.wren = 5; + btv->tea_gpio.most = 6; + btv->tea_gpio.clk = 3; + btv->tea_gpio.data = 4; + tea575x_init(btv); + break; + case BTTV_BOARD_VOBIS_BOOSTAR: + case BTTV_BOARD_TERRATV: + terratec_active_radio_upgrade(btv); + break; + case BTTV_BOARD_MAGICTVIEW061: + if (btv->cardid == 0x3002144f) { + btv->has_radio=1; + pr_info("%d: radio detected by subsystem id (CPH05x)\n", + btv->c.nr); + } + break; + case BTTV_BOARD_STB2: + if (btv->cardid == 0x3060121a) { + /* Fix up entry for 3DFX VoodooTV 100, + which is an OEM STB card variant. */ + btv->has_radio=0; + btv->tuner_type=TUNER_TEMIC_NTSC; + } + break; + case BTTV_BOARD_OSPREY1x0: + case BTTV_BOARD_OSPREY1x0_848: + case BTTV_BOARD_OSPREY101_848: + case BTTV_BOARD_OSPREY1x1: + case BTTV_BOARD_OSPREY1x1_SVID: + case BTTV_BOARD_OSPREY2xx: + case BTTV_BOARD_OSPREY2x0_SVID: + case BTTV_BOARD_OSPREY2x0: + case BTTV_BOARD_OSPREY440: + case BTTV_BOARD_OSPREY500: + case BTTV_BOARD_OSPREY540: + case BTTV_BOARD_OSPREY2000: + bttv_readee(btv,eeprom_data,0xa0); + osprey_eeprom(btv, eeprom_data); + break; + case BTTV_BOARD_IDS_EAGLE: + init_ids_eagle(btv); + break; + case BTTV_BOARD_MODTEC_205: + bttv_readee(btv,eeprom_data,0xa0); + modtec_eeprom(btv); + break; + case BTTV_BOARD_LMLBT4: + init_lmlbt4x(btv); + break; + case BTTV_BOARD_TIBET_CS16: + tibetCS16_init(btv); + break; + case BTTV_BOARD_KODICOM_4400R: + kodicom4400r_init(btv); + break; + case BTTV_BOARD_GEOVISION_GV800S: + gv800s_init(btv); + break; + } + + /* pll configuration */ + if (!(btv->id==848 && btv->revision==0x11)) { + /* defaults from card list */ + if (PLL_28 == bttv_tvcards[btv->c.type].pll) { + btv->pll.pll_ifreq=28636363; + btv->pll.pll_crystal=BT848_IFORM_XT0; + } + if (PLL_35 == bttv_tvcards[btv->c.type].pll) { + btv->pll.pll_ifreq=35468950; + btv->pll.pll_crystal=BT848_IFORM_XT1; + } + if (PLL_14 == bttv_tvcards[btv->c.type].pll) { + btv->pll.pll_ifreq = 14318181; + btv->pll.pll_crystal = BT848_IFORM_XT0; + } + /* insmod options can override */ + switch (pll[btv->c.nr]) { + case 0: /* none */ + btv->pll.pll_crystal = 0; + btv->pll.pll_ifreq = 0; + btv->pll.pll_ofreq = 0; + break; + case 1: /* 28 MHz */ + case 28: + btv->pll.pll_ifreq = 28636363; + btv->pll.pll_ofreq = 0; + btv->pll.pll_crystal = BT848_IFORM_XT0; + break; + case 2: /* 35 MHz */ + case 35: + btv->pll.pll_ifreq = 35468950; + btv->pll.pll_ofreq = 0; + btv->pll.pll_crystal = BT848_IFORM_XT1; + break; + case 3: /* 14 MHz */ + case 14: + btv->pll.pll_ifreq = 14318181; + btv->pll.pll_ofreq = 0; + btv->pll.pll_crystal = BT848_IFORM_XT0; + break; + } + } + btv->pll.pll_current = -1; + + /* tuner configuration (from card list / autodetect / insmod option) */ + if (UNSET != bttv_tvcards[btv->c.type].tuner_type) + if (UNSET == btv->tuner_type) + btv->tuner_type = bttv_tvcards[btv->c.type].tuner_type; + if (UNSET != tuner[btv->c.nr]) + btv->tuner_type = tuner[btv->c.nr]; + + if (btv->tuner_type == TUNER_ABSENT) + pr_info("%d: tuner absent\n", btv->c.nr); + else if (btv->tuner_type == UNSET) + pr_warn("%d: tuner type unset\n", btv->c.nr); + else + pr_info("%d: tuner type=%d\n", btv->c.nr, btv->tuner_type); + + if (autoload != UNSET) { + pr_warn("%d: the autoload option is obsolete\n", btv->c.nr); + pr_warn("%d: use option msp3400, tda7432 or tvaudio to override which audio module should be used\n", + btv->c.nr); + } + + if (UNSET == btv->tuner_type) + btv->tuner_type = TUNER_ABSENT; + + btv->dig = bttv_tvcards[btv->c.type].has_dig_in ? + bttv_tvcards[btv->c.type].video_inputs - 1 : UNSET; + btv->svhs = bttv_tvcards[btv->c.type].svhs == NO_SVHS ? + UNSET : bttv_tvcards[btv->c.type].svhs; + if (svhs[btv->c.nr] != UNSET) + btv->svhs = svhs[btv->c.nr]; + if (remote[btv->c.nr] != UNSET) + btv->has_remote = remote[btv->c.nr]; + + if (bttv_tvcards[btv->c.type].has_radio) + btv->has_radio = 1; + if (bttv_tvcards[btv->c.type].has_remote) + btv->has_remote = 1; + if (!bttv_tvcards[btv->c.type].no_gpioirq) + btv->gpioirq = 1; + if (bttv_tvcards[btv->c.type].volume_gpio) + btv->volume_gpio = bttv_tvcards[btv->c.type].volume_gpio; + if (bttv_tvcards[btv->c.type].audio_mode_gpio) + btv->audio_mode_gpio = bttv_tvcards[btv->c.type].audio_mode_gpio; + + if (btv->tuner_type == TUNER_ABSENT) + return; /* no tuner or related drivers to load */ + + if (btv->has_saa6588 || saa6588[btv->c.nr]) { + /* Probe for RDS receiver chip */ + static const unsigned short addrs[] = { + 0x20 >> 1, + 0x22 >> 1, + I2C_CLIENT_END + }; + struct v4l2_subdev *sd; + + sd = v4l2_i2c_new_subdev(&btv->c.v4l2_dev, + &btv->c.i2c_adap, "saa6588", 0, addrs); + btv->has_saa6588 = (sd != NULL); + } + + /* try to detect audio/fader chips */ + + /* First check if the user specified the audio chip via a module + option. */ + + switch (audiodev[btv->c.nr]) { + case -1: + return; /* do not load any audio module */ + + case 0: /* autodetect */ + break; + + case 1: { + /* The user specified that we should probe for msp3400 */ + static const unsigned short addrs[] = { + I2C_ADDR_MSP3400 >> 1, + I2C_ADDR_MSP3400_ALT >> 1, + I2C_CLIENT_END + }; + + btv->sd_msp34xx = v4l2_i2c_new_subdev(&btv->c.v4l2_dev, + &btv->c.i2c_adap, "msp3400", 0, addrs); + if (btv->sd_msp34xx) + return; + goto no_audio; + } + + case 2: { + /* The user specified that we should probe for tda7432 */ + static const unsigned short addrs[] = { + I2C_ADDR_TDA7432 >> 1, + I2C_CLIENT_END + }; + + if (v4l2_i2c_new_subdev(&btv->c.v4l2_dev, + &btv->c.i2c_adap, "tda7432", 0, addrs)) + return; + goto no_audio; + } + + case 3: { + /* The user specified that we should probe for tvaudio */ + btv->sd_tvaudio = v4l2_i2c_new_subdev(&btv->c.v4l2_dev, + &btv->c.i2c_adap, "tvaudio", 0, tvaudio_addrs()); + if (btv->sd_tvaudio) + return; + goto no_audio; + } + + default: + pr_warn("%d: unknown audiodev value!\n", btv->c.nr); + return; + } + + /* There were no overrides, so now we try to discover this through the + card definition */ + + /* probe for msp3400 first: this driver can detect whether or not + it really is a msp3400, so it will return NULL when the device + found is really something else (e.g. a tea6300). */ + if (!bttv_tvcards[btv->c.type].no_msp34xx) { + btv->sd_msp34xx = v4l2_i2c_new_subdev(&btv->c.v4l2_dev, + &btv->c.i2c_adap, "msp3400", + 0, I2C_ADDRS(I2C_ADDR_MSP3400 >> 1)); + } else if (bttv_tvcards[btv->c.type].msp34xx_alt) { + btv->sd_msp34xx = v4l2_i2c_new_subdev(&btv->c.v4l2_dev, + &btv->c.i2c_adap, "msp3400", + 0, I2C_ADDRS(I2C_ADDR_MSP3400_ALT >> 1)); + } + + /* If we found a msp34xx, then we're done. */ + if (btv->sd_msp34xx) + return; + + /* Now see if we can find one of the tvaudio devices. */ + btv->sd_tvaudio = v4l2_i2c_new_subdev(&btv->c.v4l2_dev, + &btv->c.i2c_adap, "tvaudio", 0, tvaudio_addrs()); + if (btv->sd_tvaudio) { + /* There may be two tvaudio chips on the card, so try to + find another. */ + v4l2_i2c_new_subdev(&btv->c.v4l2_dev, + &btv->c.i2c_adap, "tvaudio", 0, tvaudio_addrs()); + } + + /* it might also be a tda7432. */ + if (!bttv_tvcards[btv->c.type].no_tda7432) { + static const unsigned short addrs[] = { + I2C_ADDR_TDA7432 >> 1, + I2C_CLIENT_END + }; + + btv->sd_tda7432 = v4l2_i2c_new_subdev(&btv->c.v4l2_dev, + &btv->c.i2c_adap, "tda7432", 0, addrs); + if (btv->sd_tda7432) + return; + } + if (btv->sd_tvaudio) + return; + +no_audio: + pr_warn("%d: audio absent, no audio device found!\n", btv->c.nr); +} + + +/* initialize the tuner */ +void bttv_init_tuner(struct bttv *btv) +{ + int addr = ADDR_UNSET; + + if (ADDR_UNSET != bttv_tvcards[btv->c.type].tuner_addr) + addr = bttv_tvcards[btv->c.type].tuner_addr; + + if (btv->tuner_type != TUNER_ABSENT) { + struct tuner_setup tun_setup; + + /* Load tuner module before issuing tuner config call! */ + if (btv->has_radio) + v4l2_i2c_new_subdev(&btv->c.v4l2_dev, + &btv->c.i2c_adap, "tuner", + 0, v4l2_i2c_tuner_addrs(ADDRS_RADIO)); + v4l2_i2c_new_subdev(&btv->c.v4l2_dev, + &btv->c.i2c_adap, "tuner", + 0, v4l2_i2c_tuner_addrs(ADDRS_DEMOD)); + v4l2_i2c_new_subdev(&btv->c.v4l2_dev, + &btv->c.i2c_adap, "tuner", + 0, v4l2_i2c_tuner_addrs(ADDRS_TV_WITH_DEMOD)); + + tun_setup.mode_mask = T_ANALOG_TV; + tun_setup.type = btv->tuner_type; + tun_setup.addr = addr; + + if (btv->has_radio) + tun_setup.mode_mask |= T_RADIO; + + bttv_call_all(btv, tuner, s_type_addr, &tun_setup); + } + + if (btv->tda9887_conf) { + struct v4l2_priv_tun_config tda9887_cfg; + + tda9887_cfg.tuner = TUNER_TDA9887; + tda9887_cfg.priv = &btv->tda9887_conf; + + bttv_call_all(btv, tuner, s_config, &tda9887_cfg); + } +} + +/* ----------------------------------------------------------------------- */ + +static void modtec_eeprom(struct bttv *btv) +{ + if( strncmp(&(eeprom_data[0x1e]),"Temic 4066 FY5",14) ==0) { + btv->tuner_type=TUNER_TEMIC_4066FY5_PAL_I; + pr_info("%d: Modtec: Tuner autodetected by eeprom: %s\n", + btv->c.nr, &eeprom_data[0x1e]); + } else if (strncmp(&(eeprom_data[0x1e]),"Alps TSBB5",10) ==0) { + btv->tuner_type=TUNER_ALPS_TSBB5_PAL_I; + pr_info("%d: Modtec: Tuner autodetected by eeprom: %s\n", + btv->c.nr, &eeprom_data[0x1e]); + } else if (strncmp(&(eeprom_data[0x1e]),"Philips FM1246",14) ==0) { + btv->tuner_type=TUNER_PHILIPS_NTSC; + pr_info("%d: Modtec: Tuner autodetected by eeprom: %s\n", + btv->c.nr, &eeprom_data[0x1e]); + } else { + pr_info("%d: Modtec: Unknown TunerString: %s\n", + btv->c.nr, &eeprom_data[0x1e]); + } +} + +static void hauppauge_eeprom(struct bttv *btv) +{ + struct tveeprom tv; + + tveeprom_hauppauge_analog(&tv, eeprom_data); + btv->tuner_type = tv.tuner_type; + btv->has_radio = tv.has_radio; + + pr_info("%d: Hauppauge eeprom indicates model#%d\n", + btv->c.nr, tv.model); + + /* + * Some of the 878 boards have duplicate PCI IDs. Switch the board + * type based on model #. + */ + if(tv.model == 64900) { + pr_info("%d: Switching board type from %s to %s\n", + btv->c.nr, + bttv_tvcards[btv->c.type].name, + bttv_tvcards[BTTV_BOARD_HAUPPAUGE_IMPACTVCB].name); + btv->c.type = BTTV_BOARD_HAUPPAUGE_IMPACTVCB; + } + + /* The 61334 needs the msp3410 to do the radio demod to get sound */ + if (tv.model == 61334) + btv->radio_uses_msp_demodulator = 1; +} + +/* ----------------------------------------------------------------------- */ + +static void bttv_tea575x_set_pins(struct snd_tea575x *tea, u8 pins) +{ + struct bttv *btv = tea->private_data; + struct bttv_tea575x_gpio gpio = btv->tea_gpio; + u16 val = 0; + + val |= (pins & TEA575X_DATA) ? (1 << gpio.data) : 0; + val |= (pins & TEA575X_CLK) ? (1 << gpio.clk) : 0; + val |= (pins & TEA575X_WREN) ? (1 << gpio.wren) : 0; + + gpio_bits((1 << gpio.data) | (1 << gpio.clk) | (1 << gpio.wren), val); + if (btv->mbox_ior) { + /* IOW and CSEL active */ + gpio_bits(btv->mbox_iow | btv->mbox_csel, 0); + udelay(5); + /* all inactive */ + gpio_bits(btv->mbox_ior | btv->mbox_iow | btv->mbox_csel, + btv->mbox_ior | btv->mbox_iow | btv->mbox_csel); + } +} + +static u8 bttv_tea575x_get_pins(struct snd_tea575x *tea) +{ + struct bttv *btv = tea->private_data; + struct bttv_tea575x_gpio gpio = btv->tea_gpio; + u8 ret = 0; + u16 val; + + if (btv->mbox_ior) { + /* IOR and CSEL active */ + gpio_bits(btv->mbox_ior | btv->mbox_csel, 0); + udelay(5); + } + val = gpio_read(); + if (btv->mbox_ior) { + /* all inactive */ + gpio_bits(btv->mbox_ior | btv->mbox_iow | btv->mbox_csel, + btv->mbox_ior | btv->mbox_iow | btv->mbox_csel); + } + + if (val & (1 << gpio.data)) + ret |= TEA575X_DATA; + if (val & (1 << gpio.most)) + ret |= TEA575X_MOST; + + return ret; +} + +static void bttv_tea575x_set_direction(struct snd_tea575x *tea, bool output) +{ + struct bttv *btv = tea->private_data; + struct bttv_tea575x_gpio gpio = btv->tea_gpio; + u32 mask = (1 << gpio.clk) | (1 << gpio.wren) | (1 << gpio.data) | + (1 << gpio.most); + + if (output) + gpio_inout(mask, (1 << gpio.data) | (1 << gpio.clk) | + (1 << gpio.wren)); + else + gpio_inout(mask, (1 << gpio.clk) | (1 << gpio.wren)); +} + +static const struct snd_tea575x_ops bttv_tea_ops = { + .set_pins = bttv_tea575x_set_pins, + .get_pins = bttv_tea575x_get_pins, + .set_direction = bttv_tea575x_set_direction, +}; + +static int tea575x_init(struct bttv *btv) +{ + btv->tea.private_data = btv; + btv->tea.ops = &bttv_tea_ops; + if (!snd_tea575x_hw_init(&btv->tea)) { + pr_info("%d: detected TEA575x radio\n", btv->c.nr); + btv->tea.mute = false; + return 0; + } + + btv->has_tea575x = 0; + btv->has_radio = 0; + + return -ENODEV; +} + +/* ----------------------------------------------------------------------- */ + +static int terratec_active_radio_upgrade(struct bttv *btv) +{ + btv->has_radio = 1; + btv->has_tea575x = 1; + btv->tea_gpio.wren = 4; + btv->tea_gpio.most = 5; + btv->tea_gpio.clk = 3; + btv->tea_gpio.data = 2; + + btv->mbox_iow = 1 << 8; + btv->mbox_ior = 1 << 9; + btv->mbox_csel = 1 << 10; + + if (!tea575x_init(btv)) { + pr_info("%d: Terratec Active Radio Upgrade found\n", btv->c.nr); + btv->has_saa6588 = 1; + } + + return 0; +} + + +/* ----------------------------------------------------------------------- */ + +/* + * minimal bootstrap for the WinTV/PVR -- upload altera firmware. + * + * The hcwamc.rbf firmware file is on the Hauppauge driver CD. Have + * a look at Pvr/pvr45xxx.EXE (self-extracting zip archive, can be + * unpacked with unzip). + */ +#define PVR_GPIO_DELAY 10 + +#define BTTV_ALT_DATA 0x000001 +#define BTTV_ALT_DCLK 0x100000 +#define BTTV_ALT_NCONFIG 0x800000 + +static int pvr_altera_load(struct bttv *btv, const u8 *micro, u32 microlen) +{ + u32 n; + u8 bits; + int i; + + gpio_inout(0xffffff,BTTV_ALT_DATA|BTTV_ALT_DCLK|BTTV_ALT_NCONFIG); + gpio_write(0); + udelay(PVR_GPIO_DELAY); + + gpio_write(BTTV_ALT_NCONFIG); + udelay(PVR_GPIO_DELAY); + + for (n = 0; n < microlen; n++) { + bits = micro[n]; + for (i = 0 ; i < 8 ; i++) { + gpio_bits(BTTV_ALT_DCLK,0); + if (bits & 0x01) + gpio_bits(BTTV_ALT_DATA,BTTV_ALT_DATA); + else + gpio_bits(BTTV_ALT_DATA,0); + gpio_bits(BTTV_ALT_DCLK,BTTV_ALT_DCLK); + bits >>= 1; + } + } + gpio_bits(BTTV_ALT_DCLK,0); + udelay(PVR_GPIO_DELAY); + + /* begin Altera init loop (Not necessary,but doesn't hurt) */ + for (i = 0 ; i < 30 ; i++) { + gpio_bits(BTTV_ALT_DCLK,0); + gpio_bits(BTTV_ALT_DCLK,BTTV_ALT_DCLK); + } + gpio_bits(BTTV_ALT_DCLK,0); + return 0; +} + +static int pvr_boot(struct bttv *btv) +{ + const struct firmware *fw_entry; + int rc; + + rc = request_firmware(&fw_entry, "hcwamc.rbf", &btv->c.pci->dev); + if (rc != 0) { + pr_warn("%d: no altera firmware [via hotplug]\n", btv->c.nr); + return rc; + } + rc = pvr_altera_load(btv, fw_entry->data, fw_entry->size); + pr_info("%d: altera firmware upload %s\n", + btv->c.nr, (rc < 0) ? "failed" : "ok"); + release_firmware(fw_entry); + return rc; +} + +/* ----------------------------------------------------------------------- */ +/* some osprey specific stuff */ + +static void osprey_eeprom(struct bttv *btv, const u8 ee[256]) +{ + int i; + u32 serial = 0; + int cardid = -1; + + /* This code will never actually get called in this case.... */ + if (btv->c.type == BTTV_BOARD_UNKNOWN) { + /* this might be an antique... check for MMAC label in eeprom */ + if (!strncmp(ee, "MMAC", 4)) { + u8 checksum = 0; + for (i = 0; i < 21; i++) + checksum += ee[i]; + if (checksum != ee[21]) + return; + cardid = BTTV_BOARD_OSPREY1x0_848; + for (i = 12; i < 21; i++) { + serial *= 10; + serial += ee[i] - '0'; + } + } + } else { + unsigned short type; + + for (i = 4 * 16; i < 8 * 16; i += 16) { + u16 checksum = (__force u16)ip_compute_csum(ee + i, 16); + + if ((checksum & 0xff) + (checksum >> 8) == 0xff) + break; + } + if (i >= 8*16) + return; + ee += i; + + /* found a valid descriptor */ + type = get_unaligned_be16((__be16 *)(ee+4)); + + switch(type) { + /* 848 based */ + case 0x0004: + cardid = BTTV_BOARD_OSPREY1x0_848; + break; + case 0x0005: + cardid = BTTV_BOARD_OSPREY101_848; + break; + + /* 878 based */ + case 0x0012: + case 0x0013: + cardid = BTTV_BOARD_OSPREY1x0; + break; + case 0x0014: + case 0x0015: + cardid = BTTV_BOARD_OSPREY1x1; + break; + case 0x0016: + case 0x0017: + case 0x0020: + cardid = BTTV_BOARD_OSPREY1x1_SVID; + break; + case 0x0018: + case 0x0019: + case 0x001E: + case 0x001F: + cardid = BTTV_BOARD_OSPREY2xx; + break; + case 0x001A: + case 0x001B: + cardid = BTTV_BOARD_OSPREY2x0_SVID; + break; + case 0x0040: + cardid = BTTV_BOARD_OSPREY500; + break; + case 0x0050: + case 0x0056: + cardid = BTTV_BOARD_OSPREY540; + /* bttv_osprey_540_init(btv); */ + break; + case 0x0060: + case 0x0070: + case 0x00A0: + cardid = BTTV_BOARD_OSPREY2x0; + /* enable output on select control lines */ + gpio_inout(0xffffff,0x000303); + break; + case 0x00D8: + cardid = BTTV_BOARD_OSPREY440; + break; + default: + /* unknown...leave generic, but get serial # */ + pr_info("%d: osprey eeprom: unknown card type 0x%04x\n", + btv->c.nr, type); + break; + } + serial = get_unaligned_be32((__be32 *)(ee+6)); + } + + pr_info("%d: osprey eeprom: card=%d '%s' serial=%u\n", + btv->c.nr, cardid, + cardid > 0 ? bttv_tvcards[cardid].name : "Unknown", serial); + + if (cardid<0 || btv->c.type == cardid) + return; + + /* card type isn't set correctly */ + if (card[btv->c.nr] < bttv_num_tvcards) { + pr_warn("%d: osprey eeprom: Not overriding user specified card type\n", + btv->c.nr); + } else { + pr_info("%d: osprey eeprom: Changing card type from %d to %d\n", + btv->c.nr, btv->c.type, cardid); + btv->c.type = cardid; + } +} + +/* ----------------------------------------------------------------------- */ +/* AVermedia specific stuff, from bktr_card.c */ + +static int tuner_0_table[] = { + TUNER_PHILIPS_NTSC, TUNER_PHILIPS_PAL /* PAL-BG*/, + TUNER_PHILIPS_PAL, TUNER_PHILIPS_PAL /* PAL-I*/, + TUNER_PHILIPS_PAL, TUNER_PHILIPS_PAL, + TUNER_PHILIPS_SECAM, TUNER_PHILIPS_SECAM, + TUNER_PHILIPS_SECAM, TUNER_PHILIPS_PAL, + TUNER_PHILIPS_FM1216ME_MK3 }; + +static int tuner_1_table[] = { + TUNER_TEMIC_NTSC, TUNER_TEMIC_PAL, + TUNER_TEMIC_PAL, TUNER_TEMIC_PAL, + TUNER_TEMIC_PAL, TUNER_TEMIC_PAL, + TUNER_TEMIC_4012FY5, TUNER_TEMIC_4012FY5, /* TUNER_TEMIC_SECAM */ + TUNER_TEMIC_4012FY5, TUNER_TEMIC_PAL}; + +static void avermedia_eeprom(struct bttv *btv) +{ + int tuner_make, tuner_tv_fm, tuner_format, tuner_type = 0; + + tuner_make = (eeprom_data[0x41] & 0x7); + tuner_tv_fm = (eeprom_data[0x41] & 0x18) >> 3; + tuner_format = (eeprom_data[0x42] & 0xf0) >> 4; + btv->has_remote = (eeprom_data[0x42] & 0x01); + + if (tuner_make == 0 || tuner_make == 2) + if (tuner_format <= 0x0a) + tuner_type = tuner_0_table[tuner_format]; + if (tuner_make == 1) + if (tuner_format <= 9) + tuner_type = tuner_1_table[tuner_format]; + + if (tuner_make == 4) + if (tuner_format == 0x09) + tuner_type = TUNER_LG_NTSC_NEW_TAPC; /* TAPC-G702P */ + + pr_info("%d: Avermedia eeprom[0x%02x%02x]: tuner=", + btv->c.nr, eeprom_data[0x41], eeprom_data[0x42]); + if (tuner_type) { + btv->tuner_type = tuner_type; + pr_cont("%d", tuner_type); + } else + pr_cont("Unknown type"); + pr_cont(" radio:%s remote control:%s\n", + tuner_tv_fm ? "yes" : "no", + btv->has_remote ? "yes" : "no"); +} + +/* + * For Voodoo TV/FM and Voodoo 200. These cards' tuners use a TDA9880 + * analog demod, which is not I2C controlled like the newer and more common + * TDA9887 series. Instead it has two tri-state input pins, S0 and S1, + * that control the IF for the video and audio. Apparently, bttv GPIO + * 0x10000 is connected to S0. S0 low selects a 38.9 MHz VIF for B/G/D/K/I + * (i.e., PAL) while high selects 45.75 MHz for M/N (i.e., NTSC). + */ +u32 bttv_tda9880_setnorm(struct bttv *btv, u32 gpiobits) +{ + + if (btv->audio_input == TVAUDIO_INPUT_TUNER) { + if (bttv_tvnorms[btv->tvnorm].v4l2_id & V4L2_STD_MN) + gpiobits |= 0x10000; + else + gpiobits &= ~0x10000; + } + + gpio_bits(bttv_tvcards[btv->c.type].gpiomask, gpiobits); + return gpiobits; +} + + +/* + * reset/enable the MSP on some Hauppauge cards + * Thanks to Kyösti Mälkki (kmalkki@cc.hut.fi)! + * + * Hauppauge: pin 5 + * Voodoo: pin 20 + */ +static void boot_msp34xx(struct bttv *btv, int pin) +{ + int mask = (1 << pin); + + gpio_inout(mask,mask); + gpio_bits(mask,0); + mdelay(2); + udelay(500); + gpio_bits(mask,mask); + + if (bttv_gpio) + bttv_gpio_tracking(btv,"msp34xx"); + if (bttv_verbose) + pr_info("%d: Hauppauge/Voodoo msp34xx: reset line init [%d]\n", + btv->c.nr, pin); +} + +/* ----------------------------------------------------------------------- */ +/* Imagenation L-Model PXC200 Framegrabber */ +/* This is basically the same procedure as + * used by Alessandro Rubini in his pxc200 + * driver, but using BTTV functions */ + +static void init_PXC200(struct bttv *btv) +{ + static int vals[] = { 0x08, 0x09, 0x0a, 0x0b, 0x0d, 0x0d, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x00 }; + unsigned int i; + int tmp; + u32 val; + + /* Initialise GPIO-connected stuff */ + gpio_inout(0xffffff, (1<<13)); + gpio_write(0); + udelay(3); + gpio_write(1<<13); + /* GPIO inputs are pulled up, so no need to drive + * reset pin any longer */ + gpio_bits(0xffffff, 0); + if (bttv_gpio) + bttv_gpio_tracking(btv,"pxc200"); + + /* we could/should try and reset/control the AD pots? but + right now we simply turned off the crushing. Without + this the AGC drifts drifts + remember the EN is reverse logic --> + setting BT848_ADC_AGC_EN disable the AGC + tboult@eecs.lehigh.edu + */ + + btwrite(BT848_ADC_RESERVED|BT848_ADC_AGC_EN, BT848_ADC); + + /* Initialise MAX517 DAC */ + pr_info("Setting DAC reference voltage level ...\n"); + bttv_I2CWrite(btv,0x5E,0,0x80,1); + + /* Initialise 12C508 PIC */ + /* The I2CWrite and I2CRead commands are actually to the + * same chips - but the R/W bit is included in the address + * argument so the numbers are different */ + + + pr_info("Initialising 12C508 PIC chip ...\n"); + + /* First of all, enable the clock line. This is used in the PXC200-F */ + val = btread(BT848_GPIO_DMA_CTL); + val |= BT848_GPIO_DMA_CTL_GPCLKMODE; + btwrite(val, BT848_GPIO_DMA_CTL); + + /* Then, push to 0 the reset pin long enough to reset the * + * device same as above for the reset line, but not the same + * value sent to the GPIO-connected stuff + * which one is the good one? */ + gpio_inout(0xffffff,(1<<2)); + gpio_write(0); + udelay(10); + gpio_write(1<<2); + + for (i = 0; i < ARRAY_SIZE(vals); i++) { + tmp=bttv_I2CWrite(btv,0x1E,0,vals[i],1); + if (tmp != -1) { + pr_info("I2C Write(%2.2x) = %i\nI2C Read () = %2.2x\n\n", + vals[i],tmp,bttv_I2CRead(btv,0x1F,NULL)); + } + } + + pr_info("PXC200 Initialised\n"); +} + + + +/* ----------------------------------------------------------------------- */ +/* + * The Adlink RTV-24 (aka Angelo) has some special initialisation to unlock + * it. This apparently involves the following procedure for each 878 chip: + * + * 1) write 0x00C3FEFF to the GPIO_OUT_EN register + * + * 2) write to GPIO_DATA + * - 0x0E + * - sleep 1ms + * - 0x10 + 0x0E + * - sleep 10ms + * - 0x0E + * read from GPIO_DATA into buf (uint_32) + * - if ( data>>18 & 0x01 != 0) || ( buf>>19 & 0x01 != 1 ) + * error. ERROR_CPLD_Check_Failed stop. + * + * 3) write to GPIO_DATA + * - write 0x4400 + 0x0E + * - sleep 10ms + * - write 0x4410 + 0x0E + * - sleep 1ms + * - write 0x0E + * read from GPIO_DATA into buf (uint_32) + * - if ( buf>>18 & 0x01 ) || ( buf>>19 & 0x01 != 0 ) + * error. ERROR_CPLD_Check_Failed. + */ +/* ----------------------------------------------------------------------- */ +static void +init_RTV24 (struct bttv *btv) +{ + uint32_t dataRead = 0; + long watchdog_value = 0x0E; + + pr_info("%d: Adlink RTV-24 initialisation in progress ...\n", + btv->c.nr); + + btwrite (0x00c3feff, BT848_GPIO_OUT_EN); + + btwrite (0 + watchdog_value, BT848_GPIO_DATA); + msleep (1); + btwrite (0x10 + watchdog_value, BT848_GPIO_DATA); + msleep (10); + btwrite (0 + watchdog_value, BT848_GPIO_DATA); + + dataRead = btread (BT848_GPIO_DATA); + + if ((((dataRead >> 18) & 0x01) != 0) || (((dataRead >> 19) & 0x01) != 1)) { + pr_info("%d: Adlink RTV-24 initialisation(1) ERROR_CPLD_Check_Failed (read %d)\n", + btv->c.nr, dataRead); + } + + btwrite (0x4400 + watchdog_value, BT848_GPIO_DATA); + msleep (10); + btwrite (0x4410 + watchdog_value, BT848_GPIO_DATA); + msleep (1); + btwrite (watchdog_value, BT848_GPIO_DATA); + msleep (1); + dataRead = btread (BT848_GPIO_DATA); + + if ((((dataRead >> 18) & 0x01) != 0) || (((dataRead >> 19) & 0x01) != 0)) { + pr_info("%d: Adlink RTV-24 initialisation(2) ERROR_CPLD_Check_Failed (read %d)\n", + btv->c.nr, dataRead); + + return; + } + + pr_info("%d: Adlink RTV-24 initialisation complete\n", btv->c.nr); +} + + + +/* ----------------------------------------------------------------------- */ +/* + * The PCI-8604PW contains a CPLD, probably an ispMACH 4A, that filters + * the PCI REQ signals coming from the four BT878 chips. After power + * up, the CPLD does not forward requests to the bus, which prevents + * the BT878 from fetching RISC instructions from memory. While the + * CPLD is connected to most of the GPIOs of PCI device 0xD, only + * five appear to play a role in unlocking the REQ signal. The following + * sequence has been determined by trial and error without access to the + * original driver. + * + * Eight GPIOs of device 0xC are provided on connector CN4 (4 in, 4 out). + * Devices 0xE and 0xF do not appear to have anything connected to their + * GPIOs. + * + * The correct GPIO_OUT_EN value might have some more bits set. It should + * be possible to derive it from a boundary scan of the CPLD. Its JTAG + * pins are routed to test points. + * + */ +/* ----------------------------------------------------------------------- */ +static void +init_PCI8604PW(struct bttv *btv) +{ + int state; + + if ((PCI_SLOT(btv->c.pci->devfn) & ~3) != 0xC) { + pr_warn("This is not a PCI-8604PW\n"); + return; + } + + if (PCI_SLOT(btv->c.pci->devfn) != 0xD) + return; + + btwrite(0x080002, BT848_GPIO_OUT_EN); + + state = (btread(BT848_GPIO_DATA) >> 21) & 7; + + for (;;) { + switch (state) { + case 1: + case 5: + case 6: + case 4: + pr_debug("PCI-8604PW in state %i, toggling pin\n", + state); + btwrite(0x080000, BT848_GPIO_DATA); + msleep(1); + btwrite(0x000000, BT848_GPIO_DATA); + msleep(1); + break; + case 7: + pr_info("PCI-8604PW unlocked\n"); + return; + case 0: + /* FIXME: If we are in state 7 and toggle GPIO[19] one + more time, the CPLD goes into state 0, where PCI bus + mastering is inhibited again. We have not managed to + get out of that state. */ + + pr_err("PCI-8604PW locked until reset\n"); + return; + default: + pr_err("PCI-8604PW in unknown state %i\n", state); + return; + } + + state = (state << 4) | ((btread(BT848_GPIO_DATA) >> 21) & 7); + + switch (state) { + case 0x15: + case 0x56: + case 0x64: + case 0x47: + /* The transition from state 7 to state 0 is, as explained + above, valid but undesired and with this code impossible + as we exit as soon as we are in state 7. + case 0x70: */ + break; + default: + pr_err("PCI-8604PW invalid transition %i -> %i\n", + state >> 4, state & 7); + return; + } + state &= 7; + } +} + +/* RemoteVision MX (rv605) muxsel helper [Miguel Freitas] + * + * This is needed because rv605 don't use a normal multiplex, but a crosspoint + * switch instead (CD22M3494E). This IC can have multiple active connections + * between Xn (input) and Yn (output) pins. We need to clear any existing + * connection prior to establish a new one, pulsing the STROBE pin. + * + * The board hardwire Y0 (xpoint) to MUX1 and MUXOUT to Yin. + * GPIO pins are wired as: + * GPIO[0:3] - AX[0:3] (xpoint) - P1[0:3] (microcontroller) + * GPIO[4:6] - AY[0:2] (xpoint) - P1[4:6] (microcontroller) + * GPIO[7] - DATA (xpoint) - P1[7] (microcontroller) + * GPIO[8] - - P3[5] (microcontroller) + * GPIO[9] - RESET (xpoint) - P3[6] (microcontroller) + * GPIO[10] - STROBE (xpoint) - P3[7] (microcontroller) + * GPINTR - - P3[4] (microcontroller) + * + * The microcontroller is a 80C32 like. It should be possible to change xpoint + * configuration either directly (as we are doing) or using the microcontroller + * which is also wired to I2C interface. I have no further info on the + * microcontroller features, one would need to disassembly the firmware. + * note: the vendor refused to give any information on this product, all + * that stuff was found using a multimeter! :) + */ +static void rv605_muxsel(struct bttv *btv, unsigned int input) +{ + static const u8 muxgpio[] = { 0x3, 0x1, 0x2, 0x4, 0xf, 0x7, 0xe, 0x0, + 0xd, 0xb, 0xc, 0x6, 0x9, 0x5, 0x8, 0xa }; + + gpio_bits(0x07f, muxgpio[input]); + + /* reset all connections */ + gpio_bits(0x200,0x200); + mdelay(1); + gpio_bits(0x200,0x000); + mdelay(1); + + /* create a new connection */ + gpio_bits(0x480,0x480); + mdelay(1); + gpio_bits(0x480,0x080); + mdelay(1); +} + +/* Tibet Systems 'Progress DVR' CS16 muxsel helper [Chris Fanning] + * + * The CS16 (available on eBay cheap) is a PCI board with four Fusion + * 878A chips, a PCI bridge, an Atmel microcontroller, four sync separator + * chips, ten eight input analog multiplexors, a not chip and a few + * other components. + * + * 16 inputs on a secondary bracket are provided and can be selected + * from each of the four capture chips. Two of the eight input + * multiplexors are used to select from any of the 16 input signals. + * + * Unsupported hardware capabilities: + * . A video output monitor on the secondary bracket can be selected from + * one of the 878A chips. + * . Another passthrough but I haven't spent any time investigating it. + * . Digital I/O (logic level connected to GPIO) is available from an + * onboard header. + * + * The on chip input mux should always be set to 2. + * GPIO[16:19] - Video input selection + * GPIO[0:3] - Video output monitor select (only available from one 878A) + * GPIO[?:?] - Digital I/O. + * + * There is an ATMEL microcontroller with an 8031 core on board. I have not + * determined what function (if any) it provides. With the microcontroller + * and sync separator chips a guess is that it might have to do with video + * switching and maybe some digital I/O. + */ +static void tibetCS16_muxsel(struct bttv *btv, unsigned int input) +{ + /* video mux */ + gpio_bits(0x0f0000, input << 16); +} + +static void tibetCS16_init(struct bttv *btv) +{ + /* enable gpio bits, mask obtained via btSpy */ + gpio_inout(0xffffff, 0x0f7fff); + gpio_write(0x0f7fff); +} + +/* + * The following routines for the Kodicom-4400r get a little mind-twisting. + * There is a "master" controller and three "slave" controllers, together + * an analog switch which connects any of 16 cameras to any of the BT87A's. + * The analog switch is controlled by the "master", but the detection order + * of the four BT878A chips is in an order which I just don't understand. + * The "master" is actually the second controller to be detected. The + * logic on the board uses logical numbers for the 4 controllers, but + * those numbers are different from the detection sequence. When working + * with the analog switch, we need to "map" from the detection sequence + * over to the board's logical controller number. This mapping sequence + * is {3, 0, 2, 1}, i.e. the first controller to be detected is logical + * unit 3, the second (which is the master) is logical unit 0, etc. + * We need to maintain the status of the analog switch (which of the 16 + * cameras is connected to which of the 4 controllers) in sw_status array. + */ + +/* + * First a routine to set the analog switch, which controls which camera + * is routed to which controller. The switch comprises an X-address + * (gpio bits 0-3, representing the camera, ranging from 0-15), and a + * Y-address (gpio bits 4-6, representing the controller, ranging from 0-3). + * A data value (gpio bit 7) of '1' enables the switch, and '0' disables + * the switch. A STROBE bit (gpio bit 8) latches the data value into the + * specified address. The idea is to set the address and data, then bring + * STROBE high, and finally bring STROBE back to low. + */ +static void kodicom4400r_write(struct bttv *btv, + unsigned char xaddr, + unsigned char yaddr, + unsigned char data) { + unsigned int udata; + + udata = (data << 7) | ((yaddr&3) << 4) | (xaddr&0xf); + gpio_bits(0x1ff, udata); /* write ADDR and DAT */ + gpio_bits(0x1ff, udata | (1 << 8)); /* strobe high */ + gpio_bits(0x1ff, udata); /* strobe low */ +} + +/* + * Next the mux select. Both the "master" and "slave" 'cards' (controllers) + * use this routine. The routine finds the "master" for the card, maps + * the controller number from the detected position over to the logical + * number, writes the appropriate data to the analog switch, and housekeeps + * the local copy of the switch information. The parameter 'input' is the + * requested camera number (0 - 15). + */ +static void kodicom4400r_muxsel(struct bttv *btv, unsigned int input) +{ + int xaddr, yaddr; + struct bttv *mctlr; + static unsigned char map[4] = {3, 0, 2, 1}; + + mctlr = master[btv->c.nr]; + if (mctlr == NULL) { /* ignore if master not yet detected */ + return; + } + yaddr = (btv->c.nr - mctlr->c.nr + 1) & 3; /* the '&' is for safety */ + yaddr = map[yaddr]; + xaddr = input & 0xf; + /* Check if the controller/camera pair has changed, else ignore */ + if (mctlr->sw_status[yaddr] != xaddr) + { + /* "open" the old switch, "close" the new one, save the new */ + kodicom4400r_write(mctlr, mctlr->sw_status[yaddr], yaddr, 0); + mctlr->sw_status[yaddr] = xaddr; + kodicom4400r_write(mctlr, xaddr, yaddr, 1); + } +} + +/* + * During initialisation, we need to reset the analog switch. We + * also preset the switch to map the 4 connectors on the card to the + * *user's* (see above description of kodicom4400r_muxsel) channels + * 0 through 3 + */ +static void kodicom4400r_init(struct bttv *btv) +{ + int ix; + + gpio_inout(0x0003ff, 0x0003ff); + gpio_write(1 << 9); /* reset MUX */ + gpio_write(0); + /* Preset camera 0 to the 4 controllers */ + for (ix = 0; ix < 4; ix++) { + btv->sw_status[ix] = ix; + kodicom4400r_write(btv, ix, ix, 1); + } + /* + * Since this is the "master", we need to set up the + * other three controller chips' pointers to this structure + * for later use in the muxsel routine. + */ + if ((btv->c.nr<1) || (btv->c.nr>BTTV_MAX-3)) + return; + master[btv->c.nr-1] = btv; + master[btv->c.nr] = btv; + master[btv->c.nr+1] = btv; + master[btv->c.nr+2] = btv; +} + +/* The Grandtec X-Guard framegrabber card uses two Dual 4-channel + * video multiplexers to provide up to 16 video inputs. These + * multiplexers are controlled by the lower 8 GPIO pins of the + * bt878. The multiplexers probably Pericom PI5V331Q or similar. + + * xxx0 is pin xxx of multiplexer U5, + * yyy1 is pin yyy of multiplexer U2 + */ +#define ENA0 0x01 +#define ENB0 0x02 +#define ENA1 0x04 +#define ENB1 0x08 + +#define IN10 0x10 +#define IN00 0x20 +#define IN11 0x40 +#define IN01 0x80 + +static void xguard_muxsel(struct bttv *btv, unsigned int input) +{ + static const int masks[] = { + ENB0, ENB0|IN00, ENB0|IN10, ENB0|IN00|IN10, + ENA0, ENA0|IN00, ENA0|IN10, ENA0|IN00|IN10, + ENB1, ENB1|IN01, ENB1|IN11, ENB1|IN01|IN11, + ENA1, ENA1|IN01, ENA1|IN11, ENA1|IN01|IN11, + }; + gpio_write(masks[input%16]); +} +static void picolo_tetra_init(struct bttv *btv) +{ + /*This is the video input redirection functionality : I DID NOT USE IT. */ + btwrite (0x08<<16,BT848_GPIO_DATA);/*GPIO[19] [==> 4053 B+C] set to 1 */ + btwrite (0x04<<16,BT848_GPIO_DATA);/*GPIO[18] [==> 4053 A] set to 1*/ +} +static void picolo_tetra_muxsel (struct bttv* btv, unsigned int input) +{ + + dprintk("%d : picolo_tetra_muxsel => input = %d\n", btv->c.nr, input); + /*Just set the right path in the analog multiplexers : channel 1 -> 4 ==> Analog Mux ==> MUX0*/ + /*GPIO[20]&GPIO[21] used to choose the right input*/ + btwrite (input<<20,BT848_GPIO_DATA); + +} + +/* + * ivc120_muxsel [Added by Alan Garfield ] + * + * The IVC120G security card has 4 i2c controlled TDA8540 matrix + * switchers to provide 16 channels to MUX0. The TDA8540's have + * 4 independent outputs and as such the IVC120G also has the + * optional "Monitor Out" bus. This allows the card to be looking + * at one input while the monitor is looking at another. + * + * Since I've couldn't be bothered figuring out how to add an + * independent muxsel for the monitor bus, I've just set it to + * whatever the card is looking at. + * + * OUT0 of the TDA8540's is connected to MUX0 (0x03) + * OUT1 of the TDA8540's is connected to "Monitor Out" (0x0C) + * + * TDA8540_ALT3 IN0-3 = Channel 13 - 16 (0x03) + * TDA8540_ALT4 IN0-3 = Channel 1 - 4 (0x03) + * TDA8540_ALT5 IN0-3 = Channel 5 - 8 (0x03) + * TDA8540_ALT6 IN0-3 = Channel 9 - 12 (0x03) + * + */ + +/* All 7 possible sub-ids for the TDA8540 Matrix Switcher */ +#define I2C_TDA8540 0x90 +#define I2C_TDA8540_ALT1 0x92 +#define I2C_TDA8540_ALT2 0x94 +#define I2C_TDA8540_ALT3 0x96 +#define I2C_TDA8540_ALT4 0x98 +#define I2C_TDA8540_ALT5 0x9a +#define I2C_TDA8540_ALT6 0x9c + +static void ivc120_muxsel(struct bttv *btv, unsigned int input) +{ + /* Simple maths */ + int key = input % 4; + int matrix = input / 4; + + dprintk("%d: ivc120_muxsel: Input - %02d | TDA - %02d | In - %02d\n", + btv->c.nr, input, matrix, key); + + /* Handles the input selection on the TDA8540's */ + bttv_I2CWrite(btv, I2C_TDA8540_ALT3, 0x00, + ((matrix == 3) ? (key | key << 2) : 0x00), 1); + bttv_I2CWrite(btv, I2C_TDA8540_ALT4, 0x00, + ((matrix == 0) ? (key | key << 2) : 0x00), 1); + bttv_I2CWrite(btv, I2C_TDA8540_ALT5, 0x00, + ((matrix == 1) ? (key | key << 2) : 0x00), 1); + bttv_I2CWrite(btv, I2C_TDA8540_ALT6, 0x00, + ((matrix == 2) ? (key | key << 2) : 0x00), 1); + + /* Handles the output enables on the TDA8540's */ + bttv_I2CWrite(btv, I2C_TDA8540_ALT3, 0x02, + ((matrix == 3) ? 0x03 : 0x00), 1); /* 13 - 16 */ + bttv_I2CWrite(btv, I2C_TDA8540_ALT4, 0x02, + ((matrix == 0) ? 0x03 : 0x00), 1); /* 1-4 */ + bttv_I2CWrite(btv, I2C_TDA8540_ALT5, 0x02, + ((matrix == 1) ? 0x03 : 0x00), 1); /* 5-8 */ + bttv_I2CWrite(btv, I2C_TDA8540_ALT6, 0x02, + ((matrix == 2) ? 0x03 : 0x00), 1); /* 9-12 */ + + /* 878's MUX0 is already selected for input via muxsel values */ +} + + +/* PXC200 muxsel helper + * luke@syseng.anu.edu.au + * another transplant + * from Alessandro Rubini (rubini@linux.it) + * + * There are 4 kinds of cards: + * PXC200L which is bt848 + * PXC200F which is bt848 with PIC controlling mux + * PXC200AL which is bt878 + * PXC200AF which is bt878 with PIC controlling mux + */ +#define PX_CFG_PXC200F 0x01 +#define PX_FLAG_PXC200A 0x00001000 /* a pxc200A is bt-878 based */ +#define PX_I2C_PIC 0x0f +#define PX_PXC200A_CARDID 0x200a1295 +#define PX_I2C_CMD_CFG 0x00 + +static void PXC200_muxsel(struct bttv *btv, unsigned int input) +{ + int rc; + long mux; + int bitmask; + unsigned char buf[2]; + + /* Read PIC config to determine if this is a PXC200F */ + /* PX_I2C_CMD_CFG*/ + buf[0]=0; + buf[1]=0; + rc=bttv_I2CWrite(btv,(PX_I2C_PIC<<1),buf[0],buf[1],1); + if (rc) { + pr_debug("%d: PXC200_muxsel: pic cfg write failed:%d\n", + btv->c.nr, rc); + /* not PXC ? do nothing */ + return; + } + + rc=bttv_I2CRead(btv,(PX_I2C_PIC<<1),NULL); + if (!(rc & PX_CFG_PXC200F)) { + pr_debug("%d: PXC200_muxsel: not PXC200F rc:%d\n", + btv->c.nr, rc); + return; + } + + + /* The multiplexer in the 200F is handled by the GPIO port */ + /* get correct mapping between inputs */ + /* mux = bttv_tvcards[btv->type].muxsel[input] & 3; */ + /* ** not needed!? */ + mux = input; + + /* make sure output pins are enabled */ + /* bitmask=0x30f; */ + bitmask=0x302; + /* check whether we have a PXC200A */ + if (btv->cardid == PX_PXC200A_CARDID) { + bitmask ^= 0x180; /* use 7 and 9, not 8 and 9 */ + bitmask |= 7<<4; /* the DAC */ + } + btwrite(bitmask, BT848_GPIO_OUT_EN); + + bitmask = btread(BT848_GPIO_DATA); + if (btv->cardid == PX_PXC200A_CARDID) + bitmask = (bitmask & ~0x280) | ((mux & 2) << 8) | ((mux & 1) << 7); + else /* older device */ + bitmask = (bitmask & ~0x300) | ((mux & 3) << 8); + btwrite(bitmask,BT848_GPIO_DATA); + + /* + * Was "to be safe, set the bt848 to input 0" + * Actually, since it's ok at load time, better not messing + * with these bits (on PXC200AF you need to set mux 2 here) + * + * needed because bttv-driver sets mux before calling this function + */ + if (btv->cardid == PX_PXC200A_CARDID) + btaor(2<<5, ~BT848_IFORM_MUXSEL, BT848_IFORM); + else /* older device */ + btand(~BT848_IFORM_MUXSEL,BT848_IFORM); + + pr_debug("%d: setting input channel to:%d\n", btv->c.nr, (int)mux); +} + +static void phytec_muxsel(struct bttv *btv, unsigned int input) +{ + unsigned int mux = input % 4; + + if (input == btv->svhs) + mux = 0; + + gpio_bits(0x3, mux); +} + +/* + * GeoVision GV-800(S) functions + * Bruno Christo +*/ + +/* This is a function to control the analog switch, which determines which + * camera is routed to which controller. The switch comprises an X-address + * (gpio bits 0-3, representing the camera, ranging from 0-15), and a + * Y-address (gpio bits 4-6, representing the controller, ranging from 0-3). + * A data value (gpio bit 18) of '1' enables the switch, and '0' disables + * the switch. A STROBE bit (gpio bit 17) latches the data value into the + * specified address. There is also a chip select (gpio bit 16). + * The idea is to set the address and chip select together, bring + * STROBE high, write the data, and finally bring STROBE back to low. + */ +static void gv800s_write(struct bttv *btv, + unsigned char xaddr, + unsigned char yaddr, + unsigned char data) { + /* On the "master" 878A: + * GPIO bits 0-9 are used for the analog switch: + * 00 - 03: camera selector + * 04 - 06: 878A (controller) selector + * 16: cselect + * 17: strobe + * 18: data (1->on, 0->off) + * 19: reset + */ + const u32 ADDRESS = ((xaddr&0xf) | (yaddr&3)<<4); + const u32 CSELECT = 1<<16; + const u32 STROBE = 1<<17; + const u32 DATA = data<<18; + + gpio_bits(0x1007f, ADDRESS | CSELECT); /* write ADDRESS and CSELECT */ + gpio_bits(0x20000, STROBE); /* STROBE high */ + gpio_bits(0x40000, DATA); /* write DATA */ + gpio_bits(0x20000, ~STROBE); /* STROBE low */ +} + +/* + * GeoVision GV-800(S) muxsel + * + * Each of the 4 cards (controllers) use this function. + * The controller using this function selects the input through the GPIO pins + * of the "master" card. A pointer to this card is stored in master[btv->c.nr]. + * + * The parameter 'input' is the requested camera number (0-4) on the controller. + * The map array has the address of each input. Note that the addresses in the + * array are in the sequence the original GeoVision driver uses, that is, set + * every controller to input 0, then to input 1, 2, 3, repeat. This means that + * the physical "camera 1" connector corresponds to controller 0 input 0, + * "camera 2" corresponds to controller 1 input 0, and so on. + * + * After getting the input address, the function then writes the appropriate + * data to the analog switch, and housekeeps the local copy of the switch + * information. + */ +static void gv800s_muxsel(struct bttv *btv, unsigned int input) +{ + struct bttv *mctlr; + int xaddr, yaddr; + static unsigned int map[4][4] = { { 0x0, 0x4, 0xa, 0x6 }, + { 0x1, 0x5, 0xb, 0x7 }, + { 0x2, 0x8, 0xc, 0xe }, + { 0x3, 0x9, 0xd, 0xf } }; + input = input%4; + mctlr = master[btv->c.nr]; + if (mctlr == NULL) { + /* do nothing until the "master" is detected */ + return; + } + yaddr = (btv->c.nr - mctlr->c.nr) & 3; + xaddr = map[yaddr][input] & 0xf; + + /* Check if the controller/camera pair has changed, ignore otherwise */ + if (mctlr->sw_status[yaddr] != xaddr) { + /* disable the old switch, enable the new one and save status */ + gv800s_write(mctlr, mctlr->sw_status[yaddr], yaddr, 0); + mctlr->sw_status[yaddr] = xaddr; + gv800s_write(mctlr, xaddr, yaddr, 1); + } +} + +/* GeoVision GV-800(S) "master" chip init */ +static void gv800s_init(struct bttv *btv) +{ + int ix; + + gpio_inout(0xf107f, 0xf107f); + gpio_write(1<<19); /* reset the analog MUX */ + gpio_write(0); + + /* Preset camera 0 to the 4 controllers */ + for (ix = 0; ix < 4; ix++) { + btv->sw_status[ix] = ix; + gv800s_write(btv, ix, ix, 1); + } + + /* Inputs on the "master" controller need this brightness fix */ + bttv_I2CWrite(btv, 0x18, 0x5, 0x90, 1); + + if (btv->c.nr > BTTV_MAX-4) + return; + /* + * Store the "master" controller pointer in the master + * array for later use in the muxsel function. + */ + master[btv->c.nr] = btv; + master[btv->c.nr+1] = btv; + master[btv->c.nr+2] = btv; + master[btv->c.nr+3] = btv; +} + +/* ----------------------------------------------------------------------- */ +/* motherboard chipset specific stuff */ + +void __init bttv_check_chipset(void) +{ + struct pci_dev *dev = NULL; + + if (pci_pci_problems & (PCIPCI_TRITON|PCIPCI_NATOMA|PCIPCI_VIAETBF)) + triton1 = 1; + if (pci_pci_problems & PCIPCI_VSFX) + vsfx = 1; +#ifdef PCIPCI_ALIMAGIK + if (pci_pci_problems & PCIPCI_ALIMAGIK) + latency = 0x0A; +#endif + + + /* print warnings about any quirks found */ + if (triton1) + pr_info("Host bridge needs ETBF enabled\n"); + if (vsfx) + pr_info("Host bridge needs VSFX enabled\n"); + if (UNSET != latency) + pr_info("pci latency fixup [%d]\n", latency); + while ((dev = pci_get_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_82441, dev))) { + unsigned char b; + pci_read_config_byte(dev, 0x53, &b); + if (bttv_debug) + pr_info("Host bridge: 82441FX Natoma, bufcon=0x%02x\n", + b); + } +} + +int bttv_handle_chipset(struct bttv *btv) +{ + unsigned char command; + + if (!triton1 && !vsfx && UNSET == latency) + return 0; + + if (bttv_verbose) { + if (triton1) + pr_info("%d: enabling ETBF (430FX/VP3 compatibility)\n", + btv->c.nr); + if (vsfx && btv->id >= 878) + pr_info("%d: enabling VSFX\n", btv->c.nr); + if (UNSET != latency) + pr_info("%d: setting pci timer to %d\n", + btv->c.nr, latency); + } + + if (btv->id < 878) { + /* bt848 (mis)uses a bit in the irq mask for etbf */ + if (triton1) + btv->triton1 = BT848_INT_ETBF; + } else { + /* bt878 has a bit in the pci config space for it */ + pci_read_config_byte(btv->c.pci, BT878_DEVCTRL, &command); + if (triton1) + command |= BT878_EN_TBFX; + if (vsfx) + command |= BT878_EN_VSFX; + pci_write_config_byte(btv->c.pci, BT878_DEVCTRL, command); + } + if (UNSET != latency) + pci_write_config_byte(btv->c.pci, PCI_LATENCY_TIMER, latency); + return 0; +} diff --git a/drivers/media/pci/bt8xx/bttv-driver.c b/drivers/media/pci/bt8xx/bttv-driver.c new file mode 100644 index 000000000..c631b8bbd --- /dev/null +++ b/drivers/media/pci/bt8xx/bttv-driver.c @@ -0,0 +1,3625 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + + bttv - Bt848 frame grabber driver + + Copyright (C) 1996,97,98 Ralph Metzler + & Marcus Metzler + (c) 1999-2002 Gerd Knorr + + some v4l2 code lines are taken from Justin's bttv2 driver which is + (c) 2000 Justin Schoeman + + V4L1 removal from: + (c) 2005-2006 Nickolay V. Shmyrev + + Fixes to be fully V4L2 compliant by + (c) 2006 Mauro Carvalho Chehab + + Cropping and overscan support + Copyright (C) 2005, 2006 Michael H. Schimek + Sponsored by OPQ Systems AB + +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "bttvp.h" +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#define BTTV_VERSION "0.9.19" + +unsigned int bttv_num; /* number of Bt848s in use */ +struct bttv *bttvs[BTTV_MAX]; + +unsigned int bttv_debug; +unsigned int bttv_verbose = 1; +unsigned int bttv_gpio; + +/* config variables */ +#ifdef __BIG_ENDIAN +static unsigned int bigendian=1; +#else +static unsigned int bigendian; +#endif +static unsigned int radio[BTTV_MAX]; +static unsigned int irq_debug; +static unsigned int gbuffers = 8; +static unsigned int gbufsize = 0x208000; +static unsigned int reset_crop = 1; + +static int video_nr[BTTV_MAX] = { [0 ... (BTTV_MAX-1)] = -1 }; +static int radio_nr[BTTV_MAX] = { [0 ... (BTTV_MAX-1)] = -1 }; +static int vbi_nr[BTTV_MAX] = { [0 ... (BTTV_MAX-1)] = -1 }; +static int debug_latency; +static int disable_ir; + +static unsigned int fdsr; + +/* options */ +static unsigned int combfilter; +static unsigned int lumafilter; +static unsigned int automute = 1; +static unsigned int chroma_agc; +static unsigned int agc_crush = 1; +static unsigned int whitecrush_upper = 0xCF; +static unsigned int whitecrush_lower = 0x7F; +static unsigned int vcr_hack; +static unsigned int irq_iswitch; +static unsigned int uv_ratio = 50; +static unsigned int full_luma_range; +static unsigned int coring; + +/* API features (turn on/off stuff for testing) */ +static unsigned int v4l2 = 1; + +/* insmod args */ +module_param(bttv_verbose, int, 0644); +module_param(bttv_gpio, int, 0644); +module_param(bttv_debug, int, 0644); +module_param(irq_debug, int, 0644); +module_param(debug_latency, int, 0644); +module_param(disable_ir, int, 0444); + +module_param(fdsr, int, 0444); +module_param(gbuffers, int, 0444); +module_param(gbufsize, int, 0444); +module_param(reset_crop, int, 0444); + +module_param(v4l2, int, 0644); +module_param(bigendian, int, 0644); +module_param(irq_iswitch, int, 0644); +module_param(combfilter, int, 0444); +module_param(lumafilter, int, 0444); +module_param(automute, int, 0444); +module_param(chroma_agc, int, 0444); +module_param(agc_crush, int, 0444); +module_param(whitecrush_upper, int, 0444); +module_param(whitecrush_lower, int, 0444); +module_param(vcr_hack, int, 0444); +module_param(uv_ratio, int, 0444); +module_param(full_luma_range, int, 0444); +module_param(coring, int, 0444); + +module_param_array(radio, int, NULL, 0444); +module_param_array(video_nr, int, NULL, 0444); +module_param_array(radio_nr, int, NULL, 0444); +module_param_array(vbi_nr, int, NULL, 0444); + +MODULE_PARM_DESC(radio, "The TV card supports radio, default is 0 (no)"); +MODULE_PARM_DESC(bigendian, "byte order of the framebuffer, default is native endian"); +MODULE_PARM_DESC(bttv_verbose, "verbose startup messages, default is 1 (yes)"); +MODULE_PARM_DESC(bttv_gpio, "log gpio changes, default is 0 (no)"); +MODULE_PARM_DESC(bttv_debug, "debug messages, default is 0 (no)"); +MODULE_PARM_DESC(irq_debug, "irq handler debug messages, default is 0 (no)"); +MODULE_PARM_DESC(disable_ir, "disable infrared remote support"); +MODULE_PARM_DESC(gbuffers, "number of capture buffers. range 2-32, default 8"); +MODULE_PARM_DESC(gbufsize, "size of the capture buffers, default is 0x208000"); +MODULE_PARM_DESC(reset_crop, "reset cropping parameters at open(), default is 1 (yes) for compatibility with older applications"); +MODULE_PARM_DESC(automute, "mute audio on bad/missing video signal, default is 1 (yes)"); +MODULE_PARM_DESC(chroma_agc, "enables the AGC of chroma signal, default is 0 (no)"); +MODULE_PARM_DESC(agc_crush, "enables the luminance AGC crush, default is 1 (yes)"); +MODULE_PARM_DESC(whitecrush_upper, "sets the white crush upper value, default is 207"); +MODULE_PARM_DESC(whitecrush_lower, "sets the white crush lower value, default is 127"); +MODULE_PARM_DESC(vcr_hack, "enables the VCR hack (improves synch on poor VCR tapes), default is 0 (no)"); +MODULE_PARM_DESC(irq_iswitch, "switch inputs in irq handler"); +MODULE_PARM_DESC(uv_ratio, "ratio between u and v gains, default is 50"); +MODULE_PARM_DESC(full_luma_range, "use the full luma range, default is 0 (no)"); +MODULE_PARM_DESC(coring, "set the luma coring level, default is 0 (no)"); +MODULE_PARM_DESC(video_nr, "video device numbers"); +MODULE_PARM_DESC(vbi_nr, "vbi device numbers"); +MODULE_PARM_DESC(radio_nr, "radio device numbers"); + +MODULE_DESCRIPTION("bttv - v4l/v4l2 driver module for bt848/878 based cards"); +MODULE_AUTHOR("Ralph Metzler & Marcus Metzler & Gerd Knorr"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(BTTV_VERSION); + +#define V4L2_CID_PRIVATE_COMBFILTER (V4L2_CID_USER_BTTV_BASE + 0) +#define V4L2_CID_PRIVATE_AUTOMUTE (V4L2_CID_USER_BTTV_BASE + 1) +#define V4L2_CID_PRIVATE_LUMAFILTER (V4L2_CID_USER_BTTV_BASE + 2) +#define V4L2_CID_PRIVATE_AGC_CRUSH (V4L2_CID_USER_BTTV_BASE + 3) +#define V4L2_CID_PRIVATE_VCR_HACK (V4L2_CID_USER_BTTV_BASE + 4) +#define V4L2_CID_PRIVATE_WHITECRUSH_LOWER (V4L2_CID_USER_BTTV_BASE + 5) +#define V4L2_CID_PRIVATE_WHITECRUSH_UPPER (V4L2_CID_USER_BTTV_BASE + 6) +#define V4L2_CID_PRIVATE_UV_RATIO (V4L2_CID_USER_BTTV_BASE + 7) +#define V4L2_CID_PRIVATE_FULL_LUMA_RANGE (V4L2_CID_USER_BTTV_BASE + 8) +#define V4L2_CID_PRIVATE_CORING (V4L2_CID_USER_BTTV_BASE + 9) + +/* ----------------------------------------------------------------------- */ +/* sysfs */ + +static ssize_t card_show(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct video_device *vfd = to_video_device(cd); + struct bttv *btv = video_get_drvdata(vfd); + return sprintf(buf, "%d\n", btv ? btv->c.type : UNSET); +} +static DEVICE_ATTR_RO(card); + +/* ----------------------------------------------------------------------- */ +/* dvb auto-load setup */ +#if defined(CONFIG_MODULES) && defined(MODULE) +static void request_module_async(struct work_struct *work) +{ + request_module("dvb-bt8xx"); +} + +static void request_modules(struct bttv *dev) +{ + INIT_WORK(&dev->request_module_wk, request_module_async); + schedule_work(&dev->request_module_wk); +} + +static void flush_request_modules(struct bttv *dev) +{ + flush_work(&dev->request_module_wk); +} +#else +#define request_modules(dev) +#define flush_request_modules(dev) do {} while(0) +#endif /* CONFIG_MODULES */ + + +/* ----------------------------------------------------------------------- */ +/* static data */ + +/* special timing tables from conexant... */ +static u8 SRAM_Table[][60] = +{ + /* PAL digital input over GPIO[7:0] */ + { + 45, // 45 bytes following + 0x36,0x11,0x01,0x00,0x90,0x02,0x05,0x10,0x04,0x16, + 0x12,0x05,0x11,0x00,0x04,0x12,0xC0,0x00,0x31,0x00, + 0x06,0x51,0x08,0x03,0x89,0x08,0x07,0xC0,0x44,0x00, + 0x81,0x01,0x01,0xA9,0x0D,0x02,0x02,0x50,0x03,0x37, + 0x37,0x00,0xAF,0x21,0x00 + }, + /* NTSC digital input over GPIO[7:0] */ + { + 51, // 51 bytes following + 0x0C,0xC0,0x00,0x00,0x90,0x02,0x03,0x10,0x03,0x06, + 0x10,0x04,0x12,0x12,0x05,0x02,0x13,0x04,0x19,0x00, + 0x04,0x39,0x00,0x06,0x59,0x08,0x03,0x83,0x08,0x07, + 0x03,0x50,0x00,0xC0,0x40,0x00,0x86,0x01,0x01,0xA6, + 0x0D,0x02,0x03,0x11,0x01,0x05,0x37,0x00,0xAC,0x21, + 0x00, + }, + // TGB_NTSC392 // quartzsight + // This table has been modified to be used for Fusion Rev D + { + 0x2A, // size of table = 42 + 0x06, 0x08, 0x04, 0x0a, 0xc0, 0x00, 0x18, 0x08, 0x03, 0x24, + 0x08, 0x07, 0x02, 0x90, 0x02, 0x08, 0x10, 0x04, 0x0c, 0x10, + 0x05, 0x2c, 0x11, 0x04, 0x55, 0x48, 0x00, 0x05, 0x50, 0x00, + 0xbf, 0x0c, 0x02, 0x2f, 0x3d, 0x00, 0x2f, 0x3f, 0x00, 0xc3, + 0x20, 0x00 + } +}; + +/* minhdelayx1 first video pixel we can capture on a line and + hdelayx1 start of active video, both relative to rising edge of + /HRESET pulse (0H) in 1 / fCLKx1. + swidth width of active video and + totalwidth total line width, both in 1 / fCLKx1. + sqwidth total line width in square pixels. + vdelay start of active video in 2 * field lines relative to + trailing edge of /VRESET pulse (VDELAY register). + sheight height of active video in 2 * field lines. + extraheight Added to sheight for cropcap.bounds.height only + videostart0 ITU-R frame line number of the line corresponding + to vdelay in the first field. */ +#define CROPCAP(minhdelayx1, hdelayx1, swidth, totalwidth, sqwidth, \ + vdelay, sheight, extraheight, videostart0) \ + .cropcap.bounds.left = minhdelayx1, \ + /* * 2 because vertically we count field lines times two, */ \ + /* e.g. 23 * 2 to 23 * 2 + 576 in PAL-BGHI defrect. */ \ + .cropcap.bounds.top = (videostart0) * 2 - (vdelay) + MIN_VDELAY, \ + /* 4 is a safety margin at the end of the line. */ \ + .cropcap.bounds.width = (totalwidth) - (minhdelayx1) - 4, \ + .cropcap.bounds.height = (sheight) + (extraheight) + (vdelay) - \ + MIN_VDELAY, \ + .cropcap.defrect.left = hdelayx1, \ + .cropcap.defrect.top = (videostart0) * 2, \ + .cropcap.defrect.width = swidth, \ + .cropcap.defrect.height = sheight, \ + .cropcap.pixelaspect.numerator = totalwidth, \ + .cropcap.pixelaspect.denominator = sqwidth, + +const struct bttv_tvnorm bttv_tvnorms[] = { + /* PAL-BDGHI */ + /* max. active video is actually 922, but 924 is divisible by 4 and 3! */ + /* actually, max active PAL with HSCALE=0 is 948, NTSC is 768 - nil */ + { + .v4l2_id = V4L2_STD_PAL, + .name = "PAL", + .Fsc = 35468950, + .swidth = 924, + .sheight = 576, + .totalwidth = 1135, + .adelay = 0x7f, + .bdelay = 0x72, + .iform = (BT848_IFORM_PAL_BDGHI|BT848_IFORM_XT1), + .scaledtwidth = 1135, + .hdelayx1 = 186, + .hactivex1 = 924, + .vdelay = 0x20, + .vbipack = 255, /* min (2048 / 4, 0x1ff) & 0xff */ + .sram = 0, + /* ITU-R frame line number of the first VBI line + we can capture, of the first and second field. + The last line is determined by cropcap.bounds. */ + .vbistart = { 7, 320 }, + CROPCAP(/* minhdelayx1 */ 68, + /* hdelayx1 */ 186, + /* Should be (768 * 1135 + 944 / 2) / 944. + cropcap.defrect is used for image width + checks, so we keep the old value 924. */ + /* swidth */ 924, + /* totalwidth */ 1135, + /* sqwidth */ 944, + /* vdelay */ 0x20, + /* sheight */ 576, + /* bt878 (and bt848?) can capture another + line below active video. */ + /* extraheight */ 2, + /* videostart0 */ 23) + },{ + .v4l2_id = V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_KR, + .name = "NTSC", + .Fsc = 28636363, + .swidth = 768, + .sheight = 480, + .totalwidth = 910, + .adelay = 0x68, + .bdelay = 0x5d, + .iform = (BT848_IFORM_NTSC|BT848_IFORM_XT0), + .scaledtwidth = 910, + .hdelayx1 = 128, + .hactivex1 = 910, + .vdelay = 0x1a, + .vbipack = 144, /* min (1600 / 4, 0x1ff) & 0xff */ + .sram = 1, + .vbistart = { 10, 273 }, + CROPCAP(/* minhdelayx1 */ 68, + /* hdelayx1 */ 128, + /* Should be (640 * 910 + 780 / 2) / 780? */ + /* swidth */ 768, + /* totalwidth */ 910, + /* sqwidth */ 780, + /* vdelay */ 0x1a, + /* sheight */ 480, + /* extraheight */ 0, + /* videostart0 */ 23) + },{ + .v4l2_id = V4L2_STD_SECAM, + .name = "SECAM", + .Fsc = 35468950, + .swidth = 924, + .sheight = 576, + .totalwidth = 1135, + .adelay = 0x7f, + .bdelay = 0xb0, + .iform = (BT848_IFORM_SECAM|BT848_IFORM_XT1), + .scaledtwidth = 1135, + .hdelayx1 = 186, + .hactivex1 = 922, + .vdelay = 0x20, + .vbipack = 255, + .sram = 0, /* like PAL, correct? */ + .vbistart = { 7, 320 }, + CROPCAP(/* minhdelayx1 */ 68, + /* hdelayx1 */ 186, + /* swidth */ 924, + /* totalwidth */ 1135, + /* sqwidth */ 944, + /* vdelay */ 0x20, + /* sheight */ 576, + /* extraheight */ 0, + /* videostart0 */ 23) + },{ + .v4l2_id = V4L2_STD_PAL_Nc, + .name = "PAL-Nc", + .Fsc = 28636363, + .swidth = 640, + .sheight = 576, + .totalwidth = 910, + .adelay = 0x68, + .bdelay = 0x5d, + .iform = (BT848_IFORM_PAL_NC|BT848_IFORM_XT0), + .scaledtwidth = 780, + .hdelayx1 = 130, + .hactivex1 = 734, + .vdelay = 0x1a, + .vbipack = 144, + .sram = -1, + .vbistart = { 7, 320 }, + CROPCAP(/* minhdelayx1 */ 68, + /* hdelayx1 */ 130, + /* swidth */ (640 * 910 + 780 / 2) / 780, + /* totalwidth */ 910, + /* sqwidth */ 780, + /* vdelay */ 0x1a, + /* sheight */ 576, + /* extraheight */ 0, + /* videostart0 */ 23) + },{ + .v4l2_id = V4L2_STD_PAL_M, + .name = "PAL-M", + .Fsc = 28636363, + .swidth = 640, + .sheight = 480, + .totalwidth = 910, + .adelay = 0x68, + .bdelay = 0x5d, + .iform = (BT848_IFORM_PAL_M|BT848_IFORM_XT0), + .scaledtwidth = 780, + .hdelayx1 = 135, + .hactivex1 = 754, + .vdelay = 0x1a, + .vbipack = 144, + .sram = -1, + .vbistart = { 10, 273 }, + CROPCAP(/* minhdelayx1 */ 68, + /* hdelayx1 */ 135, + /* swidth */ (640 * 910 + 780 / 2) / 780, + /* totalwidth */ 910, + /* sqwidth */ 780, + /* vdelay */ 0x1a, + /* sheight */ 480, + /* extraheight */ 0, + /* videostart0 */ 23) + },{ + .v4l2_id = V4L2_STD_PAL_N, + .name = "PAL-N", + .Fsc = 35468950, + .swidth = 768, + .sheight = 576, + .totalwidth = 1135, + .adelay = 0x7f, + .bdelay = 0x72, + .iform = (BT848_IFORM_PAL_N|BT848_IFORM_XT1), + .scaledtwidth = 944, + .hdelayx1 = 186, + .hactivex1 = 922, + .vdelay = 0x20, + .vbipack = 144, + .sram = -1, + .vbistart = { 7, 320 }, + CROPCAP(/* minhdelayx1 */ 68, + /* hdelayx1 */ 186, + /* swidth */ (768 * 1135 + 944 / 2) / 944, + /* totalwidth */ 1135, + /* sqwidth */ 944, + /* vdelay */ 0x20, + /* sheight */ 576, + /* extraheight */ 0, + /* videostart0 */ 23) + },{ + .v4l2_id = V4L2_STD_NTSC_M_JP, + .name = "NTSC-JP", + .Fsc = 28636363, + .swidth = 640, + .sheight = 480, + .totalwidth = 910, + .adelay = 0x68, + .bdelay = 0x5d, + .iform = (BT848_IFORM_NTSC_J|BT848_IFORM_XT0), + .scaledtwidth = 780, + .hdelayx1 = 135, + .hactivex1 = 754, + .vdelay = 0x16, + .vbipack = 144, + .sram = -1, + .vbistart = { 10, 273 }, + CROPCAP(/* minhdelayx1 */ 68, + /* hdelayx1 */ 135, + /* swidth */ (640 * 910 + 780 / 2) / 780, + /* totalwidth */ 910, + /* sqwidth */ 780, + /* vdelay */ 0x16, + /* sheight */ 480, + /* extraheight */ 0, + /* videostart0 */ 23) + },{ + /* that one hopefully works with the strange timing + * which video recorders produce when playing a NTSC + * tape on a PAL TV ... */ + .v4l2_id = V4L2_STD_PAL_60, + .name = "PAL-60", + .Fsc = 35468950, + .swidth = 924, + .sheight = 480, + .totalwidth = 1135, + .adelay = 0x7f, + .bdelay = 0x72, + .iform = (BT848_IFORM_PAL_BDGHI|BT848_IFORM_XT1), + .scaledtwidth = 1135, + .hdelayx1 = 186, + .hactivex1 = 924, + .vdelay = 0x1a, + .vbipack = 255, + .vtotal = 524, + .sram = -1, + .vbistart = { 10, 273 }, + CROPCAP(/* minhdelayx1 */ 68, + /* hdelayx1 */ 186, + /* swidth */ 924, + /* totalwidth */ 1135, + /* sqwidth */ 944, + /* vdelay */ 0x1a, + /* sheight */ 480, + /* extraheight */ 0, + /* videostart0 */ 23) + } +}; +static const unsigned int BTTV_TVNORMS = ARRAY_SIZE(bttv_tvnorms); + +/* ----------------------------------------------------------------------- */ +/* bttv format list + packed pixel formats must come first */ +static const struct bttv_format formats[] = { + { + .fourcc = V4L2_PIX_FMT_GREY, + .btformat = BT848_COLOR_FMT_Y8, + .depth = 8, + .flags = FORMAT_FLAGS_PACKED, + },{ + .fourcc = V4L2_PIX_FMT_HI240, + .btformat = BT848_COLOR_FMT_RGB8, + .depth = 8, + .flags = FORMAT_FLAGS_PACKED | FORMAT_FLAGS_DITHER, + },{ + .fourcc = V4L2_PIX_FMT_RGB555, + .btformat = BT848_COLOR_FMT_RGB15, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .fourcc = V4L2_PIX_FMT_RGB555X, + .btformat = BT848_COLOR_FMT_RGB15, + .btswap = 0x03, /* byteswap */ + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .fourcc = V4L2_PIX_FMT_RGB565, + .btformat = BT848_COLOR_FMT_RGB16, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .fourcc = V4L2_PIX_FMT_RGB565X, + .btformat = BT848_COLOR_FMT_RGB16, + .btswap = 0x03, /* byteswap */ + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .fourcc = V4L2_PIX_FMT_BGR24, + .btformat = BT848_COLOR_FMT_RGB24, + .depth = 24, + .flags = FORMAT_FLAGS_PACKED, + },{ + .fourcc = V4L2_PIX_FMT_BGR32, + .btformat = BT848_COLOR_FMT_RGB32, + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + },{ + .fourcc = V4L2_PIX_FMT_RGB32, + .btformat = BT848_COLOR_FMT_RGB32, + .btswap = 0x0f, /* byte+word swap */ + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + },{ + .fourcc = V4L2_PIX_FMT_YUYV, + .btformat = BT848_COLOR_FMT_YUY2, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .fourcc = V4L2_PIX_FMT_UYVY, + .btformat = BT848_COLOR_FMT_YUY2, + .btswap = 0x03, /* byteswap */ + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .fourcc = V4L2_PIX_FMT_YUV422P, + .btformat = BT848_COLOR_FMT_YCrCb422, + .depth = 16, + .flags = FORMAT_FLAGS_PLANAR, + .hshift = 1, + .vshift = 0, + },{ + .fourcc = V4L2_PIX_FMT_YUV420, + .btformat = BT848_COLOR_FMT_YCrCb422, + .depth = 12, + .flags = FORMAT_FLAGS_PLANAR, + .hshift = 1, + .vshift = 1, + },{ + .fourcc = V4L2_PIX_FMT_YVU420, + .btformat = BT848_COLOR_FMT_YCrCb422, + .depth = 12, + .flags = FORMAT_FLAGS_PLANAR | FORMAT_FLAGS_CrCb, + .hshift = 1, + .vshift = 1, + },{ + .fourcc = V4L2_PIX_FMT_YUV411P, + .btformat = BT848_COLOR_FMT_YCrCb411, + .depth = 12, + .flags = FORMAT_FLAGS_PLANAR, + .hshift = 2, + .vshift = 0, + },{ + .fourcc = V4L2_PIX_FMT_YUV410, + .btformat = BT848_COLOR_FMT_YCrCb411, + .depth = 9, + .flags = FORMAT_FLAGS_PLANAR, + .hshift = 2, + .vshift = 2, + },{ + .fourcc = V4L2_PIX_FMT_YVU410, + .btformat = BT848_COLOR_FMT_YCrCb411, + .depth = 9, + .flags = FORMAT_FLAGS_PLANAR | FORMAT_FLAGS_CrCb, + .hshift = 2, + .vshift = 2, + },{ + .fourcc = -1, + .btformat = BT848_COLOR_FMT_RAW, + .depth = 8, + .flags = FORMAT_FLAGS_RAW, + } +}; +static const unsigned int FORMATS = ARRAY_SIZE(formats); + +/* ----------------------------------------------------------------------- */ +/* resource management */ + +/* + RESOURCE_ allocated by freed by + + VIDEO_READ bttv_read 1) bttv_read 2) + + VIDEO_STREAM VIDIOC_STREAMON VIDIOC_STREAMOFF + VIDIOC_QBUF 1) bttv_release + VIDIOCMCAPTURE 1) + + VBI VIDIOC_STREAMON VIDIOC_STREAMOFF + VIDIOC_QBUF 1) bttv_release + bttv_read, bttv_poll 1) 3) + + 1) The resource must be allocated when we enter buffer prepare functions + and remain allocated while buffers are in the DMA queue. + 2) This is a single frame read. + 3) This is a continuous read, implies VIDIOC_STREAMON. + + Note this driver permits video input and standard changes regardless if + resources are allocated. +*/ + +#define VBI_RESOURCES (RESOURCE_VBI) +#define VIDEO_RESOURCES (RESOURCE_VIDEO_READ | \ + RESOURCE_VIDEO_STREAM) + +int check_alloc_btres_lock(struct bttv *btv, int bit) +{ + int xbits; /* mutual exclusive resources */ + + xbits = bit; + if (bit & (RESOURCE_VIDEO_READ | RESOURCE_VIDEO_STREAM)) + xbits |= RESOURCE_VIDEO_READ | RESOURCE_VIDEO_STREAM; + + /* is it free? */ + if (btv->resources & xbits) { + /* no, someone else uses it */ + goto fail; + } + + if ((bit & VIDEO_RESOURCES) + && 0 == (btv->resources & VIDEO_RESOURCES)) { + /* Do crop - use current, don't - use default parameters. */ + __s32 top = btv->crop[!!btv->do_crop].rect.top; + + if (btv->vbi_end > top) + goto fail; + + /* We cannot capture the same line as video and VBI data. + Claim scan lines crop[].rect.top to bottom. */ + btv->crop_start = top; + } else if (bit & VBI_RESOURCES) { + __s32 end = btv->vbi_fmt.end; + + if (end > btv->crop_start) + goto fail; + + /* Claim scan lines above btv->vbi_fmt.end. */ + btv->vbi_end = end; + } + + /* it's free, grab it */ + btv->resources |= bit; + return 1; + + fail: + return 0; +} + +static +int check_btres(struct bttv *btv, int bit) +{ + return (btv->resources & bit); +} + +static +int locked_btres(struct bttv *btv, int bit) +{ + return (btv->resources & bit); +} + +/* Call with btv->lock down. */ +static void +disclaim_vbi_lines(struct bttv *btv) +{ + btv->vbi_end = 0; +} + +/* Call with btv->lock down. */ +static void +disclaim_video_lines(struct bttv *btv) +{ + const struct bttv_tvnorm *tvnorm; + u8 crop; + + tvnorm = &bttv_tvnorms[btv->tvnorm]; + btv->crop_start = tvnorm->cropcap.bounds.top + + tvnorm->cropcap.bounds.height; + + /* VBI capturing ends at VDELAY, start of video capturing, no + matter how many lines the VBI RISC program expects. When video + capturing is off, it shall no longer "preempt" VBI capturing, + so we set VDELAY to maximum. */ + crop = btread(BT848_E_CROP) | 0xc0; + btwrite(crop, BT848_E_CROP); + btwrite(0xfe, BT848_E_VDELAY_LO); + btwrite(crop, BT848_O_CROP); + btwrite(0xfe, BT848_O_VDELAY_LO); +} + +void free_btres_lock(struct bttv *btv, int bits) +{ + if ((btv->resources & bits) != bits) { + /* trying to free resources not allocated by us ... */ + pr_err("BUG! (btres)\n"); + } + btv->resources &= ~bits; + + bits = btv->resources; + + if (0 == (bits & VIDEO_RESOURCES)) + disclaim_video_lines(btv); + + if (0 == (bits & VBI_RESOURCES)) + disclaim_vbi_lines(btv); +} + +/* ----------------------------------------------------------------------- */ +/* If Bt848a or Bt849, use PLL for PAL/SECAM and crystal for NTSC */ + +/* Frequency = (F_input / PLL_X) * PLL_I.PLL_F/PLL_C + PLL_X = Reference pre-divider (0=1, 1=2) + PLL_C = Post divider (0=6, 1=4) + PLL_I = Integer input + PLL_F = Fractional input + + F_input = 28.636363 MHz: + PAL (CLKx2 = 35.46895 MHz): PLL_X = 1, PLL_I = 0x0E, PLL_F = 0xDCF9, PLL_C = 0 +*/ + +static void set_pll_freq(struct bttv *btv, unsigned int fin, unsigned int fout) +{ + unsigned char fl, fh, fi; + + /* prevent overflows */ + fin/=4; + fout/=4; + + fout*=12; + fi=fout/fin; + + fout=(fout%fin)*256; + fh=fout/fin; + + fout=(fout%fin)*256; + fl=fout/fin; + + btwrite(fl, BT848_PLL_F_LO); + btwrite(fh, BT848_PLL_F_HI); + btwrite(fi|BT848_PLL_X, BT848_PLL_XCI); +} + +static void set_pll(struct bttv *btv) +{ + int i; + + if (!btv->pll.pll_crystal) + return; + + if (btv->pll.pll_ofreq == btv->pll.pll_current) { + dprintk("%d: PLL: no change required\n", btv->c.nr); + return; + } + + if (btv->pll.pll_ifreq == btv->pll.pll_ofreq) { + /* no PLL needed */ + if (btv->pll.pll_current == 0) + return; + if (bttv_verbose) + pr_info("%d: PLL can sleep, using XTAL (%d)\n", + btv->c.nr, btv->pll.pll_ifreq); + btwrite(0x00,BT848_TGCTRL); + btwrite(0x00,BT848_PLL_XCI); + btv->pll.pll_current = 0; + return; + } + + if (bttv_verbose) + pr_info("%d: Setting PLL: %d => %d (needs up to 100ms)\n", + btv->c.nr, + btv->pll.pll_ifreq, btv->pll.pll_ofreq); + set_pll_freq(btv, btv->pll.pll_ifreq, btv->pll.pll_ofreq); + + for (i=0; i<10; i++) { + /* Let other people run while the PLL stabilizes */ + msleep(10); + + if (btread(BT848_DSTATUS) & BT848_DSTATUS_PLOCK) { + btwrite(0,BT848_DSTATUS); + } else { + btwrite(0x08,BT848_TGCTRL); + btv->pll.pll_current = btv->pll.pll_ofreq; + if (bttv_verbose) + pr_info("PLL set ok\n"); + return; + } + } + btv->pll.pll_current = -1; + if (bttv_verbose) + pr_info("Setting PLL failed\n"); + return; +} + +/* used to switch between the bt848's analog/digital video capture modes */ +static void bt848A_set_timing(struct bttv *btv) +{ + int i, len; + int table_idx = bttv_tvnorms[btv->tvnorm].sram; + int fsc = bttv_tvnorms[btv->tvnorm].Fsc; + + if (btv->input == btv->dig) { + dprintk("%d: load digital timing table (table_idx=%d)\n", + btv->c.nr,table_idx); + + /* timing change...reset timing generator address */ + btwrite(0x00, BT848_TGCTRL); + btwrite(0x02, BT848_TGCTRL); + btwrite(0x00, BT848_TGCTRL); + + len=SRAM_Table[table_idx][0]; + for(i = 1; i <= len; i++) + btwrite(SRAM_Table[table_idx][i],BT848_TGLB); + btv->pll.pll_ofreq = 27000000; + + set_pll(btv); + btwrite(0x11, BT848_TGCTRL); + btwrite(0x41, BT848_DVSIF); + } else { + btv->pll.pll_ofreq = fsc; + set_pll(btv); + btwrite(0x0, BT848_DVSIF); + } +} + +/* ----------------------------------------------------------------------- */ + +static void bt848_bright(struct bttv *btv, int bright) +{ + int value; + + // printk("set bright: %d\n", bright); // DEBUG + btv->bright = bright; + + /* We want -128 to 127 we get 0-65535 */ + value = (bright >> 8) - 128; + btwrite(value & 0xff, BT848_BRIGHT); +} + +static void bt848_hue(struct bttv *btv, int hue) +{ + int value; + + btv->hue = hue; + + /* -128 to 127 */ + value = (hue >> 8) - 128; + btwrite(value & 0xff, BT848_HUE); +} + +static void bt848_contrast(struct bttv *btv, int cont) +{ + int value,hibit; + + btv->contrast = cont; + + /* 0-511 */ + value = (cont >> 7); + hibit = (value >> 6) & 4; + btwrite(value & 0xff, BT848_CONTRAST_LO); + btaor(hibit, ~4, BT848_E_CONTROL); + btaor(hibit, ~4, BT848_O_CONTROL); +} + +static void bt848_sat(struct bttv *btv, int color) +{ + int val_u,val_v,hibits; + + btv->saturation = color; + + /* 0-511 for the color */ + val_u = ((color * btv->opt_uv_ratio) / 50) >> 7; + val_v = (((color * (100 - btv->opt_uv_ratio) / 50) >>7)*180L)/254; + hibits = (val_u >> 7) & 2; + hibits |= (val_v >> 8) & 1; + btwrite(val_u & 0xff, BT848_SAT_U_LO); + btwrite(val_v & 0xff, BT848_SAT_V_LO); + btaor(hibits, ~3, BT848_E_CONTROL); + btaor(hibits, ~3, BT848_O_CONTROL); +} + +/* ----------------------------------------------------------------------- */ + +static int +video_mux(struct bttv *btv, unsigned int input) +{ + int mux,mask2; + + if (input >= bttv_tvcards[btv->c.type].video_inputs) + return -EINVAL; + + /* needed by RemoteVideo MX */ + mask2 = bttv_tvcards[btv->c.type].gpiomask2; + if (mask2) + gpio_inout(mask2,mask2); + + if (input == btv->svhs) { + btor(BT848_CONTROL_COMP, BT848_E_CONTROL); + btor(BT848_CONTROL_COMP, BT848_O_CONTROL); + } else { + btand(~BT848_CONTROL_COMP, BT848_E_CONTROL); + btand(~BT848_CONTROL_COMP, BT848_O_CONTROL); + } + mux = bttv_muxsel(btv, input); + btaor(mux<<5, ~(3<<5), BT848_IFORM); + dprintk("%d: video mux: input=%d mux=%d\n", btv->c.nr, input, mux); + + /* card specific hook */ + if(bttv_tvcards[btv->c.type].muxsel_hook) + bttv_tvcards[btv->c.type].muxsel_hook (btv, input); + return 0; +} + +static char *audio_modes[] = { + "audio: tuner", "audio: radio", "audio: extern", + "audio: intern", "audio: mute" +}; + +static void +audio_mux_gpio(struct bttv *btv, int input, int mute) +{ + int gpio_val, signal, mute_gpio; + + gpio_inout(bttv_tvcards[btv->c.type].gpiomask, + bttv_tvcards[btv->c.type].gpiomask); + signal = btread(BT848_DSTATUS) & BT848_DSTATUS_HLOC; + + /* automute */ + mute_gpio = mute || (btv->opt_automute && (!signal || !btv->users) + && !btv->has_radio_tuner); + + if (mute_gpio) + gpio_val = bttv_tvcards[btv->c.type].gpiomute; + else + gpio_val = bttv_tvcards[btv->c.type].gpiomux[input]; + + switch (btv->c.type) { + case BTTV_BOARD_VOODOOTV_FM: + case BTTV_BOARD_VOODOOTV_200: + gpio_val = bttv_tda9880_setnorm(btv, gpio_val); + break; + + default: + gpio_bits(bttv_tvcards[btv->c.type].gpiomask, gpio_val); + } + + if (bttv_gpio) + bttv_gpio_tracking(btv, audio_modes[mute_gpio ? 4 : input]); +} + +static int +audio_mute(struct bttv *btv, int mute) +{ + struct v4l2_ctrl *ctrl; + + audio_mux_gpio(btv, btv->audio_input, mute); + + if (btv->sd_msp34xx) { + ctrl = v4l2_ctrl_find(btv->sd_msp34xx->ctrl_handler, V4L2_CID_AUDIO_MUTE); + if (ctrl) + v4l2_ctrl_s_ctrl(ctrl, mute); + } + if (btv->sd_tvaudio) { + ctrl = v4l2_ctrl_find(btv->sd_tvaudio->ctrl_handler, V4L2_CID_AUDIO_MUTE); + if (ctrl) + v4l2_ctrl_s_ctrl(ctrl, mute); + } + if (btv->sd_tda7432) { + ctrl = v4l2_ctrl_find(btv->sd_tda7432->ctrl_handler, V4L2_CID_AUDIO_MUTE); + if (ctrl) + v4l2_ctrl_s_ctrl(ctrl, mute); + } + return 0; +} + +static int +audio_input(struct bttv *btv, int input) +{ + audio_mux_gpio(btv, input, btv->mute); + + if (btv->sd_msp34xx) { + u32 in; + + /* Note: the inputs tuner/radio/extern/intern are translated + to msp routings. This assumes common behavior for all msp3400 + based TV cards. When this assumption fails, then the + specific MSP routing must be added to the card table. + For now this is sufficient. */ + switch (input) { + case TVAUDIO_INPUT_RADIO: + /* Some boards need the msp do to the radio demod */ + if (btv->radio_uses_msp_demodulator) { + in = MSP_INPUT_DEFAULT; + break; + } + in = MSP_INPUT(MSP_IN_SCART2, MSP_IN_TUNER1, + MSP_DSP_IN_SCART, MSP_DSP_IN_SCART); + break; + case TVAUDIO_INPUT_EXTERN: + in = MSP_INPUT(MSP_IN_SCART1, MSP_IN_TUNER1, + MSP_DSP_IN_SCART, MSP_DSP_IN_SCART); + break; + case TVAUDIO_INPUT_INTERN: + /* Yes, this is the same input as for RADIO. I doubt + if this is ever used. The only board with an INTERN + input is the BTTV_BOARD_AVERMEDIA98. I wonder how + that was tested. My guess is that the whole INTERN + input does not work. */ + in = MSP_INPUT(MSP_IN_SCART2, MSP_IN_TUNER1, + MSP_DSP_IN_SCART, MSP_DSP_IN_SCART); + break; + case TVAUDIO_INPUT_TUNER: + default: + /* This is the only card that uses TUNER2, and afaik, + is the only difference between the VOODOOTV_FM + and VOODOOTV_200 */ + if (btv->c.type == BTTV_BOARD_VOODOOTV_200) + in = MSP_INPUT(MSP_IN_SCART1, MSP_IN_TUNER2, \ + MSP_DSP_IN_TUNER, MSP_DSP_IN_TUNER); + else + in = MSP_INPUT_DEFAULT; + break; + } + v4l2_subdev_call(btv->sd_msp34xx, audio, s_routing, + in, MSP_OUTPUT_DEFAULT, 0); + } + if (btv->sd_tvaudio) { + v4l2_subdev_call(btv->sd_tvaudio, audio, s_routing, + input, 0, 0); + } + return 0; +} + +static void +bttv_crop_calc_limits(struct bttv_crop *c) +{ + /* Scale factor min. 1:1, max. 16:1. Min. image size + 48 x 32. Scaled width must be a multiple of 4. */ + + if (1) { + /* For bug compatibility with VIDIOCGCAP and image + size checks in earlier driver versions. */ + c->min_scaled_width = 48; + c->min_scaled_height = 32; + } else { + c->min_scaled_width = + (max_t(unsigned int, 48, c->rect.width >> 4) + 3) & ~3; + c->min_scaled_height = + max_t(unsigned int, 32, c->rect.height >> 4); + } + + c->max_scaled_width = c->rect.width & ~3; + c->max_scaled_height = c->rect.height; +} + +static void +bttv_crop_reset(struct bttv_crop *c, unsigned int norm) +{ + c->rect = bttv_tvnorms[norm].cropcap.defrect; + bttv_crop_calc_limits(c); +} + +/* Call with btv->lock down. */ +static int +set_tvnorm(struct bttv *btv, unsigned int norm) +{ + const struct bttv_tvnorm *tvnorm; + v4l2_std_id id; + + WARN_ON(norm >= BTTV_TVNORMS); + WARN_ON(btv->tvnorm >= BTTV_TVNORMS); + + tvnorm = &bttv_tvnorms[norm]; + + if (memcmp(&bttv_tvnorms[btv->tvnorm].cropcap, &tvnorm->cropcap, + sizeof (tvnorm->cropcap))) { + bttv_crop_reset(&btv->crop[0], norm); + btv->crop[1] = btv->crop[0]; /* current = default */ + + if (0 == (btv->resources & VIDEO_RESOURCES)) { + btv->crop_start = tvnorm->cropcap.bounds.top + + tvnorm->cropcap.bounds.height; + } + } + + btv->tvnorm = norm; + + btwrite(tvnorm->adelay, BT848_ADELAY); + btwrite(tvnorm->bdelay, BT848_BDELAY); + btaor(tvnorm->iform,~(BT848_IFORM_NORM|BT848_IFORM_XTBOTH), + BT848_IFORM); + btwrite(tvnorm->vbipack, BT848_VBI_PACK_SIZE); + btwrite(1, BT848_VBI_PACK_DEL); + bt848A_set_timing(btv); + + switch (btv->c.type) { + case BTTV_BOARD_VOODOOTV_FM: + case BTTV_BOARD_VOODOOTV_200: + bttv_tda9880_setnorm(btv, gpio_read()); + break; + } + id = tvnorm->v4l2_id; + bttv_call_all(btv, video, s_std, id); + + return 0; +} + +/* Call with btv->lock down. */ +static void +set_input(struct bttv *btv, unsigned int input, unsigned int norm) +{ + unsigned long flags; + + btv->input = input; + if (irq_iswitch) { + spin_lock_irqsave(&btv->s_lock,flags); + if (btv->curr.frame_irq) { + /* active capture -> delayed input switch */ + btv->new_input = input; + } else { + video_mux(btv,input); + } + spin_unlock_irqrestore(&btv->s_lock,flags); + } else { + video_mux(btv,input); + } + btv->audio_input = (btv->tuner_type != TUNER_ABSENT && input == 0) ? + TVAUDIO_INPUT_TUNER : TVAUDIO_INPUT_EXTERN; + audio_input(btv, btv->audio_input); + set_tvnorm(btv, norm); +} + +void init_irqreg(struct bttv *btv) +{ + /* clear status */ + btwrite(0xfffffUL, BT848_INT_STAT); + + if (bttv_tvcards[btv->c.type].no_video) { + /* i2c only */ + btwrite(BT848_INT_I2CDONE, + BT848_INT_MASK); + } else { + /* full video */ + btwrite((btv->triton1) | + (btv->gpioirq ? BT848_INT_GPINT : 0) | + BT848_INT_SCERR | + (fdsr ? BT848_INT_FDSR : 0) | + BT848_INT_RISCI | BT848_INT_OCERR | + BT848_INT_FMTCHG|BT848_INT_HLOCK| + BT848_INT_I2CDONE, + BT848_INT_MASK); + } +} + +static void init_bt848(struct bttv *btv) +{ + if (bttv_tvcards[btv->c.type].no_video) { + /* very basic init only */ + init_irqreg(btv); + return; + } + + btwrite(0x00, BT848_CAP_CTL); + btwrite(BT848_COLOR_CTL_GAMMA, BT848_COLOR_CTL); + btwrite(BT848_IFORM_XTAUTO | BT848_IFORM_AUTO, BT848_IFORM); + + /* set planar and packed mode trigger points and */ + /* set rising edge of inverted GPINTR pin as irq trigger */ + btwrite(BT848_GPIO_DMA_CTL_PKTP_32| + BT848_GPIO_DMA_CTL_PLTP1_16| + BT848_GPIO_DMA_CTL_PLTP23_16| + BT848_GPIO_DMA_CTL_GPINTC| + BT848_GPIO_DMA_CTL_GPINTI, + BT848_GPIO_DMA_CTL); + + btwrite(0x20, BT848_E_VSCALE_HI); + btwrite(0x20, BT848_O_VSCALE_HI); + + v4l2_ctrl_handler_setup(&btv->ctrl_handler); + + /* interrupt */ + init_irqreg(btv); +} + +static void bttv_reinit_bt848(struct bttv *btv) +{ + unsigned long flags; + + if (bttv_verbose) + pr_info("%d: reset, reinitialize\n", btv->c.nr); + spin_lock_irqsave(&btv->s_lock,flags); + btv->errors=0; + bttv_set_dma(btv,0); + spin_unlock_irqrestore(&btv->s_lock,flags); + + init_bt848(btv); + btv->pll.pll_current = -1; + set_input(btv, btv->input, btv->tvnorm); +} + +static int bttv_s_ctrl(struct v4l2_ctrl *c) +{ + struct bttv *btv = container_of(c->handler, struct bttv, ctrl_handler); + int val; + + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + bt848_bright(btv, c->val); + break; + case V4L2_CID_HUE: + bt848_hue(btv, c->val); + break; + case V4L2_CID_CONTRAST: + bt848_contrast(btv, c->val); + break; + case V4L2_CID_SATURATION: + bt848_sat(btv, c->val); + break; + case V4L2_CID_COLOR_KILLER: + if (c->val) { + btor(BT848_SCLOOP_CKILL, BT848_E_SCLOOP); + btor(BT848_SCLOOP_CKILL, BT848_O_SCLOOP); + } else { + btand(~BT848_SCLOOP_CKILL, BT848_E_SCLOOP); + btand(~BT848_SCLOOP_CKILL, BT848_O_SCLOOP); + } + break; + case V4L2_CID_AUDIO_MUTE: + audio_mute(btv, c->val); + btv->mute = c->val; + break; + case V4L2_CID_AUDIO_VOLUME: + btv->volume_gpio(btv, c->val); + break; + + case V4L2_CID_CHROMA_AGC: + val = c->val ? BT848_SCLOOP_CAGC : 0; + btwrite(val, BT848_E_SCLOOP); + btwrite(val, BT848_O_SCLOOP); + break; + case V4L2_CID_PRIVATE_COMBFILTER: + btv->opt_combfilter = c->val; + break; + case V4L2_CID_PRIVATE_LUMAFILTER: + if (c->val) { + btand(~BT848_CONTROL_LDEC, BT848_E_CONTROL); + btand(~BT848_CONTROL_LDEC, BT848_O_CONTROL); + } else { + btor(BT848_CONTROL_LDEC, BT848_E_CONTROL); + btor(BT848_CONTROL_LDEC, BT848_O_CONTROL); + } + break; + case V4L2_CID_PRIVATE_AUTOMUTE: + btv->opt_automute = c->val; + break; + case V4L2_CID_PRIVATE_AGC_CRUSH: + btwrite(BT848_ADC_RESERVED | + (c->val ? BT848_ADC_CRUSH : 0), + BT848_ADC); + break; + case V4L2_CID_PRIVATE_VCR_HACK: + btv->opt_vcr_hack = c->val; + break; + case V4L2_CID_PRIVATE_WHITECRUSH_UPPER: + btwrite(c->val, BT848_WC_UP); + break; + case V4L2_CID_PRIVATE_WHITECRUSH_LOWER: + btwrite(c->val, BT848_WC_DOWN); + break; + case V4L2_CID_PRIVATE_UV_RATIO: + btv->opt_uv_ratio = c->val; + bt848_sat(btv, btv->saturation); + break; + case V4L2_CID_PRIVATE_FULL_LUMA_RANGE: + btaor((c->val << 7), ~BT848_OFORM_RANGE, BT848_OFORM); + break; + case V4L2_CID_PRIVATE_CORING: + btaor((c->val << 5), ~BT848_OFORM_CORE32, BT848_OFORM); + break; + default: + return -EINVAL; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops bttv_ctrl_ops = { + .s_ctrl = bttv_s_ctrl, +}; + +static struct v4l2_ctrl_config bttv_ctrl_combfilter = { + .ops = &bttv_ctrl_ops, + .id = V4L2_CID_PRIVATE_COMBFILTER, + .name = "Comb Filter", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, +}; + +static struct v4l2_ctrl_config bttv_ctrl_automute = { + .ops = &bttv_ctrl_ops, + .id = V4L2_CID_PRIVATE_AUTOMUTE, + .name = "Auto Mute", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, +}; + +static struct v4l2_ctrl_config bttv_ctrl_lumafilter = { + .ops = &bttv_ctrl_ops, + .id = V4L2_CID_PRIVATE_LUMAFILTER, + .name = "Luma Decimation Filter", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, +}; + +static struct v4l2_ctrl_config bttv_ctrl_agc_crush = { + .ops = &bttv_ctrl_ops, + .id = V4L2_CID_PRIVATE_AGC_CRUSH, + .name = "AGC Crush", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, +}; + +static struct v4l2_ctrl_config bttv_ctrl_vcr_hack = { + .ops = &bttv_ctrl_ops, + .id = V4L2_CID_PRIVATE_VCR_HACK, + .name = "VCR Hack", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, +}; + +static struct v4l2_ctrl_config bttv_ctrl_whitecrush_lower = { + .ops = &bttv_ctrl_ops, + .id = V4L2_CID_PRIVATE_WHITECRUSH_LOWER, + .name = "Whitecrush Lower", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 255, + .step = 1, + .def = 0x7f, +}; + +static struct v4l2_ctrl_config bttv_ctrl_whitecrush_upper = { + .ops = &bttv_ctrl_ops, + .id = V4L2_CID_PRIVATE_WHITECRUSH_UPPER, + .name = "Whitecrush Upper", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 255, + .step = 1, + .def = 0xcf, +}; + +static struct v4l2_ctrl_config bttv_ctrl_uv_ratio = { + .ops = &bttv_ctrl_ops, + .id = V4L2_CID_PRIVATE_UV_RATIO, + .name = "UV Ratio", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 100, + .step = 1, + .def = 50, +}; + +static struct v4l2_ctrl_config bttv_ctrl_full_luma = { + .ops = &bttv_ctrl_ops, + .id = V4L2_CID_PRIVATE_FULL_LUMA_RANGE, + .name = "Full Luma Range", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, +}; + +static struct v4l2_ctrl_config bttv_ctrl_coring = { + .ops = &bttv_ctrl_ops, + .id = V4L2_CID_PRIVATE_CORING, + .name = "Coring", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 3, + .step = 1, +}; + + +/* ----------------------------------------------------------------------- */ + +void bttv_gpio_tracking(struct bttv *btv, char *comment) +{ + unsigned int outbits, data; + outbits = btread(BT848_GPIO_OUT_EN); + data = btread(BT848_GPIO_DATA); + pr_debug("%d: gpio: en=%08x, out=%08x in=%08x [%s]\n", + btv->c.nr, outbits, data & outbits, data & ~outbits, comment); +} + +static const struct bttv_format* +format_by_fourcc(int fourcc) +{ + unsigned int i; + + for (i = 0; i < FORMATS; i++) { + if (-1 == formats[i].fourcc) + continue; + if (formats[i].fourcc == fourcc) + return formats+i; + } + return NULL; +} + +/* ----------------------------------------------------------------------- */ +/* video4linux (1) interface */ + +static int queue_setup(struct vb2_queue *q, unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct bttv *btv = vb2_get_drv_priv(q); + unsigned int size = btv->fmt->depth * btv->width * btv->height >> 3; + + if (*num_planes) + return sizes[0] < size ? -EINVAL : 0; + *num_planes = 1; + sizes[0] = size; + + return 0; +} + +static void buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vb2_queue *vq = vb->vb2_queue; + struct bttv *btv = vb2_get_drv_priv(vq); + struct bttv_buffer *buf = container_of(vbuf, struct bttv_buffer, vbuf); + unsigned long flags; + + spin_lock_irqsave(&btv->s_lock, flags); + if (list_empty(&btv->capture)) { + btv->loop_irq = BT848_RISC_VIDEO; + if (vb2_is_streaming(&btv->vbiq)) + btv->loop_irq |= BT848_RISC_VBI; + bttv_set_dma(btv, BT848_CAP_CTL_CAPTURE_ODD | + BT848_CAP_CTL_CAPTURE_EVEN); + } + list_add_tail(&buf->list, &btv->capture); + spin_unlock_irqrestore(&btv->s_lock, flags); +} + +static int buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct bttv *btv = vb2_get_drv_priv(vq); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct bttv_buffer *buf = container_of(vbuf, struct bttv_buffer, vbuf); + unsigned int size = (btv->fmt->depth * btv->width * btv->height) >> 3; + + if (vb2_plane_size(vb, 0) < size) + return -EINVAL; + vb2_set_plane_payload(vb, 0, size); + + if (btv->field != V4L2_FIELD_ALTERNATE) { + buf->vbuf.field = btv->field; + } else if (btv->field_last == V4L2_FIELD_TOP) { + buf->vbuf.field = V4L2_FIELD_BOTTOM; + btv->field_last = V4L2_FIELD_BOTTOM; + } else { + buf->vbuf.field = V4L2_FIELD_TOP; + btv->field_last = V4L2_FIELD_TOP; + } + + /* Allocate memory for risc struct and create the risc program. */ + return bttv_buffer_risc(btv, buf); +} + +static void buf_cleanup(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct bttv *btv = vb2_get_drv_priv(vq); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct bttv_buffer *buf = container_of(vbuf, struct bttv_buffer, vbuf); + + btcx_riscmem_free(btv->c.pci, &buf->top); + btcx_riscmem_free(btv->c.pci, &buf->bottom); +} + +static int start_streaming(struct vb2_queue *q, unsigned int count) +{ + int seqnr = 0; + struct bttv_buffer *buf; + struct bttv *btv = vb2_get_drv_priv(q); + + if (!check_alloc_btres_lock(btv, RESOURCE_VIDEO_STREAM)) { + if (btv->field_count) + seqnr++; + while (!list_empty(&btv->capture)) { + buf = list_entry(btv->capture.next, + struct bttv_buffer, list); + list_del(&buf->list); + buf->vbuf.sequence = (btv->field_count >> 1) + seqnr++; + vb2_buffer_done(&buf->vbuf.vb2_buf, + VB2_BUF_STATE_QUEUED); + } + return -EBUSY; + } + if (!vb2_is_streaming(&btv->vbiq)) { + init_irqreg(btv); + btv->field_count = 0; + } + btv->framedrop = 0; + + return 0; +} + +static void stop_streaming(struct vb2_queue *q) +{ + unsigned long flags; + struct bttv *btv = vb2_get_drv_priv(q); + + vb2_wait_for_all_buffers(q); + spin_lock_irqsave(&btv->s_lock, flags); + free_btres_lock(btv, RESOURCE_VIDEO_STREAM); + if (!vb2_is_streaming(&btv->vbiq)) { + /* stop field counter */ + btand(~BT848_INT_VSYNC, BT848_INT_MASK); + } + spin_unlock_irqrestore(&btv->s_lock, flags); +} + +static const struct vb2_ops bttv_video_qops = { + .queue_setup = queue_setup, + .buf_queue = buf_queue, + .buf_prepare = buf_prepare, + .buf_cleanup = buf_cleanup, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, +}; + +static void radio_enable(struct bttv *btv) +{ + /* Switch to the radio tuner */ + if (!btv->has_radio_tuner) { + btv->has_radio_tuner = 1; + bttv_call_all(btv, tuner, s_radio); + btv->audio_input = TVAUDIO_INPUT_RADIO; + audio_input(btv, btv->audio_input); + } +} + +static int bttv_s_std(struct file *file, void *priv, v4l2_std_id id) +{ + struct bttv *btv = video_drvdata(file); + unsigned int i; + + for (i = 0; i < BTTV_TVNORMS; i++) + if (id & bttv_tvnorms[i].v4l2_id) + break; + if (i == BTTV_TVNORMS) + return -EINVAL; + btv->std = id; + set_tvnorm(btv, i); + return 0; +} + +static int bttv_g_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct bttv *btv = video_drvdata(file); + + *id = btv->std; + return 0; +} + +static int bttv_querystd(struct file *file, void *priv, v4l2_std_id *id) +{ + struct bttv *btv = video_drvdata(file); + + if (btread(BT848_DSTATUS) & BT848_DSTATUS_NUML) + *id &= V4L2_STD_625_50; + else + *id &= V4L2_STD_525_60; + return 0; +} + +static int bttv_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct bttv *btv = video_drvdata(file); + + if (i->index >= bttv_tvcards[btv->c.type].video_inputs) + return -EINVAL; + + i->type = V4L2_INPUT_TYPE_CAMERA; + i->audioset = 0; + + if (btv->tuner_type != TUNER_ABSENT && i->index == 0) { + sprintf(i->name, "Television"); + i->type = V4L2_INPUT_TYPE_TUNER; + i->tuner = 0; + } else if (i->index == btv->svhs) { + sprintf(i->name, "S-Video"); + } else { + sprintf(i->name, "Composite%d", i->index); + } + + if (i->index == btv->input) { + __u32 dstatus = btread(BT848_DSTATUS); + if (0 == (dstatus & BT848_DSTATUS_PRES)) + i->status |= V4L2_IN_ST_NO_SIGNAL; + if (0 == (dstatus & BT848_DSTATUS_HLOC)) + i->status |= V4L2_IN_ST_NO_H_LOCK; + } + + i->std = BTTV_NORMS; + return 0; +} + +static int bttv_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct bttv *btv = video_drvdata(file); + + *i = btv->input; + + return 0; +} + +static int bttv_s_input(struct file *file, void *priv, unsigned int i) +{ + struct bttv *btv = video_drvdata(file); + + if (i >= bttv_tvcards[btv->c.type].video_inputs) + return -EINVAL; + + set_input(btv, i, btv->tvnorm); + return 0; +} + +static int bttv_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *t) +{ + struct bttv *btv = video_drvdata(file); + + if (t->index) + return -EINVAL; + + bttv_call_all(btv, tuner, s_tuner, t); + + if (btv->audio_mode_gpio) { + struct v4l2_tuner copy = *t; + + btv->audio_mode_gpio(btv, ©, 1); + } + return 0; +} + +static int bttv_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct bttv *btv = video_drvdata(file); + + if (f->tuner) + return -EINVAL; + + if (f->type == V4L2_TUNER_RADIO) + radio_enable(btv); + f->frequency = f->type == V4L2_TUNER_RADIO ? + btv->radio_freq : btv->tv_freq; + + return 0; +} + +static void bttv_set_frequency(struct bttv *btv, const struct v4l2_frequency *f) +{ + struct v4l2_frequency new_freq = *f; + + bttv_call_all(btv, tuner, s_frequency, f); + /* s_frequency may clamp the frequency, so get the actual + frequency before assigning radio/tv_freq. */ + bttv_call_all(btv, tuner, g_frequency, &new_freq); + if (new_freq.type == V4L2_TUNER_RADIO) { + radio_enable(btv); + btv->radio_freq = new_freq.frequency; + if (btv->has_tea575x) { + btv->tea.freq = btv->radio_freq; + snd_tea575x_set_freq(&btv->tea); + } + } else { + btv->tv_freq = new_freq.frequency; + } +} + +static int bttv_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct bttv *btv = video_drvdata(file); + + if (f->tuner) + return -EINVAL; + + bttv_set_frequency(btv, f); + return 0; +} + +static int bttv_log_status(struct file *file, void *priv) +{ + struct video_device *vdev = video_devdata(file); + struct bttv *btv = video_drvdata(file); + + v4l2_ctrl_handler_log_status(vdev->ctrl_handler, btv->c.v4l2_dev.name); + bttv_call_all(btv, core, log_status); + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int bttv_g_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct bttv *btv = video_drvdata(file); + + /* bt848 has a 12-bit register space */ + reg->reg &= 0xfff; + reg->val = btread(reg->reg); + reg->size = 1; + + return 0; +} + +static int bttv_s_register(struct file *file, void *priv, + const struct v4l2_dbg_register *reg) +{ + struct bttv *btv = video_drvdata(file); + + /* bt848 has a 12-bit register space */ + btwrite(reg->val, reg->reg & 0xfff); + + return 0; +} +#endif + +/* Given cropping boundaries b and the scaled width and height of a + single field or frame, which must not exceed hardware limits, this + function adjusts the cropping parameters c. */ +static void +bttv_crop_adjust (struct bttv_crop * c, + const struct v4l2_rect * b, + __s32 width, + __s32 height, + enum v4l2_field field) +{ + __s32 frame_height = height << !V4L2_FIELD_HAS_BOTH(field); + __s32 max_left; + __s32 max_top; + + if (width < c->min_scaled_width) { + /* Max. hor. scale factor 16:1. */ + c->rect.width = width * 16; + } else if (width > c->max_scaled_width) { + /* Min. hor. scale factor 1:1. */ + c->rect.width = width; + + max_left = b->left + b->width - width; + max_left = min(max_left, (__s32) MAX_HDELAY); + if (c->rect.left > max_left) + c->rect.left = max_left; + } + + if (height < c->min_scaled_height) { + /* Max. vert. scale factor 16:1, single fields 8:1. */ + c->rect.height = height * 16; + } else if (frame_height > c->max_scaled_height) { + /* Min. vert. scale factor 1:1. + Top and height count field lines times two. */ + c->rect.height = (frame_height + 1) & ~1; + + max_top = b->top + b->height - c->rect.height; + if (c->rect.top > max_top) + c->rect.top = max_top; + } + + bttv_crop_calc_limits(c); +} + +/* Returns an error if scaling to a frame or single field with the given + width and height is not possible with the current cropping parameters + and width aligned according to width_mask. If adjust_size is TRUE the + function may adjust the width and/or height instead, rounding width + to (width + width_bias) & width_mask. If adjust_crop is TRUE it may + also adjust the current cropping parameters to get closer to the + desired image size. */ +static int +limit_scaled_size_lock(struct bttv *btv, __s32 *width, __s32 *height, + enum v4l2_field field, unsigned int width_mask, + unsigned int width_bias, int adjust_size, + int adjust_crop) +{ + const struct v4l2_rect *b; + struct bttv_crop *c; + __s32 min_width; + __s32 min_height; + __s32 max_width; + __s32 max_height; + int rc; + + WARN_ON((int)width_mask >= 0 || + width_bias >= (unsigned int)(-width_mask)); + + /* Make sure tvnorm, vbi_end and the current cropping parameters + remain consistent until we're done. */ + + b = &bttv_tvnorms[btv->tvnorm].cropcap.bounds; + + /* Do crop - use current, don't - use default parameters. */ + c = &btv->crop[!!btv->do_crop]; + + if (btv->do_crop + && adjust_size + && adjust_crop + && !locked_btres(btv, VIDEO_RESOURCES)) { + min_width = 48; + min_height = 32; + + /* We cannot scale up. When the scaled image is larger + than crop.rect we adjust the crop.rect as required + by the V4L2 spec, hence cropcap.bounds are our limit. */ + max_width = min_t(unsigned int, b->width, MAX_HACTIVE); + max_height = b->height; + + /* We cannot capture the same line as video and VBI data. + Note btv->vbi_end is really a minimum, see + bttv_vbi_try_fmt(). */ + if (btv->vbi_end > b->top) { + max_height -= btv->vbi_end - b->top; + rc = -EBUSY; + if (min_height > max_height) + goto fail; + } + } else { + rc = -EBUSY; + if (btv->vbi_end > c->rect.top) + goto fail; + + min_width = c->min_scaled_width; + min_height = c->min_scaled_height; + max_width = c->max_scaled_width; + max_height = c->max_scaled_height; + + adjust_crop = 0; + } + + min_width = (min_width - width_mask - 1) & width_mask; + max_width = max_width & width_mask; + + /* Max. scale factor is 16:1 for frames, 8:1 for fields. */ + /* Min. scale factor is 1:1. */ + max_height >>= !V4L2_FIELD_HAS_BOTH(field); + + if (adjust_size) { + *width = clamp(*width, min_width, max_width); + *height = clamp(*height, min_height, max_height); + + /* Round after clamping to avoid overflow. */ + *width = (*width + width_bias) & width_mask; + + if (adjust_crop) { + bttv_crop_adjust(c, b, *width, *height, field); + + if (btv->vbi_end > c->rect.top) { + /* Move the crop window out of the way. */ + c->rect.top = btv->vbi_end; + } + } + } else { + rc = -EINVAL; + if (*width < min_width || + *height < min_height || + *width > max_width || + *height > max_height || + 0 != (*width & ~width_mask)) + goto fail; + } + + rc = 0; /* success */ + + fail: + + return rc; +} + +static int bttv_switch_type(struct bttv *btv, enum v4l2_buf_type type) +{ + int res; + struct vb2_queue *q; + + switch (type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + q = &btv->capq; + res = RESOURCE_VIDEO_STREAM; + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + q = &btv->vbiq; + res = RESOURCE_VBI; + break; + default: + WARN_ON(1); + return -EINVAL; + } + + if (check_btres(btv, res)) + return -EBUSY; + if (vb2_is_busy(q)) + return -EBUSY; + btv->type = type; + + return 0; +} + +static void +pix_format_set_size (struct v4l2_pix_format * f, + const struct bttv_format * fmt, + unsigned int width, + unsigned int height) +{ + f->width = width; + f->height = height; + + if (fmt->flags & FORMAT_FLAGS_PLANAR) { + f->bytesperline = width; /* Y plane */ + f->sizeimage = (width * height * fmt->depth) >> 3; + } else { + f->bytesperline = (width * fmt->depth) >> 3; + f->sizeimage = height * f->bytesperline; + } +} + +static int bttv_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct bttv *btv = video_drvdata(file); + + pix_format_set_size(&f->fmt.pix, btv->fmt, btv->width, btv->height); + f->fmt.pix.field = btv->field; + f->fmt.pix.pixelformat = btv->fmt->fourcc; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static void bttv_get_width_mask_vid_cap(const struct bttv_format *fmt, + unsigned int *width_mask, + unsigned int *width_bias) +{ + if (fmt->flags & FORMAT_FLAGS_PLANAR) { + *width_mask = ~15; /* width must be a multiple of 16 pixels */ + *width_bias = 8; /* nearest */ + } else { + *width_mask = ~3; /* width must be a multiple of 4 pixels */ + *width_bias = 2; /* nearest */ + } +} + +static int bttv_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + const struct bttv_format *fmt; + struct bttv *btv = video_drvdata(file); + enum v4l2_field field; + __s32 width, height; + __s32 height2; + unsigned int width_mask, width_bias; + int rc; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_ALTERNATE: + case V4L2_FIELD_INTERLACED: + break; + case V4L2_FIELD_SEQ_BT: + case V4L2_FIELD_SEQ_TB: + if (!(fmt->flags & FORMAT_FLAGS_PLANAR)) { + field = V4L2_FIELD_SEQ_TB; + break; + } + fallthrough; + default: /* FIELD_ANY case */ + height2 = btv->crop[!!btv->do_crop].rect.height >> 1; + field = (f->fmt.pix.height > height2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + break; + } + + width = f->fmt.pix.width; + height = f->fmt.pix.height; + + bttv_get_width_mask_vid_cap(fmt, &width_mask, &width_bias); + rc = limit_scaled_size_lock(btv, &width, &height, field, width_mask, + width_bias, 1, 0); + if (0 != rc) + return rc; + + /* update data for the application */ + f->fmt.pix.field = field; + pix_format_set_size(&f->fmt.pix, fmt, width, height); + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int bttv_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + int retval; + const struct bttv_format *fmt; + struct bttv *btv = video_drvdata(file); + __s32 width, height; + unsigned int width_mask, width_bias; + enum v4l2_field field; + + retval = bttv_switch_type(btv, f->type); + if (0 != retval) + return retval; + + retval = bttv_try_fmt_vid_cap(file, priv, f); + if (0 != retval) + return retval; + + width = f->fmt.pix.width; + height = f->fmt.pix.height; + field = f->fmt.pix.field; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + bttv_get_width_mask_vid_cap(fmt, &width_mask, &width_bias); + retval = limit_scaled_size_lock(btv, &width, &height, f->fmt.pix.field, + width_mask, width_bias, 1, 1); + if (0 != retval) + return retval; + + f->fmt.pix.field = field; + + /* update our state information */ + btv->fmt = fmt; + btv->width = f->fmt.pix.width; + btv->height = f->fmt.pix.height; + btv->field = f->fmt.pix.field; + /* + * When field is V4L2_FIELD_ALTERNATE, buffers will be either + * V4L2_FIELD_TOP or V4L2_FIELD_BOTTOM depending on the value of + * field_last. Initialize field_last to V4L2_FIELD_BOTTOM so that + * streaming starts with a V4L2_FIELD_TOP buffer. + */ + btv->field_last = V4L2_FIELD_BOTTOM; + + return 0; +} + +static int bttv_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct bttv *btv = video_drvdata(file); + + if (0 == v4l2) + return -EINVAL; + + strscpy(cap->driver, "bttv", sizeof(cap->driver)); + strscpy(cap->card, btv->video_dev.name, sizeof(cap->card)); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING | V4L2_CAP_DEVICE_CAPS; + if (video_is_registered(&btv->vbi_dev)) + cap->capabilities |= V4L2_CAP_VBI_CAPTURE; + if (video_is_registered(&btv->radio_dev)) { + cap->capabilities |= V4L2_CAP_RADIO; + if (btv->has_tea575x) + cap->capabilities |= V4L2_CAP_HW_FREQ_SEEK; + } + + /* + * No need to lock here: those vars are initialized during board + * probe and remains untouched during the rest of the driver lifecycle + */ + if (btv->has_saa6588) + cap->capabilities |= V4L2_CAP_RDS_CAPTURE; + if (btv->tuner_type != TUNER_ABSENT) + cap->capabilities |= V4L2_CAP_TUNER; + return 0; +} + +static int bttv_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + int index = -1, i; + + for (i = 0; i < FORMATS; i++) { + if (formats[i].fourcc != -1) + index++; + if ((unsigned int)index == f->index) + break; + } + if (FORMATS == i) + return -EINVAL; + + f->pixelformat = formats[i].fourcc; + + return 0; +} + +static int bttv_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *parm) +{ + struct bttv *btv = video_drvdata(file); + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + parm->parm.capture.readbuffers = gbuffers; + v4l2_video_std_frame_period(bttv_tvnorms[btv->tvnorm].v4l2_id, + &parm->parm.capture.timeperframe); + + return 0; +} + +static int bttv_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct bttv *btv = video_drvdata(file); + + if (0 != t->index) + return -EINVAL; + + t->rxsubchans = V4L2_TUNER_SUB_MONO; + t->capability = V4L2_TUNER_CAP_NORM; + bttv_call_all(btv, tuner, g_tuner, t); + strscpy(t->name, "Television", sizeof(t->name)); + t->type = V4L2_TUNER_ANALOG_TV; + if (btread(BT848_DSTATUS)&BT848_DSTATUS_HLOC) + t->signal = 0xffff; + + if (btv->audio_mode_gpio) + btv->audio_mode_gpio(btv, t, 0); + + return 0; +} + +static int bttv_g_pixelaspect(struct file *file, void *priv, + int type, struct v4l2_fract *f) +{ + struct bttv *btv = video_drvdata(file); + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + /* defrect and bounds are set via g_selection */ + *f = bttv_tvnorms[btv->tvnorm].cropcap.pixelaspect; + return 0; +} + +static int bttv_g_selection(struct file *file, void *priv, struct v4l2_selection *sel) +{ + struct bttv *btv = video_drvdata(file); + + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = btv->crop[!!btv->do_crop].rect; + break; + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r = bttv_tvnorms[btv->tvnorm].cropcap.defrect; + break; + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r = bttv_tvnorms[btv->tvnorm].cropcap.bounds; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int bttv_s_selection(struct file *file, void *priv, struct v4l2_selection *sel) +{ + struct bttv *btv = video_drvdata(file); + const struct v4l2_rect *b; + int retval; + struct bttv_crop c; + __s32 b_left; + __s32 b_top; + __s32 b_right; + __s32 b_bottom; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + /* Make sure tvnorm, vbi_end and the current cropping + parameters remain consistent until we're done. Note + read() may change vbi_end in check_alloc_btres_lock(). */ + retval = -EBUSY; + + if (locked_btres(btv, VIDEO_RESOURCES)) + return retval; + + b = &bttv_tvnorms[btv->tvnorm].cropcap.bounds; + + b_left = b->left; + b_right = b_left + b->width; + b_bottom = b->top + b->height; + + b_top = max(b->top, btv->vbi_end); + if (b_top + 32 >= b_bottom) { + return retval; + } + + /* Min. scaled size 48 x 32. */ + c.rect.left = clamp_t(s32, sel->r.left, b_left, b_right - 48); + c.rect.left = min(c.rect.left, (__s32) MAX_HDELAY); + + c.rect.width = clamp_t(s32, sel->r.width, + 48, b_right - c.rect.left); + + c.rect.top = clamp_t(s32, sel->r.top, b_top, b_bottom - 32); + /* Top and height must be a multiple of two. */ + c.rect.top = (c.rect.top + 1) & ~1; + + c.rect.height = clamp_t(s32, sel->r.height, + 32, b_bottom - c.rect.top); + c.rect.height = (c.rect.height + 1) & ~1; + + bttv_crop_calc_limits(&c); + + sel->r = c.rect; + + btv->crop[1] = c; + + btv->do_crop = 1; + + if (btv->width < c.min_scaled_width) + btv->width = c.min_scaled_width; + else if (btv->width > c.max_scaled_width) + btv->width = c.max_scaled_width; + + if (btv->height < c.min_scaled_height) + btv->height = c.min_scaled_height; + else if (btv->height > c.max_scaled_height) + btv->height = c.max_scaled_height; + + return 0; +} + +static const struct v4l2_file_operations bttv_fops = +{ + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .unlocked_ioctl = video_ioctl2, + .read = vb2_fop_read, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + +static const struct v4l2_ioctl_ops bttv_ioctl_ops = { + .vidioc_querycap = bttv_querycap, + .vidioc_enum_fmt_vid_cap = bttv_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = bttv_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = bttv_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = bttv_s_fmt_vid_cap, + .vidioc_g_fmt_vbi_cap = bttv_g_fmt_vbi_cap, + .vidioc_try_fmt_vbi_cap = bttv_try_fmt_vbi_cap, + .vidioc_s_fmt_vbi_cap = bttv_s_fmt_vbi_cap, + .vidioc_g_pixelaspect = bttv_g_pixelaspect, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_s_std = bttv_s_std, + .vidioc_g_std = bttv_g_std, + .vidioc_enum_input = bttv_enum_input, + .vidioc_g_input = bttv_g_input, + .vidioc_s_input = bttv_s_input, + .vidioc_g_tuner = bttv_g_tuner, + .vidioc_s_tuner = bttv_s_tuner, + .vidioc_g_selection = bttv_g_selection, + .vidioc_s_selection = bttv_s_selection, + .vidioc_g_parm = bttv_g_parm, + .vidioc_g_frequency = bttv_g_frequency, + .vidioc_s_frequency = bttv_s_frequency, + .vidioc_log_status = bttv_log_status, + .vidioc_querystd = bttv_querystd, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = bttv_g_register, + .vidioc_s_register = bttv_s_register, +#endif +}; + +static struct video_device bttv_video_template = { + .fops = &bttv_fops, + .ioctl_ops = &bttv_ioctl_ops, + .tvnorms = BTTV_NORMS, +}; + +/* ----------------------------------------------------------------------- */ +/* radio interface */ + +static int radio_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct bttv *btv = video_drvdata(file); + int ret = v4l2_fh_open(file); + + if (ret) + return ret; + + dprintk("open dev=%s\n", video_device_node_name(vdev)); + dprintk("%d: open called (radio)\n", btv->c.nr); + + btv->radio_user++; + audio_mute(btv, btv->mute); + + return 0; +} + +static int radio_release(struct file *file) +{ + struct bttv *btv = video_drvdata(file); + struct saa6588_command cmd; + + btv->radio_user--; + + bttv_call_all(btv, core, command, SAA6588_CMD_CLOSE, &cmd); + + if (btv->radio_user == 0) + btv->has_radio_tuner = 0; + + v4l2_fh_release(file); + + return 0; +} + +static int radio_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t) +{ + struct bttv *btv = video_drvdata(file); + + if (0 != t->index) + return -EINVAL; + strscpy(t->name, "Radio", sizeof(t->name)); + t->type = V4L2_TUNER_RADIO; + radio_enable(btv); + + bttv_call_all(btv, tuner, g_tuner, t); + + if (btv->audio_mode_gpio) + btv->audio_mode_gpio(btv, t, 0); + + if (btv->has_tea575x) + return snd_tea575x_g_tuner(&btv->tea, t); + + return 0; +} + +static int radio_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *t) +{ + struct bttv *btv = video_drvdata(file); + + if (0 != t->index) + return -EINVAL; + + radio_enable(btv); + bttv_call_all(btv, tuner, s_tuner, t); + return 0; +} + +static int radio_s_hw_freq_seek(struct file *file, void *priv, + const struct v4l2_hw_freq_seek *a) +{ + struct bttv *btv = video_drvdata(file); + + if (btv->has_tea575x) + return snd_tea575x_s_hw_freq_seek(file, &btv->tea, a); + + return -ENOTTY; +} + +static int radio_enum_freq_bands(struct file *file, void *priv, + struct v4l2_frequency_band *band) +{ + struct bttv *btv = video_drvdata(file); + + if (btv->has_tea575x) + return snd_tea575x_enum_freq_bands(&btv->tea, band); + + return -ENOTTY; +} + +static ssize_t radio_read(struct file *file, char __user *data, + size_t count, loff_t *ppos) +{ + struct bttv *btv = video_drvdata(file); + struct saa6588_command cmd; + + cmd.block_count = count / 3; + cmd.nonblocking = file->f_flags & O_NONBLOCK; + cmd.buffer = data; + cmd.instance = file; + cmd.result = -ENODEV; + radio_enable(btv); + + bttv_call_all(btv, core, command, SAA6588_CMD_READ, &cmd); + + return cmd.result; +} + +static __poll_t radio_poll(struct file *file, poll_table *wait) +{ + struct bttv *btv = video_drvdata(file); + struct saa6588_command cmd; + __poll_t rc = v4l2_ctrl_poll(file, wait); + + radio_enable(btv); + cmd.instance = file; + cmd.event_list = wait; + cmd.poll_mask = 0; + bttv_call_all(btv, core, command, SAA6588_CMD_POLL, &cmd); + + return rc | cmd.poll_mask; +} + +static const struct v4l2_file_operations radio_fops = +{ + .owner = THIS_MODULE, + .open = radio_open, + .read = radio_read, + .release = radio_release, + .unlocked_ioctl = video_ioctl2, + .poll = radio_poll, +}; + +static const struct v4l2_ioctl_ops radio_ioctl_ops = { + .vidioc_querycap = bttv_querycap, + .vidioc_log_status = bttv_log_status, + .vidioc_g_tuner = radio_g_tuner, + .vidioc_s_tuner = radio_s_tuner, + .vidioc_g_frequency = bttv_g_frequency, + .vidioc_s_frequency = bttv_s_frequency, + .vidioc_s_hw_freq_seek = radio_s_hw_freq_seek, + .vidioc_enum_freq_bands = radio_enum_freq_bands, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static struct video_device radio_template = { + .fops = &radio_fops, + .ioctl_ops = &radio_ioctl_ops, +}; + +/* ----------------------------------------------------------------------- */ +/* some debug code */ + +static int bttv_risc_decode(u32 risc) +{ + static char *instr[16] = { + [ BT848_RISC_WRITE >> 28 ] = "write", + [ BT848_RISC_SKIP >> 28 ] = "skip", + [ BT848_RISC_WRITEC >> 28 ] = "writec", + [ BT848_RISC_JUMP >> 28 ] = "jump", + [ BT848_RISC_SYNC >> 28 ] = "sync", + [ BT848_RISC_WRITE123 >> 28 ] = "write123", + [ BT848_RISC_SKIP123 >> 28 ] = "skip123", + [ BT848_RISC_WRITE1S23 >> 28 ] = "write1s23", + }; + static int incr[16] = { + [ BT848_RISC_WRITE >> 28 ] = 2, + [ BT848_RISC_JUMP >> 28 ] = 2, + [ BT848_RISC_SYNC >> 28 ] = 2, + [ BT848_RISC_WRITE123 >> 28 ] = 5, + [ BT848_RISC_SKIP123 >> 28 ] = 2, + [ BT848_RISC_WRITE1S23 >> 28 ] = 3, + }; + static char *bits[] = { + "be0", "be1", "be2", "be3/resync", + "set0", "set1", "set2", "set3", + "clr0", "clr1", "clr2", "clr3", + "irq", "res", "eol", "sol", + }; + int i; + + pr_cont("0x%08x [ %s", risc, + instr[risc >> 28] ? instr[risc >> 28] : "INVALID"); + for (i = ARRAY_SIZE(bits)-1; i >= 0; i--) + if (risc & (1 << (i + 12))) + pr_cont(" %s", bits[i]); + pr_cont(" count=%d ]\n", risc & 0xfff); + return incr[risc >> 28] ? incr[risc >> 28] : 1; +} + +static void bttv_risc_disasm(struct bttv *btv, + struct btcx_riscmem *risc) +{ + unsigned int i,j,n; + + pr_info("%s: risc disasm: %p [dma=0x%08lx]\n", + btv->c.v4l2_dev.name, risc->cpu, (unsigned long)risc->dma); + for (i = 0; i < (risc->size >> 2); i += n) { + pr_info("%s: 0x%lx: ", + btv->c.v4l2_dev.name, + (unsigned long)(risc->dma + (i<<2))); + n = bttv_risc_decode(le32_to_cpu(risc->cpu[i])); + for (j = 1; j < n; j++) + pr_info("%s: 0x%lx: 0x%08x [ arg #%d ]\n", + btv->c.v4l2_dev.name, + (unsigned long)(risc->dma + ((i+j)<<2)), + risc->cpu[i+j], j); + if (0 == risc->cpu[i]) + break; + } +} + +static void bttv_print_riscaddr(struct bttv *btv) +{ + pr_info(" main: %08llx\n", (unsigned long long)btv->main.dma); + pr_info(" vbi : o=%08llx e=%08llx\n", + btv->cvbi ? (unsigned long long)btv->cvbi->top.dma : 0, + btv->cvbi ? (unsigned long long)btv->cvbi->bottom.dma : 0); + pr_info(" cap : o=%08llx e=%08llx\n", + btv->curr.top + ? (unsigned long long)btv->curr.top->top.dma : 0, + btv->curr.bottom + ? (unsigned long long)btv->curr.bottom->bottom.dma : 0); + bttv_risc_disasm(btv, &btv->main); +} + +/* ----------------------------------------------------------------------- */ +/* irq handler */ + +static char *irq_name[] = { + "FMTCHG", // format change detected (525 vs. 625) + "VSYNC", // vertical sync (new field) + "HSYNC", // horizontal sync + "OFLOW", // chroma/luma AGC overflow + "HLOCK", // horizontal lock changed + "VPRES", // video presence changed + "6", "7", + "I2CDONE", // hw irc operation finished + "GPINT", // gpio port triggered irq + "10", + "RISCI", // risc instruction triggered irq + "FBUS", // pixel data fifo dropped data (high pci bus latencies) + "FTRGT", // pixel data fifo overrun + "FDSR", // fifo data stream resyncronisation + "PPERR", // parity error (data transfer) + "RIPERR", // parity error (read risc instructions) + "PABORT", // pci abort + "OCERR", // risc instruction error + "SCERR", // syncronisation error +}; + +static void bttv_print_irqbits(u32 print, u32 mark) +{ + unsigned int i; + + pr_cont("bits:"); + for (i = 0; i < ARRAY_SIZE(irq_name); i++) { + if (print & (1 << i)) + pr_cont(" %s", irq_name[i]); + if (mark & (1 << i)) + pr_cont("*"); + } +} + +static void bttv_irq_debug_low_latency(struct bttv *btv, u32 rc) +{ + pr_warn("%d: irq: skipped frame [main=%lx,o_vbi=%lx,o_field=%lx,rc=%lx]\n", + btv->c.nr, + (unsigned long)btv->main.dma, + (unsigned long)le32_to_cpu(btv->main.cpu[RISC_SLOT_O_VBI+1]), + (unsigned long)le32_to_cpu(btv->main.cpu[RISC_SLOT_O_FIELD+1]), + (unsigned long)rc); + + if (0 == (btread(BT848_DSTATUS) & BT848_DSTATUS_HLOC)) { + pr_notice("%d: Oh, there (temporarily?) is no input signal. Ok, then this is harmless, don't worry ;)\n", + btv->c.nr); + return; + } + pr_notice("%d: Uhm. Looks like we have unusual high IRQ latencies\n", + btv->c.nr); + pr_notice("%d: Lets try to catch the culprit red-handed ...\n", + btv->c.nr); + dump_stack(); +} + +static int +bttv_irq_next_video(struct bttv *btv, struct bttv_buffer_set *set) +{ + struct bttv_buffer *item; + + memset(set,0,sizeof(*set)); + + /* capture request ? */ + if (!list_empty(&btv->capture)) { + set->frame_irq = BT848_RISC_VIDEO; + item = list_entry(btv->capture.next, struct bttv_buffer, list); + + if (V4L2_FIELD_HAS_TOP(item->vbuf.field)) + set->top = item; + if (V4L2_FIELD_HAS_BOTTOM(item->vbuf.field)) + set->bottom = item; + + /* capture request for other field ? */ + if (!V4L2_FIELD_HAS_BOTH(item->vbuf.field) && + item->list.next != &btv->capture) { + item = list_entry(item->list.next, + struct bttv_buffer, list); + /* Mike Isely - Only check + * and set up the bottom field in the logic + * below. Don't ever do the top field. This + * of course means that if we set up the + * bottom field in the above code that we'll + * actually skip a field. But that's OK. + * Having processed only a single buffer this + * time, then the next time around the first + * available buffer should be for a top field. + * That will then cause us here to set up a + * top then a bottom field in the normal way. + * The alternative to this understanding is + * that we set up the second available buffer + * as a top field, but that's out of order + * since this driver always processes the top + * field first - the effect will be the two + * buffers being returned in the wrong order, + * with the second buffer also being delayed + * by one field time (owing to the fifo nature + * of videobuf). Worse still, we'll be stuck + * doing fields out of order now every time + * until something else causes a field to be + * dropped. By effectively forcing a field to + * drop this way then we always get back into + * sync within a single frame time. (Out of + * order fields can screw up deinterlacing + * algorithms.) */ + if (!V4L2_FIELD_HAS_BOTH(item->vbuf.field)) { + if (!set->bottom && + item->vbuf.field == V4L2_FIELD_BOTTOM) + set->bottom = item; + if (set->top && set->bottom) { + /* + * The buffer set has a top buffer and + * a bottom buffer and they are not + * copies of each other. + */ + set->top_irq = BT848_RISC_TOP; + } + } + } + } + + dprintk("%d: next set: top=%p bottom=%p [irq=%d,%d]\n", + btv->c.nr, set->top, set->bottom, + set->frame_irq, set->top_irq); + return 0; +} + +static void +bttv_irq_wakeup_video(struct bttv *btv, struct bttv_buffer_set *wakeup, + struct bttv_buffer_set *curr, unsigned int state) +{ + u64 ts = ktime_get_ns(); + + if (wakeup->top == wakeup->bottom) { + if (NULL != wakeup->top && curr->top != wakeup->top) { + if (irq_debug > 1) + pr_debug("%d: wakeup: both=%p\n", + btv->c.nr, wakeup->top); + wakeup->top->vbuf.vb2_buf.timestamp = ts; + wakeup->top->vbuf.sequence = btv->field_count >> 1; + vb2_buffer_done(&wakeup->top->vbuf.vb2_buf, state); + if (btv->field_count == 0) + btor(BT848_INT_VSYNC, BT848_INT_MASK); + } + } else { + if (NULL != wakeup->top && curr->top != wakeup->top) { + if (irq_debug > 1) + pr_debug("%d: wakeup: top=%p\n", + btv->c.nr, wakeup->top); + wakeup->top->vbuf.vb2_buf.timestamp = ts; + wakeup->top->vbuf.sequence = btv->field_count >> 1; + vb2_buffer_done(&wakeup->top->vbuf.vb2_buf, state); + if (btv->field_count == 0) + btor(BT848_INT_VSYNC, BT848_INT_MASK); + } + if (NULL != wakeup->bottom && curr->bottom != wakeup->bottom) { + if (irq_debug > 1) + pr_debug("%d: wakeup: bottom=%p\n", + btv->c.nr, wakeup->bottom); + wakeup->bottom->vbuf.vb2_buf.timestamp = ts; + wakeup->bottom->vbuf.sequence = btv->field_count >> 1; + vb2_buffer_done(&wakeup->bottom->vbuf.vb2_buf, state); + if (btv->field_count == 0) + btor(BT848_INT_VSYNC, BT848_INT_MASK); + } + } +} + +static void +bttv_irq_wakeup_vbi(struct bttv *btv, struct bttv_buffer *wakeup, + unsigned int state) +{ + if (NULL == wakeup) + return; + wakeup->vbuf.vb2_buf.timestamp = ktime_get_ns(); + wakeup->vbuf.sequence = btv->field_count >> 1; + + /* + * Ugly hack for backwards compatibility. + * Some applications expect that the last 4 bytes of + * the VBI data contains the sequence number. + * + * This makes it possible to associate the VBI data + * with the video frame if you use read() to get the + * VBI data. + */ + if (vb2_fileio_is_active(wakeup->vbuf.vb2_buf.vb2_queue)) { + u32 *vaddr = vb2_plane_vaddr(&wakeup->vbuf.vb2_buf, 0); + unsigned long size = + vb2_get_plane_payload(&wakeup->vbuf.vb2_buf, 0) / 4; + + if (vaddr && size) { + vaddr += size - 1; + *vaddr = wakeup->vbuf.sequence; + } + } + + vb2_buffer_done(&wakeup->vbuf.vb2_buf, state); + if (btv->field_count == 0) + btor(BT848_INT_VSYNC, BT848_INT_MASK); +} + +static void bttv_irq_timeout(struct timer_list *t) +{ + struct bttv *btv = timer_container_of(btv, t, timeout); + struct bttv_buffer_set old,new; + struct bttv_buffer *ovbi; + struct bttv_buffer *item; + unsigned long flags; + int seqnr = 0; + + if (bttv_verbose) { + pr_info("%d: timeout: drop=%d irq=%d/%d, risc=%08x, ", + btv->c.nr, btv->framedrop, btv->irq_me, btv->irq_total, + btread(BT848_RISC_COUNT)); + bttv_print_irqbits(btread(BT848_INT_STAT),0); + pr_cont("\n"); + } + + spin_lock_irqsave(&btv->s_lock,flags); + + /* deactivate stuff */ + memset(&new,0,sizeof(new)); + old = btv->curr; + ovbi = btv->cvbi; + btv->curr = new; + btv->cvbi = NULL; + btv->loop_irq = 0; + bttv_buffer_activate_video(btv, &new); + bttv_buffer_activate_vbi(btv, NULL); + bttv_set_dma(btv, 0); + + /* wake up */ + bttv_irq_wakeup_video(btv, &old, &new, VB2_BUF_STATE_DONE); + bttv_irq_wakeup_vbi(btv, ovbi, VB2_BUF_STATE_DONE); + + /* cancel all outstanding capture / vbi requests */ + if (btv->field_count) + seqnr++; + while (!list_empty(&btv->capture)) { + item = list_entry(btv->capture.next, struct bttv_buffer, list); + list_del(&item->list); + item->vbuf.vb2_buf.timestamp = ktime_get_ns(); + item->vbuf.sequence = (btv->field_count >> 1) + seqnr++; + vb2_buffer_done(&item->vbuf.vb2_buf, VB2_BUF_STATE_ERROR); + } + while (!list_empty(&btv->vcapture)) { + item = list_entry(btv->vcapture.next, struct bttv_buffer, list); + list_del(&item->list); + item->vbuf.vb2_buf.timestamp = ktime_get_ns(); + item->vbuf.sequence = (btv->field_count >> 1) + seqnr++; + vb2_buffer_done(&item->vbuf.vb2_buf, VB2_BUF_STATE_ERROR); + } + + btv->errors++; + spin_unlock_irqrestore(&btv->s_lock,flags); +} + +static void +bttv_irq_wakeup_top(struct bttv *btv) +{ + struct bttv_buffer *wakeup = btv->curr.top; + + if (NULL == wakeup) + return; + + spin_lock(&btv->s_lock); + btv->curr.top_irq = 0; + btv->curr.top = NULL; + bttv_risc_hook(btv, RISC_SLOT_O_FIELD, NULL, 0); + wakeup->vbuf.vb2_buf.timestamp = ktime_get_ns(); + wakeup->vbuf.sequence = btv->field_count >> 1; + vb2_buffer_done(&wakeup->vbuf.vb2_buf, VB2_BUF_STATE_DONE); + if (btv->field_count == 0) + btor(BT848_INT_VSYNC, BT848_INT_MASK); + spin_unlock(&btv->s_lock); +} + +static inline int is_active(struct btcx_riscmem *risc, u32 rc) +{ + if (rc < risc->dma) + return 0; + if (rc > risc->dma + risc->size) + return 0; + return 1; +} + +static void +bttv_irq_switch_video(struct bttv *btv) +{ + struct bttv_buffer_set new; + struct bttv_buffer_set old; + dma_addr_t rc; + + spin_lock(&btv->s_lock); + + /* new buffer set */ + bttv_irq_next_video(btv, &new); + rc = btread(BT848_RISC_COUNT); + if ((btv->curr.top && is_active(&btv->curr.top->top, rc)) || + (btv->curr.bottom && is_active(&btv->curr.bottom->bottom, rc))) { + btv->framedrop++; + if (debug_latency) + bttv_irq_debug_low_latency(btv, rc); + spin_unlock(&btv->s_lock); + return; + } + + /* switch over */ + old = btv->curr; + btv->curr = new; + btv->loop_irq &= ~BT848_RISC_VIDEO; + bttv_buffer_activate_video(btv, &new); + bttv_set_dma(btv, 0); + + /* switch input */ + if (UNSET != btv->new_input) { + video_mux(btv,btv->new_input); + btv->new_input = UNSET; + } + + /* wake up finished buffers */ + bttv_irq_wakeup_video(btv, &old, &new, VB2_BUF_STATE_DONE); + spin_unlock(&btv->s_lock); +} + +static void +bttv_irq_switch_vbi(struct bttv *btv) +{ + struct bttv_buffer *new = NULL; + struct bttv_buffer *old; + u32 rc; + + spin_lock(&btv->s_lock); + + if (!list_empty(&btv->vcapture)) + new = list_entry(btv->vcapture.next, struct bttv_buffer, list); + old = btv->cvbi; + + rc = btread(BT848_RISC_COUNT); + if (NULL != old && (is_active(&old->top, rc) || + is_active(&old->bottom, rc))) { + btv->framedrop++; + if (debug_latency) + bttv_irq_debug_low_latency(btv, rc); + spin_unlock(&btv->s_lock); + return; + } + + /* switch */ + btv->cvbi = new; + btv->loop_irq &= ~BT848_RISC_VBI; + bttv_buffer_activate_vbi(btv, new); + bttv_set_dma(btv, 0); + + bttv_irq_wakeup_vbi(btv, old, VB2_BUF_STATE_DONE); + spin_unlock(&btv->s_lock); +} + +static irqreturn_t bttv_irq(int irq, void *dev_id) +{ + u32 stat,astat; + u32 dstat; + int count; + struct bttv *btv; + int handled = 0; + + btv=(struct bttv *)dev_id; + + count=0; + while (1) { + /* get/clear interrupt status bits */ + stat=btread(BT848_INT_STAT); + astat=stat&btread(BT848_INT_MASK); + if (!astat) + break; + handled = 1; + btwrite(stat,BT848_INT_STAT); + + /* get device status bits */ + dstat=btread(BT848_DSTATUS); + + if (irq_debug) { + pr_debug("%d: irq loop=%d fc=%d riscs=%x, riscc=%08x, ", + btv->c.nr, count, btv->field_count, + stat>>28, btread(BT848_RISC_COUNT)); + bttv_print_irqbits(stat,astat); + if (stat & BT848_INT_HLOCK) + pr_cont(" HLOC => %s", + dstat & BT848_DSTATUS_HLOC + ? "yes" : "no"); + if (stat & BT848_INT_VPRES) + pr_cont(" PRES => %s", + dstat & BT848_DSTATUS_PRES + ? "yes" : "no"); + if (stat & BT848_INT_FMTCHG) + pr_cont(" NUML => %s", + dstat & BT848_DSTATUS_NUML + ? "625" : "525"); + pr_cont("\n"); + } + + if (astat&BT848_INT_VSYNC) + btv->field_count++; + + if ((astat & BT848_INT_GPINT) && btv->remote) { + bttv_input_irq(btv); + } + + if (astat & BT848_INT_I2CDONE) { + btv->i2c_done = stat; + wake_up(&btv->i2c_queue); + } + + if ((astat & BT848_INT_RISCI) && (stat & BT848_INT_RISCS_VBI)) + bttv_irq_switch_vbi(btv); + + if ((astat & BT848_INT_RISCI) && (stat & BT848_INT_RISCS_TOP)) + bttv_irq_wakeup_top(btv); + + if ((astat & BT848_INT_RISCI) && (stat & BT848_INT_RISCS_VIDEO)) + bttv_irq_switch_video(btv); + + if ((astat & BT848_INT_HLOCK) && btv->opt_automute) + /* trigger automute */ + audio_mux_gpio(btv, btv->audio_input, btv->mute); + + if (astat & (BT848_INT_SCERR|BT848_INT_OCERR)) { + pr_info("%d: %s%s @ %08x,", + btv->c.nr, + (astat & BT848_INT_SCERR) ? "SCERR" : "", + (astat & BT848_INT_OCERR) ? "OCERR" : "", + btread(BT848_RISC_COUNT)); + bttv_print_irqbits(stat,astat); + pr_cont("\n"); + if (bttv_debug) + bttv_print_riscaddr(btv); + } + if (fdsr && astat & BT848_INT_FDSR) { + pr_info("%d: FDSR @ %08x\n", + btv->c.nr, btread(BT848_RISC_COUNT)); + if (bttv_debug) + bttv_print_riscaddr(btv); + } + + count++; + if (count > 4) { + + if (count > 8 || !(astat & BT848_INT_GPINT)) { + btwrite(0, BT848_INT_MASK); + + pr_err("%d: IRQ lockup, cleared int mask [", + btv->c.nr); + } else { + pr_err("%d: IRQ lockup, clearing GPINT from int mask [", + btv->c.nr); + + btwrite(btread(BT848_INT_MASK) & (-1 ^ BT848_INT_GPINT), + BT848_INT_MASK); + } + + bttv_print_irqbits(stat,astat); + + pr_cont("]\n"); + } + } + btv->irq_total++; + if (handled) + btv->irq_me++; + return IRQ_RETVAL(handled); +} + + +/* ----------------------------------------------------------------------- */ +/* initialization */ + +static int vdev_init(struct bttv *btv, struct video_device *vfd, + const struct video_device *template, + const char *type_name) +{ + int err; + struct vb2_queue *q; + *vfd = *template; + vfd->v4l2_dev = &btv->c.v4l2_dev; + vfd->release = video_device_release_empty; + video_set_drvdata(vfd, btv); + snprintf(vfd->name, sizeof(vfd->name), "BT%d%s %s (%s)", + btv->id, (btv->id==848 && btv->revision==0x12) ? "A" : "", + type_name, bttv_tvcards[btv->c.type].name); + if (btv->tuner_type == TUNER_ABSENT) { + v4l2_disable_ioctl(vfd, VIDIOC_G_FREQUENCY); + v4l2_disable_ioctl(vfd, VIDIOC_S_FREQUENCY); + v4l2_disable_ioctl(vfd, VIDIOC_G_TUNER); + v4l2_disable_ioctl(vfd, VIDIOC_S_TUNER); + } + + if (strcmp(type_name, "radio") == 0) + return 0; + + if (strcmp(type_name, "video") == 0) { + q = &btv->capq; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->ops = &bttv_video_qops; + } else if (strcmp(type_name, "vbi") == 0) { + q = &btv->vbiq; + q->type = V4L2_BUF_TYPE_VBI_CAPTURE; + q->ops = &bttv_vbi_qops; + } else { + return -EINVAL; + } + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ | VB2_DMABUF; + q->mem_ops = &vb2_dma_sg_memops; + q->drv_priv = btv; + q->gfp_flags = __GFP_DMA32; + q->buf_struct_size = sizeof(struct bttv_buffer); + q->lock = &btv->lock; + q->min_queued_buffers = 2; + q->dev = &btv->c.pci->dev; + err = vb2_queue_init(q); + if (err) + return err; + vfd->queue = q; + + return 0; +} + +static void bttv_unregister_video(struct bttv *btv) +{ + video_unregister_device(&btv->video_dev); + video_unregister_device(&btv->vbi_dev); + video_unregister_device(&btv->radio_dev); +} + +/* register video4linux devices */ +static int bttv_register_video(struct bttv *btv) +{ + /* video */ + vdev_init(btv, &btv->video_dev, &bttv_video_template, "video"); + btv->video_dev.device_caps = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; + if (btv->tuner_type != TUNER_ABSENT) + btv->video_dev.device_caps |= V4L2_CAP_TUNER; + + if (video_register_device(&btv->video_dev, VFL_TYPE_VIDEO, + video_nr[btv->c.nr]) < 0) + goto err; + pr_info("%d: registered device %s\n", + btv->c.nr, video_device_node_name(&btv->video_dev)); + if (device_create_file(&btv->video_dev.dev, + &dev_attr_card)<0) { + pr_err("%d: device_create_file 'card' failed\n", btv->c.nr); + goto err; + } + + /* vbi */ + vdev_init(btv, &btv->vbi_dev, &bttv_video_template, "vbi"); + btv->vbi_dev.device_caps = V4L2_CAP_VBI_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + if (btv->tuner_type != TUNER_ABSENT) + btv->vbi_dev.device_caps |= V4L2_CAP_TUNER; + + if (video_register_device(&btv->vbi_dev, VFL_TYPE_VBI, + vbi_nr[btv->c.nr]) < 0) + goto err; + pr_info("%d: registered device %s\n", + btv->c.nr, video_device_node_name(&btv->vbi_dev)); + + if (!btv->has_radio) + return 0; + /* radio */ + vdev_init(btv, &btv->radio_dev, &radio_template, "radio"); + btv->radio_dev.device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER; + if (btv->has_saa6588) + btv->radio_dev.device_caps |= V4L2_CAP_READWRITE | + V4L2_CAP_RDS_CAPTURE; + if (btv->has_tea575x) + btv->radio_dev.device_caps |= V4L2_CAP_HW_FREQ_SEEK; + btv->radio_dev.ctrl_handler = &btv->radio_ctrl_handler; + if (video_register_device(&btv->radio_dev, VFL_TYPE_RADIO, + radio_nr[btv->c.nr]) < 0) + goto err; + pr_info("%d: registered device %s\n", + btv->c.nr, video_device_node_name(&btv->radio_dev)); + + /* all done */ + return 0; + + err: + bttv_unregister_video(btv); + return -1; +} + + +/* on OpenFirmware machines (PowerMac at least), PCI memory cycle */ +/* response on cards with no firmware is not enabled by OF */ +static void pci_set_command(struct pci_dev *dev) +{ +#if defined(__powerpc__) + unsigned int cmd; + + pci_read_config_dword(dev, PCI_COMMAND, &cmd); + cmd = (cmd | PCI_COMMAND_MEMORY ); + pci_write_config_dword(dev, PCI_COMMAND, cmd); +#endif +} + +static int bttv_probe(struct pci_dev *dev, const struct pci_device_id *pci_id) +{ + struct v4l2_frequency init_freq = { + .tuner = 0, + .type = V4L2_TUNER_ANALOG_TV, + .frequency = 980, + }; + int result; + unsigned char lat; + struct bttv *btv; + struct v4l2_ctrl_handler *hdl; + + if (bttv_num == BTTV_MAX) + return -ENOMEM; + pr_info("Bt8xx card found (%d)\n", bttv_num); + bttvs[bttv_num] = btv = kzalloc_obj(*btv); + if (btv == NULL) { + pr_err("out of memory\n"); + return -ENOMEM; + } + btv->c.nr = bttv_num; + snprintf(btv->c.v4l2_dev.name, sizeof(btv->c.v4l2_dev.name), + "bttv%d", btv->c.nr); + + /* initialize structs / fill in defaults */ + mutex_init(&btv->lock); + spin_lock_init(&btv->s_lock); + spin_lock_init(&btv->gpio_lock); + init_waitqueue_head(&btv->i2c_queue); + INIT_LIST_HEAD(&btv->c.subs); + INIT_LIST_HEAD(&btv->capture); + INIT_LIST_HEAD(&btv->vcapture); + + timer_setup(&btv->timeout, bttv_irq_timeout, 0); + + btv->i2c_rc = -1; + btv->tuner_type = UNSET; + btv->new_input = UNSET; + btv->has_radio=radio[btv->c.nr]; + + /* pci stuff (init, get irq/mmio, ... */ + btv->c.pci = dev; + btv->id = dev->device; + if (pci_enable_device(dev)) { + pr_warn("%d: Can't enable device\n", btv->c.nr); + result = -EIO; + goto free_mem; + } + if (dma_set_mask(&dev->dev, DMA_BIT_MASK(32))) { + pr_warn("%d: No suitable DMA available\n", btv->c.nr); + result = -EIO; + goto free_mem; + } + if (!request_mem_region(pci_resource_start(dev,0), + pci_resource_len(dev,0), + btv->c.v4l2_dev.name)) { + pr_warn("%d: can't request iomem (0x%llx)\n", + btv->c.nr, + (unsigned long long)pci_resource_start(dev, 0)); + result = -EBUSY; + goto free_mem; + } + pci_set_master(dev); + pci_set_command(dev); + + result = v4l2_device_register(&dev->dev, &btv->c.v4l2_dev); + if (result < 0) { + pr_warn("%d: v4l2_device_register() failed\n", btv->c.nr); + goto fail0; + } + hdl = &btv->ctrl_handler; + v4l2_ctrl_handler_init(hdl, 20); + btv->c.v4l2_dev.ctrl_handler = hdl; + v4l2_ctrl_handler_init(&btv->radio_ctrl_handler, 6); + + btv->revision = dev->revision; + pci_read_config_byte(dev, PCI_LATENCY_TIMER, &lat); + pr_info("%d: Bt%d (rev %d) at %s, irq: %d, latency: %d, mmio: 0x%llx\n", + bttv_num, btv->id, btv->revision, pci_name(dev), + btv->c.pci->irq, lat, + (unsigned long long)pci_resource_start(dev, 0)); + schedule(); + + btv->bt848_mmio = ioremap(pci_resource_start(dev, 0), 0x1000); + if (NULL == btv->bt848_mmio) { + pr_err("%d: ioremap() failed\n", btv->c.nr); + result = -EIO; + goto fail1; + } + + /* identify card */ + bttv_idcard(btv); + + /* disable irqs, register irq handler */ + btwrite(0, BT848_INT_MASK); + result = request_irq(btv->c.pci->irq, bttv_irq, + IRQF_SHARED, btv->c.v4l2_dev.name, (void *)btv); + if (result < 0) { + pr_err("%d: can't get IRQ %d\n", + bttv_num, btv->c.pci->irq); + goto fail1; + } + + if (0 != bttv_handle_chipset(btv)) { + result = -EIO; + goto fail2; + } + + /* init options from insmod args */ + btv->opt_combfilter = combfilter; + bttv_ctrl_combfilter.def = combfilter; + bttv_ctrl_lumafilter.def = lumafilter; + btv->opt_automute = automute; + bttv_ctrl_automute.def = automute; + bttv_ctrl_agc_crush.def = agc_crush; + btv->opt_vcr_hack = vcr_hack; + bttv_ctrl_vcr_hack.def = vcr_hack; + bttv_ctrl_whitecrush_upper.def = whitecrush_upper; + bttv_ctrl_whitecrush_lower.def = whitecrush_lower; + btv->opt_uv_ratio = uv_ratio; + bttv_ctrl_uv_ratio.def = uv_ratio; + bttv_ctrl_full_luma.def = full_luma_range; + bttv_ctrl_coring.def = coring; + + /* fill struct bttv with some useful defaults */ + btv->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24); + btv->width = 320; + btv->height = 240; + btv->field = V4L2_FIELD_INTERLACED; + btv->input = 0; + btv->tvnorm = 0; /* Index into bttv_tvnorms[] i.e. PAL. */ + bttv_vbi_fmt_reset(&btv->vbi_fmt, btv->tvnorm); + btv->vbi_count[0] = VBI_DEFLINES; + btv->vbi_count[1] = VBI_DEFLINES; + btv->do_crop = 0; + + v4l2_ctrl_new_std(hdl, &bttv_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 0xff00, 0x100, 32768); + v4l2_ctrl_new_std(hdl, &bttv_ctrl_ops, + V4L2_CID_CONTRAST, 0, 0xff80, 0x80, 0x6c00); + v4l2_ctrl_new_std(hdl, &bttv_ctrl_ops, + V4L2_CID_SATURATION, 0, 0xff80, 0x80, 32768); + v4l2_ctrl_new_std(hdl, &bttv_ctrl_ops, + V4L2_CID_COLOR_KILLER, 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &bttv_ctrl_ops, + V4L2_CID_HUE, 0, 0xff00, 0x100, 32768); + v4l2_ctrl_new_std(hdl, &bttv_ctrl_ops, + V4L2_CID_CHROMA_AGC, 0, 1, 1, !!chroma_agc); + v4l2_ctrl_new_std(hdl, &bttv_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + if (btv->volume_gpio) + v4l2_ctrl_new_std(hdl, &bttv_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, 0, 0xff00, 0x100, 0xff00); + v4l2_ctrl_new_custom(hdl, &bttv_ctrl_combfilter, NULL); + v4l2_ctrl_new_custom(hdl, &bttv_ctrl_automute, NULL); + v4l2_ctrl_new_custom(hdl, &bttv_ctrl_lumafilter, NULL); + v4l2_ctrl_new_custom(hdl, &bttv_ctrl_agc_crush, NULL); + v4l2_ctrl_new_custom(hdl, &bttv_ctrl_vcr_hack, NULL); + v4l2_ctrl_new_custom(hdl, &bttv_ctrl_whitecrush_lower, NULL); + v4l2_ctrl_new_custom(hdl, &bttv_ctrl_whitecrush_upper, NULL); + v4l2_ctrl_new_custom(hdl, &bttv_ctrl_uv_ratio, NULL); + v4l2_ctrl_new_custom(hdl, &bttv_ctrl_full_luma, NULL); + v4l2_ctrl_new_custom(hdl, &bttv_ctrl_coring, NULL); + + /* initialize hardware */ + if (bttv_gpio) + bttv_gpio_tracking(btv,"pre-init"); + + bttv_risc_init_main(btv); + init_bt848(btv); + + /* gpio */ + btwrite(0x00, BT848_GPIO_REG_INP); + btwrite(0x00, BT848_GPIO_OUT_EN); + if (bttv_verbose) + bttv_gpio_tracking(btv,"init"); + + /* needs to be done before i2c is registered */ + bttv_init_card1(btv); + + /* register i2c + gpio */ + init_bttv_i2c(btv); + + /* some card-specific stuff (needs working i2c) */ + bttv_init_card2(btv); + bttv_init_tuner(btv); + if (btv->tuner_type != TUNER_ABSENT) { + bttv_set_frequency(btv, &init_freq); + btv->radio_freq = 90500 * 16; /* 90.5Mhz default */ + } + btv->std = V4L2_STD_PAL; + init_irqreg(btv); + if (!bttv_tvcards[btv->c.type].no_video) + v4l2_ctrl_handler_setup(hdl); + if (hdl->error) { + result = hdl->error; + goto fail2; + } + /* mute device */ + audio_mute(btv, 1); + + /* register video4linux + input */ + if (!bttv_tvcards[btv->c.type].no_video) { + v4l2_ctrl_add_handler(&btv->radio_ctrl_handler, hdl, + v4l2_ctrl_radio_filter, false); + if (btv->radio_ctrl_handler.error) { + result = btv->radio_ctrl_handler.error; + goto fail2; + } + set_input(btv, btv->input, btv->tvnorm); + bttv_crop_reset(&btv->crop[0], btv->tvnorm); + btv->crop[1] = btv->crop[0]; /* current = default */ + disclaim_vbi_lines(btv); + disclaim_video_lines(btv); + bttv_register_video(btv); + } + + /* add subdevices and autoload dvb-bt8xx if needed */ + if (bttv_tvcards[btv->c.type].has_dvb) { + bttv_sub_add_device(&btv->c, "dvb"); + request_modules(btv); + } + + if (!disable_ir) { + init_bttv_i2c_ir(btv); + bttv_input_init(btv); + } + + /* everything is fine */ + bttv_num++; + return 0; + +fail2: + free_irq(btv->c.pci->irq,btv); + +fail1: + v4l2_ctrl_handler_free(&btv->ctrl_handler); + v4l2_ctrl_handler_free(&btv->radio_ctrl_handler); + v4l2_device_unregister(&btv->c.v4l2_dev); + +fail0: + if (btv->bt848_mmio) + iounmap(btv->bt848_mmio); + release_mem_region(pci_resource_start(btv->c.pci,0), + pci_resource_len(btv->c.pci,0)); + pci_disable_device(btv->c.pci); + +free_mem: + bttvs[btv->c.nr] = NULL; + kfree(btv); + return result; +} + +static void bttv_remove(struct pci_dev *pci_dev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct bttv *btv = to_bttv(v4l2_dev); + + if (bttv_verbose) + pr_info("%d: unloading\n", btv->c.nr); + + if (bttv_tvcards[btv->c.type].has_dvb) + flush_request_modules(btv); + + /* shutdown everything (DMA+IRQs) */ + btand(~15, BT848_GPIO_DMA_CTL); + btwrite(0, BT848_INT_MASK); + btwrite(~0x0, BT848_INT_STAT); + btwrite(0x0, BT848_GPIO_OUT_EN); + if (bttv_gpio) + bttv_gpio_tracking(btv,"cleanup"); + + /* tell gpio modules we are leaving ... */ + btv->shutdown=1; + bttv_input_fini(btv); + bttv_sub_del_devices(&btv->c); + + /* unregister i2c_bus + input */ + fini_bttv_i2c(btv); + + /* unregister video4linux */ + bttv_unregister_video(btv); + + /* free allocated memory */ + v4l2_ctrl_handler_free(&btv->ctrl_handler); + v4l2_ctrl_handler_free(&btv->radio_ctrl_handler); + btcx_riscmem_free(btv->c.pci,&btv->main); + + /* free resources */ + free_irq(btv->c.pci->irq,btv); + timer_delete_sync(&btv->timeout); + iounmap(btv->bt848_mmio); + release_mem_region(pci_resource_start(btv->c.pci,0), + pci_resource_len(btv->c.pci,0)); + pci_disable_device(btv->c.pci); + + v4l2_device_unregister(&btv->c.v4l2_dev); + bttvs[btv->c.nr] = NULL; + kfree(btv); + + return; +} + +static int __maybe_unused bttv_suspend(struct device *dev) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); + struct bttv *btv = to_bttv(v4l2_dev); + struct bttv_buffer_set idle; + unsigned long flags; + + dprintk("%d: suspend\n", btv->c.nr); + + /* stop dma + irqs */ + spin_lock_irqsave(&btv->s_lock,flags); + memset(&idle, 0, sizeof(idle)); + btv->state.video = btv->curr; + btv->state.vbi = btv->cvbi; + btv->state.loop_irq = btv->loop_irq; + btv->curr = idle; + btv->loop_irq = 0; + bttv_buffer_activate_video(btv, &idle); + bttv_buffer_activate_vbi(btv, NULL); + bttv_set_dma(btv, 0); + btwrite(0, BT848_INT_MASK); + spin_unlock_irqrestore(&btv->s_lock,flags); + + /* save bt878 state */ + btv->state.gpio_enable = btread(BT848_GPIO_OUT_EN); + btv->state.gpio_data = gpio_read(); + + btv->state.disabled = 1; + return 0; +} + +static int __maybe_unused bttv_resume(struct device *dev) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); + struct bttv *btv = to_bttv(v4l2_dev); + unsigned long flags; + + dprintk("%d: resume\n", btv->c.nr); + + btv->state.disabled = 0; + + /* restore bt878 state */ + bttv_reinit_bt848(btv); + gpio_inout(0xffffff, btv->state.gpio_enable); + gpio_write(btv->state.gpio_data); + + /* restart dma */ + spin_lock_irqsave(&btv->s_lock,flags); + btv->curr = btv->state.video; + btv->cvbi = btv->state.vbi; + btv->loop_irq = btv->state.loop_irq; + bttv_buffer_activate_video(btv, &btv->curr); + bttv_buffer_activate_vbi(btv, btv->cvbi); + bttv_set_dma(btv, 0); + spin_unlock_irqrestore(&btv->s_lock,flags); + return 0; +} + +static const struct pci_device_id bttv_pci_tbl[] = { + { PCI_VDEVICE(BROOKTREE, PCI_DEVICE_ID_BT848) }, + { PCI_VDEVICE(BROOKTREE, PCI_DEVICE_ID_BT849) }, + { PCI_VDEVICE(BROOKTREE, PCI_DEVICE_ID_BT878) }, + { PCI_VDEVICE(BROOKTREE, PCI_DEVICE_ID_BT879) }, + { PCI_VDEVICE(BROOKTREE, PCI_DEVICE_ID_FUSION879) }, + { } +}; + +MODULE_DEVICE_TABLE(pci, bttv_pci_tbl); + +static SIMPLE_DEV_PM_OPS(bttv_pm_ops, + bttv_suspend, + bttv_resume); + +static struct pci_driver bttv_pci_driver = { + .name = "bttv", + .id_table = bttv_pci_tbl, + .probe = bttv_probe, + .remove = bttv_remove, + .driver.pm = &bttv_pm_ops, +}; + +static int __init bttv_init_module(void) +{ + int ret; + + bttv_num = 0; + + pr_info("driver version %s loaded\n", BTTV_VERSION); + if (gbuffers < 2 || gbuffers > VIDEO_MAX_FRAME) + gbuffers = 2; + if (gbufsize > BTTV_MAX_FBUF) + gbufsize = BTTV_MAX_FBUF; + gbufsize = (gbufsize + PAGE_SIZE - 1) & PAGE_MASK; + if (bttv_verbose) + pr_info("using %d buffers with %dk (%d pages) each for capture\n", + gbuffers, gbufsize >> 10, gbufsize >> PAGE_SHIFT); + + bttv_check_chipset(); + + ret = bus_register(&bttv_sub_bus_type); + if (ret < 0) { + pr_warn("bus_register error: %d\n", ret); + return ret; + } + ret = pci_register_driver(&bttv_pci_driver); + if (ret < 0) + bus_unregister(&bttv_sub_bus_type); + + return ret; +} + +static void __exit bttv_cleanup_module(void) +{ + pci_unregister_driver(&bttv_pci_driver); + bus_unregister(&bttv_sub_bus_type); +} + +module_init(bttv_init_module); +module_exit(bttv_cleanup_module); diff --git a/drivers/media/pci/bt8xx/bttv-gpio.c b/drivers/media/pci/bt8xx/bttv-gpio.c new file mode 100644 index 000000000..b79daa2d4 --- /dev/null +++ b/drivers/media/pci/bt8xx/bttv-gpio.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + + bttv-gpio.c -- gpio sub drivers + + sysfs-based sub driver interface for bttv + mainly intended for gpio access + + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 1999-2003 Gerd Knorr + + +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +#include "bttvp.h" + +/* ----------------------------------------------------------------------- */ +/* internal: the bttv "bus" */ + +static int bttv_sub_bus_match(struct device *dev, const struct device_driver *drv) +{ + const struct bttv_sub_driver *sub = to_bttv_sub_drv(drv); + int len = strlen(sub->wanted); + + if (0 == strncmp(dev_name(dev), sub->wanted, len)) + return 1; + return 0; +} + +static int bttv_sub_probe(struct device *dev) +{ + struct bttv_sub_device *sdev = to_bttv_sub_dev(dev); + struct bttv_sub_driver *sub = to_bttv_sub_drv(dev->driver); + + return sub->probe ? sub->probe(sdev) : -ENODEV; +} + +static void bttv_sub_remove(struct device *dev) +{ + struct bttv_sub_device *sdev = to_bttv_sub_dev(dev); + struct bttv_sub_driver *sub = to_bttv_sub_drv(dev->driver); + + if (sub->remove) + sub->remove(sdev); +} + +const struct bus_type bttv_sub_bus_type = { + .name = "bttv-sub", + .match = &bttv_sub_bus_match, + .probe = bttv_sub_probe, + .remove = bttv_sub_remove, +}; + +static void release_sub_device(struct device *dev) +{ + struct bttv_sub_device *sub = to_bttv_sub_dev(dev); + kfree(sub); +} + +int bttv_sub_add_device(struct bttv_core *core, char *name) +{ + struct bttv_sub_device *sub; + int err; + + sub = kzalloc_obj(*sub); + if (NULL == sub) + return -ENOMEM; + + sub->core = core; + sub->dev.parent = &core->pci->dev; + sub->dev.bus = &bttv_sub_bus_type; + sub->dev.release = release_sub_device; + dev_set_name(&sub->dev, "%s%d", name, core->nr); + + err = device_register(&sub->dev); + if (0 != err) { + put_device(&sub->dev); + return err; + } + pr_info("%d: add subdevice \"%s\"\n", core->nr, dev_name(&sub->dev)); + list_add_tail(&sub->list,&core->subs); + return 0; +} + +int bttv_sub_del_devices(struct bttv_core *core) +{ + struct bttv_sub_device *sub, *save; + + list_for_each_entry_safe(sub, save, &core->subs, list) { + list_del(&sub->list); + device_unregister(&sub->dev); + } + return 0; +} + +/* ----------------------------------------------------------------------- */ +/* external: sub-driver register/unregister */ + +int bttv_sub_register(struct bttv_sub_driver *sub, char *wanted) +{ + sub->drv.bus = &bttv_sub_bus_type; + snprintf(sub->wanted,sizeof(sub->wanted),"%s",wanted); + return driver_register(&sub->drv); +} +EXPORT_SYMBOL(bttv_sub_register); + +int bttv_sub_unregister(struct bttv_sub_driver *sub) +{ + driver_unregister(&sub->drv); + return 0; +} +EXPORT_SYMBOL(bttv_sub_unregister); + +/* ----------------------------------------------------------------------- */ +/* external: gpio access functions */ + +void bttv_gpio_inout(struct bttv_core *core, u32 mask, u32 outbits) +{ + struct bttv *btv = container_of(core, struct bttv, c); + unsigned long flags; + u32 data; + + spin_lock_irqsave(&btv->gpio_lock,flags); + data = btread(BT848_GPIO_OUT_EN); + data = data & ~mask; + data = data | (mask & outbits); + btwrite(data,BT848_GPIO_OUT_EN); + spin_unlock_irqrestore(&btv->gpio_lock,flags); +} + +u32 bttv_gpio_read(struct bttv_core *core) +{ + struct bttv *btv = container_of(core, struct bttv, c); + u32 value; + + value = btread(BT848_GPIO_DATA); + return value; +} + +void bttv_gpio_write(struct bttv_core *core, u32 value) +{ + struct bttv *btv = container_of(core, struct bttv, c); + + btwrite(value,BT848_GPIO_DATA); +} + +void bttv_gpio_bits(struct bttv_core *core, u32 mask, u32 bits) +{ + struct bttv *btv = container_of(core, struct bttv, c); + unsigned long flags; + u32 data; + + spin_lock_irqsave(&btv->gpio_lock,flags); + data = btread(BT848_GPIO_DATA); + data = data & ~mask; + data = data | (mask & bits); + btwrite(data,BT848_GPIO_DATA); + spin_unlock_irqrestore(&btv->gpio_lock,flags); +} diff --git a/drivers/media/pci/bt8xx/bttv-i2c.c b/drivers/media/pci/bt8xx/bttv-i2c.c new file mode 100644 index 000000000..4a8a3f80c --- /dev/null +++ b/drivers/media/pci/bt8xx/bttv-i2c.c @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + + bttv-i2c.c -- all the i2c code is here + + bttv - Bt848 frame grabber driver + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 1999-2003 Gerd Knorr + + (c) 2005 Mauro Carvalho Chehab + - Multituner support and i2c address binding + + +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include + +#include "bttvp.h" +#include +#include +#include + +static int i2c_debug; +static int i2c_hw; +static int i2c_scan; +module_param(i2c_debug, int, 0644); +MODULE_PARM_DESC(i2c_debug, "configure i2c debug level"); +module_param(i2c_hw, int, 0444); +MODULE_PARM_DESC(i2c_hw, "force use of hardware i2c support, instead of software bitbang"); +module_param(i2c_scan, int, 0444); +MODULE_PARM_DESC(i2c_scan,"scan i2c bus at insmod time"); + +static unsigned int i2c_udelay = 5; +module_param(i2c_udelay, int, 0444); +MODULE_PARM_DESC(i2c_udelay, "soft i2c delay at insmod time, in usecs (should be 5 or higher). Lower value means higher bus speed."); + +/* ----------------------------------------------------------------------- */ +/* I2C functions - bitbanging adapter (software i2c) */ + +static void bttv_bit_setscl(void *data, int state) +{ + struct bttv *btv = (struct bttv*)data; + + if (state) + btv->i2c_state |= 0x02; + else + btv->i2c_state &= ~0x02; + btwrite(btv->i2c_state, BT848_I2C); + btread(BT848_I2C); +} + +static void bttv_bit_setsda(void *data, int state) +{ + struct bttv *btv = (struct bttv*)data; + + if (state) + btv->i2c_state |= 0x01; + else + btv->i2c_state &= ~0x01; + btwrite(btv->i2c_state, BT848_I2C); + btread(BT848_I2C); +} + +static int bttv_bit_getscl(void *data) +{ + struct bttv *btv = (struct bttv*)data; + int state; + + state = btread(BT848_I2C) & 0x02 ? 1 : 0; + return state; +} + +static int bttv_bit_getsda(void *data) +{ + struct bttv *btv = (struct bttv*)data; + int state; + + state = btread(BT848_I2C) & 0x01; + return state; +} + +static const struct i2c_algo_bit_data bttv_i2c_algo_bit_template = { + .setsda = bttv_bit_setsda, + .setscl = bttv_bit_setscl, + .getsda = bttv_bit_getsda, + .getscl = bttv_bit_getscl, + .udelay = 16, + .timeout = 200, +}; + +/* ----------------------------------------------------------------------- */ +/* I2C functions - hardware i2c */ + +static u32 functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_EMUL; +} + +static int +bttv_i2c_wait_done(struct bttv *btv) +{ + int rc = 0; + + /* timeout */ + if (wait_event_interruptible_timeout(btv->i2c_queue, + btv->i2c_done, msecs_to_jiffies(85)) == -ERESTARTSYS) + rc = -EIO; + + if (btv->i2c_done & BT848_INT_RACK) + rc = 1; + btv->i2c_done = 0; + return rc; +} + +#define I2C_HW (BT878_I2C_MODE | BT848_I2C_SYNC |\ + BT848_I2C_SCL | BT848_I2C_SDA) + +static int +bttv_i2c_sendbytes(struct bttv *btv, const struct i2c_msg *msg, int last) +{ + u32 xmit; + int retval,cnt; + + /* sanity checks */ + if (0 == msg->len) + return -EINVAL; + + /* start, address + first byte */ + xmit = (msg->addr << 25) | (msg->buf[0] << 16) | I2C_HW; + if (msg->len > 1 || !last) + xmit |= BT878_I2C_NOSTOP; + btwrite(xmit, BT848_I2C); + retval = bttv_i2c_wait_done(btv); + if (retval < 0) + goto err; + if (retval == 0) + goto eio; + if (i2c_debug) { + pr_cont(" addr << 1, msg->buf[0]); + } + + for (cnt = 1; cnt < msg->len; cnt++ ) { + /* following bytes */ + xmit = (msg->buf[cnt] << 24) | I2C_HW | BT878_I2C_NOSTART; + if (cnt < msg->len-1 || !last) + xmit |= BT878_I2C_NOSTOP; + btwrite(xmit, BT848_I2C); + retval = bttv_i2c_wait_done(btv); + if (retval < 0) + goto err; + if (retval == 0) + goto eio; + if (i2c_debug) + pr_cont(" %02x", msg->buf[cnt]); + } + if (i2c_debug && !(xmit & BT878_I2C_NOSTOP)) + pr_cont(">\n"); + return msg->len; + + eio: + retval = -EIO; + err: + if (i2c_debug) + pr_cont(" ERR: %d\n",retval); + return retval; +} + +static int +bttv_i2c_readbytes(struct bttv *btv, const struct i2c_msg *msg, int last) +{ + u32 xmit; + u32 cnt; + int retval; + + for (cnt = 0; cnt < msg->len; cnt++) { + xmit = (msg->addr << 25) | (1 << 24) | I2C_HW; + if (cnt < msg->len-1) + xmit |= BT848_I2C_W3B; + if (cnt < msg->len-1 || !last) + xmit |= BT878_I2C_NOSTOP; + if (cnt) + xmit |= BT878_I2C_NOSTART; + + if (i2c_debug) { + if (!(xmit & BT878_I2C_NOSTART)) + pr_cont(" addr << 1) +1); + } + + btwrite(xmit, BT848_I2C); + retval = bttv_i2c_wait_done(btv); + if (retval < 0) + goto err; + if (retval == 0) + goto eio; + msg->buf[cnt] = ((u32)btread(BT848_I2C) >> 8) & 0xff; + if (i2c_debug) { + pr_cont(" =%02x", msg->buf[cnt]); + } + if (i2c_debug && !(xmit & BT878_I2C_NOSTOP)) + pr_cont(" >\n"); + } + + + return msg->len; + + eio: + retval = -EIO; + err: + if (i2c_debug) + pr_cont(" ERR: %d\n",retval); + return retval; +} + +static int bttv_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, int num) +{ + struct v4l2_device *v4l2_dev = i2c_get_adapdata(i2c_adap); + struct bttv *btv = to_bttv(v4l2_dev); + int retval = 0; + int i; + + if (i2c_debug) + pr_debug("bt-i2c:"); + + btwrite(BT848_INT_I2CDONE|BT848_INT_RACK, BT848_INT_STAT); + for (i = 0 ; i < num; i++) { + if (msgs[i].flags & I2C_M_RD) { + /* read */ + retval = bttv_i2c_readbytes(btv, &msgs[i], i+1 == num); + if (retval < 0) + goto err; + } else { + /* write */ + retval = bttv_i2c_sendbytes(btv, &msgs[i], i+1 == num); + if (retval < 0) + goto err; + } + } + return num; + + err: + return retval; +} + +static const struct i2c_algorithm bttv_algo = { + .master_xfer = bttv_i2c_xfer, + .functionality = functionality, +}; + +/* ----------------------------------------------------------------------- */ +/* I2C functions - common stuff */ + +/* read I2C */ +int bttv_I2CRead(struct bttv *btv, unsigned char addr, char *probe_for) +{ + unsigned char buffer = 0; + + if (0 != btv->i2c_rc) + return -1; + if (bttv_verbose && NULL != probe_for) + pr_info("%d: i2c: checking for %s @ 0x%02x... ", + btv->c.nr, probe_for, addr); + btv->i2c_client.addr = addr >> 1; + if (1 != i2c_master_recv(&btv->i2c_client, &buffer, 1)) { + if (NULL != probe_for) { + if (bttv_verbose) + pr_cont("not found\n"); + } else + pr_warn("%d: i2c read 0x%x: error\n", + btv->c.nr, addr); + return -1; + } + if (bttv_verbose && NULL != probe_for) + pr_cont("found\n"); + return buffer; +} + +/* write I2C */ +int bttv_I2CWrite(struct bttv *btv, unsigned char addr, unsigned char b1, + unsigned char b2, int both) +{ + unsigned char buffer[2]; + int bytes = both ? 2 : 1; + + if (0 != btv->i2c_rc) + return -1; + btv->i2c_client.addr = addr >> 1; + buffer[0] = b1; + buffer[1] = b2; + if (bytes != i2c_master_send(&btv->i2c_client, buffer, bytes)) + return -1; + return 0; +} + +/* read EEPROM content */ +void bttv_readee(struct bttv *btv, unsigned char *eedata, int addr) +{ + memset(eedata, 0, 256); + if (0 != btv->i2c_rc) + return; + btv->i2c_client.addr = addr >> 1; + tveeprom_read(&btv->i2c_client, eedata, 256); +} + +static char *i2c_devs[128] = { + [ 0x1c >> 1 ] = "lgdt330x", + [ 0x30 >> 1 ] = "IR (hauppauge)", + [ 0x80 >> 1 ] = "msp34xx", + [ 0x86 >> 1 ] = "tda9887", + [ 0xa0 >> 1 ] = "eeprom", + [ 0xc0 >> 1 ] = "tuner (analog)", + [ 0xc2 >> 1 ] = "tuner (analog)", +}; + +static void do_i2c_scan(char *name, struct i2c_client *c) +{ + unsigned char buf; + int i,rc; + + for (i = 0; i < ARRAY_SIZE(i2c_devs); i++) { + c->addr = i; + rc = i2c_master_recv(c,&buf,0); + if (rc < 0) + continue; + pr_info("%s: i2c scan: found device @ 0x%x [%s]\n", + name, i << 1, i2c_devs[i] ? i2c_devs[i] : "???"); + } +} + +/* init + register i2c adapter */ +int init_bttv_i2c(struct bttv *btv) +{ + strscpy(btv->i2c_client.name, "bttv internal", I2C_NAME_SIZE); + + if (i2c_hw) + btv->use_i2c_hw = 1; + if (btv->use_i2c_hw) { + /* bt878 */ + strscpy(btv->c.i2c_adap.name, "bt878", + sizeof(btv->c.i2c_adap.name)); + btv->c.i2c_adap.algo = &bttv_algo; + } else { + /* bt848 */ + /* Prevents usage of invalid delay values */ + if (i2c_udelay<5) + i2c_udelay=5; + + strscpy(btv->c.i2c_adap.name, "bttv", + sizeof(btv->c.i2c_adap.name)); + btv->i2c_algo = bttv_i2c_algo_bit_template; + btv->i2c_algo.udelay = i2c_udelay; + btv->i2c_algo.data = btv; + btv->c.i2c_adap.algo_data = &btv->i2c_algo; + } + btv->c.i2c_adap.owner = THIS_MODULE; + + btv->c.i2c_adap.dev.parent = &btv->c.pci->dev; + snprintf(btv->c.i2c_adap.name, sizeof(btv->c.i2c_adap.name), + "bt%d #%d [%s]", btv->id, btv->c.nr, + btv->use_i2c_hw ? "hw" : "sw"); + + i2c_set_adapdata(&btv->c.i2c_adap, &btv->c.v4l2_dev); + btv->i2c_client.adapter = &btv->c.i2c_adap; + + + if (btv->use_i2c_hw) { + btv->i2c_rc = i2c_add_adapter(&btv->c.i2c_adap); + } else { + bttv_bit_setscl(btv,1); + bttv_bit_setsda(btv,1); + btv->i2c_rc = i2c_bit_add_bus(&btv->c.i2c_adap); + } + if (0 == btv->i2c_rc && i2c_scan) + do_i2c_scan(btv->c.v4l2_dev.name, &btv->i2c_client); + + return btv->i2c_rc; +} + +int fini_bttv_i2c(struct bttv *btv) +{ + if (btv->i2c_rc == 0) + i2c_del_adapter(&btv->c.i2c_adap); + + return 0; +} diff --git a/drivers/media/pci/bt8xx/bttv-if.c b/drivers/media/pci/bt8xx/bttv-if.c new file mode 100644 index 000000000..363c84bac --- /dev/null +++ b/drivers/media/pci/bt8xx/bttv-if.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + + bttv-if.c -- old gpio interface to other kernel modules + don't use in new code, will go away in 2.7 + have a look at bttv-gpio.c instead. + + bttv - Bt848 frame grabber driver + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 1999-2003 Gerd Knorr + + +*/ + +#include +#include +#include +#include + +#include "bttvp.h" + +EXPORT_SYMBOL(bttv_get_pcidev); +EXPORT_SYMBOL(bttv_gpio_enable); +EXPORT_SYMBOL(bttv_read_gpio); +EXPORT_SYMBOL(bttv_write_gpio); + +/* ----------------------------------------------------------------------- */ +/* Exported functions - for other modules which want to access the */ +/* gpio ports (IR for example) */ +/* see bttv.h for comments */ + +struct pci_dev* bttv_get_pcidev(unsigned int card) +{ + if (card >= bttv_num) + return NULL; + if (!bttvs[card]) + return NULL; + + return bttvs[card]->c.pci; +} + + +int bttv_gpio_enable(unsigned int card, unsigned long mask, unsigned long data) +{ + struct bttv *btv; + + if (card >= bttv_num) { + return -EINVAL; + } + + btv = bttvs[card]; + if (!btv) + return -ENODEV; + + gpio_inout(mask,data); + if (bttv_gpio) + bttv_gpio_tracking(btv,"extern enable"); + return 0; +} + +int bttv_read_gpio(unsigned int card, unsigned long *data) +{ + struct bttv *btv; + + if (card >= bttv_num) { + return -EINVAL; + } + + btv = bttvs[card]; + if (!btv) + return -ENODEV; + + if(btv->shutdown) { + return -ENODEV; + } + +/* prior setting BT848_GPIO_REG_INP is (probably) not needed + because we set direct input on init */ + *data = gpio_read(); + return 0; +} + +int bttv_write_gpio(unsigned int card, unsigned long mask, unsigned long data) +{ + struct bttv *btv; + + if (card >= bttv_num) { + return -EINVAL; + } + + btv = bttvs[card]; + if (!btv) + return -ENODEV; + +/* prior setting BT848_GPIO_REG_INP is (probably) not needed + because direct input is set on init */ + gpio_bits(mask,data); + if (bttv_gpio) + bttv_gpio_tracking(btv,"extern write"); + return 0; +} diff --git a/drivers/media/pci/bt8xx/bttv-input.c b/drivers/media/pci/bt8xx/bttv-input.c new file mode 100644 index 000000000..f70447650 --- /dev/null +++ b/drivers/media/pci/bt8xx/bttv-input.c @@ -0,0 +1,580 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright (c) 2003 Gerd Knorr + * Copyright (c) 2003 Pavel Machek + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +#include "bttv.h" +#include "bttvp.h" + + +static int ir_debug; +module_param(ir_debug, int, 0644); + +static int ir_rc5_remote_gap = 885; +module_param(ir_rc5_remote_gap, int, 0644); + +#undef dprintk +#define dprintk(fmt, ...) \ +do { \ + if (ir_debug >= 1) \ + pr_info(fmt, ##__VA_ARGS__); \ +} while (0) + +#define DEVNAME "bttv-input" + +#define MODULE_NAME "bttv" + +/* ---------------------------------------------------------------------- */ + +static void ir_handle_key(struct bttv *btv) +{ + struct bttv_ir *ir = btv->remote; + u32 gpio,data; + + /* read gpio value */ + gpio = bttv_gpio_read(&btv->c); + if (ir->polling) { + if (ir->last_gpio == gpio) + return; + ir->last_gpio = gpio; + } + + /* extract data */ + data = ir_extract_bits(gpio, ir->mask_keycode); + dprintk("irq gpio=0x%x code=%d | %s%s%s\n", + gpio, data, + ir->polling ? "poll" : "irq", + (gpio & ir->mask_keydown) ? " down" : "", + (gpio & ir->mask_keyup) ? " up" : ""); + + if ((ir->mask_keydown && (gpio & ir->mask_keydown)) || + (ir->mask_keyup && !(gpio & ir->mask_keyup))) { + rc_keydown_notimeout(ir->dev, RC_PROTO_UNKNOWN, data, 0); + } else { + /* HACK: Probably, ir->mask_keydown is missing + for this board */ + if (btv->c.type == BTTV_BOARD_WINFAST2000) + rc_keydown_notimeout(ir->dev, RC_PROTO_UNKNOWN, data, + 0); + + rc_keyup(ir->dev); + } +} + +static void ir_enltv_handle_key(struct bttv *btv) +{ + struct bttv_ir *ir = btv->remote; + u32 gpio, data, keyup; + + /* read gpio value */ + gpio = bttv_gpio_read(&btv->c); + + /* extract data */ + data = ir_extract_bits(gpio, ir->mask_keycode); + + /* Check if it is keyup */ + keyup = (gpio & ir->mask_keyup) ? 1UL << 31 : 0; + + if ((ir->last_gpio & 0x7f) != data) { + dprintk("gpio=0x%x code=%d | %s\n", + gpio, data, + (gpio & ir->mask_keyup) ? " up" : "up/down"); + + rc_keydown_notimeout(ir->dev, RC_PROTO_UNKNOWN, data, 0); + if (keyup) + rc_keyup(ir->dev); + } else { + if ((ir->last_gpio & 1UL << 31) == keyup) + return; + + dprintk("(cnt) gpio=0x%x code=%d | %s\n", + gpio, data, + (gpio & ir->mask_keyup) ? " up" : "down"); + + if (keyup) + rc_keyup(ir->dev); + else + rc_keydown_notimeout(ir->dev, RC_PROTO_UNKNOWN, data, + 0); + } + + ir->last_gpio = data | keyup; +} + +static int bttv_rc5_irq(struct bttv *btv); + +void bttv_input_irq(struct bttv *btv) +{ + struct bttv_ir *ir = btv->remote; + + if (ir->rc5_gpio) + bttv_rc5_irq(btv); + else if (!ir->polling) + ir_handle_key(btv); +} + +static void bttv_input_timer(struct timer_list *t) +{ + struct bttv_ir *ir = timer_container_of(ir, t, timer); + struct bttv *btv = ir->btv; + + if (btv->c.type == BTTV_BOARD_ENLTV_FM_2) + ir_enltv_handle_key(btv); + else + ir_handle_key(btv); + mod_timer(&ir->timer, jiffies + msecs_to_jiffies(ir->polling)); +} + +/* + * FIXME: Nebula digi uses the legacy way to decode RC5, instead of relying + * on the rc-core way. As we need to be sure that both IRQ transitions are + * properly triggered, Better to touch it only with this hardware for + * testing. + */ + +#define RC5_START(x) (((x) >> 12) & 0x03) +#define RC5_TOGGLE(x) (((x) >> 11) & 0x01) +#define RC5_ADDR(x) (((x) >> 6) & 0x1f) +#define RC5_INSTR(x) (((x) >> 0) & 0x3f) + +/* decode raw bit pattern to RC5 code */ +static u32 bttv_rc5_decode(unsigned int code) +{ + unsigned int org_code = code; + unsigned int pair; + unsigned int rc5 = 0; + int i; + + for (i = 0; i < 14; ++i) { + pair = code & 0x3; + code >>= 2; + + rc5 <<= 1; + switch (pair) { + case 0: + case 2: + break; + case 1: + rc5 |= 1; + break; + case 3: + dprintk("rc5_decode(%x) bad code\n", + org_code); + return 0; + } + } + dprintk("code=%x, rc5=%x, start=%x, toggle=%x, address=%x, instr=%x\n", + rc5, org_code, RC5_START(rc5), + RC5_TOGGLE(rc5), RC5_ADDR(rc5), RC5_INSTR(rc5)); + return rc5; +} + +static void bttv_rc5_timer_end(struct timer_list *t) +{ + struct bttv_ir *ir = timer_container_of(ir, t, timer); + ktime_t tv; + u32 gap, rc5, scancode; + u8 toggle, command, system; + + /* get time */ + tv = ktime_get(); + + gap = ktime_to_us(ktime_sub(tv, ir->base_time)); + /* avoid overflow with gap >1s */ + if (gap > USEC_PER_SEC) { + gap = 200000; + } + /* signal we're ready to start a new code */ + ir->active = false; + + /* Allow some timer jitter (RC5 is ~24ms anyway so this is ok) */ + if (gap < 28000) { + dprintk("spurious timer_end\n"); + return; + } + + if (ir->last_bit < 20) { + /* ignore spurious codes (caused by light/other remotes) */ + dprintk("short code: %x\n", ir->code); + return; + } + + ir->code = (ir->code << ir->shift_by) | 1; + rc5 = bttv_rc5_decode(ir->code); + + toggle = RC5_TOGGLE(rc5); + system = RC5_ADDR(rc5); + command = RC5_INSTR(rc5); + + switch (RC5_START(rc5)) { + case 0x3: + break; + case 0x2: + command += 0x40; + break; + default: + return; + } + + scancode = RC_SCANCODE_RC5(system, command); + rc_keydown(ir->dev, RC_PROTO_RC5, scancode, toggle); + dprintk("scancode %x, toggle %x\n", scancode, toggle); +} + +static int bttv_rc5_irq(struct bttv *btv) +{ + struct bttv_ir *ir = btv->remote; + ktime_t tv; + u32 gpio; + u32 gap; + unsigned long current_jiffies; + + /* read gpio port */ + gpio = bttv_gpio_read(&btv->c); + + /* get time of bit */ + current_jiffies = jiffies; + tv = ktime_get(); + + gap = ktime_to_us(ktime_sub(tv, ir->base_time)); + /* avoid overflow with gap >1s */ + if (gap > USEC_PER_SEC) { + gap = 200000; + } + + dprintk("RC5 IRQ: gap %d us for %s\n", + gap, (gpio & 0x20) ? "mark" : "space"); + + /* remote IRQ? */ + if (!(gpio & 0x20)) + return 0; + + /* active code => add bit */ + if (ir->active) { + /* only if in the code (otherwise spurious IRQ or timer + late) */ + if (ir->last_bit < 28) { + ir->last_bit = (gap - ir_rc5_remote_gap / 2) / + ir_rc5_remote_gap; + ir->code |= 1 << ir->last_bit; + } + /* starting new code */ + } else { + ir->active = true; + ir->code = 0; + ir->base_time = tv; + ir->last_bit = 0; + + mod_timer(&ir->timer, current_jiffies + msecs_to_jiffies(30)); + } + + /* toggle GPIO pin 4 to reset the irq */ + bttv_gpio_write(&btv->c, gpio & ~(1 << 4)); + bttv_gpio_write(&btv->c, gpio | (1 << 4)); + return 1; +} + +/* ---------------------------------------------------------------------- */ + +static void bttv_ir_start(struct bttv_ir *ir) +{ + if (ir->polling) { + timer_setup(&ir->timer, bttv_input_timer, 0); + ir->timer.expires = jiffies + msecs_to_jiffies(1000); + add_timer(&ir->timer); + } else if (ir->rc5_gpio) { + /* set timer_end for code completion */ + timer_setup(&ir->timer, bttv_rc5_timer_end, 0); + ir->shift_by = 1; + ir->rc5_remote_gap = ir_rc5_remote_gap; + } +} + +static void bttv_ir_stop(struct bttv *btv) +{ + if (btv->remote->polling) + timer_delete_sync(&btv->remote->timer); + + if (btv->remote->rc5_gpio) { + u32 gpio; + + timer_delete_sync(&btv->remote->timer); + + gpio = bttv_gpio_read(&btv->c); + bttv_gpio_write(&btv->c, gpio & ~(1 << 4)); + } +} + +/* + * Get_key functions used by I2C remotes + */ + +static int get_key_pv951(struct IR_i2c *ir, enum rc_proto *protocol, + u32 *scancode, u8 *toggle) +{ + int rc; + unsigned char b; + + /* poll IR chip */ + rc = i2c_master_recv(ir->c, &b, 1); + if (rc != 1) { + dprintk("read error\n"); + if (rc < 0) + return rc; + return -EIO; + } + + /* ignore 0xaa */ + if (b==0xaa) + return 0; + dprintk("key %02x\n", b); + + /* + * NOTE: + * lirc_i2c maps the pv951 code as: + * addr = 0x61D6 + * cmd = bit_reverse (b) + * So, it seems that this device uses NEC extended + * I decided to not fix the table, due to two reasons: + * 1) Without the actual device, this is only a guess; + * 2) As the addr is not reported via I2C, nor can be changed, + * the device is bound to the vendor-provided RC. + */ + + *protocol = RC_PROTO_UNKNOWN; + *scancode = b; + *toggle = 0; + return 1; +} + +/* Instantiate the I2C IR receiver device, if present */ +void init_bttv_i2c_ir(struct bttv *btv) +{ + static const unsigned short addr_list[] = { + 0x1a, 0x18, 0x64, 0x30, 0x71, + I2C_CLIENT_END + }; + struct i2c_board_info info; + struct i2c_client *i2c_dev; + + if (0 != btv->i2c_rc) + return; + + memset(&info, 0, sizeof(struct i2c_board_info)); + memset(&btv->init_data, 0, sizeof(btv->init_data)); + strscpy(info.type, "ir_video", I2C_NAME_SIZE); + + switch (btv->c.type) { + case BTTV_BOARD_PV951: + btv->init_data.name = "PV951"; + btv->init_data.get_key = get_key_pv951; + btv->init_data.ir_codes = RC_MAP_PV951; + info.addr = 0x4b; + break; + } + + if (btv->init_data.name) { + info.platform_data = &btv->init_data; + i2c_dev = i2c_new_client_device(&btv->c.i2c_adap, &info); + } else { + /* + * The external IR receiver is at i2c address 0x34 (0x35 for + * reads). Future Hauppauge cards will have an internal + * receiver at 0x30 (0x31 for reads). In theory, both can be + * fitted, and Hauppauge suggest an external overrides an + * internal. + * That's why we probe 0x1a (~0x34) first. CB + */ + i2c_dev = i2c_new_scanned_device(&btv->c.i2c_adap, &info, addr_list, NULL); + } + if (IS_ERR(i2c_dev)) + return; + +#if defined(CONFIG_MODULES) && defined(MODULE) + request_module("ir-kbd-i2c"); +#endif +} + +int bttv_input_init(struct bttv *btv) +{ + struct bttv_ir *ir; + char *ir_codes = NULL; + struct rc_dev *rc; + int err = -ENOMEM; + + if (!btv->has_remote) + return -ENODEV; + + ir = kzalloc_obj(*ir); + rc = rc_allocate_device(RC_DRIVER_SCANCODE); + if (!ir || !rc) + goto err_out_free; + + /* detect & configure */ + switch (btv->c.type) { + case BTTV_BOARD_AVERMEDIA: + case BTTV_BOARD_AVPHONE98: + case BTTV_BOARD_AVERMEDIA98: + ir_codes = RC_MAP_AVERMEDIA; + ir->mask_keycode = 0xf88000; + ir->mask_keydown = 0x010000; + ir->polling = 50; // ms + break; + + case BTTV_BOARD_AVDVBT_761: + case BTTV_BOARD_AVDVBT_771: + ir_codes = RC_MAP_AVERMEDIA_DVBT; + ir->mask_keycode = 0x0f00c0; + ir->mask_keydown = 0x000020; + ir->polling = 50; // ms + break; + + case BTTV_BOARD_PXELVWPLTVPAK: + ir_codes = RC_MAP_PIXELVIEW; + ir->mask_keycode = 0x003e00; + ir->mask_keyup = 0x010000; + ir->polling = 50; // ms + break; + case BTTV_BOARD_PV_M4900: + case BTTV_BOARD_PV_BT878P_9B: + case BTTV_BOARD_PV_BT878P_PLUS: + ir_codes = RC_MAP_PIXELVIEW; + ir->mask_keycode = 0x001f00; + ir->mask_keyup = 0x008000; + ir->polling = 50; // ms + break; + + case BTTV_BOARD_WINFAST2000: + ir_codes = RC_MAP_WINFAST; + ir->mask_keycode = 0x1f8; + break; + case BTTV_BOARD_MAGICTVIEW061: + case BTTV_BOARD_MAGICTVIEW063: + ir_codes = RC_MAP_WINFAST; + ir->mask_keycode = 0x0008e000; + ir->mask_keydown = 0x00200000; + break; + case BTTV_BOARD_APAC_VIEWCOMP: + ir_codes = RC_MAP_APAC_VIEWCOMP; + ir->mask_keycode = 0x001f00; + ir->mask_keyup = 0x008000; + ir->polling = 50; // ms + break; + case BTTV_BOARD_ASKEY_CPH03X: + case BTTV_BOARD_CONCEPTRONIC_CTVFMI2: + case BTTV_BOARD_CONTVFMI: + case BTTV_BOARD_KWORLD_VSTREAM_XPERT: + ir_codes = RC_MAP_PIXELVIEW; + ir->mask_keycode = 0x001F00; + ir->mask_keyup = 0x006000; + ir->polling = 50; // ms + break; + case BTTV_BOARD_NEBULA_DIGITV: + ir_codes = RC_MAP_NEBULA; + ir->rc5_gpio = true; + break; + case BTTV_BOARD_MACHTV_MAGICTV: + ir_codes = RC_MAP_APAC_VIEWCOMP; + ir->mask_keycode = 0x001F00; + ir->mask_keyup = 0x004000; + ir->polling = 50; /* ms */ + break; + case BTTV_BOARD_KOZUMI_KTV_01C: + ir_codes = RC_MAP_PCTV_SEDNA; + ir->mask_keycode = 0x001f00; + ir->mask_keyup = 0x006000; + ir->polling = 50; /* ms */ + break; + case BTTV_BOARD_ENLTV_FM_2: + ir_codes = RC_MAP_ENCORE_ENLTV2; + ir->mask_keycode = 0x00fd00; + ir->mask_keyup = 0x000080; + ir->polling = 1; /* ms */ + ir->last_gpio = ir_extract_bits(bttv_gpio_read(&btv->c), + ir->mask_keycode); + break; + } + + if (!ir_codes) { + dprintk("Ooops: IR config error [card=%d]\n", btv->c.type); + err = -ENODEV; + goto err_out_free; + } + + if (ir->rc5_gpio) { + u32 gpio; + /* enable remote irq */ + bttv_gpio_inout(&btv->c, (1 << 4), 1 << 4); + gpio = bttv_gpio_read(&btv->c); + bttv_gpio_write(&btv->c, gpio & ~(1 << 4)); + bttv_gpio_write(&btv->c, gpio | (1 << 4)); + } else { + /* init hardware-specific stuff */ + bttv_gpio_inout(&btv->c, ir->mask_keycode | ir->mask_keydown, 0); + } + + /* init input device */ + ir->dev = rc; + ir->btv = btv; + + snprintf(ir->name, sizeof(ir->name), "bttv IR (card=%d)", + btv->c.type); + snprintf(ir->phys, sizeof(ir->phys), "pci-%s/ir0", + pci_name(btv->c.pci)); + + rc->device_name = ir->name; + rc->input_phys = ir->phys; + rc->input_id.bustype = BUS_PCI; + rc->input_id.version = 1; + if (btv->c.pci->subsystem_vendor) { + rc->input_id.vendor = btv->c.pci->subsystem_vendor; + rc->input_id.product = btv->c.pci->subsystem_device; + } else { + rc->input_id.vendor = btv->c.pci->vendor; + rc->input_id.product = btv->c.pci->device; + } + rc->dev.parent = &btv->c.pci->dev; + rc->map_name = ir_codes; + rc->driver_name = MODULE_NAME; + + btv->remote = ir; + bttv_ir_start(ir); + + /* all done */ + err = rc_register_device(rc); + if (err) + goto err_out_stop; + + return 0; + + err_out_stop: + bttv_ir_stop(btv); + btv->remote = NULL; + err_out_free: + rc_free_device(rc); + kfree(ir); + return err; +} + +void bttv_input_fini(struct bttv *btv) +{ + if (btv->remote == NULL) + return; + + rc_unregister_device(btv->remote->dev); + bttv_ir_stop(btv); + rc_free_device(btv->remote->dev); + kfree(btv->remote); + btv->remote = NULL; +} diff --git a/drivers/media/pci/bt8xx/bttv-risc.c b/drivers/media/pci/bt8xx/bttv-risc.c new file mode 100644 index 000000000..79581cd7b --- /dev/null +++ b/drivers/media/pci/bt8xx/bttv-risc.c @@ -0,0 +1,804 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + + bttv-risc.c -- interfaces to other kernel modules + + bttv risc code handling + - memory management + - generation + + (c) 2000-2003 Gerd Knorr + + +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bttvp.h" + +#define VCR_HACK_LINES 4 + +/* ---------------------------------------------------------- */ +/* risc code generators */ + +int +bttv_risc_packed(struct bttv *btv, struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int offset, unsigned int bpl, + unsigned int padding, unsigned int skip_lines, + unsigned int store_lines) +{ + u32 instructions,line,todo; + struct scatterlist *sg; + __le32 *rp; + int rc; + + /* estimate risc mem: worst case is one write per page border + + one write per scan line + sync + jump (all 2 dwords). padding + can cause next bpl to start close to a page border. First DMA + region may be smaller than PAGE_SIZE */ + instructions = skip_lines * 4; + instructions += (1 + ((bpl + padding) * store_lines) + / PAGE_SIZE + store_lines) * 8; + instructions += 2 * 8; + if ((rc = btcx_riscmem_alloc(btv->c.pci,risc,instructions)) < 0) + return rc; + + /* sync instruction */ + rp = risc->cpu; + *(rp++) = cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1); + *(rp++) = cpu_to_le32(0); + + while (skip_lines-- > 0) { + *(rp++) = cpu_to_le32(BT848_RISC_SKIP | BT848_RISC_SOL | + BT848_RISC_EOL | bpl); + } + + /* scan lines */ + sg = sglist; + for (line = 0; line < store_lines; line++) { + if ((line >= (store_lines - VCR_HACK_LINES)) && + btv->opt_vcr_hack) + continue; + while (offset && offset >= sg_dma_len(sg)) { + offset -= sg_dma_len(sg); + sg = sg_next(sg); + } + if (bpl <= sg_dma_len(sg)-offset) { + /* fits into current chunk */ + *(rp++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_SOL| + BT848_RISC_EOL|bpl); + *(rp++)=cpu_to_le32(sg_dma_address(sg)+offset); + offset+=bpl; + } else { + /* scanline needs to be split */ + todo = bpl; + *(rp++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_SOL| + (sg_dma_len(sg)-offset)); + *(rp++)=cpu_to_le32(sg_dma_address(sg)+offset); + todo -= (sg_dma_len(sg)-offset); + offset = 0; + sg = sg_next(sg); + while (todo > sg_dma_len(sg)) { + *(rp++)=cpu_to_le32(BT848_RISC_WRITE| + sg_dma_len(sg)); + *(rp++)=cpu_to_le32(sg_dma_address(sg)); + todo -= sg_dma_len(sg); + sg = sg_next(sg); + } + *(rp++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_EOL| + todo); + *(rp++)=cpu_to_le32(sg_dma_address(sg)); + offset += todo; + } + offset += padding; + } + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + WARN_ON((risc->jmp - risc->cpu + 2) * sizeof(*risc->cpu) > risc->size); + return 0; +} + +static int +bttv_risc_planar(struct bttv *btv, struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int yoffset, unsigned int ybpl, + unsigned int ypadding, unsigned int ylines, + unsigned int uoffset, unsigned int voffset, + unsigned int hshift, unsigned int vshift, + unsigned int cpadding) +{ + unsigned int instructions,line,todo,ylen,chroma; + __le32 *rp; + u32 ri; + struct scatterlist *ysg; + struct scatterlist *usg; + struct scatterlist *vsg; + int topfield = (0 == yoffset); + int rc; + + /* estimate risc mem: worst case is one write per page border + + one write per scan line (5 dwords) + plus sync + jump (2 dwords) */ + instructions = ((3 + (ybpl + ypadding) * ylines * 2) + / PAGE_SIZE) + ylines; + instructions += 2; + if ((rc = btcx_riscmem_alloc(btv->c.pci,risc,instructions*4*5)) < 0) + return rc; + + /* sync instruction */ + rp = risc->cpu; + *(rp++) = cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM3); + *(rp++) = cpu_to_le32(0); + + /* scan lines */ + ysg = sglist; + usg = sglist; + vsg = sglist; + for (line = 0; line < ylines; line++) { + if ((btv->opt_vcr_hack) && + (line >= (ylines - VCR_HACK_LINES))) + continue; + switch (vshift) { + case 0: + chroma = 1; + break; + case 1: + if (topfield) + chroma = ((line & 1) == 0); + else + chroma = ((line & 1) == 1); + break; + case 2: + if (topfield) + chroma = ((line & 3) == 0); + else + chroma = ((line & 3) == 2); + break; + default: + chroma = 0; + break; + } + + for (todo = ybpl; todo > 0; todo -= ylen) { + /* go to next sg entry if needed */ + while (yoffset && yoffset >= sg_dma_len(ysg)) { + yoffset -= sg_dma_len(ysg); + ysg = sg_next(ysg); + } + + /* calculate max number of bytes we can write */ + ylen = todo; + if (yoffset + ylen > sg_dma_len(ysg)) + ylen = sg_dma_len(ysg) - yoffset; + if (chroma) { + while (uoffset && uoffset >= sg_dma_len(usg)) { + uoffset -= sg_dma_len(usg); + usg = sg_next(usg); + } + while (voffset && voffset >= sg_dma_len(vsg)) { + voffset -= sg_dma_len(vsg); + vsg = sg_next(vsg); + } + + if (uoffset + (ylen>>hshift) > sg_dma_len(usg)) + ylen = (sg_dma_len(usg) - uoffset) << hshift; + if (voffset + (ylen>>hshift) > sg_dma_len(vsg)) + ylen = (sg_dma_len(vsg) - voffset) << hshift; + ri = BT848_RISC_WRITE123; + } else { + ri = BT848_RISC_WRITE1S23; + } + if (ybpl == todo) + ri |= BT848_RISC_SOL; + if (ylen == todo) + ri |= BT848_RISC_EOL; + + /* write risc instruction */ + *(rp++)=cpu_to_le32(ri | ylen); + *(rp++)=cpu_to_le32(((ylen >> hshift) << 16) | + (ylen >> hshift)); + *(rp++)=cpu_to_le32(sg_dma_address(ysg)+yoffset); + yoffset += ylen; + if (chroma) { + *(rp++)=cpu_to_le32(sg_dma_address(usg)+uoffset); + uoffset += ylen >> hshift; + *(rp++)=cpu_to_le32(sg_dma_address(vsg)+voffset); + voffset += ylen >> hshift; + } + } + yoffset += ypadding; + if (chroma) { + uoffset += cpadding; + voffset += cpadding; + } + } + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + WARN_ON((risc->jmp - risc->cpu + 2) * sizeof(*risc->cpu) > risc->size); + return 0; +} + +/* ---------------------------------------------------------- */ + +static void +bttv_calc_geo_old(struct bttv *btv, struct bttv_geometry *geo, + int width, int height, int interleaved, + const struct bttv_tvnorm *tvnorm) +{ + u32 xsf, sr; + int vdelay; + + int swidth = tvnorm->swidth; + int totalwidth = tvnorm->totalwidth; + int scaledtwidth = tvnorm->scaledtwidth; + + if (btv->input == btv->dig) { + swidth = 720; + totalwidth = 858; + scaledtwidth = 858; + } + + vdelay = tvnorm->vdelay; + + xsf = (width*scaledtwidth)/swidth; + geo->hscale = ((totalwidth*4096UL)/xsf-4096); + geo->hdelay = tvnorm->hdelayx1; + geo->hdelay = (geo->hdelay*width)/swidth; + geo->hdelay &= 0x3fe; + sr = ((tvnorm->sheight >> (interleaved?0:1))*512)/height - 512; + geo->vscale = (0x10000UL-sr) & 0x1fff; + geo->crop = ((width>>8)&0x03) | ((geo->hdelay>>6)&0x0c) | + ((tvnorm->sheight>>4)&0x30) | ((vdelay>>2)&0xc0); + geo->vscale |= interleaved ? (BT848_VSCALE_INT<<8) : 0; + geo->vdelay = vdelay; + geo->width = width; + geo->sheight = tvnorm->sheight; + geo->vtotal = tvnorm->vtotal; + + if (btv->opt_combfilter) { + geo->vtc = (width < 193) ? 2 : ((width < 385) ? 1 : 0); + geo->comb = (width < 769) ? 1 : 0; + } else { + geo->vtc = 0; + geo->comb = 0; + } +} + +static void +bttv_calc_geo (struct bttv * btv, + struct bttv_geometry * geo, + unsigned int width, + unsigned int height, + int both_fields, + const struct bttv_tvnorm * tvnorm, + const struct v4l2_rect * crop) +{ + unsigned int c_width; + unsigned int c_height; + u32 sr; + + if ((crop->left == tvnorm->cropcap.defrect.left + && crop->top == tvnorm->cropcap.defrect.top + && crop->width == tvnorm->cropcap.defrect.width + && crop->height == tvnorm->cropcap.defrect.height + && width <= tvnorm->swidth /* see PAL-Nc et al */) + || btv->input == btv->dig) { + bttv_calc_geo_old(btv, geo, width, height, + both_fields, tvnorm); + return; + } + + /* For bug compatibility the image size checks permit scale + factors > 16. See bttv_crop_calc_limits(). */ + c_width = min((unsigned int) crop->width, width * 16); + c_height = min((unsigned int) crop->height, height * 16); + + geo->width = width; + geo->hscale = (c_width * 4096U + (width >> 1)) / width - 4096; + /* Even to store Cb first, odd for Cr. */ + geo->hdelay = ((crop->left * width + c_width) / c_width) & ~1; + + geo->sheight = c_height; + geo->vdelay = crop->top - tvnorm->cropcap.bounds.top + MIN_VDELAY; + sr = c_height >> !both_fields; + sr = (sr * 512U + (height >> 1)) / height - 512; + geo->vscale = (0x10000UL - sr) & 0x1fff; + geo->vscale |= both_fields ? (BT848_VSCALE_INT << 8) : 0; + geo->vtotal = tvnorm->vtotal; + + geo->crop = (((geo->width >> 8) & 0x03) | + ((geo->hdelay >> 6) & 0x0c) | + ((geo->sheight >> 4) & 0x30) | + ((geo->vdelay >> 2) & 0xc0)); + + if (btv->opt_combfilter) { + geo->vtc = (width < 193) ? 2 : ((width < 385) ? 1 : 0); + geo->comb = (width < 769) ? 1 : 0; + } else { + geo->vtc = 0; + geo->comb = 0; + } +} + +static void +bttv_apply_geo(struct bttv *btv, struct bttv_geometry *geo, int odd) +{ + int off = odd ? 0x80 : 0x00; + + if (geo->comb) + btor(BT848_VSCALE_COMB, BT848_E_VSCALE_HI+off); + else + btand(~BT848_VSCALE_COMB, BT848_E_VSCALE_HI+off); + + btwrite(geo->vtc, BT848_E_VTC+off); + btwrite(geo->hscale >> 8, BT848_E_HSCALE_HI+off); + btwrite(geo->hscale & 0xff, BT848_E_HSCALE_LO+off); + btaor((geo->vscale>>8), 0xe0, BT848_E_VSCALE_HI+off); + btwrite(geo->vscale & 0xff, BT848_E_VSCALE_LO+off); + btwrite(geo->width & 0xff, BT848_E_HACTIVE_LO+off); + btwrite(geo->hdelay & 0xff, BT848_E_HDELAY_LO+off); + btwrite(geo->sheight & 0xff, BT848_E_VACTIVE_LO+off); + btwrite(geo->vdelay & 0xff, BT848_E_VDELAY_LO+off); + btwrite(geo->crop, BT848_E_CROP+off); + btwrite(geo->vtotal>>8, BT848_VTOTAL_HI); + btwrite(geo->vtotal & 0xff, BT848_VTOTAL_LO); +} + +/* ---------------------------------------------------------- */ +/* risc group / risc main loop / dma management */ + +static void bttv_set_risc_status(struct bttv *btv) +{ + unsigned long cmd = BT848_RISC_JUMP; + if (btv->loop_irq) { + cmd |= BT848_RISC_IRQ; + cmd |= (btv->loop_irq & 0x0f) << 16; + cmd |= (~btv->loop_irq & 0x0f) << 20; + } + btv->main.cpu[RISC_SLOT_LOOP] = cpu_to_le32(cmd); +} + +static void bttv_set_irq_timer(struct bttv *btv) +{ + if (btv->curr.frame_irq || btv->loop_irq || btv->cvbi) + mod_timer(&btv->timeout, jiffies + BTTV_TIMEOUT); + else + timer_delete(&btv->timeout); +} + +static int bttv_set_capture_control(struct bttv *btv, int start_capture) +{ + int capctl = 0; + + if (btv->curr.top || btv->curr.bottom) + capctl = BT848_CAP_CTL_CAPTURE_ODD | + BT848_CAP_CTL_CAPTURE_EVEN; + + if (btv->cvbi) + capctl |= BT848_CAP_CTL_CAPTURE_VBI_ODD | + BT848_CAP_CTL_CAPTURE_VBI_EVEN; + + capctl |= start_capture; + + btaor(capctl, ~0x0f, BT848_CAP_CTL); + + return capctl; +} + +static void bttv_start_dma(struct bttv *btv) +{ + if (btv->dma_on) + return; + btwrite(btv->main.dma, BT848_RISC_STRT_ADD); + btor(BT848_GPIO_DMA_CTL_RISC_ENABLE | BT848_GPIO_DMA_CTL_FIFO_ENABLE, + BT848_GPIO_DMA_CTL); + btv->dma_on = 1; +} + +static void bttv_stop_dma(struct bttv *btv) +{ + if (!btv->dma_on) + return; + btand(~(BT848_GPIO_DMA_CTL_RISC_ENABLE | + BT848_GPIO_DMA_CTL_FIFO_ENABLE), BT848_GPIO_DMA_CTL); + btv->dma_on = 0; +} + +void bttv_set_dma(struct bttv *btv, int start_capture) +{ + int capctl = 0; + + bttv_set_risc_status(btv); + bttv_set_irq_timer(btv); + capctl = bttv_set_capture_control(btv, start_capture); + + if (capctl) + bttv_start_dma(btv); + else + bttv_stop_dma(btv); + + d2printk("%d: capctl=%x lirq=%d top=%08llx/%08llx even=%08llx/%08llx\n", + btv->c.nr,capctl,btv->loop_irq, + btv->cvbi ? (unsigned long long)btv->cvbi->top.dma : 0, + btv->curr.top ? (unsigned long long)btv->curr.top->top.dma : 0, + btv->cvbi ? (unsigned long long)btv->cvbi->bottom.dma : 0, + btv->curr.bottom ? (unsigned long long)btv->curr.bottom->bottom.dma : 0); +} + +int +bttv_risc_init_main(struct bttv *btv) +{ + int rc; + + if ((rc = btcx_riscmem_alloc(btv->c.pci,&btv->main,PAGE_SIZE)) < 0) + return rc; + dprintk("%d: risc main @ %08llx\n", + btv->c.nr, (unsigned long long)btv->main.dma); + + btv->main.cpu[0] = cpu_to_le32(BT848_RISC_SYNC | BT848_RISC_RESYNC | + BT848_FIFO_STATUS_VRE); + btv->main.cpu[1] = cpu_to_le32(0); + btv->main.cpu[2] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[3] = cpu_to_le32(btv->main.dma + (4<<2)); + + /* top field */ + btv->main.cpu[4] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[5] = cpu_to_le32(btv->main.dma + (6<<2)); + btv->main.cpu[6] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[7] = cpu_to_le32(btv->main.dma + (8<<2)); + + btv->main.cpu[8] = cpu_to_le32(BT848_RISC_SYNC | BT848_RISC_RESYNC | + BT848_FIFO_STATUS_VRO); + btv->main.cpu[9] = cpu_to_le32(0); + + /* bottom field */ + btv->main.cpu[10] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[11] = cpu_to_le32(btv->main.dma + (12<<2)); + btv->main.cpu[12] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[13] = cpu_to_le32(btv->main.dma + (14<<2)); + + /* jump back to top field */ + btv->main.cpu[14] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[15] = cpu_to_le32(btv->main.dma + (0<<2)); + + return 0; +} + +int +bttv_risc_hook(struct bttv *btv, int slot, struct btcx_riscmem *risc, + int irqflags) +{ + unsigned long cmd; + unsigned long next = btv->main.dma + ((slot+2) << 2); + + if (NULL == risc) { + d2printk("%d: risc=%p slot[%d]=NULL\n", btv->c.nr, risc, slot); + btv->main.cpu[slot+1] = cpu_to_le32(next); + } else { + d2printk("%d: risc=%p slot[%d]=%08llx irq=%d\n", + btv->c.nr, risc, slot, + (unsigned long long)risc->dma, irqflags); + cmd = BT848_RISC_JUMP; + if (irqflags) { + cmd |= BT848_RISC_IRQ; + cmd |= (irqflags & 0x0f) << 16; + cmd |= (~irqflags & 0x0f) << 20; + } + risc->jmp[0] = cpu_to_le32(cmd); + risc->jmp[1] = cpu_to_le32(next); + btv->main.cpu[slot+1] = cpu_to_le32(risc->dma); + } + return 0; +} + +int bttv_buffer_risc_vbi(struct bttv *btv, struct bttv_buffer *buf) +{ + int r = 0; + unsigned int offset; + unsigned int bpl = 2044; /* max. vbipack */ + unsigned int padding = VBI_BPL - bpl; + unsigned int skip_lines0 = 0; + unsigned int skip_lines1 = 0; + unsigned int min_vdelay = MIN_VDELAY; + + const struct bttv_tvnorm *tvnorm = btv->vbi_fmt.tvnorm; + struct sg_table *sgt = vb2_dma_sg_plane_desc(&buf->vbuf.vb2_buf, 0); + struct scatterlist *list = sgt->sgl; + + if (btv->vbi_fmt.fmt.count[0] > 0) + skip_lines0 = max(0, (btv->vbi_fmt.fmt.start[0] - + tvnorm->vbistart[0])); + if (btv->vbi_fmt.fmt.count[1] > 0) + skip_lines1 = max(0, (btv->vbi_fmt.fmt.start[1] - + tvnorm->vbistart[1])); + + if (btv->vbi_fmt.fmt.count[0] > 0) { + r = bttv_risc_packed(btv, &buf->top, list, 0, bpl, padding, + skip_lines0, btv->vbi_fmt.fmt.count[0]); + if (r) + return r; + } + + if (btv->vbi_fmt.fmt.count[1] > 0) { + offset = btv->vbi_fmt.fmt.count[0] * VBI_BPL; + r = bttv_risc_packed(btv, &buf->bottom, list, offset, bpl, + padding, skip_lines1, + btv->vbi_fmt.fmt.count[1]); + if (r) + return r; + } + + if (btv->vbi_fmt.end >= tvnorm->cropcap.bounds.top) + min_vdelay += btv->vbi_fmt.end - tvnorm->cropcap.bounds.top; + + /* For bttv_buffer_activate_vbi(). */ + buf->geo.vdelay = min_vdelay; + + return r; +} + +int +bttv_buffer_activate_vbi(struct bttv *btv, + struct bttv_buffer *vbi) +{ + struct btcx_riscmem *top; + struct btcx_riscmem *bottom; + int top_irq_flags; + int bottom_irq_flags; + + top = NULL; + bottom = NULL; + top_irq_flags = 0; + bottom_irq_flags = 0; + + if (vbi) { + unsigned int crop, vdelay; + + list_del(&vbi->list); + + /* VDELAY is start of video, end of VBI capturing. */ + crop = btread(BT848_E_CROP); + vdelay = btread(BT848_E_VDELAY_LO) + ((crop & 0xc0) << 2); + + if (vbi->geo.vdelay > vdelay) { + vdelay = vbi->geo.vdelay & 0xfe; + crop = (crop & 0x3f) | ((vbi->geo.vdelay >> 2) & 0xc0); + + btwrite(vdelay, BT848_E_VDELAY_LO); + btwrite(crop, BT848_E_CROP); + btwrite(vdelay, BT848_O_VDELAY_LO); + btwrite(crop, BT848_O_CROP); + } + + if (btv->vbi_count[0] > 0) { + top = &vbi->top; + top_irq_flags = 4; + } + + if (btv->vbi_count[1] > 0) { + top_irq_flags = 0; + bottom = &vbi->bottom; + bottom_irq_flags = 4; + } + } + + bttv_risc_hook(btv, RISC_SLOT_O_VBI, top, top_irq_flags); + bttv_risc_hook(btv, RISC_SLOT_E_VBI, bottom, bottom_irq_flags); + + return 0; +} + +int +bttv_buffer_activate_video(struct bttv *btv, + struct bttv_buffer_set *set) +{ + /* video capture */ + if (NULL != set->top && NULL != set->bottom) { + if (set->top == set->bottom) { + if (set->top->list.next) + list_del(&set->top->list); + } else { + if (set->top->list.next) + list_del(&set->top->list); + if (set->bottom->list.next) + list_del(&set->bottom->list); + } + bttv_apply_geo(btv, &set->top->geo, 1); + bttv_apply_geo(btv, &set->bottom->geo,0); + bttv_risc_hook(btv, RISC_SLOT_O_FIELD, &set->top->top, + set->top_irq); + bttv_risc_hook(btv, RISC_SLOT_E_FIELD, &set->bottom->bottom, + set->frame_irq); + btaor((set->top->btformat & 0xf0) | (set->bottom->btformat & 0x0f), + ~0xff, BT848_COLOR_FMT); + btaor((set->top->btswap & 0x0a) | (set->bottom->btswap & 0x05), + ~0x0f, BT848_COLOR_CTL); + } else if (NULL != set->top) { + if (set->top->list.next) + list_del(&set->top->list); + bttv_apply_geo(btv, &set->top->geo,1); + bttv_apply_geo(btv, &set->top->geo,0); + bttv_risc_hook(btv, RISC_SLOT_O_FIELD, &set->top->top, + set->frame_irq); + bttv_risc_hook(btv, RISC_SLOT_E_FIELD, NULL, 0); + btaor(set->top->btformat & 0xff, ~0xff, BT848_COLOR_FMT); + btaor(set->top->btswap & 0x0f, ~0x0f, BT848_COLOR_CTL); + } else if (NULL != set->bottom) { + if (set->bottom->list.next) + list_del(&set->bottom->list); + bttv_apply_geo(btv, &set->bottom->geo,1); + bttv_apply_geo(btv, &set->bottom->geo,0); + bttv_risc_hook(btv, RISC_SLOT_O_FIELD, NULL, 0); + bttv_risc_hook(btv, RISC_SLOT_E_FIELD, &set->bottom->bottom, + set->frame_irq); + btaor(set->bottom->btformat & 0xff, ~0xff, BT848_COLOR_FMT); + btaor(set->bottom->btswap & 0x0f, ~0x0f, BT848_COLOR_CTL); + } else { + bttv_risc_hook(btv, RISC_SLOT_O_FIELD, NULL, 0); + bttv_risc_hook(btv, RISC_SLOT_E_FIELD, NULL, 0); + } + return 0; +} + +/* ---------------------------------------------------------- */ + +/* calculate geometry, build risc code */ +int +bttv_buffer_risc(struct bttv *btv, struct bttv_buffer *buf) +{ + int r = 0; + const struct bttv_tvnorm *tvnorm = bttv_tvnorms + btv->tvnorm; + struct sg_table *sgt = vb2_dma_sg_plane_desc(&buf->vbuf.vb2_buf, 0); + struct scatterlist *list = sgt->sgl; + unsigned long size = (btv->fmt->depth * btv->width * btv->height) >> 3; + + /* packed pixel modes */ + if (btv->fmt->flags & FORMAT_FLAGS_PACKED) { + int bpl = (btv->fmt->depth >> 3) * btv->width; + int bpf = bpl * (btv->height >> 1); + + bttv_calc_geo(btv, &buf->geo, btv->width, btv->height, + V4L2_FIELD_HAS_BOTH(buf->vbuf.field), tvnorm, + &btv->crop[!!btv->do_crop].rect); + switch (buf->vbuf.field) { + case V4L2_FIELD_TOP: + r = bttv_risc_packed(btv, &buf->top, list, 0, bpl, 0, + 0, btv->height); + break; + case V4L2_FIELD_BOTTOM: + r = bttv_risc_packed(btv, &buf->bottom, list, 0, bpl, + 0, 0, btv->height); + break; + case V4L2_FIELD_INTERLACED: + r = bttv_risc_packed(btv, &buf->top, list, 0, bpl, + bpl, 0, btv->height >> 1); + r = bttv_risc_packed(btv, &buf->bottom, list, bpl, + bpl, bpl, 0, btv->height >> 1); + break; + case V4L2_FIELD_SEQ_TB: + r = bttv_risc_packed(btv, &buf->top, list, 0, bpl, 0, + 0, btv->height >> 1); + r = bttv_risc_packed(btv, &buf->bottom, list, bpf, + bpl, 0, 0, btv->height >> 1); + break; + default: + WARN_ON(1); + return -EINVAL; + } + } + /* planar modes */ + if (btv->fmt->flags & FORMAT_FLAGS_PLANAR) { + int uoffset, voffset; + int ypadding, cpadding, lines; + + /* calculate chroma offsets */ + uoffset = btv->width * btv->height; + voffset = btv->width * btv->height; + if (btv->fmt->flags & FORMAT_FLAGS_CrCb) { + /* Y-Cr-Cb plane order */ + uoffset >>= btv->fmt->hshift; + uoffset >>= btv->fmt->vshift; + uoffset += voffset; + } else { + /* Y-Cb-Cr plane order */ + voffset >>= btv->fmt->hshift; + voffset >>= btv->fmt->vshift; + voffset += uoffset; + } + switch (buf->vbuf.field) { + case V4L2_FIELD_TOP: + bttv_calc_geo(btv, &buf->geo, btv->width, btv->height, + 0, tvnorm, + &btv->crop[!!btv->do_crop].rect); + r = bttv_risc_planar(btv, &buf->top, list, 0, + btv->width, 0, btv->height, + uoffset, voffset, + btv->fmt->hshift, + btv->fmt->vshift, 0); + break; + case V4L2_FIELD_BOTTOM: + bttv_calc_geo(btv, &buf->geo, btv->width, btv->height, + 0, tvnorm, + &btv->crop[!!btv->do_crop].rect); + r = bttv_risc_planar(btv, &buf->bottom, list, 0, + btv->width, 0, btv->height, + uoffset, voffset, + btv->fmt->hshift, + btv->fmt->vshift, 0); + break; + case V4L2_FIELD_INTERLACED: + bttv_calc_geo(btv, &buf->geo, btv->width, btv->height, + 1, tvnorm, + &btv->crop[!!btv->do_crop].rect); + lines = btv->height >> 1; + ypadding = btv->width; + cpadding = btv->width >> btv->fmt->hshift; + r = bttv_risc_planar(btv, &buf->top, list, 0, + btv->width, ypadding, lines, + uoffset, voffset, + btv->fmt->hshift, + btv->fmt->vshift, cpadding); + + r = bttv_risc_planar(btv, &buf->bottom, list, + ypadding, btv->width, ypadding, + lines, uoffset + cpadding, + voffset + cpadding, + btv->fmt->hshift, + btv->fmt->vshift, cpadding); + break; + case V4L2_FIELD_SEQ_TB: + bttv_calc_geo(btv, &buf->geo, btv->width, btv->height, + 1, tvnorm, + &btv->crop[!!btv->do_crop].rect); + lines = btv->height >> 1; + ypadding = btv->width; + cpadding = btv->width >> btv->fmt->hshift; + r = bttv_risc_planar(btv, &buf->top, list, 0, + btv->width, 0, lines, + uoffset >> 1, voffset >> 1, + btv->fmt->hshift, + btv->fmt->vshift, 0); + r = bttv_risc_planar(btv, &buf->bottom, list, + lines * ypadding, + btv->width, 0, lines, + lines * ypadding + (uoffset >> 1), + lines * ypadding + (voffset >> 1), + btv->fmt->hshift, + btv->fmt->vshift, 0); + break; + default: + WARN_ON(1); + return -EINVAL; + } + } + /* raw data */ + if (btv->fmt->flags & FORMAT_FLAGS_RAW) { + /* build risc code */ + buf->vbuf.field = V4L2_FIELD_SEQ_TB; + bttv_calc_geo(btv, &buf->geo, tvnorm->swidth, tvnorm->sheight, + 1, tvnorm, &btv->crop[!!btv->do_crop].rect); + r = bttv_risc_packed(btv, &buf->top, list, 0, RAW_BPL, 0, 0, + RAW_LINES); + r = bttv_risc_packed(btv, &buf->bottom, list, size / 2, + RAW_BPL, 0, 0, RAW_LINES); + } + + /* copy format info */ + buf->btformat = btv->fmt->btformat; + buf->btswap = btv->fmt->btswap; + + return r; +} diff --git a/drivers/media/pci/bt8xx/bttv-vbi.c b/drivers/media/pci/bt8xx/bttv-vbi.c new file mode 100644 index 000000000..0ca88a240 --- /dev/null +++ b/drivers/media/pci/bt8xx/bttv-vbi.c @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + + bttv - Bt848 frame grabber driver + vbi interface + + (c) 2002 Gerd Knorr + + Copyright (C) 2005, 2006 Michael H. Schimek + Sponsored by OPQ Systems AB + +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include "bttvp.h" + +/* Offset from line sync pulse leading edge (0H) to start of VBI capture, + in fCLKx2 pixels. According to the datasheet, VBI capture starts + VBI_HDELAY fCLKx1 pixels from the tailing edgeof /HRESET, and /HRESET + is 64 fCLKx1 pixels wide. VBI_HDELAY is set to 0, so this should be + (64 + 0) * 2 = 128 fCLKx2 pixels. But it's not! The datasheet is + Just Plain Wrong. The real value appears to be different for + different revisions of the bt8x8 chips, and to be affected by the + horizontal scaling factor. Experimentally, the value is measured + to be about 244. */ +#define VBI_OFFSET 244 + +static unsigned int vbibufs = 4; +static unsigned int vbi_debug; + +module_param(vbibufs, int, 0444); +module_param(vbi_debug, int, 0644); +MODULE_PARM_DESC(vbibufs,"number of vbi buffers, range 2-32, default 4"); +MODULE_PARM_DESC(vbi_debug,"vbi code debug messages, default is 0 (no)"); + +#ifdef dprintk +# undef dprintk +#endif +#define dprintk(fmt, ...) \ +do { \ + if (vbi_debug) \ + pr_debug("%d: " fmt, btv->c.nr, ##__VA_ARGS__); \ +} while (0) + +#define IMAGE_SIZE(fmt) \ + (((fmt)->count[0] + (fmt)->count[1]) * (fmt)->samples_per_line) + +/* ----------------------------------------------------------------------- */ +/* vbi risc code + mm */ + +static int queue_setup_vbi(struct vb2_queue *q, unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct bttv *btv = vb2_get_drv_priv(q); + unsigned int size = IMAGE_SIZE(&btv->vbi_fmt.fmt); + + if (*num_planes) + return sizes[0] < size ? -EINVAL : 0; + *num_planes = 1; + sizes[0] = size; + + return 0; +} + +static void buf_queue_vbi(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vb2_queue *vq = vb->vb2_queue; + struct bttv *btv = vb2_get_drv_priv(vq); + struct bttv_buffer *buf = container_of(vbuf, struct bttv_buffer, vbuf); + unsigned long flags; + + spin_lock_irqsave(&btv->s_lock, flags); + if (list_empty(&btv->vcapture)) { + btv->loop_irq = BT848_RISC_VBI; + if (vb2_is_streaming(&btv->capq)) + btv->loop_irq |= BT848_RISC_VIDEO; + bttv_set_dma(btv, BT848_CAP_CTL_CAPTURE_VBI_ODD | + BT848_CAP_CTL_CAPTURE_VBI_EVEN); + } + list_add_tail(&buf->list, &btv->vcapture); + spin_unlock_irqrestore(&btv->s_lock, flags); +} + +static int buf_prepare_vbi(struct vb2_buffer *vb) +{ + int ret = 0; + struct vb2_queue *vq = vb->vb2_queue; + struct bttv *btv = vb2_get_drv_priv(vq); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct bttv_buffer *buf = container_of(vbuf, struct bttv_buffer, vbuf); + unsigned int size = IMAGE_SIZE(&btv->vbi_fmt.fmt); + + if (vb2_plane_size(vb, 0) < size) + return -EINVAL; + vb2_set_plane_payload(vb, 0, size); + buf->vbuf.field = V4L2_FIELD_NONE; + ret = bttv_buffer_risc_vbi(btv, buf); + + return ret; +} + +static void buf_cleanup_vbi(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct bttv_buffer *buf = container_of(vbuf, struct bttv_buffer, vbuf); + struct vb2_queue *vq = vb->vb2_queue; + struct bttv *btv = vb2_get_drv_priv(vq); + + btcx_riscmem_free(btv->c.pci, &buf->top); + btcx_riscmem_free(btv->c.pci, &buf->bottom); +} + +static int start_streaming_vbi(struct vb2_queue *q, unsigned int count) +{ + int seqnr = 0; + struct bttv_buffer *buf; + struct bttv *btv = vb2_get_drv_priv(q); + + btv->framedrop = 0; + if (!check_alloc_btres_lock(btv, RESOURCE_VBI)) { + if (btv->field_count) + seqnr++; + while (!list_empty(&btv->vcapture)) { + buf = list_entry(btv->vcapture.next, + struct bttv_buffer, list); + list_del(&buf->list); + buf->vbuf.sequence = (btv->field_count >> 1) + seqnr++; + vb2_buffer_done(&buf->vbuf.vb2_buf, + VB2_BUF_STATE_QUEUED); + } + return -EBUSY; + } + if (!vb2_is_streaming(&btv->capq)) { + init_irqreg(btv); + btv->field_count = 0; + } + return 0; +} + +static void stop_streaming_vbi(struct vb2_queue *q) +{ + struct bttv *btv = vb2_get_drv_priv(q); + unsigned long flags; + + vb2_wait_for_all_buffers(q); + spin_lock_irqsave(&btv->s_lock, flags); + free_btres_lock(btv, RESOURCE_VBI); + if (!vb2_is_streaming(&btv->capq)) { + /* stop field counter */ + btand(~BT848_INT_VSYNC, BT848_INT_MASK); + } + spin_unlock_irqrestore(&btv->s_lock, flags); +} + +const struct vb2_ops bttv_vbi_qops = { + .queue_setup = queue_setup_vbi, + .buf_queue = buf_queue_vbi, + .buf_prepare = buf_prepare_vbi, + .buf_cleanup = buf_cleanup_vbi, + .start_streaming = start_streaming_vbi, + .stop_streaming = stop_streaming_vbi, +}; + +/* ----------------------------------------------------------------------- */ + +static int try_fmt(struct v4l2_vbi_format *f, const struct bttv_tvnorm *tvnorm, + __s32 crop_start) +{ + __s32 min_start, max_start, max_end, f2_offset; + unsigned int i; + + /* For compatibility with earlier driver versions we must pretend + the VBI and video capture window may overlap. In reality RISC + magic aborts VBI capturing at the first line of video capturing, + leaving the rest of the buffer unchanged, usually all zero. + VBI capturing must always start before video capturing. >> 1 + because cropping counts field lines times two. */ + min_start = tvnorm->vbistart[0]; + max_start = (crop_start >> 1) - 1; + max_end = (tvnorm->cropcap.bounds.top + + tvnorm->cropcap.bounds.height) >> 1; + + if (min_start > max_start) + return -EBUSY; + + WARN_ON(max_start >= max_end); + + f->sampling_rate = tvnorm->Fsc; + f->samples_per_line = VBI_BPL; + f->sample_format = V4L2_PIX_FMT_GREY; + f->offset = VBI_OFFSET; + + f2_offset = tvnorm->vbistart[1] - tvnorm->vbistart[0]; + + for (i = 0; i < 2; ++i) { + if (0 == f->count[i]) { + /* No data from this field. We leave f->start[i] + alone because VIDIOCSVBIFMT is w/o and EINVALs + when a driver does not support exactly the + requested parameters. */ + } else { + s64 start, count; + + start = clamp(f->start[i], min_start, max_start); + /* s64 to prevent overflow. */ + count = (s64) f->start[i] + f->count[i] - start; + f->start[i] = start; + f->count[i] = clamp(count, (s64) 1, + max_end - start); + } + + min_start += f2_offset; + max_start += f2_offset; + max_end += f2_offset; + } + + if (0 == (f->count[0] | f->count[1])) { + /* As in earlier driver versions. */ + f->start[0] = tvnorm->vbistart[0]; + f->start[1] = tvnorm->vbistart[1]; + f->count[0] = 1; + f->count[1] = 1; + } + + f->flags = 0; + + f->reserved[0] = 0; + f->reserved[1] = 0; + + return 0; +} + +int bttv_try_fmt_vbi_cap(struct file *file, void *priv, struct v4l2_format *frt) +{ + struct bttv *btv = video_drvdata(file); + const struct bttv_tvnorm *tvnorm; + __s32 crop_start; + + mutex_lock(&btv->lock); + + tvnorm = &bttv_tvnorms[btv->tvnorm]; + crop_start = btv->crop_start; + + mutex_unlock(&btv->lock); + + return try_fmt(&frt->fmt.vbi, tvnorm, crop_start); +} + + +int bttv_s_fmt_vbi_cap(struct file *file, void *priv, struct v4l2_format *frt) +{ + struct bttv *btv = video_drvdata(file); + const struct bttv_tvnorm *tvnorm; + __s32 start1, end; + int rc; + + mutex_lock(&btv->lock); + + rc = -EBUSY; + if (btv->resources & RESOURCE_VBI) + goto fail; + + tvnorm = &bttv_tvnorms[btv->tvnorm]; + + rc = try_fmt(&frt->fmt.vbi, tvnorm, btv->crop_start); + if (0 != rc) + goto fail; + + start1 = frt->fmt.vbi.start[1] - tvnorm->vbistart[1] + + tvnorm->vbistart[0]; + + /* First possible line of video capturing. Should be + max(f->start[0] + f->count[0], start1 + f->count[1]) * 2 + when capturing both fields. But for compatibility we must + pretend the VBI and video capture window may overlap, + so end = start + 1, the lowest possible value, times two + because vbi_fmt.end counts field lines times two. */ + end = max(frt->fmt.vbi.start[0], start1) * 2 + 2; + + btv->vbi_fmt.fmt = frt->fmt.vbi; + btv->vbi_fmt.tvnorm = tvnorm; + btv->vbi_fmt.end = end; + + rc = 0; + + fail: + mutex_unlock(&btv->lock); + + return rc; +} + + +int bttv_g_fmt_vbi_cap(struct file *file, void *priv, struct v4l2_format *frt) +{ + const struct bttv_tvnorm *tvnorm; + struct bttv *btv = video_drvdata(file); + + frt->fmt.vbi = btv->vbi_fmt.fmt; + + tvnorm = &bttv_tvnorms[btv->tvnorm]; + + if (tvnorm != btv->vbi_fmt.tvnorm) { + __s32 max_end; + unsigned int i; + + /* As in vbi_buffer_prepare() this imitates the + behaviour of earlier driver versions after video + standard changes, with default parameters anyway. */ + + max_end = (tvnorm->cropcap.bounds.top + + tvnorm->cropcap.bounds.height) >> 1; + + frt->fmt.vbi.sampling_rate = tvnorm->Fsc; + + for (i = 0; i < 2; ++i) { + __s32 new_start; + + new_start = frt->fmt.vbi.start[i] + tvnorm->vbistart[i] + - btv->vbi_fmt.tvnorm->vbistart[i]; + + frt->fmt.vbi.start[i] = min(new_start, max_end - 1); + frt->fmt.vbi.count[i] = + min((__s32) frt->fmt.vbi.count[i], + max_end - frt->fmt.vbi.start[i]); + + max_end += tvnorm->vbistart[1] + - tvnorm->vbistart[0]; + } + } + return 0; +} + +void bttv_vbi_fmt_reset(struct bttv_vbi_fmt *f, unsigned int norm) +{ + const struct bttv_tvnorm *tvnorm; + unsigned int real_samples_per_line; + unsigned int real_count; + + tvnorm = &bttv_tvnorms[norm]; + + f->fmt.sampling_rate = tvnorm->Fsc; + f->fmt.samples_per_line = VBI_BPL; + f->fmt.sample_format = V4L2_PIX_FMT_GREY; + f->fmt.offset = VBI_OFFSET; + f->fmt.start[0] = tvnorm->vbistart[0]; + f->fmt.start[1] = tvnorm->vbistart[1]; + f->fmt.count[0] = VBI_DEFLINES; + f->fmt.count[1] = VBI_DEFLINES; + f->fmt.flags = 0; + f->fmt.reserved[0] = 0; + f->fmt.reserved[1] = 0; + + /* For compatibility the buffer size must be 2 * VBI_DEFLINES * + VBI_BPL regardless of the current video standard. */ + real_samples_per_line = 1024 + tvnorm->vbipack * 4; + real_count = ((tvnorm->cropcap.defrect.top >> 1) + - tvnorm->vbistart[0]); + + WARN_ON(real_samples_per_line > VBI_BPL); + WARN_ON(real_count > VBI_DEFLINES); + + f->tvnorm = tvnorm; + + /* See bttv_vbi_fmt_set(). */ + f->end = tvnorm->vbistart[0] * 2 + 2; +} diff --git a/drivers/media/pci/bt8xx/bttv.h b/drivers/media/pci/bt8xx/bttv.h new file mode 100644 index 000000000..97bbed980 --- /dev/null +++ b/drivers/media/pci/bt8xx/bttv.h @@ -0,0 +1,379 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * + * bttv - Bt848 frame grabber driver + * + * card ID's and external interfaces of the bttv driver + * basically stuff needed by other drivers (i2c, lirc, ...) + * and is supported not to change much over time. + * + * Copyright (C) 1996,97 Ralph Metzler (rjkm@thp.uni-koeln.de) + * (c) 1999,2000 Gerd Knorr + * + */ + +#ifndef _BTTV_H_ +#define _BTTV_H_ + +#include +#include +#include +#include + +/* ---------------------------------------------------------- */ +/* exported by bttv-cards.c */ + +#define BTTV_BOARD_UNKNOWN 0x00 +#define BTTV_BOARD_MIRO 0x01 +#define BTTV_BOARD_HAUPPAUGE 0x02 +#define BTTV_BOARD_STB 0x03 +#define BTTV_BOARD_INTEL 0x04 +#define BTTV_BOARD_DIAMOND 0x05 +#define BTTV_BOARD_AVERMEDIA 0x06 +#define BTTV_BOARD_MATRIX_VISION 0x07 +#define BTTV_BOARD_FLYVIDEO 0x08 +#define BTTV_BOARD_TURBOTV 0x09 +#define BTTV_BOARD_HAUPPAUGE878 0x0a +#define BTTV_BOARD_MIROPRO 0x0b +#define BTTV_BOARD_ADSTECH_TV 0x0c +#define BTTV_BOARD_AVERMEDIA98 0x0d +#define BTTV_BOARD_VHX 0x0e +#define BTTV_BOARD_ZOLTRIX 0x0f +#define BTTV_BOARD_PIXVIEWPLAYTV 0x10 +#define BTTV_BOARD_WINVIEW_601 0x11 +#define BTTV_BOARD_AVEC_INTERCAP 0x12 +#define BTTV_BOARD_LIFE_FLYKIT 0x13 +#define BTTV_BOARD_CEI_RAFFLES 0x14 +#define BTTV_BOARD_CONFERENCETV 0x15 +#define BTTV_BOARD_PHOEBE_TVMAS 0x16 +#define BTTV_BOARD_MODTEC_205 0x17 +#define BTTV_BOARD_MAGICTVIEW061 0x18 +#define BTTV_BOARD_VOBIS_BOOSTAR 0x19 +#define BTTV_BOARD_HAUPPAUG_WCAM 0x1a +#define BTTV_BOARD_MAXI 0x1b +#define BTTV_BOARD_TERRATV 0x1c +#define BTTV_BOARD_PXC200 0x1d +#define BTTV_BOARD_FLYVIDEO_98 0x1e +#define BTTV_BOARD_IPROTV 0x1f +#define BTTV_BOARD_INTEL_C_S_PCI 0x20 +#define BTTV_BOARD_TERRATVALUE 0x21 +#define BTTV_BOARD_WINFAST2000 0x22 +#define BTTV_BOARD_CHRONOS_VS2 0x23 +#define BTTV_BOARD_TYPHOON_TVIEW 0x24 +#define BTTV_BOARD_PXELVWPLTVPRO 0x25 +#define BTTV_BOARD_MAGICTVIEW063 0x26 +#define BTTV_BOARD_PINNACLE 0x27 +#define BTTV_BOARD_STB2 0x28 +#define BTTV_BOARD_AVPHONE98 0x29 +#define BTTV_BOARD_PV951 0x2a +#define BTTV_BOARD_ONAIR_TV 0x2b +#define BTTV_BOARD_SIGMA_TVII_FM 0x2c +#define BTTV_BOARD_MATRIX_VISION2 0x2d +#define BTTV_BOARD_ZOLTRIX_GENIE 0x2e +#define BTTV_BOARD_TERRATVRADIO 0x2f +#define BTTV_BOARD_DYNALINK 0x30 +#define BTTV_BOARD_GVBCTV3PCI 0x31 +#define BTTV_BOARD_PXELVWPLTVPAK 0x32 +#define BTTV_BOARD_EAGLE 0x33 +#define BTTV_BOARD_PINNACLEPRO 0x34 +#define BTTV_BOARD_TVIEW_RDS_FM 0x35 +#define BTTV_BOARD_LIFETEC_9415 0x36 +#define BTTV_BOARD_BESTBUY_EASYTV 0x37 +#define BTTV_BOARD_FLYVIDEO_98FM 0x38 +#define BTTV_BOARD_GRANDTEC 0x39 +#define BTTV_BOARD_ASKEY_CPH060 0x3a +#define BTTV_BOARD_ASKEY_CPH03X 0x3b +#define BTTV_BOARD_MM100PCTV 0x3c +#define BTTV_BOARD_GMV1 0x3d +#define BTTV_BOARD_BESTBUY_EASYTV2 0x3e +#define BTTV_BOARD_ATI_TVWONDER 0x3f +#define BTTV_BOARD_ATI_TVWONDERVE 0x40 +#define BTTV_BOARD_FLYVIDEO2000 0x41 +#define BTTV_BOARD_TERRATVALUER 0x42 +#define BTTV_BOARD_GVBCTV4PCI 0x43 +#define BTTV_BOARD_VOODOOTV_FM 0x44 +#define BTTV_BOARD_AIMMS 0x45 +#define BTTV_BOARD_PV_BT878P_PLUS 0x46 +#define BTTV_BOARD_FLYVIDEO98EZ 0x47 +#define BTTV_BOARD_PV_BT878P_9B 0x48 +#define BTTV_BOARD_SENSORAY311_611 0x49 +#define BTTV_BOARD_RV605 0x4a +#define BTTV_BOARD_POWERCLR_MTV878 0x4b +#define BTTV_BOARD_WINDVR 0x4c +#define BTTV_BOARD_GRANDTEC_MULTI 0x4d +#define BTTV_BOARD_KWORLD 0x4e +#define BTTV_BOARD_DSP_TCVIDEO 0x4f +#define BTTV_BOARD_HAUPPAUGEPVR 0x50 +#define BTTV_BOARD_GVBCTV5PCI 0x51 +#define BTTV_BOARD_OSPREY1x0 0x52 +#define BTTV_BOARD_OSPREY1x0_848 0x53 +#define BTTV_BOARD_OSPREY101_848 0x54 +#define BTTV_BOARD_OSPREY1x1 0x55 +#define BTTV_BOARD_OSPREY1x1_SVID 0x56 +#define BTTV_BOARD_OSPREY2xx 0x57 +#define BTTV_BOARD_OSPREY2x0_SVID 0x58 +#define BTTV_BOARD_OSPREY2x0 0x59 +#define BTTV_BOARD_OSPREY500 0x5a +#define BTTV_BOARD_OSPREY540 0x5b +#define BTTV_BOARD_OSPREY2000 0x5c +#define BTTV_BOARD_IDS_EAGLE 0x5d +#define BTTV_BOARD_PINNACLESAT 0x5e +#define BTTV_BOARD_FORMAC_PROTV 0x5f +#define BTTV_BOARD_MACHTV 0x60 +#define BTTV_BOARD_EURESYS_PICOLO 0x61 +#define BTTV_BOARD_PV150 0x62 +#define BTTV_BOARD_AD_TVK503 0x63 +#define BTTV_BOARD_HERCULES_SM_TV 0x64 +#define BTTV_BOARD_PACETV 0x65 +#define BTTV_BOARD_IVC200 0x66 +#define BTTV_BOARD_XGUARD 0x67 +#define BTTV_BOARD_NEBULA_DIGITV 0x68 +#define BTTV_BOARD_PV143 0x69 +#define BTTV_BOARD_VD009X1_VD011_MINIDIN 0x6a +#define BTTV_BOARD_VD009X1_VD011_COMBI 0x6b +#define BTTV_BOARD_VD009_MINIDIN 0x6c +#define BTTV_BOARD_VD009_COMBI 0x6d +#define BTTV_BOARD_IVC100 0x6e +#define BTTV_BOARD_IVC120 0x6f +#define BTTV_BOARD_PC_HDTV 0x70 +#define BTTV_BOARD_TWINHAN_DST 0x71 +#define BTTV_BOARD_WINFASTVC100 0x72 +#define BTTV_BOARD_TEV560 0x73 +#define BTTV_BOARD_SIMUS_GVC1100 0x74 +#define BTTV_BOARD_NGSTV_PLUS 0x75 +#define BTTV_BOARD_LMLBT4 0x76 +#define BTTV_BOARD_TEKRAM_M205 0x77 +#define BTTV_BOARD_CONTVFMI 0x78 +#define BTTV_BOARD_PICOLO_TETRA_CHIP 0x79 +#define BTTV_BOARD_SPIRIT_TV 0x7a +#define BTTV_BOARD_AVDVBT_771 0x7b +#define BTTV_BOARD_AVDVBT_761 0x7c +#define BTTV_BOARD_MATRIX_VISIONSQ 0x7d +#define BTTV_BOARD_MATRIX_VISIONSLC 0x7e +#define BTTV_BOARD_APAC_VIEWCOMP 0x7f +#define BTTV_BOARD_DVICO_DVBT_LITE 0x80 +#define BTTV_BOARD_VGEAR_MYVCD 0x81 +#define BTTV_BOARD_SUPER_TV 0x82 +#define BTTV_BOARD_TIBET_CS16 0x83 +#define BTTV_BOARD_KODICOM_4400R 0x84 +#define BTTV_BOARD_KODICOM_4400R_SL 0x85 +#define BTTV_BOARD_ADLINK_RTV24 0x86 +#define BTTV_BOARD_DVICO_FUSIONHDTV_5_LITE 0x87 +#define BTTV_BOARD_ACORP_Y878F 0x88 +#define BTTV_BOARD_CONCEPTRONIC_CTVFMI2 0x89 +#define BTTV_BOARD_PV_BT878P_2E 0x8a +#define BTTV_BOARD_PV_M4900 0x8b +#define BTTV_BOARD_OSPREY440 0x8c +#define BTTV_BOARD_ASOUND_SKYEYE 0x8d +#define BTTV_BOARD_SABRENT_TVFM 0x8e +#define BTTV_BOARD_HAUPPAUGE_IMPACTVCB 0x8f +#define BTTV_BOARD_MACHTV_MAGICTV 0x90 +#define BTTV_BOARD_SSAI_SECURITY 0x91 +#define BTTV_BOARD_SSAI_ULTRASOUND 0x92 +#define BTTV_BOARD_VOODOOTV_200 0x93 +#define BTTV_BOARD_DVICO_FUSIONHDTV_2 0x94 +#define BTTV_BOARD_TYPHOON_TVTUNERPCI 0x95 +#define BTTV_BOARD_GEOVISION_GV600 0x96 +#define BTTV_BOARD_KOZUMI_KTV_01C 0x97 +#define BTTV_BOARD_ENLTV_FM_2 0x98 +#define BTTV_BOARD_VD012 0x99 +#define BTTV_BOARD_VD012_X1 0x9a +#define BTTV_BOARD_VD012_X2 0x9b +#define BTTV_BOARD_IVCE8784 0x9c +#define BTTV_BOARD_GEOVISION_GV800S 0x9d +#define BTTV_BOARD_GEOVISION_GV800S_SL 0x9e +#define BTTV_BOARD_PV183 0x9f +#define BTTV_BOARD_TVT_TD3116 0xa0 +#define BTTV_BOARD_APOSONIC_WDVR 0xa1 +#define BTTV_BOARD_ADLINK_MPG24 0xa2 +#define BTTV_BOARD_BT848_CAP_14 0xa3 +#define BTTV_BOARD_CYBERVISION_CV06 0xa4 +#define BTTV_BOARD_KWORLD_VSTREAM_XPERT 0xa5 +#define BTTV_BOARD_PCI_8604PW 0xa6 + +/* more card-specific defines */ +#define PT2254_L_CHANNEL 0x10 +#define PT2254_R_CHANNEL 0x08 +#define PT2254_DBS_IN_2 0x400 +#define PT2254_DBS_IN_10 0x20000 +#define WINVIEW_PT2254_CLK 0x40 +#define WINVIEW_PT2254_DATA 0x20 +#define WINVIEW_PT2254_STROBE 0x80 + +struct bttv_core { + /* device structs */ + struct v4l2_device v4l2_dev; + struct pci_dev *pci; + struct i2c_adapter i2c_adap; + struct list_head subs; /* struct bttv_sub_device */ + + /* device config */ + unsigned int nr; /* dev nr (for printk("bttv%d: ..."); */ + unsigned int type; /* card type (pointer into tvcards[]) */ +}; + +struct bttv; + +struct tvcard { + char *name; + void (*volume_gpio)(struct bttv *btv, __u16 volume); + void (*audio_mode_gpio)(struct bttv *btv, struct v4l2_tuner *tuner, int set); + void (*muxsel_hook)(struct bttv *btv, unsigned int input); + + /* MUX bits for each input, two bits per input starting with the LSB */ + u32 muxsel; /* Use MUXSEL() to set */ + + u32 gpiomask; + u32 gpiomux[4]; /* Tuner, Radio, external, internal */ + u32 gpiomute; /* GPIO mute setting */ + u32 gpiomask2; /* GPIO MUX mask */ + + unsigned int tuner_type; + u8 tuner_addr; + u8 video_inputs; /* Number of inputs */ + unsigned int svhs:4; /* Which input is s-video */ +#define NO_SVHS 15 + unsigned int pll:2; +#define PLL_NONE 0 +#define PLL_28 1 +#define PLL_35 2 +#define PLL_14 3 + + /* i2c audio flags */ + unsigned int no_msp34xx:1; + unsigned int no_tda7432:1; + unsigned int msp34xx_alt:1; + /* Note: currently no card definition needs to mark the presence + of a RDS saa6588 chip. If this is ever needed, then add a new + 'has_saa6588' bit here. */ + + unsigned int no_video:1; /* video pci function is unused */ + unsigned int has_dvb:1; + unsigned int has_remote:1; + unsigned int has_radio:1; + unsigned int has_dig_in:1; /* Has digital input (always last input) */ + unsigned int no_gpioirq:1; +}; + +extern struct tvcard bttv_tvcards[]; + +/* + * This bit of cpp voodoo is used to create a macro with a variable number of + * arguments (1 to 16). It will pack each argument into a word two bits at a + * time. It can't be a function because it needs to be compile time constant to + * initialize structures. Since each argument must fit in two bits, it's ok + * that they are changed to octal. One should not use hex number, macros, or + * anything else with this macro. Just use plain integers from 0 to 3. + */ +#define _MUXSELf(a) 0##a << 30 +#define _MUXSELe(a, b...) 0##a << 28 | _MUXSELf(b) +#define _MUXSELd(a, b...) 0##a << 26 | _MUXSELe(b) +#define _MUXSELc(a, b...) 0##a << 24 | _MUXSELd(b) +#define _MUXSELb(a, b...) 0##a << 22 | _MUXSELc(b) +#define _MUXSELa(a, b...) 0##a << 20 | _MUXSELb(b) +#define _MUXSEL9(a, b...) 0##a << 18 | _MUXSELa(b) +#define _MUXSEL8(a, b...) 0##a << 16 | _MUXSEL9(b) +#define _MUXSEL7(a, b...) 0##a << 14 | _MUXSEL8(b) +#define _MUXSEL6(a, b...) 0##a << 12 | _MUXSEL7(b) +#define _MUXSEL5(a, b...) 0##a << 10 | _MUXSEL6(b) +#define _MUXSEL4(a, b...) 0##a << 8 | _MUXSEL5(b) +#define _MUXSEL3(a, b...) 0##a << 6 | _MUXSEL4(b) +#define _MUXSEL2(a, b...) 0##a << 4 | _MUXSEL3(b) +#define _MUXSEL1(a, b...) 0##a << 2 | _MUXSEL2(b) +#define MUXSEL(a, b...) (a | _MUXSEL1(b)) + +/* identification / initialization of the card */ +extern void bttv_idcard(struct bttv *btv); +extern void bttv_init_card1(struct bttv *btv); +extern void bttv_init_card2(struct bttv *btv); +extern void bttv_init_tuner(struct bttv *btv); + +/* card-specific functions */ +extern u32 bttv_tda9880_setnorm(struct bttv *btv, u32 gpiobits); + +/* extra tweaks for some chipsets */ +extern void bttv_check_chipset(void); +extern int bttv_handle_chipset(struct bttv *btv); + +/* ---------------------------------------------------------- */ +/* exported by bttv-if.c */ + +/* this obsolete -- please use the sysfs-based + interface below for new code */ + +extern struct pci_dev* bttv_get_pcidev(unsigned int card); + +/* sets GPOE register (BT848_GPIO_OUT_EN) to new value: + data | (current_GPOE_value & ~mask) + returns negative value if error occurred +*/ +extern int bttv_gpio_enable(unsigned int card, + unsigned long mask, unsigned long data); + +/* fills data with GPDATA register contents + returns negative value if error occurred +*/ +extern int bttv_read_gpio(unsigned int card, unsigned long *data); + +/* sets GPDATA register to new value: + (data & mask) | (current_GPDATA_value & ~mask) + returns negative value if error occurred +*/ +extern int bttv_write_gpio(unsigned int card, + unsigned long mask, unsigned long data); + + + + +/* ---------------------------------------------------------- */ +/* sysfs/driver-moded based gpio access interface */ + +struct bttv_sub_device { + struct device dev; + struct bttv_core *core; + struct list_head list; +}; +#define to_bttv_sub_dev(x) container_of((x), struct bttv_sub_device, dev) + +struct bttv_sub_driver { + struct device_driver drv; + char wanted[20]; + int (*probe)(struct bttv_sub_device *sub); + void (*remove)(struct bttv_sub_device *sub); +}; +#define to_bttv_sub_drv(x) container_of_const((x), struct bttv_sub_driver, drv) + +int bttv_sub_register(struct bttv_sub_driver *drv, char *wanted); +int bttv_sub_unregister(struct bttv_sub_driver *drv); + +/* gpio access functions */ +void bttv_gpio_inout(struct bttv_core *core, u32 mask, u32 outbits); +u32 bttv_gpio_read(struct bttv_core *core); +void bttv_gpio_write(struct bttv_core *core, u32 value); +void bttv_gpio_bits(struct bttv_core *core, u32 mask, u32 bits); + +#define gpio_inout(mask,bits) bttv_gpio_inout(&btv->c, mask, bits) +#define gpio_read() bttv_gpio_read(&btv->c) +#define gpio_write(value) bttv_gpio_write(&btv->c, value) +#define gpio_bits(mask,bits) bttv_gpio_bits(&btv->c, mask, bits) + + +/* ---------------------------------------------------------- */ +/* i2c */ + +#define bttv_call_all(btv, o, f, args...) \ + v4l2_device_call_all(&btv->c.v4l2_dev, 0, o, f, ##args) + +#define bttv_call_all_err(btv, o, f, args...) \ + v4l2_device_call_until_err(&btv->c.v4l2_dev, 0, o, f, ##args) + +extern int bttv_I2CRead(struct bttv *btv, unsigned char addr, char *probe_for); +extern int bttv_I2CWrite(struct bttv *btv, unsigned char addr, unsigned char b1, + unsigned char b2, int both); +extern void bttv_readee(struct bttv *btv, unsigned char *eedata, int addr); + +extern int bttv_input_init(struct bttv *dev); +extern void bttv_input_fini(struct bttv *dev); +extern void bttv_input_irq(struct bttv *dev); + +#endif /* _BTTV_H_ */ diff --git a/drivers/media/pci/bt8xx/bttvp.h b/drivers/media/pci/bt8xx/bttvp.h new file mode 100644 index 000000000..a534e63b9 --- /dev/null +++ b/drivers/media/pci/bt8xx/bttvp.h @@ -0,0 +1,498 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + + bttv - Bt848 frame grabber driver + + bttv's *private* header file -- nobody other than bttv itself + should ever include this file. + + (c) 2000-2002 Gerd Knorr + +*/ + +#ifndef _BTTVP_H_ +#define _BTTVP_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bt848.h" +#include "bttv.h" +#include "btcx-risc.h" + +#ifdef __KERNEL__ + +#define FORMAT_FLAGS_DITHER 0x01 +#define FORMAT_FLAGS_PACKED 0x02 +#define FORMAT_FLAGS_PLANAR 0x04 +#define FORMAT_FLAGS_RAW 0x08 +#define FORMAT_FLAGS_CrCb 0x10 + +#define RISC_SLOT_O_VBI 4 +#define RISC_SLOT_O_FIELD 6 +#define RISC_SLOT_E_VBI 10 +#define RISC_SLOT_E_FIELD 12 +#define RISC_SLOT_LOOP 14 + +#define RESOURCE_VIDEO_STREAM 2 +#define RESOURCE_VBI 4 +#define RESOURCE_VIDEO_READ 8 + +#define RAW_LINES 640 +#define RAW_BPL 1024 + +#define UNSET (-1U) + +/* Min. value in VDELAY register. */ +#define MIN_VDELAY 2 +/* Even to get Cb first, odd for Cr. */ +#define MAX_HDELAY (0x3FF & -2) +/* Limits scaled width, which must be a multiple of 4. */ +#define MAX_HACTIVE (0x3FF & -4) + +#define BTTV_NORMS (\ + V4L2_STD_PAL | V4L2_STD_PAL_N | \ + V4L2_STD_PAL_Nc | V4L2_STD_SECAM | \ + V4L2_STD_NTSC | V4L2_STD_PAL_M | \ + V4L2_STD_PAL_60) +/* ---------------------------------------------------------- */ + +struct bttv_tvnorm { + int v4l2_id; + char *name; + u32 Fsc; + u16 swidth, sheight; /* scaled standard width, height */ + u16 totalwidth; + u8 adelay, bdelay, iform; + u32 scaledtwidth; + u16 hdelayx1, hactivex1; + u16 vdelay; + u8 vbipack; + u16 vtotal; + int sram; + /* ITU-R frame line number of the first VBI line we can + capture, of the first and second field. The last possible line + is determined by cropcap.bounds. */ + u16 vbistart[2]; + /* Horizontally this counts fCLKx1 samples following the leading + edge of the horizontal sync pulse, vertically ITU-R frame line + numbers of the first field times two (2, 4, 6, ... 524 or 624). */ + struct v4l2_cropcap cropcap; +}; +extern const struct bttv_tvnorm bttv_tvnorms[]; + +struct bttv_format { + int fourcc; /* video4linux 2 */ + int btformat; /* BT848_COLOR_FMT_* */ + int btswap; /* BT848_COLOR_CTL_* */ + int depth; /* bit/pixel */ + int flags; + int hshift,vshift; /* for planar modes */ +}; + +struct bttv_ir { + struct rc_dev *dev; + struct bttv *btv; + struct timer_list timer; + + char name[32]; + char phys[32]; + + /* Usual gpio signalling */ + u32 mask_keycode; + u32 mask_keydown; + u32 mask_keyup; + u32 polling; + u32 last_gpio; + int shift_by; + int rc5_remote_gap; + + /* RC5 gpio */ + bool rc5_gpio; /* Is RC5 legacy GPIO enabled? */ + u32 last_bit; /* last raw bit seen */ + u32 code; /* raw code under construction */ + ktime_t base_time; /* time of last seen code */ + bool active; /* building raw code */ +}; + + +/* ---------------------------------------------------------- */ + +struct bttv_geometry { + u8 vtc,crop,comb; + u16 width,hscale,hdelay; + u16 sheight,vscale,vdelay,vtotal; +}; + +struct bttv_buffer { + /* common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer vbuf; + struct list_head list; + + /* bttv specific */ + int btformat; + int btswap; + struct bttv_geometry geo; + struct btcx_riscmem top; + struct btcx_riscmem bottom; +}; + +struct bttv_buffer_set { + struct bttv_buffer *top; /* top field buffer */ + struct bttv_buffer *bottom; /* bottom field buffer */ + unsigned int top_irq; + unsigned int frame_irq; +}; + +struct bttv_vbi_fmt { + struct v4l2_vbi_format fmt; + + /* fmt.start[] and count[] refer to this video standard. */ + const struct bttv_tvnorm *tvnorm; + + /* Earliest possible start of video capturing with this + v4l2_vbi_format, in struct bttv_crop.rect units. */ + __s32 end; +}; + +/* bttv-vbi.c */ +extern const struct vb2_ops bttv_vbi_qops; + +void bttv_vbi_fmt_reset(struct bttv_vbi_fmt *f, unsigned int norm); + +struct bttv_crop { + /* A cropping rectangle in struct bttv_tvnorm.cropcap units. */ + struct v4l2_rect rect; + + /* Scaled image size limits with this crop rect. Divide + max_height, but not min_height, by two when capturing + single fields. See also bttv_crop_reset() and + bttv_crop_adjust() in bttv-driver.c. */ + __s32 min_scaled_width; + __s32 min_scaled_height; + __s32 max_scaled_width; + __s32 max_scaled_height; +}; + +/* ---------------------------------------------------------- */ +/* bttv-risc.c */ + +/* risc code generators - capture */ +int bttv_risc_packed(struct bttv *btv, struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int offset, unsigned int bpl, + unsigned int pitch, unsigned int skip_lines, + unsigned int store_lines); + +/* control dma register + risc main loop */ +void bttv_set_dma(struct bttv *btv, int override); +int bttv_risc_init_main(struct bttv *btv); +int bttv_risc_hook(struct bttv *btv, int slot, struct btcx_riscmem *risc, + int irqflags); + +/* capture buffer handling */ +int bttv_buffer_risc(struct bttv *btv, struct bttv_buffer *buf); +int bttv_buffer_activate_video(struct bttv *btv, + struct bttv_buffer_set *set); +int bttv_buffer_risc_vbi(struct bttv *btv, struct bttv_buffer *buf); +int bttv_buffer_activate_vbi(struct bttv *btv, + struct bttv_buffer *vbi); + +/* ---------------------------------------------------------- */ +/* bttv-vbi.c */ + +/* + * 2048 for compatibility with earlier driver versions. The driver really + * stores 1024 + tvnorm->vbipack * 4 samples per line in the buffer. Note + * tvnorm->vbipack is <= 0xFF (limit of VBIPACK_LO + HI is 0x1FF DWORDs) and + * VBI read()s store a frame counter in the last four bytes of the VBI image. + */ +#define VBI_BPL 2048 + +#define VBI_DEFLINES 16 + +int bttv_try_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *f); +int bttv_g_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *f); +int bttv_s_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *f); + +/* ---------------------------------------------------------- */ +/* bttv-gpio.c */ + +extern const struct bus_type bttv_sub_bus_type; +int bttv_sub_add_device(struct bttv_core *core, char *name); +int bttv_sub_del_devices(struct bttv_core *core); + +/* ---------------------------------------------------------- */ +/* bttv-input.c */ + +extern void init_bttv_i2c_ir(struct bttv *btv); + +/* ---------------------------------------------------------- */ +/* bttv-i2c.c */ +extern int init_bttv_i2c(struct bttv *btv); +extern int fini_bttv_i2c(struct bttv *btv); + +/* ---------------------------------------------------------- */ +/* bttv-driver.c */ + +/* insmod options */ +extern unsigned int bttv_verbose; +extern unsigned int bttv_debug; +extern unsigned int bttv_gpio; +int check_alloc_btres_lock(struct bttv *btv, int bit); +void free_btres_lock(struct bttv *btv, int bits); +extern void bttv_gpio_tracking(struct bttv *btv, char *comment); + +#define dprintk(fmt, ...) \ +do { \ + if (bttv_debug >= 1) \ + pr_debug(fmt, ##__VA_ARGS__); \ +} while (0) +#define dprintk_cont(fmt, ...) \ +do { \ + if (bttv_debug >= 1) \ + pr_cont(fmt, ##__VA_ARGS__); \ +} while (0) +#define d2printk(fmt, ...) \ +do { \ + if (bttv_debug >= 2) \ + printk(fmt, ##__VA_ARGS__); \ +} while (0) + +#define BTTV_MAX_FBUF 0x208000 +#define BTTV_TIMEOUT msecs_to_jiffies(500) /* 0.5 seconds */ +#define BTTV_FREE_IDLE msecs_to_jiffies(1000) /* one second */ + + +struct bttv_pll_info { + unsigned int pll_ifreq; /* PLL input frequency */ + unsigned int pll_ofreq; /* PLL output frequency */ + unsigned int pll_crystal; /* Crystal used for input */ + unsigned int pll_current; /* Currently programmed ofreq */ +}; + +/* for gpio-connected remote control */ +struct bttv_input { + struct input_dev *dev; + char name[32]; + char phys[32]; + u32 mask_keycode; + u32 mask_keydown; +}; + +struct bttv_suspend_state { + u32 gpio_enable; + u32 gpio_data; + int disabled; + int loop_irq; + struct bttv_buffer_set video; + struct bttv_buffer *vbi; +}; + +struct bttv_tea575x_gpio { + u8 data, clk, wren, most; +}; + +struct bttv { + struct bttv_core c; + + /* pci device config */ + unsigned short id; + unsigned char revision; + unsigned char __iomem *bt848_mmio; /* pointer to mmio */ + + /* card configuration info */ + unsigned int cardid; /* pci subsystem id (bt878 based ones) */ + unsigned int tuner_type; /* tuner chip type */ + unsigned int tda9887_conf; + unsigned int svhs, dig; + unsigned int has_saa6588:1; + struct bttv_pll_info pll; + int triton1; + int gpioirq; + + int use_i2c_hw; + + /* old gpio interface */ + int shutdown; + + void (*volume_gpio)(struct bttv *btv, __u16 volume); + void (*audio_mode_gpio)(struct bttv *btv, struct v4l2_tuner *tuner, int set); + + /* new gpio interface */ + spinlock_t gpio_lock; + + /* i2c layer */ + struct i2c_algo_bit_data i2c_algo; + struct i2c_client i2c_client; + int i2c_state, i2c_rc; + int i2c_done; + wait_queue_head_t i2c_queue; + struct v4l2_subdev *sd_msp34xx; + struct v4l2_subdev *sd_tvaudio; + struct v4l2_subdev *sd_tda7432; + + /* video4linux (1) */ + struct video_device video_dev; + struct video_device radio_dev; + struct video_device vbi_dev; + + /* controls */ + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl_handler radio_ctrl_handler; + + /* infrared remote */ + int has_remote; + struct bttv_ir *remote; + + /* I2C remote data */ + struct IR_i2c_init_data init_data; + + /* locking */ + spinlock_t s_lock; + struct mutex lock; + int resources; + + /* video state */ + unsigned int input; + unsigned int audio_input; + unsigned int mute; + unsigned long tv_freq; + unsigned int tvnorm; + v4l2_std_id std; + int hue, contrast, bright, saturation; + struct v4l2_framebuffer fbuf; + __u32 field_count; + + /* various options */ + int opt_combfilter; + int opt_automute; + int opt_vcr_hack; + int opt_uv_ratio; + + /* radio data/state */ + int has_radio; + int has_radio_tuner; + int radio_user; + int radio_uses_msp_demodulator; + unsigned long radio_freq; + + /* miro/pinnacle + Aimslab VHX + philips matchbox (tea5757 radio tuner) support */ + int has_tea575x; + struct bttv_tea575x_gpio tea_gpio; + struct snd_tea575x tea; + + /* ISA stuff (Terratec Active Radio Upgrade) */ + int mbox_ior; + int mbox_iow; + int mbox_csel; + + /* switch status for multi-controller cards */ + char sw_status[4]; + + /* risc memory management data + - must acquire s_lock before changing these + - only the irq handler is supported to touch top + bottom + vcurr */ + struct btcx_riscmem main; + struct list_head capture; /* video capture queue */ + struct list_head vcapture; /* vbi capture queue */ + struct bttv_buffer_set curr; /* active buffers */ + struct bttv_buffer *cvbi; /* active vbi buffer */ + int loop_irq; + int new_input; + + unsigned long dma_on; + struct timer_list timeout; + struct bttv_suspend_state state; + + /* stats */ + unsigned int errors; + unsigned int framedrop; + unsigned int irq_total; + unsigned int irq_me; + + unsigned int users; + struct v4l2_fh fh; + enum v4l2_buf_type type; + + enum v4l2_field field; + int field_last; + + /* video capture */ + struct vb2_queue capq; + const struct bttv_format *fmt; + int width; + int height; + + /* vbi capture */ + struct vb2_queue vbiq; + struct bttv_vbi_fmt vbi_fmt; + unsigned int vbi_count[2]; + + /* Application called VIDIOC_S_SELECTION. */ + int do_crop; + + /* used to make dvb-bt8xx autoloadable */ + struct work_struct request_module_wk; + + /* Default (0) and current (1) video capturing + cropping parameters in bttv_tvnorm.cropcap units. Protected + by bttv.lock. */ + struct bttv_crop crop[2]; + + /* Earliest possible start of video capturing in + bttv_tvnorm.cropcap line units. Set by check_alloc_btres() + and free_btres(). Protected by bttv.lock. */ + __s32 vbi_end; + + /* Latest possible end of VBI capturing (= crop[x].rect.top when + VIDEO_RESOURCES are locked). Set by check_alloc_btres() + and free_btres(). Protected by bttv.lock. */ + __s32 crop_start; +}; + +static inline struct bttv *to_bttv(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct bttv, c.v4l2_dev); +} + +/* our devices */ +#define BTTV_MAX 32 +extern unsigned int bttv_num; +extern struct bttv *bttvs[BTTV_MAX]; + +static inline unsigned int bttv_muxsel(const struct bttv *btv, + unsigned int input) +{ + return (bttv_tvcards[btv->c.type].muxsel >> (input * 2)) & 3; +} + +#endif + +void init_irqreg(struct bttv *btv); + +#define btwrite(dat,adr) writel((dat), btv->bt848_mmio+(adr)) +#define btread(adr) readl(btv->bt848_mmio+(adr)) + +#define btand(dat,adr) btwrite((dat) & btread(adr), adr) +#define btor(dat,adr) btwrite((dat) | btread(adr), adr) +#define btaor(dat,mask,adr) btwrite((dat) | ((mask) & btread(adr)), adr) + +#endif /* _BTTVP_H_ */ diff --git a/drivers/media/pci/bt8xx/dst.c b/drivers/media/pci/bt8xx/dst.c new file mode 100644 index 000000000..110651e47 --- /dev/null +++ b/drivers/media/pci/bt8xx/dst.c @@ -0,0 +1,1839 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Frontend/Card driver for TwinHan DST Frontend + Copyright (C) 2003 Jamie Honan + Copyright (C) 2004, 2005 Manu Abraham (manu@kromtek.com) + +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dst_priv.h" +#include "dst_common.h" + +static unsigned int verbose; +module_param(verbose, int, 0644); +MODULE_PARM_DESC(verbose, "verbosity level (0 to 3)"); + +static unsigned int dst_addons; +module_param(dst_addons, int, 0644); +MODULE_PARM_DESC(dst_addons, "CA daughterboard, default is 0 (No addons)"); + +static unsigned int dst_algo; +module_param(dst_algo, int, 0644); +MODULE_PARM_DESC(dst_algo, "tuning algo: default is 0=(SW), 1=(HW)"); + +#define HAS_LOCK 1 +#define ATTEMPT_TUNE 2 +#define HAS_POWER 4 + +#define dprintk(level, fmt, arg...) do { \ + if (level >= verbose) \ + printk(KERN_DEBUG pr_fmt("%s: " fmt), \ + __func__, ##arg); \ +} while(0) + +static int dst_command(struct dst_state *state, u8 *data, u8 len); + +static void dst_packsize(struct dst_state *state, int psize) +{ + union dst_gpio_packet bits; + + bits.psize = psize; + bt878_device_control(state->bt, DST_IG_TS, &bits); +} + +static int dst_gpio_outb(struct dst_state *state, u32 mask, u32 enbb, + u32 outhigh, int delay) +{ + union dst_gpio_packet enb; + union dst_gpio_packet bits; + int err; + + enb.enb.mask = mask; + enb.enb.enable = enbb; + + dprintk(2, "mask=[%04x], enbb=[%04x], outhigh=[%04x]\n", + mask, enbb, outhigh); + if ((err = bt878_device_control(state->bt, DST_IG_ENABLE, &enb)) < 0) { + dprintk(2, "dst_gpio_enb error (err == %i, mask == %02x, enb == %02x)\n", + err, mask, enbb); + return -EREMOTEIO; + } + udelay(1000); + /* because complete disabling means no output, no need to do output packet */ + if (enbb == 0) + return 0; + if (delay) + msleep(10); + bits.outp.mask = enbb; + bits.outp.highvals = outhigh; + if ((err = bt878_device_control(state->bt, DST_IG_WRITE, &bits)) < 0) { + dprintk(2, "dst_gpio_outb error (err == %i, enbb == %02x, outhigh == %02x)\n", + err, enbb, outhigh); + return -EREMOTEIO; + } + + return 0; +} + +static int dst_gpio_inb(struct dst_state *state, u8 *result) +{ + union dst_gpio_packet rd_packet; + int err; + + *result = 0; + if ((err = bt878_device_control(state->bt, DST_IG_READ, &rd_packet)) < 0) { + pr_err("dst_gpio_inb error (err == %i)\n", err); + return -EREMOTEIO; + } + *result = (u8) rd_packet.rd.value; + + return 0; +} + +int rdc_reset_state(struct dst_state *state) +{ + dprintk(2, "Resetting state machine\n"); + if (dst_gpio_outb(state, RDC_8820_INT, RDC_8820_INT, 0, NO_DELAY) < 0) { + pr_err("dst_gpio_outb ERROR !\n"); + return -1; + } + msleep(10); + if (dst_gpio_outb(state, RDC_8820_INT, RDC_8820_INT, RDC_8820_INT, NO_DELAY) < 0) { + pr_err("dst_gpio_outb ERROR !\n"); + msleep(10); + return -1; + } + + return 0; +} +EXPORT_SYMBOL(rdc_reset_state); + +static int rdc_8820_reset(struct dst_state *state) +{ + dprintk(3, "Resetting DST\n"); + if (dst_gpio_outb(state, RDC_8820_RESET, RDC_8820_RESET, 0, NO_DELAY) < 0) { + pr_err("dst_gpio_outb ERROR !\n"); + return -1; + } + udelay(1000); + if (dst_gpio_outb(state, RDC_8820_RESET, RDC_8820_RESET, RDC_8820_RESET, DELAY) < 0) { + pr_err("dst_gpio_outb ERROR !\n"); + return -1; + } + + return 0; +} + +static int dst_pio_enable(struct dst_state *state) +{ + if (dst_gpio_outb(state, ~0, RDC_8820_PIO_0_ENABLE, 0, NO_DELAY) < 0) { + pr_err("dst_gpio_outb ERROR !\n"); + return -1; + } + udelay(1000); + + return 0; +} + +int dst_pio_disable(struct dst_state *state) +{ + if (dst_gpio_outb(state, ~0, RDC_8820_PIO_0_DISABLE, RDC_8820_PIO_0_DISABLE, NO_DELAY) < 0) { + pr_err("dst_gpio_outb ERROR !\n"); + return -1; + } + if (state->type_flags & DST_TYPE_HAS_FW_1) + udelay(1000); + + return 0; +} +EXPORT_SYMBOL(dst_pio_disable); + +int dst_wait_dst_ready(struct dst_state *state, u8 delay_mode) +{ + u8 reply; + int i; + + for (i = 0; i < 200; i++) { + if (dst_gpio_inb(state, &reply) < 0) { + pr_err("dst_gpio_inb ERROR !\n"); + return -1; + } + if ((reply & RDC_8820_PIO_0_ENABLE) == 0) { + dprintk(2, "dst wait ready after %d\n", i); + return 1; + } + msleep(10); + } + dprintk(1, "dst wait NOT ready after %d\n", i); + + return 0; +} +EXPORT_SYMBOL(dst_wait_dst_ready); + +int dst_error_recovery(struct dst_state *state) +{ + dprintk(1, "Trying to return from previous errors.\n"); + dst_pio_disable(state); + msleep(10); + dst_pio_enable(state); + msleep(10); + + return 0; +} +EXPORT_SYMBOL(dst_error_recovery); + +int dst_error_bailout(struct dst_state *state) +{ + dprintk(2, "Trying to bailout from previous error.\n"); + rdc_8820_reset(state); + dst_pio_disable(state); + msleep(10); + + return 0; +} +EXPORT_SYMBOL(dst_error_bailout); + +int dst_comm_init(struct dst_state *state) +{ + dprintk(2, "Initializing DST.\n"); + if ((dst_pio_enable(state)) < 0) { + pr_err("PIO Enable Failed\n"); + return -1; + } + if ((rdc_reset_state(state)) < 0) { + pr_err("RDC 8820 State RESET Failed.\n"); + return -1; + } + if (state->type_flags & DST_TYPE_HAS_FW_1) + msleep(100); + else + msleep(5); + + return 0; +} +EXPORT_SYMBOL(dst_comm_init); + +int write_dst(struct dst_state *state, u8 *data, u8 len) +{ + struct i2c_msg msg = { + .addr = state->config->demod_address, + .flags = 0, + .buf = data, + .len = len + }; + + int err; + u8 cnt; + + dprintk(1, "writing [ %*ph ]\n", len, data); + + for (cnt = 0; cnt < 2; cnt++) { + if ((err = i2c_transfer(state->i2c, &msg, 1)) < 0) { + dprintk(2, "_write_dst error (err == %i, len == 0x%02x, b0 == 0x%02x)\n", + err, len, data[0]); + dst_error_recovery(state); + continue; + } else + break; + } + if (cnt >= 2) { + dprintk(2, "RDC 8820 RESET\n"); + dst_error_bailout(state); + + return -1; + } + + return 0; +} +EXPORT_SYMBOL(write_dst); + +int read_dst(struct dst_state *state, u8 *ret, u8 len) +{ + struct i2c_msg msg = { + .addr = state->config->demod_address, + .flags = I2C_M_RD, + .buf = ret, + .len = len + }; + + int err; + int cnt; + + for (cnt = 0; cnt < 2; cnt++) { + if ((err = i2c_transfer(state->i2c, &msg, 1)) < 0) { + dprintk(2, "read_dst error (err == %i, len == 0x%02x, b0 == 0x%02x)\n", + err, len, ret[0]); + dst_error_recovery(state); + continue; + } else + break; + } + if (cnt >= 2) { + dprintk(2, "RDC 8820 RESET\n"); + dst_error_bailout(state); + + return -1; + } + dprintk(3, "reply is %*ph\n", len, ret); + + return 0; +} +EXPORT_SYMBOL(read_dst); + +static int dst_set_polarization(struct dst_state *state) +{ + switch (state->voltage) { + case SEC_VOLTAGE_13: /* Vertical */ + dprintk(2, "Polarization=[Vertical]\n"); + state->tx_tuna[8] &= ~0x40; + break; + case SEC_VOLTAGE_18: /* Horizontal */ + dprintk(2, "Polarization=[Horizontal]\n"); + state->tx_tuna[8] |= 0x40; + break; + case SEC_VOLTAGE_OFF: + break; + } + + return 0; +} + +static int dst_set_freq(struct dst_state *state, u32 freq) +{ + state->frequency = freq; + dprintk(2, "set Frequency %u\n", freq); + + if (state->dst_type == DST_TYPE_IS_SAT) { + freq = freq / 1000; + if (freq < 950 || freq > 2150) + return -EINVAL; + state->tx_tuna[2] = (freq >> 8); + state->tx_tuna[3] = (u8) freq; + state->tx_tuna[4] = 0x01; + state->tx_tuna[8] &= ~0x04; + if (state->type_flags & DST_TYPE_HAS_OBS_REGS) { + if (freq < 1531) + state->tx_tuna[8] |= 0x04; + } + } else if (state->dst_type == DST_TYPE_IS_TERR) { + freq = freq / 1000; + if (freq < 137000 || freq > 858000) + return -EINVAL; + state->tx_tuna[2] = (freq >> 16) & 0xff; + state->tx_tuna[3] = (freq >> 8) & 0xff; + state->tx_tuna[4] = (u8) freq; + } else if (state->dst_type == DST_TYPE_IS_CABLE) { + freq = freq / 1000; + state->tx_tuna[2] = (freq >> 16) & 0xff; + state->tx_tuna[3] = (freq >> 8) & 0xff; + state->tx_tuna[4] = (u8) freq; + } else if (state->dst_type == DST_TYPE_IS_ATSC) { + freq = freq / 1000; + if (freq < 51000 || freq > 858000) + return -EINVAL; + state->tx_tuna[2] = (freq >> 16) & 0xff; + state->tx_tuna[3] = (freq >> 8) & 0xff; + state->tx_tuna[4] = (u8) freq; + state->tx_tuna[5] = 0x00; /* ATSC */ + state->tx_tuna[6] = 0x00; + if (state->dst_hw_cap & DST_TYPE_HAS_ANALOG) + state->tx_tuna[7] = 0x00; /* Digital */ + } else + return -EINVAL; + + return 0; +} + +static int dst_set_bandwidth(struct dst_state *state, u32 bandwidth) +{ + state->bandwidth = bandwidth; + + if (state->dst_type != DST_TYPE_IS_TERR) + return -EOPNOTSUPP; + + switch (bandwidth) { + case 6000000: + if (state->dst_hw_cap & DST_TYPE_HAS_CA) + state->tx_tuna[7] = 0x06; + else { + state->tx_tuna[6] = 0x06; + state->tx_tuna[7] = 0x00; + } + break; + case 7000000: + if (state->dst_hw_cap & DST_TYPE_HAS_CA) + state->tx_tuna[7] = 0x07; + else { + state->tx_tuna[6] = 0x07; + state->tx_tuna[7] = 0x00; + } + break; + case 8000000: + if (state->dst_hw_cap & DST_TYPE_HAS_CA) + state->tx_tuna[7] = 0x08; + else { + state->tx_tuna[6] = 0x08; + state->tx_tuna[7] = 0x00; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int dst_set_inversion(struct dst_state *state, + enum fe_spectral_inversion inversion) +{ + state->inversion = inversion; + switch (inversion) { + case INVERSION_OFF: /* Inversion = Normal */ + state->tx_tuna[8] &= ~0x80; + break; + case INVERSION_ON: + state->tx_tuna[8] |= 0x80; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int dst_set_fec(struct dst_state *state, enum fe_code_rate fec) +{ + state->fec = fec; + return 0; +} + +static enum fe_code_rate dst_get_fec(struct dst_state *state) +{ + return state->fec; +} + +static int dst_set_symbolrate(struct dst_state *state, u32 srate) +{ + u32 symcalc; + u64 sval; + + state->symbol_rate = srate; + if (state->dst_type == DST_TYPE_IS_TERR) { + return -EOPNOTSUPP; + } + dprintk(2, "set symrate %u\n", srate); + srate /= 1000; + if (state->dst_type == DST_TYPE_IS_SAT) { + if (state->type_flags & DST_TYPE_HAS_SYMDIV) { + sval = srate; + sval <<= 20; + do_div(sval, 88000); + symcalc = (u32) sval; + dprintk(2, "set symcalc %u\n", symcalc); + state->tx_tuna[5] = (u8) (symcalc >> 12); + state->tx_tuna[6] = (u8) (symcalc >> 4); + state->tx_tuna[7] = (u8) (symcalc << 4); + } else { + state->tx_tuna[5] = (u8) (srate >> 16) & 0x7f; + state->tx_tuna[6] = (u8) (srate >> 8); + state->tx_tuna[7] = (u8) srate; + } + state->tx_tuna[8] &= ~0x20; + if (state->type_flags & DST_TYPE_HAS_OBS_REGS) { + if (srate > 8000) + state->tx_tuna[8] |= 0x20; + } + } else if (state->dst_type == DST_TYPE_IS_CABLE) { + dprintk(3, "%s\n", state->fw_name); + if (!strncmp(state->fw_name, "DCTNEW", 6)) { + state->tx_tuna[5] = (u8) (srate >> 8); + state->tx_tuna[6] = (u8) srate; + state->tx_tuna[7] = 0x00; + } else if (!strncmp(state->fw_name, "DCT-CI", 6)) { + state->tx_tuna[5] = 0x00; + state->tx_tuna[6] = (u8) (srate >> 8); + state->tx_tuna[7] = (u8) srate; + } + } + return 0; +} + +static int dst_set_modulation(struct dst_state *state, + enum fe_modulation modulation) +{ + if (state->dst_type != DST_TYPE_IS_CABLE) + return -EOPNOTSUPP; + + state->modulation = modulation; + switch (modulation) { + case QAM_16: + state->tx_tuna[8] = 0x10; + break; + case QAM_32: + state->tx_tuna[8] = 0x20; + break; + case QAM_64: + state->tx_tuna[8] = 0x40; + break; + case QAM_128: + state->tx_tuna[8] = 0x80; + break; + case QAM_256: + if (!strncmp(state->fw_name, "DCTNEW", 6)) + state->tx_tuna[8] = 0xff; + else if (!strncmp(state->fw_name, "DCT-CI", 6)) + state->tx_tuna[8] = 0x00; + break; + case QPSK: + case QAM_AUTO: + case VSB_8: + case VSB_16: + default: + return -EINVAL; + + } + + return 0; +} + +static enum fe_modulation dst_get_modulation(struct dst_state *state) +{ + return state->modulation; +} + + +u8 dst_check_sum(u8 *buf, u32 len) +{ + u32 i; + u8 val = 0; + if (!len) + return 0; + for (i = 0; i < len; i++) { + val += buf[i]; + } + return ((~val) + 1); +} +EXPORT_SYMBOL(dst_check_sum); + +static void dst_type_flags_print(struct dst_state *state) +{ + u32 type_flags = state->type_flags; + + pr_err("DST type flags :\n"); + if (type_flags & DST_TYPE_HAS_TS188) + pr_err(" 0x%x newtuner\n", DST_TYPE_HAS_TS188); + if (type_flags & DST_TYPE_HAS_NEWTUNE_2) + pr_err(" 0x%x newtuner 2\n", DST_TYPE_HAS_NEWTUNE_2); + if (type_flags & DST_TYPE_HAS_TS204) + pr_err(" 0x%x ts204\n", DST_TYPE_HAS_TS204); + if (type_flags & DST_TYPE_HAS_VLF) + pr_err(" 0x%x VLF\n", DST_TYPE_HAS_VLF); + if (type_flags & DST_TYPE_HAS_SYMDIV) + pr_err(" 0x%x symdiv\n", DST_TYPE_HAS_SYMDIV); + if (type_flags & DST_TYPE_HAS_FW_1) + pr_err(" 0x%x firmware version = 1\n", DST_TYPE_HAS_FW_1); + if (type_flags & DST_TYPE_HAS_FW_2) + pr_err(" 0x%x firmware version = 2\n", DST_TYPE_HAS_FW_2); + if (type_flags & DST_TYPE_HAS_FW_3) + pr_err(" 0x%x firmware version = 3\n", DST_TYPE_HAS_FW_3); + pr_err("\n"); +} + + +static int dst_type_print(struct dst_state *state, u8 type) +{ + char *otype; + switch (type) { + case DST_TYPE_IS_SAT: + otype = "satellite"; + break; + + case DST_TYPE_IS_TERR: + otype = "terrestrial"; + break; + + case DST_TYPE_IS_CABLE: + otype = "cable"; + break; + + case DST_TYPE_IS_ATSC: + otype = "atsc"; + break; + + default: + dprintk(2, "invalid dst type %d\n", type); + return -EINVAL; + } + dprintk(2, "DST type: %s\n", otype); + + return 0; +} + +static struct tuner_types tuner_list[] = { + { + .tuner_type = TUNER_TYPE_L64724, + .tuner_name = "L 64724", + .board_name = "UNKNOWN", + .fw_name = "UNKNOWN" + }, + + { + .tuner_type = TUNER_TYPE_STV0299, + .tuner_name = "STV 0299", + .board_name = "VP1020", + .fw_name = "DST-MOT" + }, + + { + .tuner_type = TUNER_TYPE_STV0299, + .tuner_name = "STV 0299", + .board_name = "VP1020", + .fw_name = "DST-03T" + }, + + { + .tuner_type = TUNER_TYPE_MB86A15, + .tuner_name = "MB 86A15", + .board_name = "VP1022", + .fw_name = "DST-03T" + }, + + { + .tuner_type = TUNER_TYPE_MB86A15, + .tuner_name = "MB 86A15", + .board_name = "VP1025", + .fw_name = "DST-03T" + }, + + { + .tuner_type = TUNER_TYPE_STV0299, + .tuner_name = "STV 0299", + .board_name = "VP1030", + .fw_name = "DST-CI" + }, + + { + .tuner_type = TUNER_TYPE_STV0299, + .tuner_name = "STV 0299", + .board_name = "VP1030", + .fw_name = "DSTMCI" + }, + + { + .tuner_type = TUNER_TYPE_UNKNOWN, + .tuner_name = "UNKNOWN", + .board_name = "VP2021", + .fw_name = "DCTNEW" + }, + + { + .tuner_type = TUNER_TYPE_UNKNOWN, + .tuner_name = "UNKNOWN", + .board_name = "VP2030", + .fw_name = "DCT-CI" + }, + + { + .tuner_type = TUNER_TYPE_UNKNOWN, + .tuner_name = "UNKNOWN", + .board_name = "VP2031", + .fw_name = "DCT-CI" + }, + + { + .tuner_type = TUNER_TYPE_UNKNOWN, + .tuner_name = "UNKNOWN", + .board_name = "VP2040", + .fw_name = "DCT-CI" + }, + + { + .tuner_type = TUNER_TYPE_UNKNOWN, + .tuner_name = "UNKNOWN", + .board_name = "VP3020", + .fw_name = "DTTFTA" + }, + + { + .tuner_type = TUNER_TYPE_UNKNOWN, + .tuner_name = "UNKNOWN", + .board_name = "VP3021", + .fw_name = "DTTFTA" + }, + + { + .tuner_type = TUNER_TYPE_TDA10046, + .tuner_name = "TDA10046", + .board_name = "VP3040", + .fw_name = "DTT-CI" + }, + + { + .tuner_type = TUNER_TYPE_UNKNOWN, + .tuner_name = "UNKNOWN", + .board_name = "VP3051", + .fw_name = "DTTNXT" + }, + + { + .tuner_type = TUNER_TYPE_NXT200x, + .tuner_name = "NXT200x", + .board_name = "VP3220", + .fw_name = "ATSCDI" + }, + + { + .tuner_type = TUNER_TYPE_NXT200x, + .tuner_name = "NXT200x", + .board_name = "VP3250", + .fw_name = "ATSCAD" + }, +}; + +/* + Known cards list + Satellite + ------------------- + 200103A + VP-1020 DST-MOT LG(old), TS=188 + + VP-1020 DST-03T LG(new), TS=204 + VP-1022 DST-03T LG(new), TS=204 + VP-1025 DST-03T LG(new), TS=204 + + VP-1030 DSTMCI, LG(new), TS=188 + VP-1032 DSTMCI, LG(new), TS=188 + + Cable + ------------------- + VP-2030 DCT-CI, Samsung, TS=204 + VP-2021 DCT-CI, Unknown, TS=204 + VP-2031 DCT-CI, Philips, TS=188 + VP-2040 DCT-CI, Philips, TS=188, with CA daughter board + VP-2040 DCT-CI, Philips, TS=204, without CA daughter board + + Terrestrial + ------------------- + VP-3050 DTTNXT TS=188 + VP-3040 DTT-CI, Philips, TS=188 + VP-3040 DTT-CI, Philips, TS=204 + + ATSC + ------------------- + VP-3220 ATSCDI, TS=188 + VP-3250 ATSCAD, TS=188 + +*/ + +static struct dst_types dst_tlist[] = { + { + .device_id = "200103A", + .offset = 0, + .dst_type = DST_TYPE_IS_SAT, + .type_flags = DST_TYPE_HAS_SYMDIV | DST_TYPE_HAS_FW_1 | DST_TYPE_HAS_OBS_REGS, + .dst_feature = 0, + .tuner_type = 0 + }, /* obsolete */ + + { + .device_id = "DST-020", + .offset = 0, + .dst_type = DST_TYPE_IS_SAT, + .type_flags = DST_TYPE_HAS_SYMDIV | DST_TYPE_HAS_FW_1, + .dst_feature = 0, + .tuner_type = 0 + }, /* obsolete */ + + { + .device_id = "DST-030", + .offset = 0, + .dst_type = DST_TYPE_IS_SAT, + .type_flags = DST_TYPE_HAS_TS204 | DST_TYPE_HAS_TS188 | DST_TYPE_HAS_FW_1, + .dst_feature = 0, + .tuner_type = 0 + }, /* obsolete */ + + { + .device_id = "DST-03T", + .offset = 0, + .dst_type = DST_TYPE_IS_SAT, + .type_flags = DST_TYPE_HAS_SYMDIV | DST_TYPE_HAS_TS204 | DST_TYPE_HAS_FW_2, + .dst_feature = DST_TYPE_HAS_DISEQC3 | DST_TYPE_HAS_DISEQC4 | DST_TYPE_HAS_DISEQC5 + | DST_TYPE_HAS_MAC | DST_TYPE_HAS_MOTO, + .tuner_type = TUNER_TYPE_MULTI + }, + + { + .device_id = "DST-MOT", + .offset = 0, + .dst_type = DST_TYPE_IS_SAT, + .type_flags = DST_TYPE_HAS_SYMDIV | DST_TYPE_HAS_FW_1, + .dst_feature = 0, + .tuner_type = 0 + }, /* obsolete */ + + { + .device_id = "DST-CI", + .offset = 1, + .dst_type = DST_TYPE_IS_SAT, + .type_flags = DST_TYPE_HAS_TS204 | DST_TYPE_HAS_FW_1, + .dst_feature = DST_TYPE_HAS_CA, + .tuner_type = 0 + }, /* An OEM board */ + + { + .device_id = "DSTMCI", + .offset = 1, + .dst_type = DST_TYPE_IS_SAT, + .type_flags = DST_TYPE_HAS_TS188 | DST_TYPE_HAS_FW_2 | DST_TYPE_HAS_FW_BUILD | DST_TYPE_HAS_INC_COUNT | DST_TYPE_HAS_VLF, + .dst_feature = DST_TYPE_HAS_CA | DST_TYPE_HAS_DISEQC3 | DST_TYPE_HAS_DISEQC4 + | DST_TYPE_HAS_MOTO | DST_TYPE_HAS_MAC, + .tuner_type = TUNER_TYPE_MULTI + }, + + { + .device_id = "DSTFCI", + .offset = 1, + .dst_type = DST_TYPE_IS_SAT, + .type_flags = DST_TYPE_HAS_TS188 | DST_TYPE_HAS_FW_1, + .dst_feature = 0, + .tuner_type = 0 + }, /* unknown to vendor */ + + { + .device_id = "DCT-CI", + .offset = 1, + .dst_type = DST_TYPE_IS_CABLE, + .type_flags = DST_TYPE_HAS_MULTI_FE | DST_TYPE_HAS_FW_1 | DST_TYPE_HAS_FW_2 | DST_TYPE_HAS_VLF, + .dst_feature = DST_TYPE_HAS_CA, + .tuner_type = 0 + }, + + { + .device_id = "DCTNEW", + .offset = 1, + .dst_type = DST_TYPE_IS_CABLE, + .type_flags = DST_TYPE_HAS_TS188 | DST_TYPE_HAS_FW_3 | DST_TYPE_HAS_FW_BUILD | DST_TYPE_HAS_MULTI_FE, + .dst_feature = 0, + .tuner_type = 0 + }, + + { + .device_id = "DTT-CI", + .offset = 1, + .dst_type = DST_TYPE_IS_TERR, + .type_flags = DST_TYPE_HAS_FW_2 | DST_TYPE_HAS_MULTI_FE | DST_TYPE_HAS_VLF, + .dst_feature = DST_TYPE_HAS_CA, + .tuner_type = 0 + }, + + { + .device_id = "DTTDIG", + .offset = 1, + .dst_type = DST_TYPE_IS_TERR, + .type_flags = DST_TYPE_HAS_FW_2, + .dst_feature = 0, + .tuner_type = 0 + }, + + { + .device_id = "DTTNXT", + .offset = 1, + .dst_type = DST_TYPE_IS_TERR, + .type_flags = DST_TYPE_HAS_FW_2, + .dst_feature = DST_TYPE_HAS_ANALOG, + .tuner_type = 0 + }, + + { + .device_id = "ATSCDI", + .offset = 1, + .dst_type = DST_TYPE_IS_ATSC, + .type_flags = DST_TYPE_HAS_FW_2, + .dst_feature = 0, + .tuner_type = 0 + }, + + { + .device_id = "ATSCAD", + .offset = 1, + .dst_type = DST_TYPE_IS_ATSC, + .type_flags = DST_TYPE_HAS_MULTI_FE | DST_TYPE_HAS_FW_2 | DST_TYPE_HAS_FW_BUILD, + .dst_feature = DST_TYPE_HAS_MAC | DST_TYPE_HAS_ANALOG, + .tuner_type = 0 + }, + + { } + +}; + +static int dst_get_mac(struct dst_state *state) +{ + u8 get_mac[] = { 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + get_mac[7] = dst_check_sum(get_mac, 7); + if (dst_command(state, get_mac, 8) < 0) { + dprintk(2, "Unsupported Command\n"); + return -1; + } + memset(&state->mac_address, '\0', 8); + memcpy(&state->mac_address, &state->rxbuffer, 6); + pr_err("MAC Address=[%pM]\n", state->mac_address); + + return 0; +} + +static int dst_fw_ver(struct dst_state *state) +{ + u8 get_ver[] = { 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + get_ver[7] = dst_check_sum(get_ver, 7); + if (dst_command(state, get_ver, 8) < 0) { + dprintk(2, "Unsupported Command\n"); + return -1; + } + memcpy(&state->fw_version, &state->rxbuffer, 8); + pr_err("Firmware Ver = %x.%x Build = %02x, on %x:%x, %x-%x-20%02x\n", + state->fw_version[0] >> 4, state->fw_version[0] & 0x0f, + state->fw_version[1], + state->fw_version[5], state->fw_version[6], + state->fw_version[4], state->fw_version[3], state->fw_version[2]); + + return 0; +} + +static int dst_card_type(struct dst_state *state) +{ + int j; + struct tuner_types *p_tuner_list = NULL; + + u8 get_type[] = { 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + get_type[7] = dst_check_sum(get_type, 7); + if (dst_command(state, get_type, 8) < 0) { + dprintk(2, "Unsupported Command\n"); + return -1; + } + memset(&state->card_info, '\0', 8); + memcpy(&state->card_info, &state->rxbuffer, 7); + pr_err("Device Model=[%s]\n", &state->card_info[0]); + + for (j = 0, p_tuner_list = tuner_list; j < ARRAY_SIZE(tuner_list); j++, p_tuner_list++) { + if (!strcmp(&state->card_info[0], p_tuner_list->board_name)) { + state->tuner_type = p_tuner_list->tuner_type; + pr_err("DST has [%s] tuner, tuner type=[%d]\n", + p_tuner_list->tuner_name, p_tuner_list->tuner_type); + } + } + + return 0; +} + +static int dst_get_vendor(struct dst_state *state) +{ + u8 get_vendor[] = { 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + get_vendor[7] = dst_check_sum(get_vendor, 7); + if (dst_command(state, get_vendor, 8) < 0) { + dprintk(2, "Unsupported Command\n"); + return -1; + } + memset(&state->vendor, '\0', 8); + memcpy(&state->vendor, &state->rxbuffer, 7); + pr_err("Vendor=[%s]\n", &state->vendor[0]); + + return 0; +} + +static void debug_dst_buffer(struct dst_state *state) +{ + dprintk(3, "%s: [ %*ph ]\n", __func__, 8, state->rxbuffer); +} + +static int dst_check_stv0299(struct dst_state *state) +{ + u8 check_stv0299[] = { 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + check_stv0299[7] = dst_check_sum(check_stv0299, 7); + if (dst_command(state, check_stv0299, 8) < 0) { + pr_err("Cmd=[0x04] failed\n"); + return -1; + } + debug_dst_buffer(state); + + if (memcmp(&check_stv0299, &state->rxbuffer, 8)) { + pr_err("Found a STV0299 NIM\n"); + state->tuner_type = TUNER_TYPE_STV0299; + return 0; + } + + return -1; +} + +static int dst_check_mb86a15(struct dst_state *state) +{ + u8 check_mb86a15[] = { 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + check_mb86a15[7] = dst_check_sum(check_mb86a15, 7); + if (dst_command(state, check_mb86a15, 8) < 0) { + pr_err("Cmd=[0x10], failed\n"); + return -1; + } + debug_dst_buffer(state); + + if (memcmp(&check_mb86a15, &state->rxbuffer, 8) < 0) { + pr_err("Found a MB86A15 NIM\n"); + state->tuner_type = TUNER_TYPE_MB86A15; + return 0; + } + + return -1; +} + +static int dst_get_tuner_info(struct dst_state *state) +{ + u8 get_tuner_1[] = { 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + u8 get_tuner_2[] = { 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + get_tuner_1[7] = dst_check_sum(get_tuner_1, 7); + get_tuner_2[7] = dst_check_sum(get_tuner_2, 7); + pr_err("DST TYpe = MULTI FE\n"); + if (state->type_flags & DST_TYPE_HAS_MULTI_FE) { + if (dst_command(state, get_tuner_1, 8) < 0) { + dprintk(2, "Cmd=[0x13], Unsupported\n"); + goto force; + } + } else { + if (dst_command(state, get_tuner_2, 8) < 0) { + dprintk(2, "Cmd=[0xb], Unsupported\n"); + goto force; + } + } + memcpy(&state->board_info, &state->rxbuffer, 8); + if (state->type_flags & DST_TYPE_HAS_MULTI_FE) { + pr_err("DST type has TS=188\n"); + } + if (state->board_info[0] == 0xbc) { + if (state->dst_type != DST_TYPE_IS_ATSC) + state->type_flags |= DST_TYPE_HAS_TS188; + else + state->type_flags |= DST_TYPE_HAS_NEWTUNE_2; + + if (state->board_info[1] == 0x01) { + state->dst_hw_cap |= DST_TYPE_HAS_DBOARD; + pr_err("DST has Daughterboard\n"); + } + } + + return 0; +force: + if (!strncmp(state->fw_name, "DCT-CI", 6)) { + state->type_flags |= DST_TYPE_HAS_TS204; + pr_err("Forcing [%s] to TS188\n", state->fw_name); + } + + return -1; +} + +static int dst_get_device_id(struct dst_state *state) +{ + u8 reply; + + int i, j; + struct dst_types *p_dst_type = NULL; + struct tuner_types *p_tuner_list = NULL; + + u8 use_dst_type = 0; + u32 use_type_flags = 0; + + static u8 device_type[8] = {0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff}; + + state->tuner_type = 0; + device_type[7] = dst_check_sum(device_type, 7); + + if (write_dst(state, device_type, FIXED_COMM)) + return -1; /* Write failed */ + if ((dst_pio_disable(state)) < 0) + return -1; + if (read_dst(state, &reply, GET_ACK)) + return -1; /* Read failure */ + if (reply != ACK) { + dprintk(2, "Write not Acknowledged! [Reply=0x%02x]\n", reply); + return -1; /* Unack'd write */ + } + if (!dst_wait_dst_ready(state, DEVICE_INIT)) + return -1; /* DST not ready yet */ + if (read_dst(state, state->rxbuffer, FIXED_COMM)) + return -1; + + dst_pio_disable(state); + if (state->rxbuffer[7] != dst_check_sum(state->rxbuffer, 7)) { + dprintk(2, "Checksum failure!\n"); + return -1; /* Checksum failure */ + } + state->rxbuffer[7] = '\0'; + + for (i = 0, p_dst_type = dst_tlist; i < ARRAY_SIZE(dst_tlist); i++, p_dst_type++) { + if (!strncmp (&state->rxbuffer[p_dst_type->offset], p_dst_type->device_id, strlen (p_dst_type->device_id))) { + use_type_flags = p_dst_type->type_flags; + use_dst_type = p_dst_type->dst_type; + + /* Card capabilities */ + state->dst_hw_cap = p_dst_type->dst_feature; + pr_err("Recognise [%s]\n", p_dst_type->device_id); + strscpy(state->fw_name, p_dst_type->device_id, + sizeof(state->fw_name)); + /* Multiple tuners */ + if (p_dst_type->tuner_type & TUNER_TYPE_MULTI) { + switch (use_dst_type) { + case DST_TYPE_IS_SAT: + /* STV0299 check */ + if (dst_check_stv0299(state) < 0) { + pr_err("Unsupported\n"); + state->tuner_type = TUNER_TYPE_MB86A15; + } + break; + default: + break; + } + if (dst_check_mb86a15(state) < 0) + pr_err("Unsupported\n"); + /* Single tuner */ + } else { + state->tuner_type = p_dst_type->tuner_type; + } + for (j = 0, p_tuner_list = tuner_list; j < ARRAY_SIZE(tuner_list); j++, p_tuner_list++) { + if (!(strncmp(p_dst_type->device_id, p_tuner_list->fw_name, 7)) && + p_tuner_list->tuner_type == state->tuner_type) { + pr_err("[%s] has a [%s]\n", + p_dst_type->device_id, p_tuner_list->tuner_name); + } + } + break; + } + } + + if (i >= ARRAY_SIZE(dst_tlist)) { + pr_err("Unable to recognize %s or %s\n", &state->rxbuffer[0], &state->rxbuffer[1]); + pr_err("please email linux-dvb@linuxtv.org with this type in"); + use_dst_type = DST_TYPE_IS_SAT; + use_type_flags = DST_TYPE_HAS_SYMDIV; + } + dst_type_print(state, use_dst_type); + state->type_flags = use_type_flags; + state->dst_type = use_dst_type; + dst_type_flags_print(state); + + return 0; +} + +static int dst_probe(struct dst_state *state) +{ + mutex_init(&state->dst_mutex); + if (dst_addons & DST_TYPE_HAS_CA) { + if ((rdc_8820_reset(state)) < 0) { + pr_err("RDC 8820 RESET Failed.\n"); + return -1; + } + msleep(4000); + } else { + msleep(100); + } + if ((dst_comm_init(state)) < 0) { + pr_err("DST Initialization Failed.\n"); + return -1; + } + msleep(100); + if (dst_get_device_id(state) < 0) { + pr_err("unknown device.\n"); + return -1; + } + if (dst_get_mac(state) < 0) { + dprintk(2, "MAC: Unsupported command\n"); + } + if ((state->type_flags & DST_TYPE_HAS_MULTI_FE) || (state->type_flags & DST_TYPE_HAS_FW_BUILD)) { + if (dst_get_tuner_info(state) < 0) + dprintk(2, "Tuner: Unsupported command\n"); + } + if (state->type_flags & DST_TYPE_HAS_TS204) { + dst_packsize(state, 204); + } + if (state->type_flags & DST_TYPE_HAS_FW_BUILD) { + if (dst_fw_ver(state) < 0) { + dprintk(2, "FW: Unsupported command\n"); + return 0; + } + if (dst_card_type(state) < 0) { + dprintk(2, "Card: Unsupported command\n"); + return 0; + } + if (dst_get_vendor(state) < 0) { + dprintk(2, "Vendor: Unsupported command\n"); + return 0; + } + } + + return 0; +} + +static int dst_command(struct dst_state *state, u8 *data, u8 len) +{ + u8 reply; + + mutex_lock(&state->dst_mutex); + if ((dst_comm_init(state)) < 0) { + dprintk(1, "DST Communication Initialization Failed.\n"); + goto error; + } + if (write_dst(state, data, len)) { + dprintk(2, "Trying to recover..\n"); + if ((dst_error_recovery(state)) < 0) { + pr_err("Recovery Failed.\n"); + goto error; + } + goto error; + } + if ((dst_pio_disable(state)) < 0) { + pr_err("PIO Disable Failed.\n"); + goto error; + } + if (state->type_flags & DST_TYPE_HAS_FW_1) + mdelay(3); + if (read_dst(state, &reply, GET_ACK)) { + dprintk(3, "Trying to recover..\n"); + if ((dst_error_recovery(state)) < 0) { + dprintk(2, "Recovery Failed.\n"); + goto error; + } + goto error; + } + if (reply != ACK) { + dprintk(2, "write not acknowledged 0x%02x\n", reply); + goto error; + } + if (len >= 2 && data[0] == 0 && (data[1] == 1 || data[1] == 3)) + goto error; + if (state->type_flags & DST_TYPE_HAS_FW_1) + mdelay(3); + else + udelay(2000); + if (!dst_wait_dst_ready(state, NO_DELAY)) + goto error; + if (read_dst(state, state->rxbuffer, FIXED_COMM)) { + dprintk(3, "Trying to recover..\n"); + if ((dst_error_recovery(state)) < 0) { + dprintk(2, "Recovery failed.\n"); + goto error; + } + goto error; + } + if (state->rxbuffer[7] != dst_check_sum(state->rxbuffer, 7)) { + dprintk(2, "checksum failure\n"); + goto error; + } + mutex_unlock(&state->dst_mutex); + return 0; + +error: + mutex_unlock(&state->dst_mutex); + return -EIO; + +} + +static int dst_get_signal(struct dst_state *state) +{ + int retval; + u8 get_signal[] = { 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb }; + //dprintk("%s: Getting Signal strength and other parameters\n", __func__); + if ((state->diseq_flags & ATTEMPT_TUNE) == 0) { + state->decode_lock = state->decode_strength = state->decode_snr = 0; + return 0; + } + if (0 == (state->diseq_flags & HAS_LOCK)) { + state->decode_lock = state->decode_strength = state->decode_snr = 0; + return 0; + } + if (time_after_eq(jiffies, state->cur_jiff + (HZ / 5))) { + retval = dst_command(state, get_signal, 8); + if (retval < 0) + return retval; + if (state->dst_type == DST_TYPE_IS_SAT) { + state->decode_lock = ((state->rxbuffer[6] & 0x10) == 0) ? 1 : 0; + state->decode_strength = state->rxbuffer[5] << 8; + state->decode_snr = state->rxbuffer[2] << 8 | state->rxbuffer[3]; + } else if ((state->dst_type == DST_TYPE_IS_TERR) || (state->dst_type == DST_TYPE_IS_CABLE)) { + state->decode_lock = (state->rxbuffer[1]) ? 1 : 0; + state->decode_strength = state->rxbuffer[4] << 8; + state->decode_snr = state->rxbuffer[3] << 8; + } else if (state->dst_type == DST_TYPE_IS_ATSC) { + state->decode_lock = (state->rxbuffer[6] == 0x00) ? 1 : 0; + state->decode_strength = state->rxbuffer[4] << 8; + state->decode_snr = state->rxbuffer[2] << 8 | state->rxbuffer[3]; + } + state->cur_jiff = jiffies; + } + return 0; +} + +static int dst_tone_power_cmd(struct dst_state *state) +{ + u8 packet[8] = { 0x00, 0x09, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00 }; + + if (state->dst_type != DST_TYPE_IS_SAT) + return -EOPNOTSUPP; + packet[4] = state->tx_tuna[4]; + packet[2] = state->tx_tuna[2]; + packet[3] = state->tx_tuna[3]; + packet[7] = dst_check_sum (packet, 7); + return dst_command(state, packet, 8); +} + +static int dst_get_tuna(struct dst_state *state) +{ + int retval; + + if ((state->diseq_flags & ATTEMPT_TUNE) == 0) + return 0; + state->diseq_flags &= ~(HAS_LOCK); + if (!dst_wait_dst_ready(state, NO_DELAY)) + return -EIO; + if ((state->type_flags & DST_TYPE_HAS_VLF) && + !(state->dst_type == DST_TYPE_IS_ATSC)) + + retval = read_dst(state, state->rx_tuna, 10); + else + retval = read_dst(state, &state->rx_tuna[2], FIXED_COMM); + if (retval < 0) { + dprintk(3, "read not successful\n"); + return retval; + } + if ((state->type_flags & DST_TYPE_HAS_VLF) && + !(state->dst_type == DST_TYPE_IS_ATSC)) { + + if (state->rx_tuna[9] != dst_check_sum(&state->rx_tuna[0], 9)) { + dprintk(2, "checksum failure ?\n"); + return -EIO; + } + } else { + if (state->rx_tuna[9] != dst_check_sum(&state->rx_tuna[2], 7)) { + dprintk(2, "checksum failure?\n"); + return -EIO; + } + } + if (state->rx_tuna[2] == 0 && state->rx_tuna[3] == 0) + return 0; + if (state->dst_type == DST_TYPE_IS_SAT) { + state->decode_freq = ((state->rx_tuna[2] & 0x7f) << 8) + state->rx_tuna[3]; + } else { + state->decode_freq = ((state->rx_tuna[2] & 0x7f) << 16) + (state->rx_tuna[3] << 8) + state->rx_tuna[4]; + } + state->decode_freq = state->decode_freq * 1000; + state->decode_lock = 1; + state->diseq_flags |= HAS_LOCK; + + return 1; +} + +static int dst_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage voltage); + +static int dst_write_tuna(struct dvb_frontend *fe) +{ + struct dst_state *state = fe->demodulator_priv; + int retval; + u8 reply; + + dprintk(2, "type_flags 0x%x\n", state->type_flags); + state->decode_freq = 0; + state->decode_lock = state->decode_strength = state->decode_snr = 0; + if (state->dst_type == DST_TYPE_IS_SAT) { + if (!(state->diseq_flags & HAS_POWER)) + dst_set_voltage(fe, SEC_VOLTAGE_13); + } + state->diseq_flags &= ~(HAS_LOCK | ATTEMPT_TUNE); + mutex_lock(&state->dst_mutex); + if ((dst_comm_init(state)) < 0) { + dprintk(3, "DST Communication initialization failed.\n"); + goto error; + } +// if (state->type_flags & DST_TYPE_HAS_NEWTUNE) { + if ((state->type_flags & DST_TYPE_HAS_VLF) && + (!(state->dst_type == DST_TYPE_IS_ATSC))) { + + state->tx_tuna[9] = dst_check_sum(&state->tx_tuna[0], 9); + retval = write_dst(state, &state->tx_tuna[0], 10); + } else { + state->tx_tuna[9] = dst_check_sum(&state->tx_tuna[2], 7); + retval = write_dst(state, &state->tx_tuna[2], FIXED_COMM); + } + if (retval < 0) { + dst_pio_disable(state); + dprintk(3, "write not successful\n"); + goto werr; + } + if ((dst_pio_disable(state)) < 0) { + dprintk(3, "DST PIO disable failed !\n"); + goto error; + } + if ((read_dst(state, &reply, GET_ACK) < 0)) { + dprintk(3, "read verify not successful.\n"); + goto error; + } + if (reply != ACK) { + dprintk(3, "write not acknowledged 0x%02x\n", reply); + goto error; + } + state->diseq_flags |= ATTEMPT_TUNE; + retval = dst_get_tuna(state); +werr: + mutex_unlock(&state->dst_mutex); + return retval; + +error: + mutex_unlock(&state->dst_mutex); + return -EIO; +} + +/* + * line22k0 0x00, 0x09, 0x00, 0xff, 0x01, 0x00, 0x00, 0x00 + * line22k1 0x00, 0x09, 0x01, 0xff, 0x01, 0x00, 0x00, 0x00 + * line22k2 0x00, 0x09, 0x02, 0xff, 0x01, 0x00, 0x00, 0x00 + * tone 0x00, 0x09, 0xff, 0x00, 0x01, 0x00, 0x00, 0x00 + * data 0x00, 0x09, 0xff, 0x01, 0x01, 0x00, 0x00, 0x00 + * power_off 0x00, 0x09, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 + * power_on 0x00, 0x09, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00 + * Diseqc 1 0x00, 0x08, 0x04, 0xe0, 0x10, 0x38, 0xf0, 0xec + * Diseqc 2 0x00, 0x08, 0x04, 0xe0, 0x10, 0x38, 0xf4, 0xe8 + * Diseqc 3 0x00, 0x08, 0x04, 0xe0, 0x10, 0x38, 0xf8, 0xe4 + * Diseqc 4 0x00, 0x08, 0x04, 0xe0, 0x10, 0x38, 0xfc, 0xe0 + */ + +static int dst_set_diseqc(struct dvb_frontend *fe, struct dvb_diseqc_master_cmd *cmd) +{ + struct dst_state *state = fe->demodulator_priv; + u8 packet[8] = { 0x00, 0x08, 0x04, 0xe0, 0x10, 0x38, 0xf0, 0xec }; + + if (state->dst_type != DST_TYPE_IS_SAT) + return -EOPNOTSUPP; + if (cmd->msg_len > 0 && cmd->msg_len < 5) + memcpy(&packet[3], cmd->msg, cmd->msg_len); + else if (cmd->msg_len == 5 && state->dst_hw_cap & DST_TYPE_HAS_DISEQC5) + memcpy(&packet[2], cmd->msg, cmd->msg_len); + else + return -EINVAL; + packet[7] = dst_check_sum(&packet[0], 7); + return dst_command(state, packet, 8); +} + +static int dst_set_voltage(struct dvb_frontend *fe, enum fe_sec_voltage voltage) +{ + int need_cmd, retval = 0; + struct dst_state *state = fe->demodulator_priv; + + state->voltage = voltage; + if (state->dst_type != DST_TYPE_IS_SAT) + return -EOPNOTSUPP; + + need_cmd = 0; + + switch (voltage) { + case SEC_VOLTAGE_13: + case SEC_VOLTAGE_18: + if ((state->diseq_flags & HAS_POWER) == 0) + need_cmd = 1; + state->diseq_flags |= HAS_POWER; + state->tx_tuna[4] = 0x01; + break; + case SEC_VOLTAGE_OFF: + need_cmd = 1; + state->diseq_flags &= ~(HAS_POWER | HAS_LOCK | ATTEMPT_TUNE); + state->tx_tuna[4] = 0x00; + break; + default: + return -EINVAL; + } + + if (need_cmd) + retval = dst_tone_power_cmd(state); + + return retval; +} + +static int dst_set_tone(struct dvb_frontend *fe, enum fe_sec_tone_mode tone) +{ + struct dst_state *state = fe->demodulator_priv; + + state->tone = tone; + if (state->dst_type != DST_TYPE_IS_SAT) + return -EOPNOTSUPP; + + switch (tone) { + case SEC_TONE_OFF: + if (state->type_flags & DST_TYPE_HAS_OBS_REGS) + state->tx_tuna[2] = 0x00; + else + state->tx_tuna[2] = 0xff; + break; + + case SEC_TONE_ON: + state->tx_tuna[2] = 0x02; + break; + default: + return -EINVAL; + } + return dst_tone_power_cmd(state); +} + +static int dst_send_burst(struct dvb_frontend *fe, enum fe_sec_mini_cmd minicmd) +{ + struct dst_state *state = fe->demodulator_priv; + + if (state->dst_type != DST_TYPE_IS_SAT) + return -EOPNOTSUPP; + state->minicmd = minicmd; + switch (minicmd) { + case SEC_MINI_A: + state->tx_tuna[3] = 0x02; + break; + case SEC_MINI_B: + state->tx_tuna[3] = 0xff; + break; + } + return dst_tone_power_cmd(state); +} + + +static int bt8xx_dst_init(struct dvb_frontend *fe) +{ + struct dst_state *state = fe->demodulator_priv; + + static u8 sat_tuna_188[] = { 0x09, 0x00, 0x03, 0xb6, 0x01, 0x00, 0x73, 0x21, 0x00, 0x00 }; + static u8 sat_tuna_204[] = { 0x00, 0x00, 0x03, 0xb6, 0x01, 0x55, 0xbd, 0x50, 0x00, 0x00 }; + static u8 ter_tuna_188[] = { 0x09, 0x00, 0x03, 0xb6, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00 }; + static u8 ter_tuna_204[] = { 0x00, 0x00, 0x03, 0xb6, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00 }; + static u8 cab_tuna_188[] = { 0x09, 0x00, 0x03, 0xb6, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00 }; + static u8 cab_tuna_204[] = { 0x00, 0x00, 0x03, 0xb6, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00 }; + static u8 atsc_tuner[] = { 0x00, 0x00, 0x03, 0xb6, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00 }; + + state->inversion = INVERSION_OFF; + state->voltage = SEC_VOLTAGE_13; + state->tone = SEC_TONE_OFF; + state->diseq_flags = 0; + state->k22 = 0x02; + state->bandwidth = 7000000; + state->cur_jiff = jiffies; + if (state->dst_type == DST_TYPE_IS_SAT) + memcpy(state->tx_tuna, ((state->type_flags & DST_TYPE_HAS_VLF) ? sat_tuna_188 : sat_tuna_204), sizeof (sat_tuna_204)); + else if (state->dst_type == DST_TYPE_IS_TERR) + memcpy(state->tx_tuna, ((state->type_flags & DST_TYPE_HAS_VLF) ? ter_tuna_188 : ter_tuna_204), sizeof (ter_tuna_204)); + else if (state->dst_type == DST_TYPE_IS_CABLE) + memcpy(state->tx_tuna, ((state->type_flags & DST_TYPE_HAS_VLF) ? cab_tuna_188 : cab_tuna_204), sizeof (cab_tuna_204)); + else if (state->dst_type == DST_TYPE_IS_ATSC) + memcpy(state->tx_tuna, atsc_tuner, sizeof (atsc_tuner)); + + return 0; +} + +static int dst_read_status(struct dvb_frontend *fe, enum fe_status *status) +{ + struct dst_state *state = fe->demodulator_priv; + + *status = 0; + if (state->diseq_flags & HAS_LOCK) { +// dst_get_signal(state); // don't require(?) to ask MCU + if (state->decode_lock) + *status |= FE_HAS_LOCK | FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_SYNC | FE_HAS_VITERBI; + } + + return 0; +} + +static int dst_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct dst_state *state = fe->demodulator_priv; + + int retval = dst_get_signal(state); + *strength = state->decode_strength; + + return retval; +} + +static int dst_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct dst_state *state = fe->demodulator_priv; + + int retval = dst_get_signal(state); + *snr = state->decode_snr; + + return retval; +} + +static int dst_set_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + int retval = -EINVAL; + struct dst_state *state = fe->demodulator_priv; + + if (p != NULL) { + retval = dst_set_freq(state, p->frequency); + if(retval != 0) + return retval; + dprintk(3, "Set Frequency=[%d]\n", p->frequency); + + if (state->dst_type == DST_TYPE_IS_SAT) { + if (state->type_flags & DST_TYPE_HAS_OBS_REGS) + dst_set_inversion(state, p->inversion); + dst_set_fec(state, p->fec_inner); + dst_set_symbolrate(state, p->symbol_rate); + dst_set_polarization(state); + dprintk(3, "Set Symbolrate=[%d]\n", p->symbol_rate); + + } else if (state->dst_type == DST_TYPE_IS_TERR) + dst_set_bandwidth(state, p->bandwidth_hz); + else if (state->dst_type == DST_TYPE_IS_CABLE) { + dst_set_fec(state, p->fec_inner); + dst_set_symbolrate(state, p->symbol_rate); + dst_set_modulation(state, p->modulation); + } + retval = dst_write_tuna(fe); + } + + return retval; +} + +static int dst_tune_frontend(struct dvb_frontend* fe, + bool re_tune, + unsigned int mode_flags, + unsigned int *delay, + enum fe_status *status) +{ + struct dst_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + + if (re_tune) { + dst_set_freq(state, p->frequency); + dprintk(3, "Set Frequency=[%d]\n", p->frequency); + + if (state->dst_type == DST_TYPE_IS_SAT) { + if (state->type_flags & DST_TYPE_HAS_OBS_REGS) + dst_set_inversion(state, p->inversion); + dst_set_fec(state, p->fec_inner); + dst_set_symbolrate(state, p->symbol_rate); + dst_set_polarization(state); + dprintk(3, "Set Symbolrate=[%d]\n", p->symbol_rate); + + } else if (state->dst_type == DST_TYPE_IS_TERR) + dst_set_bandwidth(state, p->bandwidth_hz); + else if (state->dst_type == DST_TYPE_IS_CABLE) { + dst_set_fec(state, p->fec_inner); + dst_set_symbolrate(state, p->symbol_rate); + dst_set_modulation(state, p->modulation); + } + dst_write_tuna(fe); + } + + if (!(mode_flags & FE_TUNE_MODE_ONESHOT)) + dst_read_status(fe, status); + + *delay = HZ/10; + return 0; +} + +static enum dvbfe_algo dst_get_tuning_algo(struct dvb_frontend *fe) +{ + return dst_algo ? DVBFE_ALGO_HW : DVBFE_ALGO_SW; +} + +static int dst_get_frontend(struct dvb_frontend *fe, + struct dtv_frontend_properties *p) +{ + struct dst_state *state = fe->demodulator_priv; + + p->frequency = state->decode_freq; + if (state->dst_type == DST_TYPE_IS_SAT) { + if (state->type_flags & DST_TYPE_HAS_OBS_REGS) + p->inversion = state->inversion; + p->symbol_rate = state->symbol_rate; + p->fec_inner = dst_get_fec(state); + } else if (state->dst_type == DST_TYPE_IS_TERR) { + p->bandwidth_hz = state->bandwidth; + } else if (state->dst_type == DST_TYPE_IS_CABLE) { + p->symbol_rate = state->symbol_rate; + p->fec_inner = dst_get_fec(state); + p->modulation = dst_get_modulation(state); + } + + return 0; +} + +static void bt8xx_dst_release(struct dvb_frontend *fe) +{ + struct dst_state *state = fe->demodulator_priv; + if (state->dst_ca) { + dvb_unregister_device(state->dst_ca); +#ifdef CONFIG_MEDIA_ATTACH + symbol_put(dst_ca_attach); +#endif + } + kfree(state); +} + +static const struct dvb_frontend_ops dst_dvbt_ops; +static const struct dvb_frontend_ops dst_dvbs_ops; +static const struct dvb_frontend_ops dst_dvbc_ops; +static const struct dvb_frontend_ops dst_atsc_ops; + +struct dst_state *dst_attach(struct dst_state *state, struct dvb_adapter *dvb_adapter) +{ + /* check if the ASIC is there */ + if (dst_probe(state) < 0) { + kfree(state); + return NULL; + } + /* determine settings based on type */ + /* create dvb_frontend */ + switch (state->dst_type) { + case DST_TYPE_IS_TERR: + memcpy(&state->frontend.ops, &dst_dvbt_ops, sizeof(struct dvb_frontend_ops)); + break; + case DST_TYPE_IS_CABLE: + memcpy(&state->frontend.ops, &dst_dvbc_ops, sizeof(struct dvb_frontend_ops)); + break; + case DST_TYPE_IS_SAT: + memcpy(&state->frontend.ops, &dst_dvbs_ops, sizeof(struct dvb_frontend_ops)); + break; + case DST_TYPE_IS_ATSC: + memcpy(&state->frontend.ops, &dst_atsc_ops, sizeof(struct dvb_frontend_ops)); + break; + default: + pr_err("unknown DST type. please report to the LinuxTV.org DVB mailinglist.\n"); + kfree(state); + return NULL; + } + state->frontend.demodulator_priv = state; + + return state; /* Manu (DST is a card not a frontend) */ +} + +EXPORT_SYMBOL_GPL(dst_attach); + +static const struct dvb_frontend_ops dst_dvbt_ops = { + .delsys = { SYS_DVBT }, + .info = { + .name = "DST DVB-T", + .frequency_min_hz = 137 * MHz, + .frequency_max_hz = 858 * MHz, + .frequency_stepsize_hz = 166667, + .caps = FE_CAN_FEC_AUTO | + FE_CAN_QAM_AUTO | + FE_CAN_QAM_16 | + FE_CAN_QAM_32 | + FE_CAN_QAM_64 | + FE_CAN_QAM_128 | + FE_CAN_QAM_256 | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO + }, + + .release = bt8xx_dst_release, + .init = bt8xx_dst_init, + .tune = dst_tune_frontend, + .set_frontend = dst_set_frontend, + .get_frontend = dst_get_frontend, + .get_frontend_algo = dst_get_tuning_algo, + .read_status = dst_read_status, + .read_signal_strength = dst_read_signal_strength, + .read_snr = dst_read_snr, +}; + +static const struct dvb_frontend_ops dst_dvbs_ops = { + .delsys = { SYS_DVBS }, + .info = { + .name = "DST DVB-S", + .frequency_min_hz = 950 * MHz, + .frequency_max_hz = 2150 * MHz, + .frequency_stepsize_hz = 1 * MHz, + .frequency_tolerance_hz = 29500 * kHz, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + /* . symbol_rate_tolerance = ???,*/ + .caps = FE_CAN_FEC_AUTO | FE_CAN_QPSK + }, + + .release = bt8xx_dst_release, + .init = bt8xx_dst_init, + .tune = dst_tune_frontend, + .set_frontend = dst_set_frontend, + .get_frontend = dst_get_frontend, + .get_frontend_algo = dst_get_tuning_algo, + .read_status = dst_read_status, + .read_signal_strength = dst_read_signal_strength, + .read_snr = dst_read_snr, + .diseqc_send_burst = dst_send_burst, + .diseqc_send_master_cmd = dst_set_diseqc, + .set_voltage = dst_set_voltage, + .set_tone = dst_set_tone, +}; + +static const struct dvb_frontend_ops dst_dvbc_ops = { + .delsys = { SYS_DVBC_ANNEX_A }, + .info = { + .name = "DST DVB-C", + .frequency_min_hz = 51 * MHz, + .frequency_max_hz = 858 * MHz, + .frequency_stepsize_hz = 62500, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + .caps = FE_CAN_FEC_AUTO | + FE_CAN_QAM_AUTO | + FE_CAN_QAM_16 | + FE_CAN_QAM_32 | + FE_CAN_QAM_64 | + FE_CAN_QAM_128 | + FE_CAN_QAM_256 + }, + + .release = bt8xx_dst_release, + .init = bt8xx_dst_init, + .tune = dst_tune_frontend, + .set_frontend = dst_set_frontend, + .get_frontend = dst_get_frontend, + .get_frontend_algo = dst_get_tuning_algo, + .read_status = dst_read_status, + .read_signal_strength = dst_read_signal_strength, + .read_snr = dst_read_snr, +}; + +static const struct dvb_frontend_ops dst_atsc_ops = { + .delsys = { SYS_ATSC }, + .info = { + .name = "DST ATSC", + .frequency_min_hz = 510 * MHz, + .frequency_max_hz = 858 * MHz, + .frequency_stepsize_hz = 62500, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + .caps = FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO | FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB + }, + + .release = bt8xx_dst_release, + .init = bt8xx_dst_init, + .tune = dst_tune_frontend, + .set_frontend = dst_set_frontend, + .get_frontend = dst_get_frontend, + .get_frontend_algo = dst_get_tuning_algo, + .read_status = dst_read_status, + .read_signal_strength = dst_read_signal_strength, + .read_snr = dst_read_snr, +}; + +MODULE_DESCRIPTION("DST DVB-S/T/C/ATSC Combo Frontend driver"); +MODULE_AUTHOR("Jamie Honan, Manu Abraham"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/bt8xx/dst_ca.c b/drivers/media/pci/bt8xx/dst_ca.c new file mode 100644 index 000000000..6da75c6ed --- /dev/null +++ b/drivers/media/pci/bt8xx/dst_ca.c @@ -0,0 +1,675 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + CA-driver for TwinHan DST Frontend/Card + + Copyright (C) 2004, 2005 Manu Abraham (manu@kromtek.com) + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dst_ca.h" +#include "dst_common.h" + +#define DST_CA_ERROR 0 +#define DST_CA_NOTICE 1 +#define DST_CA_INFO 2 +#define DST_CA_DEBUG 3 + +#define dprintk(x, y, z, format, arg...) do { \ + if (z) { \ + if ((x > DST_CA_ERROR) && (x > y)) \ + printk(KERN_ERR "%s: " format "\n", __func__ , ##arg); \ + else if ((x > DST_CA_NOTICE) && (x > y)) \ + printk(KERN_NOTICE "%s: " format "\n", __func__ , ##arg); \ + else if ((x > DST_CA_INFO) && (x > y)) \ + printk(KERN_INFO "%s: " format "\n", __func__ , ##arg); \ + else if ((x > DST_CA_DEBUG) && (x > y)) \ + printk(KERN_DEBUG "%s: " format "\n", __func__ , ##arg); \ + } else { \ + if (x > y) \ + printk(format, ## arg); \ + } \ +} while(0) + + +static DEFINE_MUTEX(dst_ca_mutex); +static unsigned int verbose = 5; +module_param(verbose, int, 0644); +MODULE_PARM_DESC(verbose, "verbose startup messages, default is 1 (yes)"); + +static void put_command_and_length(u8 *data, int command, int length) +{ + data[0] = (command >> 16) & 0xff; + data[1] = (command >> 8) & 0xff; + data[2] = command & 0xff; + data[3] = length; +} + +static void put_checksum(u8 *check_string, int length) +{ + dprintk(verbose, DST_CA_DEBUG, 1, " Computing string checksum."); + dprintk(verbose, DST_CA_DEBUG, 1, " -> string length : 0x%02x", length); + check_string[length] = dst_check_sum (check_string, length); + dprintk(verbose, DST_CA_DEBUG, 1, " -> checksum : 0x%02x", check_string[length]); +} + +static int dst_ci_command(struct dst_state* state, u8 * data, u8 *ca_string, u8 len, int read) +{ + u8 reply; + + mutex_lock(&state->dst_mutex); + dst_comm_init(state); + msleep(65); + + if (write_dst(state, data, len)) { + dprintk(verbose, DST_CA_INFO, 1, " Write not successful, trying to recover"); + dst_error_recovery(state); + goto error; + } + if ((dst_pio_disable(state)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " DST PIO disable failed."); + goto error; + } + if (read_dst(state, &reply, GET_ACK) < 0) { + dprintk(verbose, DST_CA_INFO, 1, " Read not successful, trying to recover"); + dst_error_recovery(state); + goto error; + } + if (read) { + if (! dst_wait_dst_ready(state, LONG_DELAY)) { + dprintk(verbose, DST_CA_NOTICE, 1, " 8820 not ready"); + goto error; + } + if (read_dst(state, ca_string, 128) < 0) { /* Try to make this dynamic */ + dprintk(verbose, DST_CA_INFO, 1, " Read not successful, trying to recover"); + dst_error_recovery(state); + goto error; + } + } + mutex_unlock(&state->dst_mutex); + return 0; + +error: + mutex_unlock(&state->dst_mutex); + return -EIO; +} + + +static int dst_put_ci(struct dst_state *state, u8 *data, int len, u8 *ca_string, int read) +{ + u8 dst_ca_comm_err = 0; + + while (dst_ca_comm_err < RETRIES) { + dprintk(verbose, DST_CA_NOTICE, 1, " Put Command"); + if (dst_ci_command(state, data, ca_string, len, read)) { // If error + dst_error_recovery(state); + dst_ca_comm_err++; // work required here. + } else { + break; + } + } + + if(dst_ca_comm_err == RETRIES) + return -EIO; + + return 0; +} + + + +static int ca_get_app_info(struct dst_state *state) +{ + int length, str_length; + static u8 command[8] = {0x07, 0x40, 0x01, 0x00, 0x01, 0x00, 0x00, 0xff}; + + put_checksum(&command[0], command[0]); + if ((dst_put_ci(state, command, sizeof(command), state->messages, GET_REPLY)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->dst_put_ci FAILED !"); + return -EIO; + } + dprintk(verbose, DST_CA_INFO, 1, " -->dst_put_ci SUCCESS !"); + dprintk(verbose, DST_CA_INFO, 1, " ================================ CI Module Application Info ======================================"); + dprintk(verbose, DST_CA_INFO, 1, " Application Type=[%d], Application Vendor=[%d], Vendor Code=[%d]\n%s: Application info=[%s]", + state->messages[7], (state->messages[8] << 8) | state->messages[9], + (state->messages[10] << 8) | state->messages[11], __func__, (char *)(&state->messages[12])); + dprintk(verbose, DST_CA_INFO, 1, " =================================================================================================="); + + // Transform dst message to correct application_info message + length = state->messages[5]; + str_length = length - 6; + if (str_length < 0) { + str_length = 0; + dprintk(verbose, DST_CA_ERROR, 1, "Invalid string length returned in ca_get_app_info(). Recovering."); + } + + // First, the command and length fields + put_command_and_length(&state->messages[0], CA_APP_INFO, length); + + // Copy application_type, application_manufacturer and manufacturer_code + memmove(&state->messages[4], &state->messages[7], 5); + + // Set string length and copy string + state->messages[9] = str_length; + memmove(&state->messages[10], &state->messages[12], str_length); + + return 0; +} + +static int ca_get_ca_info(struct dst_state *state) +{ + int srcPtr, dstPtr, i, num_ids; + static u8 slot_command[8] = {0x07, 0x40, 0x00, 0x00, 0x02, 0x00, 0x00, 0xff}; + const int in_system_id_pos = 8, out_system_id_pos = 4, in_num_ids_pos = 7; + + put_checksum(&slot_command[0], slot_command[0]); + if ((dst_put_ci(state, slot_command, sizeof (slot_command), state->messages, GET_REPLY)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->dst_put_ci FAILED !"); + return -EIO; + } + dprintk(verbose, DST_CA_INFO, 1, " -->dst_put_ci SUCCESS !"); + + // Print raw data + dprintk(verbose, DST_CA_INFO, 0, " DST data = ["); + for (i = 0; i < state->messages[0] + 1; i++) { + dprintk(verbose, DST_CA_INFO, 0, " 0x%02x", state->messages[i]); + } + dprintk(verbose, DST_CA_INFO, 0, "]\n"); + + // Set the command and length of the output + num_ids = state->messages[in_num_ids_pos]; + if (num_ids >= 100) { + num_ids = 100; + dprintk(verbose, DST_CA_ERROR, 1, "Invalid number of ids (>100). Recovering."); + } + put_command_and_length(&state->messages[0], CA_INFO, num_ids * 2); + + dprintk(verbose, DST_CA_INFO, 0, " CA_INFO = ["); + srcPtr = in_system_id_pos; + dstPtr = out_system_id_pos; + for(i = 0; i < num_ids; i++) { + dprintk(verbose, DST_CA_INFO, 0, " 0x%02x%02x", state->messages[srcPtr + 0], state->messages[srcPtr + 1]); + // Append to output + state->messages[dstPtr + 0] = state->messages[srcPtr + 0]; + state->messages[dstPtr + 1] = state->messages[srcPtr + 1]; + srcPtr += 2; + dstPtr += 2; + } + dprintk(verbose, DST_CA_INFO, 0, "]\n"); + + return 0; +} + +static int ca_get_slot_caps(struct dst_state *state, struct ca_caps *p_ca_caps, void __user *arg) +{ + int i; + u8 slot_cap[256]; + static u8 slot_command[8] = {0x07, 0x40, 0x02, 0x00, 0x02, 0x00, 0x00, 0xff}; + + put_checksum(&slot_command[0], slot_command[0]); + if ((dst_put_ci(state, slot_command, sizeof (slot_command), slot_cap, GET_REPLY)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->dst_put_ci FAILED !"); + return -EIO; + } + dprintk(verbose, DST_CA_NOTICE, 1, " -->dst_put_ci SUCCESS !"); + + /* Will implement the rest soon */ + + dprintk(verbose, DST_CA_INFO, 1, " Slot cap = [%d]", slot_cap[7]); + dprintk(verbose, DST_CA_INFO, 0, "===================================\n"); + for (i = 0; i < slot_cap[0] + 1; i++) + dprintk(verbose, DST_CA_INFO, 0, " %d", slot_cap[i]); + dprintk(verbose, DST_CA_INFO, 0, "\n"); + + p_ca_caps->slot_num = 1; + p_ca_caps->slot_type = 1; + p_ca_caps->descr_num = slot_cap[7]; + p_ca_caps->descr_type = 1; + + if (copy_to_user(arg, p_ca_caps, sizeof (struct ca_caps))) + return -EFAULT; + + return 0; +} + +/* Need some more work */ +static int ca_get_slot_descr(struct dst_state *state, struct ca_msg *p_ca_message, void __user *arg) +{ + return -EOPNOTSUPP; +} + + +static int ca_get_slot_info(struct dst_state *state, struct ca_slot_info *p_ca_slot_info, void __user *arg) +{ + int i; + static u8 slot_command[8] = {0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff}; + + u8 *slot_info = state->messages; + + put_checksum(&slot_command[0], 7); + if ((dst_put_ci(state, slot_command, sizeof (slot_command), slot_info, GET_REPLY)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->dst_put_ci FAILED !"); + return -EIO; + } + dprintk(verbose, DST_CA_INFO, 1, " -->dst_put_ci SUCCESS !"); + + /* Will implement the rest soon */ + + dprintk(verbose, DST_CA_INFO, 1, " Slot info = [%d]", slot_info[3]); + dprintk(verbose, DST_CA_INFO, 0, "===================================\n"); + for (i = 0; i < 8; i++) + dprintk(verbose, DST_CA_INFO, 0, " %d", slot_info[i]); + dprintk(verbose, DST_CA_INFO, 0, "\n"); + + if (slot_info[4] & 0x80) { + p_ca_slot_info->flags = CA_CI_MODULE_PRESENT; + p_ca_slot_info->num = 1; + p_ca_slot_info->type = CA_CI; + } else if (slot_info[4] & 0x40) { + p_ca_slot_info->flags = CA_CI_MODULE_READY; + p_ca_slot_info->num = 1; + p_ca_slot_info->type = CA_CI; + } else + p_ca_slot_info->flags = 0; + + if (copy_to_user(arg, p_ca_slot_info, sizeof (struct ca_slot_info))) + return -EFAULT; + + return 0; +} + + +static int ca_get_message(struct dst_state *state, struct ca_msg *p_ca_message, void __user *arg) +{ + u8 i = 0; + u32 command = 0; + + if (copy_from_user(p_ca_message, arg, sizeof (struct ca_msg))) + return -EFAULT; + + dprintk(verbose, DST_CA_NOTICE, 1, " Message = [%*ph]", + 3, p_ca_message->msg); + + for (i = 0; i < 3; i++) { + command = command | p_ca_message->msg[i]; + if (i < 2) + command = command << 8; + } + dprintk(verbose, DST_CA_NOTICE, 1, " Command=[0x%x]", command); + + switch (command) { + case CA_APP_INFO: + memcpy(p_ca_message->msg, state->messages, 128); + if (copy_to_user(arg, p_ca_message, sizeof (struct ca_msg)) ) + return -EFAULT; + break; + case CA_INFO: + memcpy(p_ca_message->msg, state->messages, 128); + if (copy_to_user(arg, p_ca_message, sizeof (struct ca_msg)) ) + return -EFAULT; + break; + } + + return 0; +} + +static int handle_dst_tag(struct dst_state *state, struct ca_msg *p_ca_message, struct ca_msg *hw_buffer, u32 length) +{ + if (state->dst_hw_cap & DST_TYPE_HAS_SESSION) { + hw_buffer->msg[2] = p_ca_message->msg[1]; /* MSB */ + hw_buffer->msg[3] = p_ca_message->msg[2]; /* LSB */ + } else { + if (length > 247) { + dprintk(verbose, DST_CA_ERROR, 1, " Message too long ! *** Bailing Out *** !"); + return -EIO; + } + hw_buffer->msg[0] = (length & 0xff) + 7; + hw_buffer->msg[1] = 0x40; + hw_buffer->msg[2] = 0x03; + hw_buffer->msg[3] = 0x00; + hw_buffer->msg[4] = 0x03; + hw_buffer->msg[5] = length & 0xff; + hw_buffer->msg[6] = 0x00; + + /* + * Need to compute length for EN50221 section 8.3.2, for the time being + * assuming 8.3.2 is not applicable + */ + memcpy(&hw_buffer->msg[7], &p_ca_message->msg[4], length); + } + + return 0; +} + +static int write_to_8820(struct dst_state *state, struct ca_msg *hw_buffer, u8 length, u8 reply) +{ + if ((dst_put_ci(state, hw_buffer->msg, length, hw_buffer->msg, reply)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " DST-CI Command failed."); + dprintk(verbose, DST_CA_NOTICE, 1, " Resetting DST."); + rdc_reset_state(state); + return -EIO; + } + dprintk(verbose, DST_CA_NOTICE, 1, " DST-CI Command success."); + + return 0; +} + +static u32 asn_1_decode(u8 *asn_1_array) +{ + u8 length_field = 0, word_count = 0, count = 0; + u32 length = 0; + + length_field = asn_1_array[0]; + dprintk(verbose, DST_CA_DEBUG, 1, " Length field=[%02x]", length_field); + if (length_field < 0x80) { + length = length_field & 0x7f; + dprintk(verbose, DST_CA_DEBUG, 1, " Length=[%02x]\n", length); + } else { + word_count = length_field & 0x7f; + for (count = 0; count < word_count; count++) { + length = length << 8; + length += asn_1_array[count + 1]; + dprintk(verbose, DST_CA_DEBUG, 1, " Length=[%04x]", length); + } + } + return length; +} + +static int debug_string(u8 *msg, u32 length, u32 offset) +{ + u32 i; + + dprintk(verbose, DST_CA_DEBUG, 0, " String=[ "); + for (i = offset; i < length; i++) + dprintk(verbose, DST_CA_DEBUG, 0, "%02x ", msg[i]); + dprintk(verbose, DST_CA_DEBUG, 0, "]\n"); + + return 0; +} + + +static int ca_set_pmt(struct dst_state *state, struct ca_msg *p_ca_message, struct ca_msg *hw_buffer, u8 reply, u8 query) +{ + u32 length = 0; + u8 tag_length = 8; + + length = asn_1_decode(&p_ca_message->msg[3]); + dprintk(verbose, DST_CA_DEBUG, 1, " CA Message length=[%d]", length); + debug_string(&p_ca_message->msg[4], length, 0); /* length is excluding tag & length */ + + memset(hw_buffer->msg, '\0', length); + handle_dst_tag(state, p_ca_message, hw_buffer, length); + put_checksum(hw_buffer->msg, hw_buffer->msg[0]); + + debug_string(hw_buffer->msg, (length + tag_length), 0); /* tags too */ + write_to_8820(state, hw_buffer, (length + tag_length), reply); + + return 0; +} + + +/* Board supports CA PMT reply ? */ +static int dst_check_ca_pmt(struct dst_state *state, struct ca_msg *p_ca_message, struct ca_msg *hw_buffer) +{ + int ca_pmt_reply_test = 0; + + /* Do test board */ + /* Not there yet but soon */ + + /* CA PMT Reply capable */ + if (ca_pmt_reply_test) { + if ((ca_set_pmt(state, p_ca_message, hw_buffer, 1, GET_REPLY)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " ca_set_pmt.. failed !"); + return -EIO; + } + + /* Process CA PMT Reply */ + /* will implement soon */ + dprintk(verbose, DST_CA_ERROR, 1, " Not there yet"); + } + /* CA PMT Reply not capable */ + if (!ca_pmt_reply_test) { + if ((ca_set_pmt(state, p_ca_message, hw_buffer, 0, NO_REPLY)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " ca_set_pmt.. failed !"); + return -EIO; + } + dprintk(verbose, DST_CA_NOTICE, 1, " ca_set_pmt.. success !"); + /* put a dummy message */ + + } + return 0; +} + +static int ca_send_message(struct dst_state *state, struct ca_msg *p_ca_message, void __user *arg) +{ + int i; + u32 command; + struct ca_msg *hw_buffer; + int result = 0; + + hw_buffer = kmalloc_obj(*hw_buffer); + if (!hw_buffer) + return -ENOMEM; + dprintk(verbose, DST_CA_DEBUG, 1, " "); + + if (copy_from_user(p_ca_message, arg, sizeof (struct ca_msg))) { + result = -EFAULT; + goto free_mem_and_exit; + } + + /* EN50221 tag */ + command = 0; + + for (i = 0; i < 3; i++) { + command = command | p_ca_message->msg[i]; + if (i < 2) + command = command << 8; + } + dprintk(verbose, DST_CA_DEBUG, 1, " Command=[0x%x]\n", command); + + switch (command) { + case CA_PMT: + dprintk(verbose, DST_CA_DEBUG, 1, "Command = SEND_CA_PMT"); + if ((ca_set_pmt(state, p_ca_message, hw_buffer, 0, 0)) < 0) { // code simplification started + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_PMT Failed !"); + result = -1; + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_PMT Success !"); + break; + case CA_PMT_REPLY: + dprintk(verbose, DST_CA_INFO, 1, "Command = CA_PMT_REPLY"); + /* Have to handle the 2 basic types of cards here */ + if ((dst_check_ca_pmt(state, p_ca_message, hw_buffer)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_PMT_REPLY Failed !"); + result = -1; + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_PMT_REPLY Success !"); + break; + case CA_APP_INFO_ENQUIRY: // only for debugging + dprintk(verbose, DST_CA_INFO, 1, " Getting Cam Application information"); + + if ((ca_get_app_info(state)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_APP_INFO_ENQUIRY Failed !"); + result = -1; + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_APP_INFO_ENQUIRY Success !"); + break; + case CA_INFO_ENQUIRY: + dprintk(verbose, DST_CA_INFO, 1, " Getting CA Information"); + + if ((ca_get_ca_info(state)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_INFO_ENQUIRY Failed !"); + result = -1; + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_INFO_ENQUIRY Success !"); + break; + } + +free_mem_and_exit: + kfree (hw_buffer); + + return result; +} + +static long dst_ca_ioctl(struct file *file, unsigned int cmd, unsigned long ioctl_arg) +{ + struct dvb_device *dvbdev; + struct dst_state *state; + struct ca_slot_info *p_ca_slot_info; + struct ca_caps *p_ca_caps; + struct ca_msg *p_ca_message; + void __user *arg = (void __user *)ioctl_arg; + int result = 0; + + mutex_lock(&dst_ca_mutex); + dvbdev = file->private_data; + state = dvbdev->priv; + p_ca_message = kmalloc_obj(struct ca_msg); + p_ca_slot_info = kmalloc_obj(struct ca_slot_info); + p_ca_caps = kmalloc_obj(struct ca_caps); + if (!p_ca_message || !p_ca_slot_info || !p_ca_caps) { + result = -ENOMEM; + goto free_mem_and_exit; + } + + /* We have now only the standard ioctl's, the driver is upposed to handle internals. */ + switch (cmd) { + case CA_SEND_MSG: + dprintk(verbose, DST_CA_INFO, 1, " Sending message"); + result = ca_send_message(state, p_ca_message, arg); + + if (result < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_SEND_MSG Failed !"); + goto free_mem_and_exit; + } + break; + case CA_GET_MSG: + dprintk(verbose, DST_CA_INFO, 1, " Getting message"); + result = ca_get_message(state, p_ca_message, arg); + if (result < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_GET_MSG Failed !"); + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_GET_MSG Success !"); + break; + case CA_RESET: + dprintk(verbose, DST_CA_ERROR, 1, " Resetting DST"); + dst_error_bailout(state); + msleep(4000); + break; + case CA_GET_SLOT_INFO: + dprintk(verbose, DST_CA_INFO, 1, " Getting Slot info"); + result = ca_get_slot_info(state, p_ca_slot_info, arg); + if (result < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_GET_SLOT_INFO Failed !"); + result = -1; + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_GET_SLOT_INFO Success !"); + break; + case CA_GET_CAP: + dprintk(verbose, DST_CA_INFO, 1, " Getting Slot capabilities"); + result = ca_get_slot_caps(state, p_ca_caps, arg); + if (result < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_GET_CAP Failed !"); + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_GET_CAP Success !"); + break; + case CA_GET_DESCR_INFO: + dprintk(verbose, DST_CA_INFO, 1, " Getting descrambler description"); + result = ca_get_slot_descr(state, p_ca_message, arg); + if (result < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_GET_DESCR_INFO Failed !"); + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_GET_DESCR_INFO Success !"); + break; + default: + result = -EOPNOTSUPP; + } + free_mem_and_exit: + kfree (p_ca_message); + kfree (p_ca_slot_info); + kfree (p_ca_caps); + + mutex_unlock(&dst_ca_mutex); + return result; +} + +static int dst_ca_open(struct inode *inode, struct file *file) +{ + dprintk(verbose, DST_CA_DEBUG, 1, " Device opened [%p] ", file); + + return 0; +} + +static int dst_ca_release(struct inode *inode, struct file *file) +{ + dprintk(verbose, DST_CA_DEBUG, 1, " Device closed."); + + return 0; +} + +static ssize_t dst_ca_read(struct file *file, char __user *buffer, size_t length, loff_t *offset) +{ + dprintk(verbose, DST_CA_DEBUG, 1, " Device read."); + + return 0; +} + +static ssize_t dst_ca_write(struct file *file, const char __user *buffer, size_t length, loff_t *offset) +{ + dprintk(verbose, DST_CA_DEBUG, 1, " Device write."); + + return 0; +} + +static const struct file_operations dst_ca_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = dst_ca_ioctl, + .open = dst_ca_open, + .release = dst_ca_release, + .read = dst_ca_read, + .write = dst_ca_write, + .llseek = noop_llseek, +}; + +static struct dvb_device dvbdev_ca = { + .priv = NULL, + .users = 1, + .readers = 1, + .writers = 1, + .fops = &dst_ca_fops +}; + +struct dvb_device *dst_ca_attach(struct dst_state *dst, struct dvb_adapter *dvb_adapter) +{ + struct dvb_device *dvbdev; + + dprintk(verbose, DST_CA_ERROR, 1, "registering DST-CA device"); + if (dvb_register_device(dvb_adapter, &dvbdev, &dvbdev_ca, dst, + DVB_DEVICE_CA, 0) == 0) { + dst->dst_ca = dvbdev; + return dst->dst_ca; + } + + return NULL; +} + +EXPORT_SYMBOL_GPL(dst_ca_attach); + +MODULE_DESCRIPTION("DST DVB-S/T/C Combo CA driver"); +MODULE_AUTHOR("Manu Abraham"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/bt8xx/dst_ca.h b/drivers/media/pci/bt8xx/dst_ca.h new file mode 100644 index 000000000..8a9ab9945 --- /dev/null +++ b/drivers/media/pci/bt8xx/dst_ca.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + CA-driver for TwinHan DST Frontend/Card + + Copyright (C) 2004, 2005 Manu Abraham (manu@kromtek.com) + +*/ + +#ifndef _DST_CA_H_ +#define _DST_CA_H_ + +#define RETRIES 5 + + +#define CA_APP_INFO_ENQUIRY 0x9f8020 +#define CA_APP_INFO 0x9f8021 +#define CA_ENTER_MENU 0x9f8022 +#define CA_INFO_ENQUIRY 0x9f8030 +#define CA_INFO 0x9f8031 +#define CA_PMT 0x9f8032 +#define CA_PMT_REPLY 0x9f8033 + +#define CA_CLOSE_MMI 0x9f8800 +#define CA_DISPLAY_CONTROL 0x9f8801 +#define CA_DISPLAY_REPLY 0x9f8802 +#define CA_TEXT_LAST 0x9f8803 +#define CA_TEXT_MORE 0x9f8804 +#define CA_KEYPAD_CONTROL 0x9f8805 +#define CA_KEYPRESS 0x9f8806 + +#define CA_ENQUIRY 0x9f8807 +#define CA_ANSWER 0x9f8808 +#define CA_MENU_LAST 0x9f8809 +#define CA_MENU_MORE 0x9f880a +#define CA_MENU_ANSWER 0x9f880b +#define CA_LIST_LAST 0x9f880c +#define CA_LIST_MORE 0x9f880d + + +struct dst_ca_private { + struct dst_state *dst; + struct dvb_device *dvbdev; +}; + + +#endif diff --git a/drivers/media/pci/bt8xx/dst_common.h b/drivers/media/pci/bt8xx/dst_common.h new file mode 100644 index 000000000..8918af16a --- /dev/null +++ b/drivers/media/pci/bt8xx/dst_common.h @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Frontend-driver for TwinHan DST Frontend + + Copyright (C) 2003 Jamie Honan + Copyright (C) 2004, 2005 Manu Abraham (manu@kromtek.com) + +*/ + +#ifndef DST_COMMON_H +#define DST_COMMON_H + +#include +#include +#include +#include "bt878.h" + +#include "dst_ca.h" + + +#define NO_DELAY 0 +#define LONG_DELAY 1 +#define DEVICE_INIT 2 + +#define DELAY 1 + +#define DST_TYPE_IS_SAT 0 +#define DST_TYPE_IS_TERR 1 +#define DST_TYPE_IS_CABLE 2 +#define DST_TYPE_IS_ATSC 3 + +#define DST_TYPE_HAS_TS188 1 +#define DST_TYPE_HAS_TS204 2 +#define DST_TYPE_HAS_SYMDIV 4 +#define DST_TYPE_HAS_FW_1 8 +#define DST_TYPE_HAS_FW_2 16 +#define DST_TYPE_HAS_FW_3 32 +#define DST_TYPE_HAS_FW_BUILD 64 +#define DST_TYPE_HAS_OBS_REGS 128 +#define DST_TYPE_HAS_INC_COUNT 256 +#define DST_TYPE_HAS_MULTI_FE 512 +#define DST_TYPE_HAS_NEWTUNE_2 1024 +#define DST_TYPE_HAS_DBOARD 2048 +#define DST_TYPE_HAS_VLF 4096 + +/* Card capability list */ + +#define DST_TYPE_HAS_MAC 1 +#define DST_TYPE_HAS_DISEQC3 2 +#define DST_TYPE_HAS_DISEQC4 4 +#define DST_TYPE_HAS_DISEQC5 8 +#define DST_TYPE_HAS_MOTO 16 +#define DST_TYPE_HAS_CA 32 +#define DST_TYPE_HAS_ANALOG 64 /* Analog inputs */ +#define DST_TYPE_HAS_SESSION 128 + +#define TUNER_TYPE_MULTI 1 +#define TUNER_TYPE_UNKNOWN 2 +/* DVB-S */ +#define TUNER_TYPE_L64724 4 +#define TUNER_TYPE_STV0299 8 +#define TUNER_TYPE_MB86A15 16 + +/* DVB-T */ +#define TUNER_TYPE_TDA10046 32 + +/* ATSC */ +#define TUNER_TYPE_NXT200x 64 + + +#define RDC_8820_PIO_0_DISABLE 0 +#define RDC_8820_PIO_0_ENABLE 1 +#define RDC_8820_INT 2 +#define RDC_8820_RESET 4 + +/* DST Communication */ +#define GET_REPLY 1 +#define NO_REPLY 0 + +#define GET_ACK 1 +#define FIXED_COMM 8 + +#define ACK 0xff + +struct dst_state { + + struct i2c_adapter* i2c; + + struct bt878* bt; + + /* configuration settings */ + const struct dst_config* config; + + struct dvb_frontend frontend; + + /* private ASIC data */ + u8 tx_tuna[10]; + u8 rx_tuna[10]; + u8 rxbuffer[10]; + u8 diseq_flags; + u8 dst_type; + u32 type_flags; + u32 frequency; /* intermediate frequency in kHz for QPSK */ + enum fe_spectral_inversion inversion; + u32 symbol_rate; /* symbol rate in Symbols per second */ + enum fe_code_rate fec; + enum fe_sec_voltage voltage; + enum fe_sec_tone_mode tone; + u32 decode_freq; + u8 decode_lock; + u16 decode_strength; + u16 decode_snr; + unsigned long cur_jiff; + u8 k22; + u32 bandwidth; + u32 dst_hw_cap; + u8 dst_fw_version; + enum fe_sec_mini_cmd minicmd; + enum fe_modulation modulation; + u8 messages[256]; + u8 mac_address[8]; + u8 fw_version[8]; + u8 card_info[8]; + u8 vendor[8]; + u8 board_info[8]; + u32 tuner_type; + char *tuner_name; + struct mutex dst_mutex; + char fw_name[8]; + struct dvb_device *dst_ca; +}; + +struct tuner_types { + u32 tuner_type; + char *tuner_name; + char *board_name; + char *fw_name; +}; + +struct dst_types { + char *device_id; + int offset; + u8 dst_type; + u32 type_flags; + u32 dst_feature; + u32 tuner_type; +}; + +struct dst_config +{ + /* the ASIC i2c address */ + u8 demod_address; +}; + +int rdc_reset_state(struct dst_state *state); + +int dst_wait_dst_ready(struct dst_state *state, u8 delay_mode); +int dst_pio_disable(struct dst_state *state); +int dst_error_recovery(struct dst_state* state); +int dst_error_bailout(struct dst_state *state); +int dst_comm_init(struct dst_state* state); + +int write_dst(struct dst_state *state, u8 * data, u8 len); +int read_dst(struct dst_state *state, u8 * ret, u8 len); +u8 dst_check_sum(u8 * buf, u32 len); +struct dst_state* dst_attach(struct dst_state* state, struct dvb_adapter *dvb_adapter); +struct dvb_device *dst_ca_attach(struct dst_state *state, struct dvb_adapter *dvb_adapter); + + +#endif // DST_COMMON_H diff --git a/drivers/media/pci/bt8xx/dst_priv.h b/drivers/media/pci/bt8xx/dst_priv.h new file mode 100644 index 000000000..a4319d41d --- /dev/null +++ b/drivers/media/pci/bt8xx/dst_priv.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * dst-bt878.h: part of the DST driver for the TwinHan DST Frontend + * + * Copyright (C) 2003 Jamie Honan + */ + +struct dst_gpio_enable { + u32 mask; + u32 enable; +}; + +struct dst_gpio_output { + u32 mask; + u32 highvals; +}; + +struct dst_gpio_read { + unsigned long value; +}; + +union dst_gpio_packet { + struct dst_gpio_enable enb; + struct dst_gpio_output outp; + struct dst_gpio_read rd; + int psize; +}; + +#define DST_IG_ENABLE 0 +#define DST_IG_WRITE 1 +#define DST_IG_READ 2 +#define DST_IG_TS 3 + +struct bt878; + +int bt878_device_control(struct bt878 *bt, unsigned int cmd, union dst_gpio_packet *mp); diff --git a/drivers/media/pci/bt8xx/dvb-bt8xx.c b/drivers/media/pci/bt8xx/dvb-bt8xx.c new file mode 100644 index 000000000..935503aca --- /dev/null +++ b/drivers/media/pci/bt8xx/dvb-bt8xx.c @@ -0,0 +1,968 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Bt8xx based DVB adapter driver + * + * Copyright (C) 2002,2003 Florian Schirmer + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "dvb-bt8xx.h" +#include "bt878.h" + +static int debug; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +#define dprintk(fmt, arg...) do { \ + if (debug) \ + printk(KERN_DEBUG pr_fmt("%s: " fmt), \ + __func__, ##arg); \ +} while (0) + + +#define IF_FREQUENCYx6 217 /* 6 * 36.16666666667MHz */ + +static void dvb_bt8xx_work(struct work_struct *t) +{ + struct bt878 *bt = from_work(bt, t, bh_work); + struct dvb_bt8xx_card *card = dev_get_drvdata(&bt->adapter->dev); + + dprintk("%d\n", card->bt->finished_block); + + while (card->bt->last_block != card->bt->finished_block) { + (card->bt->TS_Size ? dvb_dmx_swfilter_204 : dvb_dmx_swfilter) + (&card->demux, + &card->bt->buf_cpu[card->bt->last_block * + card->bt->block_bytes], + card->bt->block_bytes); + card->bt->last_block = (card->bt->last_block + 1) % + card->bt->block_count; + } +} + +static int dvb_bt8xx_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux*dvbdmx = dvbdmxfeed->demux; + struct dvb_bt8xx_card *card = dvbdmx->priv; + int rc; + + dprintk("dvb_bt8xx: start_feed\n"); + + if (!dvbdmx->dmx.frontend) + return -EINVAL; + + mutex_lock(&card->lock); + card->nfeeds++; + rc = card->nfeeds; + if (card->nfeeds == 1) + bt878_start(card->bt, card->gpio_mode, + card->op_sync_orin, card->irq_err_ignore); + mutex_unlock(&card->lock); + return rc; +} + +static int dvb_bt8xx_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct dvb_bt8xx_card *card = dvbdmx->priv; + + dprintk("dvb_bt8xx: stop_feed\n"); + + if (!dvbdmx->dmx.frontend) + return -EINVAL; + + mutex_lock(&card->lock); + card->nfeeds--; + if (card->nfeeds == 0) + bt878_stop(card->bt); + mutex_unlock(&card->lock); + + return 0; +} + +static int is_pci_slot_eq(struct pci_dev* adev, struct pci_dev* bdev) +{ + if ((adev->subsystem_vendor == bdev->subsystem_vendor) && + (adev->subsystem_device == bdev->subsystem_device) && + (adev->bus->number == bdev->bus->number) && + (PCI_SLOT(adev->devfn) == PCI_SLOT(bdev->devfn))) + return 1; + return 0; +} + +static struct bt878 *dvb_bt8xx_878_match(unsigned int bttv_nr, + struct pci_dev* bttv_pci_dev) +{ + unsigned int card_nr; + + /* Hmm, n squared. Hope n is small */ + for (card_nr = 0; card_nr < bt878_num; card_nr++) + if (is_pci_slot_eq(bt878[card_nr].dev, bttv_pci_dev)) + return &bt878[card_nr]; + return NULL; +} + +static int thomson_dtt7579_demod_init(struct dvb_frontend* fe) +{ + static u8 mt352_clock_config [] = { 0x89, 0x38, 0x38 }; + static u8 mt352_reset [] = { 0x50, 0x80 }; + static u8 mt352_adc_ctl_1_cfg [] = { 0x8E, 0x40 }; + static u8 mt352_agc_cfg [] = { 0x67, 0x28, 0x20 }; + static u8 mt352_gpp_ctl_cfg [] = { 0x8C, 0x33 }; + static u8 mt352_capt_range_cfg[] = { 0x75, 0x32 }; + + mt352_write(fe, mt352_clock_config, sizeof(mt352_clock_config)); + udelay(2000); + mt352_write(fe, mt352_reset, sizeof(mt352_reset)); + mt352_write(fe, mt352_adc_ctl_1_cfg, sizeof(mt352_adc_ctl_1_cfg)); + + mt352_write(fe, mt352_agc_cfg, sizeof(mt352_agc_cfg)); + mt352_write(fe, mt352_gpp_ctl_cfg, sizeof(mt352_gpp_ctl_cfg)); + mt352_write(fe, mt352_capt_range_cfg, sizeof(mt352_capt_range_cfg)); + + return 0; +} + +static int thomson_dtt7579_tuner_calc_regs(struct dvb_frontend *fe, u8* pllbuf, int buf_len) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u32 div; + unsigned char bs = 0; + unsigned char cp = 0; + + if (buf_len < 5) + return -EINVAL; + + div = (((c->frequency + 83333) * 3) / 500000) + IF_FREQUENCYx6; + + if (c->frequency < 542000000) + cp = 0xb4; + else if (c->frequency < 771000000) + cp = 0xbc; + else + cp = 0xf4; + + if (c->frequency == 0) + bs = 0x03; + else if (c->frequency < 443250000) + bs = 0x02; + else + bs = 0x08; + + pllbuf[0] = 0x60; + pllbuf[1] = div >> 8; + pllbuf[2] = div & 0xff; + pllbuf[3] = cp; + pllbuf[4] = bs; + + return 5; +} + +static struct mt352_config thomson_dtt7579_config = { + .demod_address = 0x0f, + .demod_init = thomson_dtt7579_demod_init, +}; + +static struct zl10353_config thomson_dtt7579_zl10353_config = { + .demod_address = 0x0f, +}; + +static int cx24108_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u32 freq = c->frequency; + int i, a, n, pump; + u32 band, pll; + static const u32 osci[] = { + 950000, 1019000, 1075000, 1178000, 1296000, 1432000, + 1576000, 1718000, 1856000, 2036000, 2150000 + }; + static const u32 bandsel[] = { + 0, 0x00020000, 0x00040000, 0x00100800, 0x00101000, + 0x00102000, 0x00104000, 0x00108000, 0x00110000, + 0x00120000, 0x00140000 + }; + + #define XTAL 1011100 /* Hz, really 1.0111 MHz and a /10 prescaler */ + dprintk("cx24108 debug: entering SetTunerFreq, freq=%d\n", freq); + + /* This is really the bit driving the tuner chip cx24108 */ + + if (freq<950000) + freq = 950000; /* kHz */ + else if (freq>2150000) + freq = 2150000; /* satellite IF is 950..2150MHz */ + + /* decide which VCO to use for the input frequency */ + for(i = 1; (i < ARRAY_SIZE(osci) - 1) && (osci[i] < freq); i++); + dprintk("cx24108 debug: select vco #%d (f=%d)\n", i, freq); + band=bandsel[i]; + /* the gain values must be set by SetSymbolrate */ + /* compute the pll divider needed, from Conexant data sheet, + resolved for (n*32+a), remember f(vco) is f(receive) *2 or *4, + depending on the divider bit. It is set to /4 on the 2 lowest + bands */ + n=((i<=2?2:1)*freq*10L)/(XTAL/100); + a=n%32; n/=32; if(a==0) n--; + pump=(freq<(osci[i-1]+osci[i])/2); + pll=0xf8000000| + ((pump?1:2)<<(14+11))| + ((n&0x1ff)<<(5+11))| + ((a&0x1f)<<11); + /* everything is shifted left 11 bits to left-align the bits in the + 32bit word. Output to the tuner goes MSB-aligned, after all */ + dprintk("cx24108 debug: pump=%d, n=%d, a=%d\n", pump, n, a); + cx24110_pll_write(fe,band); + /* set vga and vca to their widest-band settings, as a precaution. + SetSymbolrate might not be called to set this up */ + cx24110_pll_write(fe,0x500c0000); + cx24110_pll_write(fe,0x83f1f800); + cx24110_pll_write(fe,pll); + //writereg(client,0x56,0x7f); + + return 0; +} + +static int pinnsat_tuner_init(struct dvb_frontend* fe) +{ + struct dvb_bt8xx_card *card = fe->dvb->priv; + + bttv_gpio_enable(card->bttv_nr, 1, 1); /* output */ + bttv_write_gpio(card->bttv_nr, 1, 1); /* relay on */ + + return 0; +} + +static int pinnsat_tuner_sleep(struct dvb_frontend* fe) +{ + struct dvb_bt8xx_card *card = fe->dvb->priv; + + bttv_write_gpio(card->bttv_nr, 1, 0); /* relay off */ + + return 0; +} + +static struct cx24110_config pctvsat_config = { + .demod_address = 0x55, +}; + +static int microtune_mt7202dtf_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct dvb_bt8xx_card *card = (struct dvb_bt8xx_card *) fe->dvb->priv; + u8 cfg, cpump, band_select; + u8 data[4]; + u32 div; + struct i2c_msg msg = { .addr = 0x60, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = (36000000 + c->frequency + 83333) / 166666; + cfg = 0x88; + + if (c->frequency < 175000000) + cpump = 2; + else if (c->frequency < 390000000) + cpump = 1; + else if (c->frequency < 470000000) + cpump = 2; + else if (c->frequency < 750000000) + cpump = 2; + else + cpump = 3; + + if (c->frequency < 175000000) + band_select = 0x0e; + else if (c->frequency < 470000000) + band_select = 0x05; + else + band_select = 0x03; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = ((div >> 10) & 0x60) | cfg; + data[3] = (cpump << 6) | band_select; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + i2c_transfer(card->i2c_adapter, &msg, 1); + return (div * 166666 - 36000000); +} + +static int microtune_mt7202dtf_request_firmware(struct dvb_frontend* fe, const struct firmware **fw, char* name) +{ + struct dvb_bt8xx_card* bt = (struct dvb_bt8xx_card*) fe->dvb->priv; + + return request_firmware(fw, name, &bt->bt->dev->dev); +} + +static const struct sp887x_config microtune_mt7202dtf_config = { + .demod_address = 0x70, + .request_firmware = microtune_mt7202dtf_request_firmware, +}; + +static int advbt771_samsung_tdtc9251dh0_demod_init(struct dvb_frontend* fe) +{ + static u8 mt352_clock_config [] = { 0x89, 0x38, 0x2d }; + static u8 mt352_reset [] = { 0x50, 0x80 }; + static u8 mt352_adc_ctl_1_cfg [] = { 0x8E, 0x40 }; + static u8 mt352_agc_cfg [] = { 0x67, 0x10, 0x23, 0x00, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0x40, 0x40 }; + static u8 mt352_av771_extra[] = { 0xB5, 0x7A }; + static u8 mt352_capt_range_cfg[] = { 0x75, 0x32 }; + + mt352_write(fe, mt352_clock_config, sizeof(mt352_clock_config)); + udelay(2000); + mt352_write(fe, mt352_reset, sizeof(mt352_reset)); + mt352_write(fe, mt352_adc_ctl_1_cfg, sizeof(mt352_adc_ctl_1_cfg)); + + mt352_write(fe, mt352_agc_cfg,sizeof(mt352_agc_cfg)); + udelay(2000); + mt352_write(fe, mt352_av771_extra,sizeof(mt352_av771_extra)); + mt352_write(fe, mt352_capt_range_cfg, sizeof(mt352_capt_range_cfg)); + + return 0; +} + +static int advbt771_samsung_tdtc9251dh0_tuner_calc_regs(struct dvb_frontend *fe, u8 *pllbuf, int buf_len) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u32 div; + unsigned char bs = 0; + unsigned char cp = 0; + + if (buf_len < 5) return -EINVAL; + + div = (((c->frequency + 83333) * 3) / 500000) + IF_FREQUENCYx6; + + if (c->frequency < 150000000) + cp = 0xB4; + else if (c->frequency < 173000000) + cp = 0xBC; + else if (c->frequency < 250000000) + cp = 0xB4; + else if (c->frequency < 400000000) + cp = 0xBC; + else if (c->frequency < 420000000) + cp = 0xF4; + else if (c->frequency < 470000000) + cp = 0xFC; + else if (c->frequency < 600000000) + cp = 0xBC; + else if (c->frequency < 730000000) + cp = 0xF4; + else + cp = 0xFC; + + if (c->frequency < 150000000) + bs = 0x01; + else if (c->frequency < 173000000) + bs = 0x01; + else if (c->frequency < 250000000) + bs = 0x02; + else if (c->frequency < 400000000) + bs = 0x02; + else if (c->frequency < 420000000) + bs = 0x02; + else if (c->frequency < 470000000) + bs = 0x02; + else + bs = 0x08; + + pllbuf[0] = 0x61; + pllbuf[1] = div >> 8; + pllbuf[2] = div & 0xff; + pllbuf[3] = cp; + pllbuf[4] = bs; + + return 5; +} + +static struct mt352_config advbt771_samsung_tdtc9251dh0_config = { + .demod_address = 0x0f, + .demod_init = advbt771_samsung_tdtc9251dh0_demod_init, +}; + +static const struct dst_config dst_config = { + .demod_address = 0x55, +}; + +static int or51211_request_firmware(struct dvb_frontend* fe, const struct firmware **fw, char* name) +{ + struct dvb_bt8xx_card* bt = (struct dvb_bt8xx_card*) fe->dvb->priv; + + return request_firmware(fw, name, &bt->bt->dev->dev); +} + +static void or51211_setmode(struct dvb_frontend * fe, int mode) +{ + struct dvb_bt8xx_card *bt = fe->dvb->priv; + bttv_write_gpio(bt->bttv_nr, 0x0002, mode); /* Reset */ + msleep(20); +} + +static void or51211_reset(struct dvb_frontend * fe) +{ + struct dvb_bt8xx_card *bt = fe->dvb->priv; + + /* RESET DEVICE + * reset is controlled by GPIO-0 + * when set to 0 causes reset and when to 1 for normal op + * must remain reset for 128 clock cycles on a 50Mhz clock + * also PRM1 PRM2 & PRM4 are controlled by GPIO-1,GPIO-2 & GPIO-4 + * We assume that the reset has be held low long enough or we + * have been reset by a power on. When the driver is unloaded + * reset set to 0 so if reloaded we have been reset. + */ + /* reset & PRM1,2&4 are outputs */ + int ret = bttv_gpio_enable(bt->bttv_nr, 0x001F, 0x001F); + if (ret != 0) + pr_warn("or51211: Init Error - Can't Reset DVR (%i)\n", ret); + bttv_write_gpio(bt->bttv_nr, 0x001F, 0x0000); /* Reset */ + msleep(20); + /* Now set for normal operation */ + bttv_write_gpio(bt->bttv_nr, 0x0001F, 0x0001); + /* wait for operation to begin */ + msleep(500); +} + +static void or51211_sleep(struct dvb_frontend * fe) +{ + struct dvb_bt8xx_card *bt = fe->dvb->priv; + bttv_write_gpio(bt->bttv_nr, 0x0001, 0x0000); +} + +static const struct or51211_config or51211_config = { + .demod_address = 0x15, + .request_firmware = or51211_request_firmware, + .setmode = or51211_setmode, + .reset = or51211_reset, + .sleep = or51211_sleep, +}; + +static int vp3021_alps_tded4_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct dvb_bt8xx_card *card = (struct dvb_bt8xx_card *) fe->dvb->priv; + u8 buf[4]; + u32 div; + struct i2c_msg msg = { .addr = 0x60, .flags = 0, .buf = buf, .len = sizeof(buf) }; + + div = (c->frequency + 36166667) / 166667; + + buf[0] = (div >> 8) & 0x7F; + buf[1] = div & 0xFF; + buf[2] = 0x85; + if ((c->frequency >= 47000000) && (c->frequency < 153000000)) + buf[3] = 0x01; + else if ((c->frequency >= 153000000) && (c->frequency < 430000000)) + buf[3] = 0x02; + else if ((c->frequency >= 430000000) && (c->frequency < 824000000)) + buf[3] = 0x0C; + else if ((c->frequency >= 824000000) && (c->frequency < 863000000)) + buf[3] = 0x8C; + else + return -EINVAL; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + i2c_transfer(card->i2c_adapter, &msg, 1); + return 0; +} + +static struct nxt6000_config vp3021_alps_tded4_config = { + .demod_address = 0x0a, + .clock_inversion = 1, +}; + +static int digitv_alps_tded4_demod_init(struct dvb_frontend* fe) +{ + static u8 mt352_clock_config [] = { 0x89, 0x38, 0x2d }; + static u8 mt352_reset [] = { 0x50, 0x80 }; + static u8 mt352_adc_ctl_1_cfg [] = { 0x8E, 0x40 }; + static u8 mt352_agc_cfg [] = { 0x67, 0x20, 0xa0 }; + static u8 mt352_capt_range_cfg[] = { 0x75, 0x32 }; + + mt352_write(fe, mt352_clock_config, sizeof(mt352_clock_config)); + udelay(2000); + mt352_write(fe, mt352_reset, sizeof(mt352_reset)); + mt352_write(fe, mt352_adc_ctl_1_cfg, sizeof(mt352_adc_ctl_1_cfg)); + mt352_write(fe, mt352_agc_cfg,sizeof(mt352_agc_cfg)); + mt352_write(fe, mt352_capt_range_cfg, sizeof(mt352_capt_range_cfg)); + + return 0; +} + +static int digitv_alps_tded4_tuner_calc_regs(struct dvb_frontend *fe, u8 *pllbuf, int buf_len) +{ + u32 div; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + + if (buf_len < 5) + return -EINVAL; + + div = (((c->frequency + 83333) * 3) / 500000) + IF_FREQUENCYx6; + + pllbuf[0] = 0x61; + pllbuf[1] = (div >> 8) & 0x7F; + pllbuf[2] = div & 0xFF; + pllbuf[3] = 0x85; + + dprintk("frequency %u, div %u\n", c->frequency, div); + + if (c->frequency < 470000000) + pllbuf[4] = 0x02; + else if (c->frequency > 823000000) + pllbuf[4] = 0x88; + else + pllbuf[4] = 0x08; + + if (c->bandwidth_hz == 8000000) + pllbuf[4] |= 0x04; + + return 5; +} + +static void digitv_alps_tded4_reset(struct dvb_bt8xx_card *bt) +{ + /* + * Reset the frontend, must be called before trying + * to initialise the MT352 or mt352_attach + * will fail. Same goes for the nxt6000 frontend. + * + */ + + int ret = bttv_gpio_enable(bt->bttv_nr, 0x08, 0x08); + if (ret != 0) + pr_warn("digitv_alps_tded4: Init Error - Can't Reset DVR (%i)\n", + ret); + + /* Pulse the reset line */ + bttv_write_gpio(bt->bttv_nr, 0x08, 0x08); /* High */ + bttv_write_gpio(bt->bttv_nr, 0x08, 0x00); /* Low */ + msleep(100); + + bttv_write_gpio(bt->bttv_nr, 0x08, 0x08); /* High */ +} + +static struct mt352_config digitv_alps_tded4_config = { + .demod_address = 0x0a, + .demod_init = digitv_alps_tded4_demod_init, +}; + +static struct lgdt330x_config tdvs_tua6034_config = { + .demod_chip = LGDT3303, + .serial_mpeg = 0x40, /* TPSERIAL for 3303 in TOP_CONTROL */ +}; + +static void lgdt330x_reset(struct dvb_bt8xx_card *bt) +{ + /* Set pin 27 of the lgdt3303 chip high to reset the frontend */ + + /* Pulse the reset line */ + bttv_write_gpio(bt->bttv_nr, 0x00e00007, 0x00000001); /* High */ + bttv_write_gpio(bt->bttv_nr, 0x00e00007, 0x00000000); /* Low */ + msleep(100); + + bttv_write_gpio(bt->bttv_nr, 0x00e00007, 0x00000001); /* High */ + msleep(100); +} + +static void frontend_init(struct dvb_bt8xx_card *card, u32 type) +{ + struct dst_state* state = NULL; + + switch(type) { + case BTTV_BOARD_DVICO_DVBT_LITE: + card->fe = dvb_attach(mt352_attach, &thomson_dtt7579_config, card->i2c_adapter); + + if (card->fe == NULL) + card->fe = dvb_attach(zl10353_attach, &thomson_dtt7579_zl10353_config, + card->i2c_adapter); + + if (card->fe != NULL) { + card->fe->ops.tuner_ops.calc_regs = thomson_dtt7579_tuner_calc_regs; + card->fe->ops.info.frequency_min_hz = 174 * MHz; + card->fe->ops.info.frequency_max_hz = 862 * MHz; + } + break; + + case BTTV_BOARD_DVICO_FUSIONHDTV_5_LITE: + lgdt330x_reset(card); + card->fe = dvb_attach(lgdt330x_attach, &tdvs_tua6034_config, + 0x0e, card->i2c_adapter); + if (card->fe != NULL) { + dvb_attach(simple_tuner_attach, card->fe, + card->i2c_adapter, 0x61, + TUNER_LG_TDVS_H06XF); + dprintk("dvb_bt8xx: lgdt330x detected\n"); + } + break; + + case BTTV_BOARD_NEBULA_DIGITV: + /* + * It is possible to determine the correct frontend using the I2C bus (see the Nebula SDK); + * this would be a cleaner solution than trying each frontend in turn. + */ + + /* Old Nebula (marked (c)2003 on high profile pci card) has nxt6000 demod */ + digitv_alps_tded4_reset(card); + card->fe = dvb_attach(nxt6000_attach, &vp3021_alps_tded4_config, card->i2c_adapter); + if (card->fe != NULL) { + card->fe->ops.tuner_ops.set_params = vp3021_alps_tded4_tuner_set_params; + dprintk("dvb_bt8xx: an nxt6000 was detected on your digitv card\n"); + break; + } + + /* New Nebula (marked (c)2005 on low profile pci card) has mt352 demod */ + digitv_alps_tded4_reset(card); + card->fe = dvb_attach(mt352_attach, &digitv_alps_tded4_config, card->i2c_adapter); + + if (card->fe != NULL) { + card->fe->ops.tuner_ops.calc_regs = digitv_alps_tded4_tuner_calc_regs; + dprintk("dvb_bt8xx: an mt352 was detected on your digitv card\n"); + } + break; + + case BTTV_BOARD_AVDVBT_761: + card->fe = dvb_attach(sp887x_attach, µtune_mt7202dtf_config, card->i2c_adapter); + if (card->fe) { + card->fe->ops.tuner_ops.set_params = microtune_mt7202dtf_tuner_set_params; + } + break; + + case BTTV_BOARD_AVDVBT_771: + card->fe = dvb_attach(mt352_attach, &advbt771_samsung_tdtc9251dh0_config, card->i2c_adapter); + if (card->fe != NULL) { + card->fe->ops.tuner_ops.calc_regs = advbt771_samsung_tdtc9251dh0_tuner_calc_regs; + card->fe->ops.info.frequency_min_hz = 174 * MHz; + card->fe->ops.info.frequency_max_hz = 862 * MHz; + } + break; + + case BTTV_BOARD_TWINHAN_DST: + /* DST is not a frontend driver !!! */ + state = kmalloc_obj(struct dst_state); + if (!state) { + pr_err("No memory\n"); + break; + } + /* Setup the Card */ + state->config = &dst_config; + state->i2c = card->i2c_adapter; + state->bt = card->bt; + state->dst_ca = NULL; + /* DST is not a frontend, attaching the ASIC */ + if (dvb_attach(dst_attach, state, &card->dvb_adapter) == NULL) { + pr_err("%s: Could not find a Twinhan DST\n", __func__); + kfree(state); + break; + } + /* Attach other DST peripherals if any */ + /* Conditional Access device */ + card->fe = &state->frontend; + if (state->dst_hw_cap & DST_TYPE_HAS_CA) + dvb_attach(dst_ca_attach, state, &card->dvb_adapter); + break; + + case BTTV_BOARD_PINNACLESAT: + card->fe = dvb_attach(cx24110_attach, &pctvsat_config, card->i2c_adapter); + if (card->fe) { + card->fe->ops.tuner_ops.init = pinnsat_tuner_init; + card->fe->ops.tuner_ops.sleep = pinnsat_tuner_sleep; + card->fe->ops.tuner_ops.set_params = cx24108_tuner_set_params; + } + break; + + case BTTV_BOARD_PC_HDTV: + card->fe = dvb_attach(or51211_attach, &or51211_config, card->i2c_adapter); + if (card->fe != NULL) + dvb_attach(simple_tuner_attach, card->fe, + card->i2c_adapter, 0x61, + TUNER_PHILIPS_FCV1236D); + break; + } + + if (card->fe == NULL) + pr_err("A frontend driver was not found for device [%04x:%04x] subsystem [%04x:%04x]\n", + card->bt->dev->vendor, + card->bt->dev->device, + card->bt->dev->subsystem_vendor, + card->bt->dev->subsystem_device); + else + if (dvb_register_frontend(&card->dvb_adapter, card->fe)) { + pr_err("Frontend registration failed!\n"); + dvb_frontend_detach(card->fe); + card->fe = NULL; + } +} + +static int dvb_bt8xx_load_card(struct dvb_bt8xx_card *card, u32 type) +{ + int result; + + result = dvb_register_adapter(&card->dvb_adapter, card->card_name, + THIS_MODULE, &card->bt->dev->dev, + adapter_nr); + if (result < 0) { + pr_err("dvb_register_adapter failed (errno = %d)\n", result); + return result; + } + card->dvb_adapter.priv = card; + + card->bt->adapter = card->i2c_adapter; + + memset(&card->demux, 0, sizeof(struct dvb_demux)); + + card->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING | DMX_MEMORY_BASED_FILTERING; + + card->demux.priv = card; + card->demux.filternum = 256; + card->demux.feednum = 256; + card->demux.start_feed = dvb_bt8xx_start_feed; + card->demux.stop_feed = dvb_bt8xx_stop_feed; + card->demux.write_to_decoder = NULL; + + result = dvb_dmx_init(&card->demux); + if (result < 0) { + pr_err("dvb_dmx_init failed (errno = %d)\n", result); + goto err_unregister_adaptor; + } + + card->dmxdev.filternum = 256; + card->dmxdev.demux = &card->demux.dmx; + card->dmxdev.capabilities = 0; + + result = dvb_dmxdev_init(&card->dmxdev, &card->dvb_adapter); + if (result < 0) { + pr_err("dvb_dmxdev_init failed (errno = %d)\n", result); + goto err_dmx_release; + } + + card->fe_hw.source = DMX_FRONTEND_0; + + result = card->demux.dmx.add_frontend(&card->demux.dmx, &card->fe_hw); + if (result < 0) { + pr_err("dvb_dmx_init failed (errno = %d)\n", result); + goto err_dmxdev_release; + } + + card->fe_mem.source = DMX_MEMORY_FE; + + result = card->demux.dmx.add_frontend(&card->demux.dmx, &card->fe_mem); + if (result < 0) { + pr_err("dvb_dmx_init failed (errno = %d)\n", result); + goto err_remove_hw_frontend; + } + + result = card->demux.dmx.connect_frontend(&card->demux.dmx, &card->fe_hw); + if (result < 0) { + pr_err("dvb_dmx_init failed (errno = %d)\n", result); + goto err_remove_mem_frontend; + } + + result = dvb_net_init(&card->dvb_adapter, &card->dvbnet, &card->demux.dmx); + if (result < 0) { + pr_err("dvb_net_init failed (errno = %d)\n", result); + goto err_disconnect_frontend; + } + + INIT_WORK(&card->bt->bh_work, dvb_bt8xx_work); + + frontend_init(card, type); + + return 0; + +err_disconnect_frontend: + card->demux.dmx.disconnect_frontend(&card->demux.dmx); +err_remove_mem_frontend: + card->demux.dmx.remove_frontend(&card->demux.dmx, &card->fe_mem); +err_remove_hw_frontend: + card->demux.dmx.remove_frontend(&card->demux.dmx, &card->fe_hw); +err_dmxdev_release: + dvb_dmxdev_release(&card->dmxdev); +err_dmx_release: + dvb_dmx_release(&card->demux); +err_unregister_adaptor: + dvb_unregister_adapter(&card->dvb_adapter); + return result; +} + +static int dvb_bt8xx_probe(struct bttv_sub_device *sub) +{ + struct dvb_bt8xx_card *card; + struct pci_dev* bttv_pci_dev; + int ret; + + if (!(card = kzalloc_obj(struct dvb_bt8xx_card))) + return -ENOMEM; + + mutex_init(&card->lock); + card->bttv_nr = sub->core->nr; + strscpy(card->card_name, sub->core->v4l2_dev.name, + sizeof(card->card_name)); + card->i2c_adapter = &sub->core->i2c_adap; + + switch(sub->core->type) { + case BTTV_BOARD_PINNACLESAT: + card->gpio_mode = 0x0400c060; + /* should be: BT878_A_GAIN=0,BT878_A_PWRDN,BT878_DA_DPM,BT878_DA_SBR, + BT878_DA_IOM=1,BT878_DA_APP to enable serial highspeed mode. */ + card->op_sync_orin = BT878_RISC_SYNC_MASK; + card->irq_err_ignore = BT878_AFBUS | BT878_AFDSR; + break; + + case BTTV_BOARD_DVICO_DVBT_LITE: + card->gpio_mode = 0x0400C060; + card->op_sync_orin = BT878_RISC_SYNC_MASK; + card->irq_err_ignore = BT878_AFBUS | BT878_AFDSR; + /* 26, 15, 14, 6, 5 + * A_PWRDN DA_DPM DA_SBR DA_IOM_DA + * DA_APP(parallel) */ + break; + + case BTTV_BOARD_DVICO_FUSIONHDTV_5_LITE: + card->gpio_mode = 0x0400c060; + card->op_sync_orin = BT878_RISC_SYNC_MASK; + card->irq_err_ignore = BT878_AFBUS | BT878_AFDSR; + break; + + case BTTV_BOARD_NEBULA_DIGITV: + case BTTV_BOARD_AVDVBT_761: + card->gpio_mode = (1 << 26) | (1 << 14) | (1 << 5); + card->op_sync_orin = BT878_RISC_SYNC_MASK; + card->irq_err_ignore = BT878_AFBUS | BT878_AFDSR; + /* A_PWRDN DA_SBR DA_APP (high speed serial) */ + break; + + case BTTV_BOARD_AVDVBT_771: //case 0x07711461: + card->gpio_mode = 0x0400402B; + card->op_sync_orin = BT878_RISC_SYNC_MASK; + card->irq_err_ignore = BT878_AFBUS | BT878_AFDSR; + /* A_PWRDN DA_SBR DA_APP[0] PKTP=10 RISC_ENABLE FIFO_ENABLE*/ + break; + + case BTTV_BOARD_TWINHAN_DST: + card->gpio_mode = 0x2204f2c; + card->op_sync_orin = BT878_RISC_SYNC_MASK; + card->irq_err_ignore = BT878_APABORT | BT878_ARIPERR | + BT878_APPERR | BT878_AFBUS; + /* 25,21,14,11,10,9,8,3,2 then + * 0x33 = 5,4,1,0 + * A_SEL=SML, DA_MLB, DA_SBR, + * DA_SDR=f, fifo trigger = 32 DWORDS + * IOM = 0 == audio A/D + * DPM = 0 == digital audio mode + * == async data parallel port + * then 0x33 (13 is set by start_capture) + * DA_APP = async data parallel port, + * ACAP_EN = 1, + * RISC+FIFO ENABLE */ + break; + + case BTTV_BOARD_PC_HDTV: + card->gpio_mode = 0x0100EC7B; + card->op_sync_orin = BT878_RISC_SYNC_MASK; + card->irq_err_ignore = BT878_AFBUS | BT878_AFDSR; + break; + + default: + pr_err("Unknown bttv card type: %d\n", sub->core->type); + kfree(card); + return -ENODEV; + } + + dprintk("dvb_bt8xx: identified card%d as %s\n", card->bttv_nr, card->card_name); + + if (!(bttv_pci_dev = bttv_get_pcidev(card->bttv_nr))) { + pr_err("no pci device for card %d\n", card->bttv_nr); + kfree(card); + return -ENODEV; + } + + if (!(card->bt = dvb_bt8xx_878_match(card->bttv_nr, bttv_pci_dev))) { + pr_err("unable to determine DMA core of card %d,\n", card->bttv_nr); + pr_err("if you have the ALSA bt87x audio driver installed, try removing it.\n"); + + kfree(card); + return -ENODEV; + } + + mutex_init(&card->bt->gpio_lock); + card->bt->bttv_nr = sub->core->nr; + + if ( (ret = dvb_bt8xx_load_card(card, sub->core->type)) ) { + kfree(card); + return ret; + } + + dev_set_drvdata(&sub->dev, card); + return 0; +} + +static void dvb_bt8xx_remove(struct bttv_sub_device *sub) +{ + struct dvb_bt8xx_card *card = dev_get_drvdata(&sub->dev); + + dprintk("dvb_bt8xx: unloading card%d\n", card->bttv_nr); + + bt878_stop(card->bt); + cancel_work_sync(&card->bt->bh_work); + dvb_net_release(&card->dvbnet); + card->demux.dmx.remove_frontend(&card->demux.dmx, &card->fe_mem); + card->demux.dmx.remove_frontend(&card->demux.dmx, &card->fe_hw); + dvb_dmxdev_release(&card->dmxdev); + dvb_dmx_release(&card->demux); + if (card->fe) { + dvb_unregister_frontend(card->fe); + dvb_frontend_detach(card->fe); + } + dvb_unregister_adapter(&card->dvb_adapter); + + kfree(card); +} + +static struct bttv_sub_driver driver = { + .drv = { + .name = "dvb-bt8xx", + }, + .probe = dvb_bt8xx_probe, + .remove = dvb_bt8xx_remove, + /* FIXME: + * .shutdown = dvb_bt8xx_shutdown, + * .suspend = dvb_bt8xx_suspend, + * .resume = dvb_bt8xx_resume, + */ +}; + +static int __init dvb_bt8xx_init(void) +{ + return bttv_sub_register(&driver, "dvb"); +} + +static void __exit dvb_bt8xx_exit(void) +{ + bttv_sub_unregister(&driver); +} + +module_init(dvb_bt8xx_init); +module_exit(dvb_bt8xx_exit); + +MODULE_DESCRIPTION("Bt8xx based DVB adapter driver"); +MODULE_AUTHOR("Florian Schirmer "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/bt8xx/dvb-bt8xx.h b/drivers/media/pci/bt8xx/dvb-bt8xx.h new file mode 100644 index 000000000..4b4c182cf --- /dev/null +++ b/drivers/media/pci/bt8xx/dvb-bt8xx.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Bt8xx based DVB adapter driver + * + * Copyright (C) 2002,2003 Florian Schirmer + * Copyright (C) 2002 Peter Hettkamp + * Copyright (C) 1999-2001 Ralph Metzler & Marcus Metzler for convergence integrated media GmbH + * Copyright (C) 1998,1999 Christian Theiss + */ + +#ifndef DVB_BT8XX_H +#define DVB_BT8XX_H + +#include +#include +#include +#include +#include "bttv.h" +#include "mt352.h" +#include "sp887x.h" +#include "dst_common.h" +#include "nxt6000.h" +#include "cx24110.h" +#include "or51211.h" +#include "lgdt330x.h" +#include "zl10353.h" +#include "tuner-simple.h" + +struct dvb_bt8xx_card { + struct mutex lock; + int nfeeds; + char card_name[32]; + struct dvb_adapter dvb_adapter; + struct bt878 *bt; + unsigned int bttv_nr; + struct dvb_demux demux; + struct dmxdev dmxdev; + struct dmx_frontend fe_hw; + struct dmx_frontend fe_mem; + u32 gpio_mode; + u32 op_sync_orin; + u32 irq_err_ignore; + struct i2c_adapter *i2c_adapter; + struct dvb_net dvbnet; + + struct dvb_frontend* fe; +}; + +#endif /* DVB_BT8XX_H */ diff --git a/drivers/media/pci/cobalt/Kconfig b/drivers/media/pci/cobalt/Kconfig new file mode 100644 index 000000000..e13e36141 --- /dev/null +++ b/drivers/media/pci/cobalt/Kconfig @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_COBALT + tristate "Cisco Cobalt support" + depends on VIDEO_DEV && I2C + depends on PCI_MSI && MTD_COMPLEX_MAPPINGS + depends on (GPIOLIB && DRM_I2C_ADV7511=n) || COMPILE_TEST + depends on SND + depends on MTD + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select I2C_ALGOBIT + select SND_PCM + select VIDEO_ADV7604 + select VIDEO_ADV7511 + select VIDEO_ADV7842 + select VIDEOBUF2_DMA_SG + help + This is a video4linux driver for the Cisco PCIe Cobalt card. + + This board is sadly not available outside of Cisco, but it is + very useful as an example of a real driver that uses all the + latest frameworks and APIs. + + To compile this driver as a module, choose M here: the + module will be called cobalt. diff --git a/drivers/media/pci/cobalt/Makefile b/drivers/media/pci/cobalt/Makefile new file mode 100644 index 000000000..29eddff2f --- /dev/null +++ b/drivers/media/pci/cobalt/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +cobalt-objs := cobalt-driver.o cobalt-irq.o cobalt-v4l2.o \ + cobalt-i2c.o cobalt-omnitek.o cobalt-flash.o cobalt-cpld.o \ + cobalt-alsa-main.o cobalt-alsa-pcm.o + +obj-$(CONFIG_VIDEO_COBALT) += cobalt.o diff --git a/drivers/media/pci/cobalt/cobalt-alsa-main.c b/drivers/media/pci/cobalt/cobalt-alsa-main.c new file mode 100644 index 000000000..9ed547cd4 --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-alsa-main.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA interface to cobalt PCM capture streams + * + * Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "cobalt-driver.h" +#include "cobalt-alsa.h" +#include "cobalt-alsa-pcm.h" + +static void snd_cobalt_card_free(struct snd_cobalt_card *cobsc) +{ + if (cobsc == NULL) + return; + + cobsc->s->alsa = NULL; + + kfree(cobsc); +} + +static void snd_cobalt_card_private_free(struct snd_card *sc) +{ + if (sc == NULL) + return; + snd_cobalt_card_free(sc->private_data); + sc->private_data = NULL; + sc->private_free = NULL; +} + +static int snd_cobalt_card_create(struct cobalt_stream *s, + struct snd_card *sc, + struct snd_cobalt_card **cobsc) +{ + *cobsc = kzalloc_obj(struct snd_cobalt_card); + if (*cobsc == NULL) + return -ENOMEM; + + (*cobsc)->s = s; + (*cobsc)->sc = sc; + + sc->private_data = *cobsc; + sc->private_free = snd_cobalt_card_private_free; + + return 0; +} + +static int snd_cobalt_card_set_names(struct snd_cobalt_card *cobsc) +{ + struct cobalt_stream *s = cobsc->s; + struct cobalt *cobalt = s->cobalt; + struct snd_card *sc = cobsc->sc; + + /* sc->driver is used by alsa-lib's configurator: simple, unique */ + strscpy(sc->driver, "cobalt", sizeof(sc->driver)); + + /* sc->shortname is a symlink in /proc/asound: COBALT-M -> cardN */ + snprintf(sc->shortname, sizeof(sc->shortname), "cobalt-%d-%d", + cobalt->instance, s->video_channel); + + /* sc->longname is read from /proc/asound/cards */ + snprintf(sc->longname, sizeof(sc->longname), + "Cobalt %d HDMI %d", + cobalt->instance, s->video_channel); + + return 0; +} + +int cobalt_alsa_init(struct cobalt_stream *s) +{ + struct cobalt *cobalt = s->cobalt; + struct snd_card *sc = NULL; + struct snd_cobalt_card *cobsc; + int ret; + + /* Numbrs steps from "Writing an ALSA Driver" by Takashi Iwai */ + + /* (1) Check and increment the device index */ + /* This is a no-op for us. We'll use the cobalt->instance */ + + /* (2) Create a card instance */ + ret = snd_card_new(&cobalt->pci_dev->dev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1, THIS_MODULE, 0, &sc); + if (ret) { + cobalt_err("snd_card_new() failed with err %d\n", ret); + goto err_exit; + } + + /* (3) Create a main component */ + ret = snd_cobalt_card_create(s, sc, &cobsc); + if (ret) { + cobalt_err("snd_cobalt_card_create() failed with err %d\n", + ret); + goto err_exit_free; + } + + /* (4) Set the driver ID and name strings */ + snd_cobalt_card_set_names(cobsc); + + ret = snd_cobalt_pcm_create(cobsc); + if (ret) { + cobalt_err("snd_cobalt_pcm_create() failed with err %d\n", + ret); + goto err_exit_free; + } + /* FIXME - proc files */ + + /* (7) Set the driver data and return 0 */ + /* We do this out of normal order for PCI drivers to avoid races */ + s->alsa = cobsc; + + /* (6) Register the card instance */ + ret = snd_card_register(sc); + if (ret) { + s->alsa = NULL; + cobalt_err("snd_card_register() failed with err %d\n", ret); + goto err_exit_free; + } + + return 0; + +err_exit_free: + if (sc != NULL) + snd_card_free(sc); +err_exit: + return ret; +} + +void cobalt_alsa_exit(struct cobalt_stream *s) +{ + struct snd_cobalt_card *cobsc = s->alsa; + + if (cobsc) + snd_card_free(cobsc->sc); + s->alsa = NULL; +} diff --git a/drivers/media/pci/cobalt/cobalt-alsa-pcm.c b/drivers/media/pci/cobalt/cobalt-alsa-pcm.c new file mode 100644 index 000000000..9e7504e3c --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-alsa-pcm.c @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA PCM device for the + * ALSA interface to cobalt PCM capture streams + * + * Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#include +#include +#include + +#include + +#include +#include + +#include "cobalt-driver.h" +#include "cobalt-alsa.h" +#include "cobalt-alsa-pcm.h" + +static unsigned int pcm_debug; +module_param(pcm_debug, int, 0644); +MODULE_PARM_DESC(pcm_debug, "enable debug messages for pcm"); + +#define dprintk(fmt, arg...) \ + do { \ + if (pcm_debug) \ + pr_info("cobalt-alsa-pcm %s: " fmt, __func__, ##arg); \ + } while (0) + +static const struct snd_pcm_hardware snd_cobalt_hdmi_capture = { + .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID, + + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, + + .rates = SNDRV_PCM_RATE_48000, + + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = 4 * 240 * 8 * 4, /* 5 ms of data */ + .period_bytes_min = 1920, /* 1 sample = 8 * 4 bytes */ + .period_bytes_max = 240 * 8 * 4, /* 5 ms of 8 channel data */ + .periods_min = 1, + .periods_max = 4, +}; + +static const struct snd_pcm_hardware snd_cobalt_playback = { + .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID, + + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, + + .rates = SNDRV_PCM_RATE_48000, + + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = 4 * 240 * 8 * 4, /* 5 ms of data */ + .period_bytes_min = 1920, /* 1 sample = 8 * 4 bytes */ + .period_bytes_max = 240 * 8 * 4, /* 5 ms of 8 channel data */ + .periods_min = 1, + .periods_max = 4, +}; + +static void sample_cpy(u8 *dst, const u8 *src, u32 len, bool is_s32) +{ + static const unsigned map[8] = { 0, 1, 5, 4, 2, 3, 6, 7 }; + unsigned idx = 0; + + while (len >= (is_s32 ? 4 : 2)) { + unsigned offset = map[idx] * 4; + u32 val = src[offset + 1] + (src[offset + 2] << 8) + + (src[offset + 3] << 16); + + if (is_s32) { + *dst++ = 0; + *dst++ = val & 0xff; + } + *dst++ = (val >> 8) & 0xff; + *dst++ = (val >> 16) & 0xff; + len -= is_s32 ? 4 : 2; + idx++; + } +} + +static void cobalt_alsa_announce_pcm_data(struct snd_cobalt_card *cobsc, + u8 *pcm_data, + size_t skip, + size_t samples) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned long flags; + unsigned int oldptr; + unsigned int stride; + int length = samples; + int period_elapsed = 0; + bool is_s32; + + dprintk("cobalt alsa announce ptr=%p data=%p num_bytes=%zd\n", cobsc, + pcm_data, samples); + + substream = cobsc->capture_pcm_substream; + if (substream == NULL) { + dprintk("substream was NULL\n"); + return; + } + + runtime = substream->runtime; + if (runtime == NULL) { + dprintk("runtime was NULL\n"); + return; + } + is_s32 = runtime->format == SNDRV_PCM_FORMAT_S32_LE; + + stride = runtime->frame_bits >> 3; + if (stride == 0) { + dprintk("stride is zero\n"); + return; + } + + if (length == 0) { + dprintk("%s: length was zero\n", __func__); + return; + } + + if (runtime->dma_area == NULL) { + dprintk("dma area was NULL - ignoring\n"); + return; + } + + oldptr = cobsc->hwptr_done_capture; + if (oldptr + length >= runtime->buffer_size) { + unsigned int cnt = runtime->buffer_size - oldptr; + unsigned i; + + for (i = 0; i < cnt; i++) + sample_cpy(runtime->dma_area + (oldptr + i) * stride, + pcm_data + i * skip, + stride, is_s32); + for (i = cnt; i < length; i++) + sample_cpy(runtime->dma_area + (i - cnt) * stride, + pcm_data + i * skip, stride, is_s32); + } else { + unsigned i; + + for (i = 0; i < length; i++) + sample_cpy(runtime->dma_area + (oldptr + i) * stride, + pcm_data + i * skip, + stride, is_s32); + } + snd_pcm_stream_lock_irqsave(substream, flags); + + cobsc->hwptr_done_capture += length; + if (cobsc->hwptr_done_capture >= + runtime->buffer_size) + cobsc->hwptr_done_capture -= + runtime->buffer_size; + + cobsc->capture_transfer_done += length; + if (cobsc->capture_transfer_done >= + runtime->period_size) { + cobsc->capture_transfer_done -= + runtime->period_size; + period_elapsed = 1; + } + + snd_pcm_stream_unlock_irqrestore(substream, flags); + + if (period_elapsed) + snd_pcm_period_elapsed(substream); +} + +static int alsa_fnc(struct vb2_buffer *vb, void *priv) +{ + struct cobalt_stream *s = priv; + unsigned char *p = vb2_plane_vaddr(vb, 0); + int i; + + if (pcm_debug) { + pr_info("alsa: "); + for (i = 0; i < 8 * 4; i++) { + if (!(i & 3)) + pr_cont(" "); + pr_cont("%02x", p[i]); + } + pr_cont("\n"); + } + cobalt_alsa_announce_pcm_data(s->alsa, + vb2_plane_vaddr(vb, 0), + 8 * 4, + vb2_get_plane_payload(vb, 0) / (8 * 4)); + return 0; +} + +static int snd_cobalt_pcm_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); + struct cobalt_stream *s = cobsc->s; + + runtime->hw = snd_cobalt_hdmi_capture; + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + cobsc->capture_pcm_substream = substream; + runtime->private_data = s; + cobsc->alsa_record_cnt++; + if (cobsc->alsa_record_cnt == 1) { + int rc; + + rc = vb2_thread_start(&s->q, alsa_fnc, s, s->vdev.name); + if (rc) { + cobsc->alsa_record_cnt--; + return rc; + } + } + return 0; +} + +static int snd_cobalt_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); + struct cobalt_stream *s = cobsc->s; + + cobsc->alsa_record_cnt--; + if (cobsc->alsa_record_cnt == 0) + vb2_thread_stop(&s->q); + return 0; +} + +static int snd_cobalt_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); + + cobsc->hwptr_done_capture = 0; + cobsc->capture_transfer_done = 0; + + return 0; +} + +static int snd_cobalt_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_STOP: + return 0; + default: + return -EINVAL; + } + return 0; +} + +static +snd_pcm_uframes_t snd_cobalt_pcm_pointer(struct snd_pcm_substream *substream) +{ + snd_pcm_uframes_t hwptr_done; + struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); + + hwptr_done = cobsc->hwptr_done_capture; + + return hwptr_done; +} + +static void pb_sample_cpy(u8 *dst, const u8 *src, u32 len, bool is_s32) +{ + static const unsigned map[8] = { 0, 1, 5, 4, 2, 3, 6, 7 }; + unsigned idx = 0; + + while (len >= (is_s32 ? 4 : 2)) { + unsigned offset = map[idx] * 4; + u8 *out = dst + offset; + + *out++ = 0; + if (is_s32) { + src++; + *out++ = *src++; + } else { + *out++ = 0; + } + *out++ = *src++; + *out = *src++; + len -= is_s32 ? 4 : 2; + idx++; + } +} + +static void cobalt_alsa_pb_pcm_data(struct snd_cobalt_card *cobsc, + u8 *pcm_data, + size_t skip, + size_t samples) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned long flags; + unsigned int pos; + unsigned int stride; + bool is_s32; + unsigned i; + + dprintk("cobalt alsa pb ptr=%p data=%p samples=%zd\n", cobsc, + pcm_data, samples); + + substream = cobsc->playback_pcm_substream; + if (substream == NULL) { + dprintk("substream was NULL\n"); + return; + } + + runtime = substream->runtime; + if (runtime == NULL) { + dprintk("runtime was NULL\n"); + return; + } + + is_s32 = runtime->format == SNDRV_PCM_FORMAT_S32_LE; + stride = runtime->frame_bits >> 3; + if (stride == 0) { + dprintk("stride is zero\n"); + return; + } + + if (samples == 0) { + dprintk("%s: samples was zero\n", __func__); + return; + } + + if (runtime->dma_area == NULL) { + dprintk("dma area was NULL - ignoring\n"); + return; + } + + pos = cobsc->pb_pos % cobsc->pb_size; + for (i = 0; i < cobsc->pb_count / (8 * 4); i++) + pb_sample_cpy(pcm_data + i * skip, + runtime->dma_area + pos + i * stride, + stride, is_s32); + snd_pcm_stream_lock_irqsave(substream, flags); + + cobsc->pb_pos += i * stride; + + snd_pcm_stream_unlock_irqrestore(substream, flags); + if (cobsc->pb_pos % cobsc->pb_count == 0) + snd_pcm_period_elapsed(substream); +} + +static int alsa_pb_fnc(struct vb2_buffer *vb, void *priv) +{ + struct cobalt_stream *s = priv; + + if (s->alsa->alsa_pb_channel) + cobalt_alsa_pb_pcm_data(s->alsa, + vb2_plane_vaddr(vb, 0), + 8 * 4, + vb2_get_plane_payload(vb, 0) / (8 * 4)); + return 0; +} + +static int snd_cobalt_pcm_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct cobalt_stream *s = cobsc->s; + + runtime->hw = snd_cobalt_playback; + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + cobsc->playback_pcm_substream = substream; + runtime->private_data = s; + cobsc->alsa_playback_cnt++; + if (cobsc->alsa_playback_cnt == 1) { + int rc; + + rc = vb2_thread_start(&s->q, alsa_pb_fnc, s, s->vdev.name); + if (rc) { + cobsc->alsa_playback_cnt--; + return rc; + } + } + + return 0; +} + +static int snd_cobalt_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); + struct cobalt_stream *s = cobsc->s; + + cobsc->alsa_playback_cnt--; + if (cobsc->alsa_playback_cnt == 0) + vb2_thread_stop(&s->q); + return 0; +} + +static int snd_cobalt_pcm_pb_prepare(struct snd_pcm_substream *substream) +{ + struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); + + cobsc->pb_size = snd_pcm_lib_buffer_bytes(substream); + cobsc->pb_count = snd_pcm_lib_period_bytes(substream); + cobsc->pb_pos = 0; + + return 0; +} + +static int snd_cobalt_pcm_pb_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (cobsc->alsa_pb_channel) + return -EBUSY; + cobsc->alsa_pb_channel = true; + return 0; + case SNDRV_PCM_TRIGGER_STOP: + cobsc->alsa_pb_channel = false; + return 0; + default: + return -EINVAL; + } +} + +static +snd_pcm_uframes_t snd_cobalt_pcm_pb_pointer(struct snd_pcm_substream *substream) +{ + struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); + size_t ptr; + + ptr = cobsc->pb_pos; + + return bytes_to_frames(substream->runtime, ptr) % + substream->runtime->buffer_size; +} + +static const struct snd_pcm_ops snd_cobalt_pcm_capture_ops = { + .open = snd_cobalt_pcm_capture_open, + .close = snd_cobalt_pcm_capture_close, + .prepare = snd_cobalt_pcm_prepare, + .trigger = snd_cobalt_pcm_trigger, + .pointer = snd_cobalt_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_cobalt_pcm_playback_ops = { + .open = snd_cobalt_pcm_playback_open, + .close = snd_cobalt_pcm_playback_close, + .prepare = snd_cobalt_pcm_pb_prepare, + .trigger = snd_cobalt_pcm_pb_trigger, + .pointer = snd_cobalt_pcm_pb_pointer, +}; + +int snd_cobalt_pcm_create(struct snd_cobalt_card *cobsc) +{ + struct snd_pcm *sp; + struct snd_card *sc = cobsc->sc; + struct cobalt_stream *s = cobsc->s; + struct cobalt *cobalt = s->cobalt; + int ret; + + s->q.gfp_flags |= __GFP_ZERO; + + if (!s->is_output) { + cobalt_s_bit_sysctrl(cobalt, + COBALT_SYS_CTRL_AUDIO_IPP_RESETN_BIT(s->video_channel), + 0); + mdelay(2); + cobalt_s_bit_sysctrl(cobalt, + COBALT_SYS_CTRL_AUDIO_IPP_RESETN_BIT(s->video_channel), + 1); + mdelay(1); + + ret = snd_pcm_new(sc, "Cobalt PCM-In HDMI", + 0, /* PCM device 0, the only one for this card */ + 0, /* 0 playback substreams */ + 1, /* 1 capture substream */ + &sp); + if (ret) { + cobalt_err("snd_cobalt_pcm_create() failed for input with err %d\n", + ret); + goto err_exit; + } + + snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_CAPTURE, + &snd_cobalt_pcm_capture_ops); + snd_pcm_set_managed_buffer_all(sp, SNDRV_DMA_TYPE_VMALLOC, + NULL, 0, 0); + sp->info_flags = 0; + sp->private_data = cobsc; + strscpy(sp->name, "cobalt", sizeof(sp->name)); + } else { + cobalt_s_bit_sysctrl(cobalt, + COBALT_SYS_CTRL_AUDIO_OPP_RESETN_BIT, 0); + mdelay(2); + cobalt_s_bit_sysctrl(cobalt, + COBALT_SYS_CTRL_AUDIO_OPP_RESETN_BIT, 1); + mdelay(1); + + ret = snd_pcm_new(sc, "Cobalt PCM-Out HDMI", + 0, /* PCM device 0, the only one for this card */ + 1, /* 0 playback substreams */ + 0, /* 1 capture substream */ + &sp); + if (ret) { + cobalt_err("snd_cobalt_pcm_create() failed for output with err %d\n", + ret); + goto err_exit; + } + + snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_PLAYBACK, + &snd_cobalt_pcm_playback_ops); + snd_pcm_set_managed_buffer_all(sp, SNDRV_DMA_TYPE_VMALLOC, + NULL, 0, 0); + sp->info_flags = 0; + sp->private_data = cobsc; + strscpy(sp->name, "cobalt", sizeof(sp->name)); + } + + return 0; + +err_exit: + return ret; +} diff --git a/drivers/media/pci/cobalt/cobalt-alsa-pcm.h b/drivers/media/pci/cobalt/cobalt-alsa-pcm.h new file mode 100644 index 000000000..0e2e9c63a --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-alsa-pcm.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ALSA PCM device for the + * ALSA interface to cobalt PCM capture streams + * + * Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +int snd_cobalt_pcm_create(struct snd_cobalt_card *cobsc); diff --git a/drivers/media/pci/cobalt/cobalt-alsa.h b/drivers/media/pci/cobalt/cobalt-alsa.h new file mode 100644 index 000000000..bb7f156ad --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-alsa.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ALSA interface to cobalt PCM capture streams + * + * Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +struct snd_card; + +struct snd_cobalt_card { + struct cobalt_stream *s; + struct snd_card *sc; + unsigned int capture_transfer_done; + unsigned int hwptr_done_capture; + unsigned alsa_record_cnt; + struct snd_pcm_substream *capture_pcm_substream; + + unsigned int pb_size; + unsigned int pb_count; + unsigned int pb_pos; + unsigned pb_filled; + bool alsa_pb_channel; + unsigned alsa_playback_cnt; + struct snd_pcm_substream *playback_pcm_substream; +}; + +int cobalt_alsa_init(struct cobalt_stream *s); +void cobalt_alsa_exit(struct cobalt_stream *s); diff --git a/drivers/media/pci/cobalt/cobalt-cpld.c b/drivers/media/pci/cobalt/cobalt-cpld.c new file mode 100644 index 000000000..fad882459 --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-cpld.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Cobalt CPLD functions + * + * Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#include + +#include "cobalt-cpld.h" + +#define ADRS(offset) (COBALT_BUS_CPLD_BASE + offset) + +static u16 cpld_read(struct cobalt *cobalt, u32 offset) +{ + return cobalt_bus_read32(cobalt->bar1, ADRS(offset)); +} + +static void cpld_write(struct cobalt *cobalt, u32 offset, u16 val) +{ + return cobalt_bus_write32(cobalt->bar1, ADRS(offset), val); +} + +static void cpld_info_ver3(struct cobalt *cobalt) +{ + u32 rd; + u32 tmp; + + cobalt_info("CPLD System control register (read/write)\n"); + cobalt_info("\t\tSystem control: 0x%04x (0x0f00)\n", + cpld_read(cobalt, 0)); + cobalt_info("CPLD Clock control register (read/write)\n"); + cobalt_info("\t\tClock control: 0x%04x (0x0000)\n", + cpld_read(cobalt, 0x04)); + cobalt_info("CPLD HSMA Clk Osc register (read/write) - Must set wr trigger to load default values\n"); + cobalt_info("\t\tRegister #7:\t0x%04x (0x0022)\n", + cpld_read(cobalt, 0x08)); + cobalt_info("\t\tRegister #8:\t0x%04x (0x0047)\n", + cpld_read(cobalt, 0x0c)); + cobalt_info("\t\tRegister #9:\t0x%04x (0x00fa)\n", + cpld_read(cobalt, 0x10)); + cobalt_info("\t\tRegister #10:\t0x%04x (0x0061)\n", + cpld_read(cobalt, 0x14)); + cobalt_info("\t\tRegister #11:\t0x%04x (0x001e)\n", + cpld_read(cobalt, 0x18)); + cobalt_info("\t\tRegister #12:\t0x%04x (0x0045)\n", + cpld_read(cobalt, 0x1c)); + cobalt_info("\t\tRegister #135:\t0x%04x\n", + cpld_read(cobalt, 0x20)); + cobalt_info("\t\tRegister #137:\t0x%04x\n", + cpld_read(cobalt, 0x24)); + cobalt_info("CPLD System status register (read only)\n"); + cobalt_info("\t\tSystem status: 0x%04x\n", + cpld_read(cobalt, 0x28)); + cobalt_info("CPLD MAXII info register (read only)\n"); + cobalt_info("\t\tBoard serial number: 0x%04x\n", + cpld_read(cobalt, 0x2c)); + cobalt_info("\t\tMAXII program revision: 0x%04x\n", + cpld_read(cobalt, 0x30)); + cobalt_info("CPLD temp and voltage ADT7411 registers (read only)\n"); + cobalt_info("\t\tBoard temperature: %u Celsius\n", + cpld_read(cobalt, 0x34) / 4); + cobalt_info("\t\tFPGA temperature: %u Celsius\n", + cpld_read(cobalt, 0x38) / 4); + rd = cpld_read(cobalt, 0x3c); + tmp = (rd * 33 * 1000) / (483 * 10); + cobalt_info("\t\tVDD 3V3: %u,%03uV\n", tmp / 1000, tmp % 1000); + rd = cpld_read(cobalt, 0x40); + tmp = (rd * 74 * 2197) / (27 * 1000); + cobalt_info("\t\tADC ch3 5V: %u,%03uV\n", tmp / 1000, tmp % 1000); + rd = cpld_read(cobalt, 0x44); + tmp = (rd * 74 * 2197) / (47 * 1000); + cobalt_info("\t\tADC ch4 3V: %u,%03uV\n", tmp / 1000, tmp % 1000); + rd = cpld_read(cobalt, 0x48); + tmp = (rd * 57 * 2197) / (47 * 1000); + cobalt_info("\t\tADC ch5 2V5: %u,%03uV\n", tmp / 1000, tmp % 1000); + rd = cpld_read(cobalt, 0x4c); + tmp = (rd * 2197) / 1000; + cobalt_info("\t\tADC ch6 1V8: %u,%03uV\n", tmp / 1000, tmp % 1000); + rd = cpld_read(cobalt, 0x50); + tmp = (rd * 2197) / 1000; + cobalt_info("\t\tADC ch7 1V5: %u,%03uV\n", tmp / 1000, tmp % 1000); + rd = cpld_read(cobalt, 0x54); + tmp = (rd * 2197) / 1000; + cobalt_info("\t\tADC ch8 0V9: %u,%03uV\n", tmp / 1000, tmp % 1000); +} + +void cobalt_cpld_status(struct cobalt *cobalt) +{ + u32 rev = cpld_read(cobalt, 0x30); + + switch (rev) { + case 3: + case 4: + case 5: + cpld_info_ver3(cobalt); + break; + default: + cobalt_info("CPLD revision %u is not supported!\n", rev); + break; + } +} + +#define DCO_MIN 4850000000ULL +#define DCO_MAX 5670000000ULL + +#define SI570_CLOCK_CTRL 0x04 +#define S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_WR_TRIGGER 0x200 +#define S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_RST_TRIGGER 0x100 +#define S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_FPGA_CTRL 0x80 +#define S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_EN 0x40 + +#define SI570_REG7 0x08 +#define SI570_REG8 0x0c +#define SI570_REG9 0x10 +#define SI570_REG10 0x14 +#define SI570_REG11 0x18 +#define SI570_REG12 0x1c +#define SI570_REG135 0x20 +#define SI570_REG137 0x24 + +struct multiplier { + unsigned mult, hsdiv, n1; +}; + +/* List all possible multipliers (= hsdiv * n1). There are lots of duplicates, + which are all removed in this list to keep the list as short as possible. + The values for hsdiv and n1 are the actual values, not the register values. + */ +static const struct multiplier multipliers[] = { + { 4, 4, 1 }, { 5, 5, 1 }, { 6, 6, 1 }, + { 7, 7, 1 }, { 8, 4, 2 }, { 9, 9, 1 }, + { 10, 5, 2 }, { 11, 11, 1 }, { 12, 6, 2 }, + { 14, 7, 2 }, { 16, 4, 4 }, { 18, 9, 2 }, + { 20, 5, 4 }, { 22, 11, 2 }, { 24, 4, 6 }, + { 28, 7, 4 }, { 30, 5, 6 }, { 32, 4, 8 }, + { 36, 6, 6 }, { 40, 4, 10 }, { 42, 7, 6 }, + { 44, 11, 4 }, { 48, 4, 12 }, { 50, 5, 10 }, + { 54, 9, 6 }, { 56, 4, 14 }, { 60, 5, 12 }, + { 64, 4, 16 }, { 66, 11, 6 }, { 70, 5, 14 }, + { 72, 4, 18 }, { 80, 4, 20 }, { 84, 6, 14 }, + { 88, 11, 8 }, { 90, 5, 18 }, { 96, 4, 24 }, + { 98, 7, 14 }, { 100, 5, 20 }, { 104, 4, 26 }, + { 108, 6, 18 }, { 110, 11, 10 }, { 112, 4, 28 }, + { 120, 4, 30 }, { 126, 7, 18 }, { 128, 4, 32 }, + { 130, 5, 26 }, { 132, 11, 12 }, { 136, 4, 34 }, + { 140, 5, 28 }, { 144, 4, 36 }, { 150, 5, 30 }, + { 152, 4, 38 }, { 154, 11, 14 }, { 156, 6, 26 }, + { 160, 4, 40 }, { 162, 9, 18 }, { 168, 4, 42 }, + { 170, 5, 34 }, { 176, 11, 16 }, { 180, 5, 36 }, + { 182, 7, 26 }, { 184, 4, 46 }, { 190, 5, 38 }, + { 192, 4, 48 }, { 196, 7, 28 }, { 198, 11, 18 }, + { 198, 9, 22 }, { 200, 4, 50 }, { 204, 6, 34 }, + { 208, 4, 52 }, { 210, 5, 42 }, { 216, 4, 54 }, + { 220, 11, 20 }, { 224, 4, 56 }, { 228, 6, 38 }, + { 230, 5, 46 }, { 232, 4, 58 }, { 234, 9, 26 }, + { 238, 7, 34 }, { 240, 4, 60 }, { 242, 11, 22 }, + { 248, 4, 62 }, { 250, 5, 50 }, { 252, 6, 42 }, + { 256, 4, 64 }, { 260, 5, 52 }, { 264, 11, 24 }, + { 266, 7, 38 }, { 270, 5, 54 }, { 272, 4, 68 }, + { 276, 6, 46 }, { 280, 4, 70 }, { 286, 11, 26 }, + { 288, 4, 72 }, { 290, 5, 58 }, { 294, 7, 42 }, + { 296, 4, 74 }, { 300, 5, 60 }, { 304, 4, 76 }, + { 306, 9, 34 }, { 308, 11, 28 }, { 310, 5, 62 }, + { 312, 4, 78 }, { 320, 4, 80 }, { 322, 7, 46 }, + { 324, 6, 54 }, { 328, 4, 82 }, { 330, 11, 30 }, + { 336, 4, 84 }, { 340, 5, 68 }, { 342, 9, 38 }, + { 344, 4, 86 }, { 348, 6, 58 }, { 350, 5, 70 }, + { 352, 11, 32 }, { 360, 4, 90 }, { 364, 7, 52 }, + { 368, 4, 92 }, { 370, 5, 74 }, { 372, 6, 62 }, + { 374, 11, 34 }, { 376, 4, 94 }, { 378, 7, 54 }, + { 380, 5, 76 }, { 384, 4, 96 }, { 390, 5, 78 }, + { 392, 4, 98 }, { 396, 11, 36 }, { 400, 4, 100 }, + { 406, 7, 58 }, { 408, 4, 102 }, { 410, 5, 82 }, + { 414, 9, 46 }, { 416, 4, 104 }, { 418, 11, 38 }, + { 420, 5, 84 }, { 424, 4, 106 }, { 430, 5, 86 }, + { 432, 4, 108 }, { 434, 7, 62 }, { 440, 11, 40 }, + { 444, 6, 74 }, { 448, 4, 112 }, { 450, 5, 90 }, + { 456, 4, 114 }, { 460, 5, 92 }, { 462, 11, 42 }, + { 464, 4, 116 }, { 468, 6, 78 }, { 470, 5, 94 }, + { 472, 4, 118 }, { 476, 7, 68 }, { 480, 4, 120 }, + { 484, 11, 44 }, { 486, 9, 54 }, { 488, 4, 122 }, + { 490, 5, 98 }, { 492, 6, 82 }, { 496, 4, 124 }, + { 500, 5, 100 }, { 504, 4, 126 }, { 506, 11, 46 }, + { 510, 5, 102 }, { 512, 4, 128 }, { 516, 6, 86 }, + { 518, 7, 74 }, { 520, 5, 104 }, { 522, 9, 58 }, + { 528, 11, 48 }, { 530, 5, 106 }, { 532, 7, 76 }, + { 540, 5, 108 }, { 546, 7, 78 }, { 550, 11, 50 }, + { 552, 6, 92 }, { 558, 9, 62 }, { 560, 5, 112 }, + { 564, 6, 94 }, { 570, 5, 114 }, { 572, 11, 52 }, + { 574, 7, 82 }, { 576, 6, 96 }, { 580, 5, 116 }, + { 588, 6, 98 }, { 590, 5, 118 }, { 594, 11, 54 }, + { 600, 5, 120 }, { 602, 7, 86 }, { 610, 5, 122 }, + { 612, 6, 102 }, { 616, 11, 56 }, { 620, 5, 124 }, + { 624, 6, 104 }, { 630, 5, 126 }, { 636, 6, 106 }, + { 638, 11, 58 }, { 640, 5, 128 }, { 644, 7, 92 }, + { 648, 6, 108 }, { 658, 7, 94 }, { 660, 11, 60 }, + { 666, 9, 74 }, { 672, 6, 112 }, { 682, 11, 62 }, + { 684, 6, 114 }, { 686, 7, 98 }, { 696, 6, 116 }, + { 700, 7, 100 }, { 702, 9, 78 }, { 704, 11, 64 }, + { 708, 6, 118 }, { 714, 7, 102 }, { 720, 6, 120 }, + { 726, 11, 66 }, { 728, 7, 104 }, { 732, 6, 122 }, + { 738, 9, 82 }, { 742, 7, 106 }, { 744, 6, 124 }, + { 748, 11, 68 }, { 756, 6, 126 }, { 768, 6, 128 }, + { 770, 11, 70 }, { 774, 9, 86 }, { 784, 7, 112 }, + { 792, 11, 72 }, { 798, 7, 114 }, { 810, 9, 90 }, + { 812, 7, 116 }, { 814, 11, 74 }, { 826, 7, 118 }, + { 828, 9, 92 }, { 836, 11, 76 }, { 840, 7, 120 }, + { 846, 9, 94 }, { 854, 7, 122 }, { 858, 11, 78 }, + { 864, 9, 96 }, { 868, 7, 124 }, { 880, 11, 80 }, + { 882, 7, 126 }, { 896, 7, 128 }, { 900, 9, 100 }, + { 902, 11, 82 }, { 918, 9, 102 }, { 924, 11, 84 }, + { 936, 9, 104 }, { 946, 11, 86 }, { 954, 9, 106 }, + { 968, 11, 88 }, { 972, 9, 108 }, { 990, 11, 90 }, + { 1008, 9, 112 }, { 1012, 11, 92 }, { 1026, 9, 114 }, + { 1034, 11, 94 }, { 1044, 9, 116 }, { 1056, 11, 96 }, + { 1062, 9, 118 }, { 1078, 11, 98 }, { 1080, 9, 120 }, + { 1098, 9, 122 }, { 1100, 11, 100 }, { 1116, 9, 124 }, + { 1122, 11, 102 }, { 1134, 9, 126 }, { 1144, 11, 104 }, + { 1152, 9, 128 }, { 1166, 11, 106 }, { 1188, 11, 108 }, + { 1210, 11, 110 }, { 1232, 11, 112 }, { 1254, 11, 114 }, + { 1276, 11, 116 }, { 1298, 11, 118 }, { 1320, 11, 120 }, + { 1342, 11, 122 }, { 1364, 11, 124 }, { 1386, 11, 126 }, + { 1408, 11, 128 }, +}; + +bool cobalt_cpld_set_freq(struct cobalt *cobalt, unsigned f_out) +{ + const unsigned f_xtal = 39170000; /* xtal for si598 */ + u64 dco; + u64 rfreq; + unsigned delta = 0xffffffff; + unsigned i_best = 0; + unsigned i; + u8 n1, hsdiv; + u8 regs[6]; + int found = 0; + int retries = 3; + + for (i = 0; i < ARRAY_SIZE(multipliers); i++) { + unsigned mult = multipliers[i].mult; + u32 d; + + dco = (u64)f_out * mult; + if (dco < DCO_MIN || dco > DCO_MAX) + continue; + div_u64_rem((dco << 28) + f_xtal / 2, f_xtal, &d); + if (d < delta) { + found = 1; + i_best = i; + delta = d; + } + } + if (!found) + return false; + dco = (u64)f_out * multipliers[i_best].mult; + n1 = multipliers[i_best].n1 - 1; + hsdiv = multipliers[i_best].hsdiv - 4; + rfreq = div_u64(dco << 28, f_xtal); + + cpld_read(cobalt, SI570_CLOCK_CTRL); + + regs[0] = (hsdiv << 5) | (n1 >> 2); + regs[1] = ((n1 & 0x3) << 6) | (rfreq >> 32); + regs[2] = (rfreq >> 24) & 0xff; + regs[3] = (rfreq >> 16) & 0xff; + regs[4] = (rfreq >> 8) & 0xff; + regs[5] = rfreq & 0xff; + + /* The sequence of clock_ctrl flags to set is very weird. It looks + like I have to reset it, then set the new frequency and reset it + again. It shouldn't be necessary to do a reset, but if I don't, + then a strange frequency is set (156.412034 MHz, or register values + 0x01, 0xc7, 0xfc, 0x7f, 0x53, 0x62). + */ + + cobalt_dbg(1, "%u: %6ph\n", f_out, regs); + + while (retries--) { + u8 read_regs[6]; + + cpld_write(cobalt, SI570_CLOCK_CTRL, + S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_EN | + S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_FPGA_CTRL); + usleep_range(10000, 15000); + cpld_write(cobalt, SI570_REG7, regs[0]); + cpld_write(cobalt, SI570_REG8, regs[1]); + cpld_write(cobalt, SI570_REG9, regs[2]); + cpld_write(cobalt, SI570_REG10, regs[3]); + cpld_write(cobalt, SI570_REG11, regs[4]); + cpld_write(cobalt, SI570_REG12, regs[5]); + cpld_write(cobalt, SI570_CLOCK_CTRL, + S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_EN | + S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_WR_TRIGGER); + usleep_range(10000, 15000); + cpld_write(cobalt, SI570_CLOCK_CTRL, + S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_EN | + S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_FPGA_CTRL); + usleep_range(10000, 15000); + read_regs[0] = cpld_read(cobalt, SI570_REG7); + read_regs[1] = cpld_read(cobalt, SI570_REG8); + read_regs[2] = cpld_read(cobalt, SI570_REG9); + read_regs[3] = cpld_read(cobalt, SI570_REG10); + read_regs[4] = cpld_read(cobalt, SI570_REG11); + read_regs[5] = cpld_read(cobalt, SI570_REG12); + cpld_write(cobalt, SI570_CLOCK_CTRL, + S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_EN | + S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_FPGA_CTRL | + S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_RST_TRIGGER); + usleep_range(10000, 15000); + cpld_write(cobalt, SI570_CLOCK_CTRL, + S01755_REG_CLOCK_CTRL_BITMAP_CLKHSMA_EN); + usleep_range(10000, 15000); + + if (!memcmp(read_regs, regs, sizeof(read_regs))) + break; + cobalt_dbg(1, "retry: %6ph\n", read_regs); + } + if (2 - retries) + cobalt_info("Needed %d retries\n", 2 - retries); + + return true; +} diff --git a/drivers/media/pci/cobalt/cobalt-cpld.h b/drivers/media/pci/cobalt/cobalt-cpld.h new file mode 100644 index 000000000..8c880ed14 --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-cpld.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Cobalt CPLD functions + * + * Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#ifndef COBALT_CPLD_H +#define COBALT_CPLD_H + +#include "cobalt-driver.h" + +void cobalt_cpld_status(struct cobalt *cobalt); +bool cobalt_cpld_set_freq(struct cobalt *cobalt, unsigned freq); + +#endif diff --git a/drivers/media/pci/cobalt/cobalt-driver.c b/drivers/media/pci/cobalt/cobalt-driver.c new file mode 100644 index 000000000..7b1ca1238 --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-driver.c @@ -0,0 +1,797 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * cobalt driver initialization and card probing + * + * Derived from cx18-driver.c + * + * Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "cobalt-driver.h" +#include "cobalt-irq.h" +#include "cobalt-i2c.h" +#include "cobalt-v4l2.h" +#include "cobalt-flash.h" +#include "cobalt-alsa.h" +#include "cobalt-omnitek.h" + +/* add your revision and whatnot here */ +static const struct pci_device_id cobalt_pci_tbl[] = { + { PCI_VDEVICE(CISCO, PCI_DEVICE_ID_COBALT) }, + { } +}; + +MODULE_DEVICE_TABLE(pci, cobalt_pci_tbl); + +static atomic_t cobalt_instance = ATOMIC_INIT(0); + +int cobalt_debug; +module_param_named(debug, cobalt_debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level. Default: 0\n"); + +int cobalt_ignore_err; +module_param_named(ignore_err, cobalt_ignore_err, int, 0644); +MODULE_PARM_DESC(ignore_err, + "If set then ignore missing i2c adapters/receivers. Default: 0\n"); + +MODULE_AUTHOR("Hans Verkuil & Morten Hestnes"); +MODULE_DESCRIPTION("cobalt driver"); +MODULE_LICENSE("GPL"); + +static u8 edid[256] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x50, 0x21, 0x32, 0x27, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x1a, 0x01, 0x03, 0x80, 0x30, 0x1b, 0x78, + 0x0f, 0xee, 0x91, 0xa3, 0x54, 0x4c, 0x99, 0x26, + 0x0f, 0x50, 0x54, 0x2f, 0xcf, 0x00, 0x31, 0x59, + 0x45, 0x59, 0x61, 0x59, 0x81, 0x99, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x3a, + 0x80, 0x18, 0x71, 0x38, 0x2d, 0x40, 0x58, 0x2c, + 0x45, 0x00, 0xe0, 0x0e, 0x11, 0x00, 0x00, 0x1e, + 0x00, 0x00, 0x00, 0xfd, 0x00, 0x18, 0x55, 0x18, + 0x5e, 0x11, 0x00, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x63, + 0x6f, 0x62, 0x61, 0x6c, 0x74, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x9d, + + 0x02, 0x03, 0x1f, 0xf1, 0x4a, 0x10, 0x1f, 0x04, + 0x13, 0x22, 0x21, 0x20, 0x02, 0x11, 0x01, 0x23, + 0x09, 0x07, 0x07, 0x68, 0x03, 0x0c, 0x00, 0x10, + 0x00, 0x00, 0x22, 0x0f, 0xe2, 0x00, 0xca, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, +}; + +static void cobalt_set_interrupt(struct cobalt *cobalt, bool enable) +{ + if (enable) { + unsigned irqs = COBALT_SYSSTAT_VI0_INT1_MSK | + COBALT_SYSSTAT_VI1_INT1_MSK | + COBALT_SYSSTAT_VI2_INT1_MSK | + COBALT_SYSSTAT_VI3_INT1_MSK | + COBALT_SYSSTAT_VI0_INT2_MSK | + COBALT_SYSSTAT_VI1_INT2_MSK | + COBALT_SYSSTAT_VI2_INT2_MSK | + COBALT_SYSSTAT_VI3_INT2_MSK | + COBALT_SYSSTAT_VI0_LOST_DATA_MSK | + COBALT_SYSSTAT_VI1_LOST_DATA_MSK | + COBALT_SYSSTAT_VI2_LOST_DATA_MSK | + COBALT_SYSSTAT_VI3_LOST_DATA_MSK | + COBALT_SYSSTAT_AUD_IN_LOST_DATA_MSK; + + if (cobalt->have_hsma_rx) + irqs |= COBALT_SYSSTAT_VIHSMA_INT1_MSK | + COBALT_SYSSTAT_VIHSMA_INT2_MSK | + COBALT_SYSSTAT_VIHSMA_LOST_DATA_MSK; + + if (cobalt->have_hsma_tx) + irqs |= COBALT_SYSSTAT_VOHSMA_INT1_MSK | + COBALT_SYSSTAT_VOHSMA_LOST_DATA_MSK | + COBALT_SYSSTAT_AUD_OUT_LOST_DATA_MSK; + /* Clear any existing interrupts */ + cobalt_write_bar1(cobalt, COBALT_SYS_STAT_EDGE, 0xffffffff); + /* PIO Core interrupt mask register. + Enable ADV7604 INT1 interrupts */ + cobalt_write_bar1(cobalt, COBALT_SYS_STAT_MASK, irqs); + } else { + /* Disable all ADV7604 interrupts */ + cobalt_write_bar1(cobalt, COBALT_SYS_STAT_MASK, 0); + } +} + +static unsigned cobalt_get_sd_nr(struct v4l2_subdev *sd) +{ + struct cobalt *cobalt = to_cobalt(sd->v4l2_dev); + unsigned i; + + for (i = 0; i < COBALT_NUM_NODES; i++) + if (sd == cobalt->streams[i].sd) + return i; + cobalt_err("Invalid adv7604 subdev pointer!\n"); + return 0; +} + +static void cobalt_notify(struct v4l2_subdev *sd, + unsigned int notification, void *arg) +{ + struct cobalt *cobalt = to_cobalt(sd->v4l2_dev); + unsigned sd_nr = cobalt_get_sd_nr(sd); + struct cobalt_stream *s = &cobalt->streams[sd_nr]; + bool hotplug = arg ? *((int *)arg) : false; + + if (s->is_output) + return; + + switch (notification) { + case ADV76XX_HOTPLUG: + cobalt_s_bit_sysctrl(cobalt, + COBALT_SYS_CTRL_HPD_TO_CONNECTOR_BIT(sd_nr), hotplug); + cobalt_dbg(1, "Set hotplug for adv %d to %d\n", sd_nr, hotplug); + break; + case V4L2_DEVICE_NOTIFY_EVENT: + cobalt_dbg(1, "Format changed for adv %d\n", sd_nr); + v4l2_event_queue(&s->vdev, arg); + break; + default: + break; + } +} + +static int get_payload_size(u16 code) +{ + switch (code) { + case 0: return 128; + case 1: return 256; + case 2: return 512; + case 3: return 1024; + case 4: return 2048; + case 5: return 4096; + default: return 0; + } + return 0; +} + +static const char *get_link_speed(u16 stat) +{ + switch (stat & PCI_EXP_LNKSTA_CLS) { + case 1: return "2.5 Gbit/s"; + case 2: return "5 Gbit/s"; + case 3: return "10 Gbit/s"; + } + return "Unknown speed"; +} + +void cobalt_pcie_status_show(struct cobalt *cobalt) +{ + struct pci_dev *pci_dev = cobalt->pci_dev; + struct pci_dev *pci_bus_dev = cobalt->pci_dev->bus->self; + u32 capa; + u16 stat, ctrl; + + if (!pci_is_pcie(pci_dev) || !pci_is_pcie(pci_bus_dev)) + return; + + /* Device */ + pcie_capability_read_dword(pci_dev, PCI_EXP_DEVCAP, &capa); + pcie_capability_read_word(pci_dev, PCI_EXP_DEVCTL, &ctrl); + pcie_capability_read_word(pci_dev, PCI_EXP_DEVSTA, &stat); + cobalt_info("PCIe device capability 0x%08x: Max payload %d\n", + capa, get_payload_size(capa & PCI_EXP_DEVCAP_PAYLOAD)); + cobalt_info("PCIe device control 0x%04x: Max payload %d. Max read request %d\n", + ctrl, + get_payload_size((ctrl & PCI_EXP_DEVCTL_PAYLOAD) >> 5), + get_payload_size((ctrl & PCI_EXP_DEVCTL_READRQ) >> 12)); + cobalt_info("PCIe device status 0x%04x\n", stat); + + /* Link */ + pcie_capability_read_dword(pci_dev, PCI_EXP_LNKCAP, &capa); + pcie_capability_read_word(pci_dev, PCI_EXP_LNKCTL, &ctrl); + pcie_capability_read_word(pci_dev, PCI_EXP_LNKSTA, &stat); + cobalt_info("PCIe link capability 0x%08x: %s per lane and %u lanes\n", + capa, get_link_speed(capa), + FIELD_GET(PCI_EXP_LNKCAP_MLW, capa)); + cobalt_info("PCIe link control 0x%04x\n", ctrl); + cobalt_info("PCIe link status 0x%04x: %s per lane and %u lanes\n", + stat, get_link_speed(stat), + FIELD_GET(PCI_EXP_LNKSTA_NLW, stat)); + + /* Bus */ + pcie_capability_read_dword(pci_bus_dev, PCI_EXP_LNKCAP, &capa); + cobalt_info("PCIe bus link capability 0x%08x: %s per lane and %u lanes\n", + capa, get_link_speed(capa), + FIELD_GET(PCI_EXP_LNKCAP_MLW, capa)); + + /* Slot */ + pcie_capability_read_dword(pci_dev, PCI_EXP_SLTCAP, &capa); + pcie_capability_read_word(pci_dev, PCI_EXP_SLTCTL, &ctrl); + pcie_capability_read_word(pci_dev, PCI_EXP_SLTSTA, &stat); + cobalt_info("PCIe slot capability 0x%08x\n", capa); + cobalt_info("PCIe slot control 0x%04x\n", ctrl); + cobalt_info("PCIe slot status 0x%04x\n", stat); +} + +static unsigned pcie_link_get_lanes(struct cobalt *cobalt) +{ + struct pci_dev *pci_dev = cobalt->pci_dev; + u16 link; + + if (!pci_is_pcie(pci_dev)) + return 0; + pcie_capability_read_word(pci_dev, PCI_EXP_LNKSTA, &link); + return FIELD_GET(PCI_EXP_LNKSTA_NLW, link); +} + +static unsigned pcie_bus_link_get_lanes(struct cobalt *cobalt) +{ + struct pci_dev *pci_dev = cobalt->pci_dev->bus->self; + u32 link; + + if (!pci_is_pcie(pci_dev)) + return 0; + pcie_capability_read_dword(pci_dev, PCI_EXP_LNKCAP, &link); + return FIELD_GET(PCI_EXP_LNKCAP_MLW, link); +} + +static void msi_config_show(struct cobalt *cobalt, struct pci_dev *pci_dev) +{ + u16 ctrl, data; + u32 adrs_l, adrs_h; + + pci_read_config_word(pci_dev, 0x52, &ctrl); + cobalt_info("MSI %s\n", ctrl & 1 ? "enable" : "disable"); + cobalt_info("MSI multiple message: Capable %u. Enable %u\n", + (1 << ((ctrl >> 1) & 7)), (1 << ((ctrl >> 4) & 7))); + if (ctrl & 0x80) + cobalt_info("MSI: 64-bit address capable\n"); + pci_read_config_dword(pci_dev, 0x54, &adrs_l); + pci_read_config_dword(pci_dev, 0x58, &adrs_h); + pci_read_config_word(pci_dev, 0x5c, &data); + if (ctrl & 0x80) + cobalt_info("MSI: Address 0x%08x%08x. Data 0x%04x\n", + adrs_h, adrs_l, data); + else + cobalt_info("MSI: Address 0x%08x. Data 0x%04x\n", + adrs_l, data); +} + +static void cobalt_pci_iounmap(struct cobalt *cobalt, struct pci_dev *pci_dev) +{ + if (cobalt->bar0) { + pci_iounmap(pci_dev, cobalt->bar0); + cobalt->bar0 = NULL; + } + if (cobalt->bar1) { + pci_iounmap(pci_dev, cobalt->bar1); + cobalt->bar1 = NULL; + } +} + +static void cobalt_free_msi(struct cobalt *cobalt, struct pci_dev *pci_dev) +{ + free_irq(pci_dev->irq, (void *)cobalt); + pci_free_irq_vectors(pci_dev); +} + +static int cobalt_setup_pci(struct cobalt *cobalt, struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + u32 ctrl; + int ret; + + cobalt_dbg(1, "enabling pci device\n"); + + ret = pci_enable_device(pci_dev); + if (ret) { + cobalt_err("can't enable device\n"); + return ret; + } + pci_set_master(pci_dev); + pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &cobalt->card_rev); + pci_read_config_word(pci_dev, PCI_DEVICE_ID, &cobalt->device_id); + + switch (cobalt->device_id) { + case PCI_DEVICE_ID_COBALT: + cobalt_info("PCI Express interface from Omnitek\n"); + break; + default: + cobalt_info("PCI Express interface provider is unknown!\n"); + break; + } + + if (pcie_link_get_lanes(cobalt) != 8) { + cobalt_warn("PCI Express link width is %d lanes.\n", + pcie_link_get_lanes(cobalt)); + if (pcie_bus_link_get_lanes(cobalt) < 8) + cobalt_warn("The current slot only supports %d lanes, for best performance 8 are needed\n", + pcie_bus_link_get_lanes(cobalt)); + if (pcie_link_get_lanes(cobalt) != pcie_bus_link_get_lanes(cobalt)) { + cobalt_err("The card is most likely not seated correctly in the PCIe slot\n"); + ret = -EIO; + goto err_disable; + } + } + + if (dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(64))) { + ret = dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(32)); + if (ret) { + cobalt_err("no suitable DMA available\n"); + goto err_disable; + } + } + + ret = pci_request_regions(pci_dev, "cobalt"); + if (ret) { + cobalt_err("error requesting regions\n"); + goto err_disable; + } + + cobalt_pcie_status_show(cobalt); + + cobalt->bar0 = pci_iomap(pci_dev, 0, 0); + cobalt->bar1 = pci_iomap(pci_dev, 1, 0); + if (cobalt->bar1 == NULL) { + cobalt->bar1 = pci_iomap(pci_dev, 2, 0); + cobalt_info("64-bit BAR\n"); + } + if (!cobalt->bar0 || !cobalt->bar1) { + ret = -EIO; + goto err_release; + } + + /* Reset the video inputs before enabling any interrupts */ + ctrl = cobalt_read_bar1(cobalt, COBALT_SYS_CTRL_BASE); + cobalt_write_bar1(cobalt, COBALT_SYS_CTRL_BASE, ctrl & ~0xf00); + + /* Disable interrupts to prevent any spurious interrupts + from being generated. */ + cobalt_set_interrupt(cobalt, false); + + if (pci_alloc_irq_vectors(pci_dev, 1, 1, PCI_IRQ_MSI) < 1) { + cobalt_err("Could not enable MSI\n"); + ret = -EIO; + goto err_release; + } + msi_config_show(cobalt, pci_dev); + + /* Register IRQ */ + if (request_irq(pci_dev->irq, cobalt_irq_handler, IRQF_SHARED, + cobalt->v4l2_dev.name, (void *)cobalt)) { + cobalt_err("Failed to register irq %d\n", pci_dev->irq); + ret = -EIO; + goto err_msi; + } + + omni_sg_dma_init(cobalt); + return 0; + +err_msi: + pci_disable_msi(pci_dev); + +err_release: + cobalt_pci_iounmap(cobalt, pci_dev); + pci_release_regions(pci_dev); + +err_disable: + pci_disable_device(cobalt->pci_dev); + return ret; +} + +static int cobalt_hdl_info_get(struct cobalt *cobalt) +{ + int i; + + for (i = 0; i < COBALT_HDL_INFO_SIZE; i++) + cobalt->hdl_info[i] = + ioread8(cobalt->bar1 + COBALT_HDL_INFO_BASE + i); + cobalt->hdl_info[COBALT_HDL_INFO_SIZE - 1] = '\0'; + if (strstr(cobalt->hdl_info, COBALT_HDL_SEARCH_STR)) + return 0; + + return 1; +} + +static void cobalt_stream_struct_init(struct cobalt *cobalt) +{ + int i; + + for (i = 0; i < COBALT_NUM_STREAMS; i++) { + struct cobalt_stream *s = &cobalt->streams[i]; + + s->cobalt = cobalt; + s->flags = 0; + s->is_audio = false; + s->is_output = false; + s->is_dummy = true; + + /* The Memory DMA channels will always get a lower channel + * number than the FIFO DMA. Video input should map to the + * stream 0-3. The other can use stream struct from 4 and + * higher */ + if (i <= COBALT_HSMA_IN_NODE) { + s->dma_channel = i + cobalt->first_fifo_channel; + s->video_channel = i; + s->dma_fifo_mask = + COBALT_SYSSTAT_VI0_LOST_DATA_MSK << (4 * i); + s->adv_irq_mask = + COBALT_SYSSTAT_VI0_INT1_MSK << (4 * i); + } else if (i >= COBALT_AUDIO_IN_STREAM && + i <= COBALT_AUDIO_IN_STREAM + 4) { + unsigned idx = i - COBALT_AUDIO_IN_STREAM; + + s->dma_channel = 6 + idx; + s->is_audio = true; + s->video_channel = idx; + s->dma_fifo_mask = COBALT_SYSSTAT_AUD_IN_LOST_DATA_MSK; + } else if (i == COBALT_HSMA_OUT_NODE) { + s->dma_channel = 11; + s->is_output = true; + s->video_channel = 5; + s->dma_fifo_mask = COBALT_SYSSTAT_VOHSMA_LOST_DATA_MSK; + s->adv_irq_mask = COBALT_SYSSTAT_VOHSMA_INT1_MSK; + } else if (i == COBALT_AUDIO_OUT_STREAM) { + s->dma_channel = 12; + s->is_audio = true; + s->is_output = true; + s->video_channel = 5; + s->dma_fifo_mask = COBALT_SYSSTAT_AUD_OUT_LOST_DATA_MSK; + } else { + /* FIXME: Memory DMA for debug purpose */ + s->dma_channel = i - COBALT_NUM_NODES; + } + cobalt_info("stream #%d -> dma channel #%d <- video channel %d\n", + i, s->dma_channel, s->video_channel); + } +} + +static int cobalt_subdevs_init(struct cobalt *cobalt) +{ + static struct adv76xx_platform_data adv7604_pdata = { + .disable_pwrdnb = 1, + .ain_sel = ADV7604_AIN7_8_9_NC_SYNC_3_1, + .bus_order = ADV7604_BUS_ORDER_BRG, + .blank_data = 1, + .op_format_mode_sel = ADV7604_OP_FORMAT_MODE0, + .int1_config = ADV76XX_INT1_CONFIG_ACTIVE_HIGH, + .dr_str_data = ADV76XX_DR_STR_HIGH, + .dr_str_clk = ADV76XX_DR_STR_HIGH, + .dr_str_sync = ADV76XX_DR_STR_HIGH, + .hdmi_free_run_mode = 1, + .inv_vs_pol = 1, + .inv_hs_pol = 1, + }; + static struct i2c_board_info adv7604_info = { + .type = "adv7604", + .addr = 0x20, + .platform_data = &adv7604_pdata, + }; + + struct cobalt_stream *s = cobalt->streams; + int i; + + for (i = 0; i < COBALT_NUM_INPUTS; i++) { + struct v4l2_subdev_format sd_fmt = { + .pad = ADV7604_PAD_SOURCE, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .format.code = MEDIA_BUS_FMT_YUYV8_1X16, + }; + struct v4l2_subdev_edid cobalt_edid = { + .pad = ADV76XX_PAD_HDMI_PORT_A, + .start_block = 0, + .blocks = 2, + .edid = edid, + }; + int err; + + s[i].pad_source = ADV7604_PAD_SOURCE; + s[i].i2c_adap = &cobalt->i2c_adap[i]; + if (s[i].i2c_adap->dev.parent == NULL) + continue; + cobalt_s_bit_sysctrl(cobalt, + COBALT_SYS_CTRL_NRESET_TO_HDMI_BIT(i), 1); + s[i].sd = v4l2_i2c_new_subdev_board(&cobalt->v4l2_dev, + s[i].i2c_adap, &adv7604_info, NULL); + if (!s[i].sd) { + if (cobalt_ignore_err) + continue; + return -ENODEV; + } + err = v4l2_subdev_call(s[i].sd, video, s_routing, + ADV76XX_PAD_HDMI_PORT_A, 0, 0); + if (err) + return err; + err = v4l2_subdev_call(s[i].sd, pad, set_edid, + &cobalt_edid); + if (err) + return err; + err = v4l2_subdev_call(s[i].sd, pad, set_fmt, NULL, + &sd_fmt); + if (err) + return err; + /* Reset channel video module */ + cobalt_s_bit_sysctrl(cobalt, + COBALT_SYS_CTRL_VIDEO_RX_RESETN_BIT(i), 0); + mdelay(2); + cobalt_s_bit_sysctrl(cobalt, + COBALT_SYS_CTRL_VIDEO_RX_RESETN_BIT(i), 1); + mdelay(1); + s[i].is_dummy = false; + cobalt->streams[i + COBALT_AUDIO_IN_STREAM].is_dummy = false; + } + return 0; +} + +static int cobalt_subdevs_hsma_init(struct cobalt *cobalt) +{ + static struct adv7842_platform_data adv7842_pdata = { + .disable_pwrdnb = 1, + .ain_sel = ADV7842_AIN1_2_3_NC_SYNC_1_2, + .bus_order = ADV7842_BUS_ORDER_RBG, + .op_format_mode_sel = ADV7842_OP_FORMAT_MODE0, + .blank_data = 1, + .dr_str_data = 3, + .dr_str_clk = 3, + .dr_str_sync = 3, + .mode = ADV7842_MODE_HDMI, + .hdmi_free_run_enable = 1, + .vid_std_select = ADV7842_HDMI_COMP_VID_STD_HD_1250P, + .i2c_sdp_io = 0x4a, + .i2c_sdp = 0x48, + .i2c_cp = 0x22, + .i2c_vdp = 0x24, + .i2c_afe = 0x26, + .i2c_hdmi = 0x34, + .i2c_repeater = 0x32, + .i2c_edid = 0x36, + .i2c_infoframe = 0x3e, + .i2c_cec = 0x40, + .i2c_avlink = 0x42, + }; + static struct i2c_board_info adv7842_info = { + .type = "adv7842", + .addr = 0x20, + .platform_data = &adv7842_pdata, + }; + struct v4l2_subdev_format sd_fmt = { + .pad = ADV7842_PAD_SOURCE, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .format.code = MEDIA_BUS_FMT_YUYV8_1X16, + }; + static struct adv7511_platform_data adv7511_pdata = { + .i2c_edid = 0x7e >> 1, + .i2c_cec = 0x7c >> 1, + .i2c_pktmem = 0x70 >> 1, + .cec_clk = 12000000, + }; + static struct i2c_board_info adv7511_info = { + .type = "adv7511-v4l2", + .addr = 0x39, /* 0x39 or 0x3d */ + .platform_data = &adv7511_pdata, + }; + struct v4l2_subdev_edid cobalt_edid = { + .pad = ADV7842_EDID_PORT_A, + .start_block = 0, + .blocks = 2, + .edid = edid, + }; + struct cobalt_stream *s = &cobalt->streams[COBALT_HSMA_IN_NODE]; + + s->i2c_adap = &cobalt->i2c_adap[COBALT_NUM_ADAPTERS - 1]; + if (s->i2c_adap->dev.parent == NULL) + return 0; + cobalt_s_bit_sysctrl(cobalt, COBALT_SYS_CTRL_NRESET_TO_HDMI_BIT(4), 1); + + s->sd = v4l2_i2c_new_subdev_board(&cobalt->v4l2_dev, + s->i2c_adap, &adv7842_info, NULL); + if (s->sd) { + int err = v4l2_subdev_call(s->sd, pad, set_edid, &cobalt_edid); + + if (err) + return err; + err = v4l2_subdev_call(s->sd, pad, set_fmt, NULL, + &sd_fmt); + if (err) + return err; + cobalt->have_hsma_rx = true; + s->pad_source = ADV7842_PAD_SOURCE; + s->is_dummy = false; + cobalt->streams[4 + COBALT_AUDIO_IN_STREAM].is_dummy = false; + /* Reset channel video module */ + cobalt_s_bit_sysctrl(cobalt, + COBALT_SYS_CTRL_VIDEO_RX_RESETN_BIT(4), 0); + mdelay(2); + cobalt_s_bit_sysctrl(cobalt, + COBALT_SYS_CTRL_VIDEO_RX_RESETN_BIT(4), 1); + mdelay(1); + return err; + } + cobalt_s_bit_sysctrl(cobalt, COBALT_SYS_CTRL_NRESET_TO_HDMI_BIT(4), 0); + cobalt_s_bit_sysctrl(cobalt, COBALT_SYS_CTRL_PWRDN0_TO_HSMA_TX_BIT, 0); + s++; + s->i2c_adap = &cobalt->i2c_adap[COBALT_NUM_ADAPTERS - 1]; + s->sd = v4l2_i2c_new_subdev_board(&cobalt->v4l2_dev, + s->i2c_adap, &adv7511_info, NULL); + if (s->sd) { + /* A transmitter is hooked up, so we can set this bit */ + cobalt_s_bit_sysctrl(cobalt, + COBALT_SYS_CTRL_HSMA_TX_ENABLE_BIT, 1); + cobalt_s_bit_sysctrl(cobalt, + COBALT_SYS_CTRL_VIDEO_RX_RESETN_BIT(4), 0); + cobalt_s_bit_sysctrl(cobalt, + COBALT_SYS_CTRL_VIDEO_TX_RESETN_BIT, 1); + cobalt->have_hsma_tx = true; + v4l2_subdev_call(s->sd, core, s_power, 1); + v4l2_subdev_call(s->sd, video, s_stream, 1); + v4l2_subdev_call(s->sd, audio, s_stream, 1); + v4l2_ctrl_s_ctrl(v4l2_ctrl_find(s->sd->ctrl_handler, + V4L2_CID_DV_TX_MODE), V4L2_DV_TX_MODE_HDMI); + s->is_dummy = false; + cobalt->streams[COBALT_AUDIO_OUT_STREAM].is_dummy = false; + return 0; + } + return -ENODEV; +} + +static int cobalt_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct cobalt *cobalt; + int retval = 0; + int i; + + /* FIXME - module parameter arrays constrain max instances */ + i = atomic_inc_return(&cobalt_instance) - 1; + + cobalt = kzalloc_obj(struct cobalt); + if (cobalt == NULL) + return -ENOMEM; + cobalt->pci_dev = pci_dev; + cobalt->instance = i; + mutex_init(&cobalt->pci_lock); + + retval = v4l2_device_register(&pci_dev->dev, &cobalt->v4l2_dev); + if (retval) { + pr_err("cobalt: v4l2_device_register of card %d failed\n", + cobalt->instance); + kfree(cobalt); + return retval; + } + snprintf(cobalt->v4l2_dev.name, sizeof(cobalt->v4l2_dev.name), + "cobalt-%d", cobalt->instance); + cobalt->v4l2_dev.notify = cobalt_notify; + cobalt_info("Initializing card %d\n", cobalt->instance); + + cobalt->irq_work_queues = + create_singlethread_workqueue(cobalt->v4l2_dev.name); + if (cobalt->irq_work_queues == NULL) { + cobalt_err("Could not create workqueue\n"); + retval = -ENOMEM; + goto err; + } + + INIT_WORK(&cobalt->irq_work_queue, cobalt_irq_work_handler); + + /* PCI Device Setup */ + retval = cobalt_setup_pci(cobalt, pci_dev, pci_id); + if (retval != 0) + goto err_wq; + + /* Show HDL version info */ + if (cobalt_hdl_info_get(cobalt)) + cobalt_info("Not able to read the HDL info\n"); + else + cobalt_info("%s", cobalt->hdl_info); + + retval = cobalt_i2c_init(cobalt); + if (retval) + goto err_pci; + + cobalt_stream_struct_init(cobalt); + + retval = cobalt_subdevs_init(cobalt); + if (retval) + goto err_i2c; + + if (!(cobalt_read_bar1(cobalt, COBALT_SYS_STAT_BASE) & + COBALT_SYSSTAT_HSMA_PRSNTN_MSK)) { + retval = cobalt_subdevs_hsma_init(cobalt); + if (retval) + goto err_i2c; + } + + retval = cobalt_nodes_register(cobalt); + if (retval) { + cobalt_err("Error %d registering device nodes\n", retval); + goto err_i2c; + } + cobalt_set_interrupt(cobalt, true); + v4l2_device_call_all(&cobalt->v4l2_dev, 0, core, + interrupt_service_routine, 0, NULL); + + cobalt_info("Initialized cobalt card\n"); + + cobalt_flash_probe(cobalt); + + return 0; + +err_i2c: + cobalt_i2c_exit(cobalt); + cobalt_s_bit_sysctrl(cobalt, COBALT_SYS_CTRL_HSMA_TX_ENABLE_BIT, 0); +err_pci: + cobalt_free_msi(cobalt, pci_dev); + cobalt_pci_iounmap(cobalt, pci_dev); + pci_release_regions(cobalt->pci_dev); + pci_disable_device(cobalt->pci_dev); +err_wq: + destroy_workqueue(cobalt->irq_work_queues); +err: + cobalt_err("error %d on initialization\n", retval); + + v4l2_device_unregister(&cobalt->v4l2_dev); + kfree(cobalt); + return retval; +} + +static void cobalt_remove(struct pci_dev *pci_dev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct cobalt *cobalt = to_cobalt(v4l2_dev); + int i; + + cobalt_flash_remove(cobalt); + cobalt_set_interrupt(cobalt, false); + flush_workqueue(cobalt->irq_work_queues); + cobalt_nodes_unregister(cobalt); + for (i = 0; i < COBALT_NUM_ADAPTERS; i++) { + struct v4l2_subdev *sd = cobalt->streams[i].sd; + struct i2c_client *client; + + if (sd == NULL) + continue; + client = v4l2_get_subdevdata(sd); + v4l2_device_unregister_subdev(sd); + i2c_unregister_device(client); + } + cobalt_i2c_exit(cobalt); + cobalt_free_msi(cobalt, pci_dev); + cobalt_s_bit_sysctrl(cobalt, COBALT_SYS_CTRL_HSMA_TX_ENABLE_BIT, 0); + cobalt_pci_iounmap(cobalt, pci_dev); + pci_release_regions(cobalt->pci_dev); + pci_disable_device(cobalt->pci_dev); + destroy_workqueue(cobalt->irq_work_queues); + + cobalt_info("removed cobalt card\n"); + + v4l2_device_unregister(v4l2_dev); + kfree(cobalt); +} + +/* define a pci_driver for card detection */ +static struct pci_driver cobalt_pci_driver = { + .name = "cobalt", + .id_table = cobalt_pci_tbl, + .probe = cobalt_probe, + .remove = cobalt_remove, +}; + +module_pci_driver(cobalt_pci_driver); diff --git a/drivers/media/pci/cobalt/cobalt-driver.h b/drivers/media/pci/cobalt/cobalt-driver.h new file mode 100644 index 000000000..12c33e035 --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-driver.h @@ -0,0 +1,373 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * cobalt driver internal defines and structures + * + * Derived from cx18-driver.h + * + * Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#ifndef COBALT_DRIVER_H +#define COBALT_DRIVER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "m00233_video_measure_memmap_package.h" +#include "m00235_fdma_packer_memmap_package.h" +#include "m00389_cvi_memmap_package.h" +#include "m00460_evcnt_memmap_package.h" +#include "m00473_freewheel_memmap_package.h" +#include "m00479_clk_loss_detector_memmap_package.h" +#include "m00514_syncgen_flow_evcnt_memmap_package.h" + +/* System device ID */ +#define PCI_DEVICE_ID_COBALT 0x2732 + +/* Number of cobalt device nodes. */ +#define COBALT_NUM_INPUTS 4 +#define COBALT_NUM_NODES 6 + +/* Number of cobalt device streams. */ +#define COBALT_NUM_STREAMS 12 + +#define COBALT_HSMA_IN_NODE 4 +#define COBALT_HSMA_OUT_NODE 5 + +/* Cobalt audio streams */ +#define COBALT_AUDIO_IN_STREAM 6 +#define COBALT_AUDIO_OUT_STREAM 11 + +/* DMA stuff */ +#define DMA_CHANNELS_MAX 16 + +/* i2c stuff */ +#define I2C_CLIENTS_MAX 16 +#define COBALT_NUM_ADAPTERS 5 + +#define COBALT_CLK 50000000 + +/* System status register */ +#define COBALT_SYSSTAT_DIP0_MSK BIT(0) +#define COBALT_SYSSTAT_DIP1_MSK BIT(1) +#define COBALT_SYSSTAT_HSMA_PRSNTN_MSK BIT(2) +#define COBALT_SYSSTAT_FLASH_RDYBSYN_MSK BIT(3) +#define COBALT_SYSSTAT_VI0_5V_MSK BIT(4) +#define COBALT_SYSSTAT_VI0_INT1_MSK BIT(5) +#define COBALT_SYSSTAT_VI0_INT2_MSK BIT(6) +#define COBALT_SYSSTAT_VI0_LOST_DATA_MSK BIT(7) +#define COBALT_SYSSTAT_VI1_5V_MSK BIT(8) +#define COBALT_SYSSTAT_VI1_INT1_MSK BIT(9) +#define COBALT_SYSSTAT_VI1_INT2_MSK BIT(10) +#define COBALT_SYSSTAT_VI1_LOST_DATA_MSK BIT(11) +#define COBALT_SYSSTAT_VI2_5V_MSK BIT(12) +#define COBALT_SYSSTAT_VI2_INT1_MSK BIT(13) +#define COBALT_SYSSTAT_VI2_INT2_MSK BIT(14) +#define COBALT_SYSSTAT_VI2_LOST_DATA_MSK BIT(15) +#define COBALT_SYSSTAT_VI3_5V_MSK BIT(16) +#define COBALT_SYSSTAT_VI3_INT1_MSK BIT(17) +#define COBALT_SYSSTAT_VI3_INT2_MSK BIT(18) +#define COBALT_SYSSTAT_VI3_LOST_DATA_MSK BIT(19) +#define COBALT_SYSSTAT_VIHSMA_5V_MSK BIT(20) +#define COBALT_SYSSTAT_VIHSMA_INT1_MSK BIT(21) +#define COBALT_SYSSTAT_VIHSMA_INT2_MSK BIT(22) +#define COBALT_SYSSTAT_VIHSMA_LOST_DATA_MSK BIT(23) +#define COBALT_SYSSTAT_VOHSMA_INT1_MSK BIT(24) +#define COBALT_SYSSTAT_VOHSMA_PLL_LOCKED_MSK BIT(25) +#define COBALT_SYSSTAT_VOHSMA_LOST_DATA_MSK BIT(26) +#define COBALT_SYSSTAT_AUD_PLL_LOCKED_MSK BIT(28) +#define COBALT_SYSSTAT_AUD_IN_LOST_DATA_MSK BIT(29) +#define COBALT_SYSSTAT_AUD_OUT_LOST_DATA_MSK BIT(30) +#define COBALT_SYSSTAT_PCIE_SMBCLK_MSK BIT(31) + +/* Cobalt memory map */ +#define COBALT_I2C_0_BASE 0x0 +#define COBALT_I2C_1_BASE 0x080 +#define COBALT_I2C_2_BASE 0x100 +#define COBALT_I2C_3_BASE 0x180 +#define COBALT_I2C_HSMA_BASE 0x200 + +#define COBALT_SYS_CTRL_BASE 0x400 +#define COBALT_SYS_CTRL_HSMA_TX_ENABLE_BIT 1 +#define COBALT_SYS_CTRL_VIDEO_RX_RESETN_BIT(n) (4 + 4 * (n)) +#define COBALT_SYS_CTRL_NRESET_TO_HDMI_BIT(n) (5 + 4 * (n)) +#define COBALT_SYS_CTRL_HPD_TO_CONNECTOR_BIT(n) (6 + 4 * (n)) +#define COBALT_SYS_CTRL_AUDIO_IPP_RESETN_BIT(n) (7 + 4 * (n)) +#define COBALT_SYS_CTRL_PWRDN0_TO_HSMA_TX_BIT 24 +#define COBALT_SYS_CTRL_VIDEO_TX_RESETN_BIT 25 +#define COBALT_SYS_CTRL_AUDIO_OPP_RESETN_BIT 27 + +#define COBALT_SYS_STAT_BASE 0x500 +#define COBALT_SYS_STAT_MASK (COBALT_SYS_STAT_BASE + 0x08) +#define COBALT_SYS_STAT_EDGE (COBALT_SYS_STAT_BASE + 0x0c) + +#define COBALT_HDL_INFO_BASE 0x4800 +#define COBALT_HDL_INFO_SIZE 0x200 + +#define COBALT_VID_BASE 0x10000 +#define COBALT_VID_SIZE 0x1000 + +#define COBALT_CVI(cobalt, c) \ + (cobalt->bar1 + COBALT_VID_BASE + (c) * COBALT_VID_SIZE) +#define COBALT_CVI_VMR(cobalt, c) \ + (cobalt->bar1 + COBALT_VID_BASE + (c) * COBALT_VID_SIZE + 0x100) +#define COBALT_CVI_EVCNT(cobalt, c) \ + (cobalt->bar1 + COBALT_VID_BASE + (c) * COBALT_VID_SIZE + 0x200) +#define COBALT_CVI_FREEWHEEL(cobalt, c) \ + (cobalt->bar1 + COBALT_VID_BASE + (c) * COBALT_VID_SIZE + 0x300) +#define COBALT_CVI_CLK_LOSS(cobalt, c) \ + (cobalt->bar1 + COBALT_VID_BASE + (c) * COBALT_VID_SIZE + 0x400) +#define COBALT_CVI_PACKER(cobalt, c) \ + (cobalt->bar1 + COBALT_VID_BASE + (c) * COBALT_VID_SIZE + 0x500) + +#define COBALT_TX_BASE(cobalt) (cobalt->bar1 + COBALT_VID_BASE + 0x5000) + +#define DMA_INTERRUPT_STATUS_REG 0x08 + +#define COBALT_HDL_SEARCH_STR "** HDL version info **" + +/* Cobalt CPU bus interface */ +#define COBALT_BUS_BAR1_BASE 0x600 +#define COBALT_BUS_SRAM_BASE 0x0 +#define COBALT_BUS_CPLD_BASE 0x00600000 +#define COBALT_BUS_FLASH_BASE 0x08000000 + +/* FDMA to PCIe packing */ +#define COBALT_BYTES_PER_PIXEL_YUYV 2 +#define COBALT_BYTES_PER_PIXEL_RGB24 3 +#define COBALT_BYTES_PER_PIXEL_RGB32 4 + +/* debugging */ +extern int cobalt_debug; +extern int cobalt_ignore_err; + +#define cobalt_err(fmt, arg...) v4l2_err(&cobalt->v4l2_dev, fmt, ## arg) +#define cobalt_warn(fmt, arg...) v4l2_warn(&cobalt->v4l2_dev, fmt, ## arg) +#define cobalt_info(fmt, arg...) v4l2_info(&cobalt->v4l2_dev, fmt, ## arg) +#define cobalt_dbg(level, fmt, arg...) \ + v4l2_dbg(level, cobalt_debug, &cobalt->v4l2_dev, fmt, ## arg) + +struct cobalt; +struct cobalt_i2c_regs; + +/* Per I2C bus private algo callback data */ +struct cobalt_i2c_data { + struct cobalt *cobalt; + struct cobalt_i2c_regs __iomem *regs; +}; + +struct pci_consistent_buffer { + void *virt; + dma_addr_t bus; + size_t bytes; +}; + +struct sg_dma_desc_info { + void *virt; + dma_addr_t bus; + unsigned size; + void *last_desc_virt; + struct device *dev; +}; + +#define COBALT_MAX_WIDTH 1920 +#define COBALT_MAX_HEIGHT 1200 +#define COBALT_MAX_BPP 3 +#define COBALT_MAX_FRAMESZ \ + (COBALT_MAX_WIDTH * COBALT_MAX_HEIGHT * COBALT_MAX_BPP) + +#define NR_BUFS VIDEO_MAX_FRAME + +#define COBALT_STREAM_FL_DMA_IRQ 0 +#define COBALT_STREAM_FL_ADV_IRQ 1 + +struct cobalt_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +static inline +struct cobalt_buffer *to_cobalt_buffer(struct vb2_v4l2_buffer *vb2) +{ + return container_of(vb2, struct cobalt_buffer, vb); +} + +struct cobalt_stream { + struct video_device vdev; + struct vb2_queue q; + struct list_head bufs; + struct i2c_adapter *i2c_adap; + struct v4l2_subdev *sd; + struct mutex lock; + spinlock_t irqlock; + struct v4l2_dv_timings timings; + u32 input; + u32 pad_source; + u32 width, height, bpp; + u32 stride; + u32 pixfmt; + u32 sequence; + u32 colorspace; + u32 xfer_func; + u32 ycbcr_enc; + u32 quantization; + + u8 dma_channel; + int video_channel; + unsigned dma_fifo_mask; + unsigned adv_irq_mask; + struct sg_dma_desc_info dma_desc_info[NR_BUFS]; + unsigned long flags; + bool unstable_frame; + bool enable_cvi; + bool enable_freewheel; + unsigned skip_first_frames; + bool is_output; + bool is_audio; + bool is_dummy; + + struct cobalt *cobalt; + struct snd_cobalt_card *alsa; +}; + +struct snd_cobalt_card; + +/* Struct to hold info about cobalt cards */ +struct cobalt { + int instance; + struct pci_dev *pci_dev; + struct v4l2_device v4l2_dev; + /* serialize PCI access in cobalt_s_bit_sysctrl() */ + struct mutex pci_lock; + + void __iomem *bar0, *bar1; + + u8 card_rev; + u16 device_id; + + /* device nodes */ + struct cobalt_stream streams[DMA_CHANNELS_MAX]; + struct i2c_adapter i2c_adap[COBALT_NUM_ADAPTERS]; + struct cobalt_i2c_data i2c_data[COBALT_NUM_ADAPTERS]; + bool have_hsma_rx; + bool have_hsma_tx; + + /* irq */ + struct workqueue_struct *irq_work_queues; + struct work_struct irq_work_queue; /* work entry */ + /* irq counters */ + u32 irq_adv1; + u32 irq_adv2; + u32 irq_advout; + u32 irq_dma_tot; + u32 irq_dma[COBALT_NUM_STREAMS]; + u32 irq_none; + u32 irq_full_fifo; + + /* omnitek dma */ + int dma_channels; + int first_fifo_channel; + bool pci_32_bit; + + char hdl_info[COBALT_HDL_INFO_SIZE]; + + /* NOR flash */ + struct mtd_info *mtd; +}; + +static inline struct cobalt *to_cobalt(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct cobalt, v4l2_dev); +} + +static inline void cobalt_write_bar0(struct cobalt *cobalt, u32 reg, u32 val) +{ + iowrite32(val, cobalt->bar0 + reg); +} + +static inline u32 cobalt_read_bar0(struct cobalt *cobalt, u32 reg) +{ + return ioread32(cobalt->bar0 + reg); +} + +static inline void cobalt_write_bar1(struct cobalt *cobalt, u32 reg, u32 val) +{ + iowrite32(val, cobalt->bar1 + reg); +} + +static inline u32 cobalt_read_bar1(struct cobalt *cobalt, u32 reg) +{ + return ioread32(cobalt->bar1 + reg); +} + +static inline u32 cobalt_g_sysctrl(struct cobalt *cobalt) +{ + return cobalt_read_bar1(cobalt, COBALT_SYS_CTRL_BASE); +} + +static inline void cobalt_s_bit_sysctrl(struct cobalt *cobalt, + int bit, int val) +{ + u32 ctrl; + + mutex_lock(&cobalt->pci_lock); + ctrl = cobalt_read_bar1(cobalt, COBALT_SYS_CTRL_BASE); + cobalt_write_bar1(cobalt, COBALT_SYS_CTRL_BASE, + (ctrl & ~(1UL << bit)) | (val << bit)); + mutex_unlock(&cobalt->pci_lock); +} + +static inline u32 cobalt_g_sysstat(struct cobalt *cobalt) +{ + return cobalt_read_bar1(cobalt, COBALT_SYS_STAT_BASE); +} + +#define ADRS_REG (bar1 + COBALT_BUS_BAR1_BASE + 0) +#define LOWER_DATA (bar1 + COBALT_BUS_BAR1_BASE + 4) +#define UPPER_DATA (bar1 + COBALT_BUS_BAR1_BASE + 6) + +static inline u32 cobalt_bus_read32(void __iomem *bar1, u32 bus_adrs) +{ + iowrite32(bus_adrs, ADRS_REG); + return ioread32(LOWER_DATA); +} + +static inline void cobalt_bus_write16(void __iomem *bar1, + u32 bus_adrs, u16 data) +{ + iowrite32(bus_adrs, ADRS_REG); + if (bus_adrs & 2) + iowrite16(data, UPPER_DATA); + else + iowrite16(data, LOWER_DATA); +} + +static inline void cobalt_bus_write32(void __iomem *bar1, + u32 bus_adrs, u16 data) +{ + iowrite32(bus_adrs, ADRS_REG); + if (bus_adrs & 2) + iowrite32(data, UPPER_DATA); + else + iowrite32(data, LOWER_DATA); +} + +/*==============Prototypes==================*/ + +void cobalt_pcie_status_show(struct cobalt *cobalt); + +#endif diff --git a/drivers/media/pci/cobalt/cobalt-flash.c b/drivers/media/pci/cobalt/cobalt-flash.c new file mode 100644 index 000000000..1d3c64b4c --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-flash.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Cobalt NOR flash functions + * + * Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#include +#include +#include +#include + +#include "cobalt-flash.h" + +#define ADRS(offset) (COBALT_BUS_FLASH_BASE + offset) + +static struct map_info cobalt_flash_map = { + .name = "cobalt-flash", + .bankwidth = 2, /* 16 bits */ + .size = 0x4000000, /* 64MB */ + .phys = 0, /* offset */ +}; + +static map_word flash_read16(struct map_info *map, unsigned long offset) +{ + map_word r; + + r.x[0] = cobalt_bus_read32(map->virt, ADRS(offset)); + if (offset & 0x2) + r.x[0] >>= 16; + else + r.x[0] &= 0x0000ffff; + + return r; +} + +static void flash_write16(struct map_info *map, const map_word datum, + unsigned long offset) +{ + u16 data = (u16)datum.x[0]; + + cobalt_bus_write16(map->virt, ADRS(offset), data); +} + +static void flash_copy_from(struct map_info *map, void *to, + unsigned long from, ssize_t len) +{ + u32 src = from; + u8 *dest = to; + u32 data; + + while (len) { + data = cobalt_bus_read32(map->virt, ADRS(src)); + do { + *dest = data >> (8 * (src & 3)); + src++; + dest++; + len--; + } while (len && (src % 4)); + } +} + +static void flash_copy_to(struct map_info *map, unsigned long to, + const void *from, ssize_t len) +{ + const u8 *src = from; + u32 dest = to; + + pr_info("%s: offset 0x%x: length %zu\n", __func__, dest, len); + while (len) { + u16 data; + + do { + data = *src << (8 * (dest & 1)); + src++; + dest++; + len--; + } while (len && (dest % 2)); + + cobalt_bus_write16(map->virt, ADRS(dest - 2), data); + } +} + +int cobalt_flash_probe(struct cobalt *cobalt) +{ + struct map_info *map = &cobalt_flash_map; + struct mtd_info *mtd; + + BUG_ON(!map_bankwidth_supported(map->bankwidth)); + map->virt = cobalt->bar1; + map->read = flash_read16; + map->write = flash_write16; + map->copy_from = flash_copy_from; + map->copy_to = flash_copy_to; + + mtd = do_map_probe("cfi_probe", map); + cobalt->mtd = mtd; + if (!mtd) { + cobalt_err("Probe CFI flash failed!\n"); + return -1; + } + + mtd->owner = THIS_MODULE; + mtd->dev.parent = &cobalt->pci_dev->dev; + mtd_device_register(mtd, NULL, 0); + return 0; +} + +void cobalt_flash_remove(struct cobalt *cobalt) +{ + if (cobalt->mtd) { + mtd_device_unregister(cobalt->mtd); + map_destroy(cobalt->mtd); + } +} diff --git a/drivers/media/pci/cobalt/cobalt-flash.h b/drivers/media/pci/cobalt/cobalt-flash.h new file mode 100644 index 000000000..605ce3d37 --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-flash.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Cobalt NOR flash functions + * + * Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#ifndef COBALT_FLASH_H +#define COBALT_FLASH_H + +#include "cobalt-driver.h" + +int cobalt_flash_probe(struct cobalt *cobalt); +void cobalt_flash_remove(struct cobalt *cobalt); + +#endif diff --git a/drivers/media/pci/cobalt/cobalt-i2c.c b/drivers/media/pci/cobalt/cobalt-i2c.c new file mode 100644 index 000000000..10c9ee33f --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-i2c.c @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * cobalt I2C functions + * + * Derived from cx18-i2c.c + * + * Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#include "cobalt-driver.h" +#include "cobalt-i2c.h" + +struct cobalt_i2c_regs { + /* Clock prescaler register lo-byte */ + u8 prerlo; + u8 dummy0[3]; + /* Clock prescaler register high-byte */ + u8 prerhi; + u8 dummy1[3]; + /* Control register */ + u8 ctr; + u8 dummy2[3]; + /* Transmit/Receive register */ + u8 txr_rxr; + u8 dummy3[3]; + /* Command and Status register */ + u8 cr_sr; + u8 dummy4[3]; +}; + +/* CTR[7:0] - Control register */ + +/* I2C Core enable bit */ +#define M00018_CTR_BITMAP_EN_MSK (1 << 7) + +/* I2C Core interrupt enable bit */ +#define M00018_CTR_BITMAP_IEN_MSK (1 << 6) + +/* CR[7:0] - Command register */ + +/* I2C start condition */ +#define M00018_CR_BITMAP_STA_MSK (1 << 7) + +/* I2C stop condition */ +#define M00018_CR_BITMAP_STO_MSK (1 << 6) + +/* I2C read from slave */ +#define M00018_CR_BITMAP_RD_MSK (1 << 5) + +/* I2C write to slave */ +#define M00018_CR_BITMAP_WR_MSK (1 << 4) + +/* I2C ack */ +#define M00018_CR_BITMAP_ACK_MSK (1 << 3) + +/* I2C Interrupt ack */ +#define M00018_CR_BITMAP_IACK_MSK (1 << 0) + +/* SR[7:0] - Status register */ + +/* Receive acknowledge from slave */ +#define M00018_SR_BITMAP_RXACK_MSK (1 << 7) + +/* Busy, I2C bus busy (as defined by start / stop bits) */ +#define M00018_SR_BITMAP_BUSY_MSK (1 << 6) + +/* Arbitration lost - core lost arbitration */ +#define M00018_SR_BITMAP_AL_MSK (1 << 5) + +/* Transfer in progress */ +#define M00018_SR_BITMAP_TIP_MSK (1 << 1) + +/* Interrupt flag */ +#define M00018_SR_BITMAP_IF_MSK (1 << 0) + +/* Frequency, in Hz */ +#define I2C_FREQUENCY 400000 +#define ALT_CPU_FREQ 83333333 + +static struct cobalt_i2c_regs __iomem * +cobalt_i2c_regs(struct cobalt *cobalt, unsigned idx) +{ + switch (idx) { + case 0: + default: + return (struct cobalt_i2c_regs __iomem *) + (cobalt->bar1 + COBALT_I2C_0_BASE); + case 1: + return (struct cobalt_i2c_regs __iomem *) + (cobalt->bar1 + COBALT_I2C_1_BASE); + case 2: + return (struct cobalt_i2c_regs __iomem *) + (cobalt->bar1 + COBALT_I2C_2_BASE); + case 3: + return (struct cobalt_i2c_regs __iomem *) + (cobalt->bar1 + COBALT_I2C_3_BASE); + case 4: + return (struct cobalt_i2c_regs __iomem *) + (cobalt->bar1 + COBALT_I2C_HSMA_BASE); + } +} + +/* Do low-level i2c byte transfer. + * Returns -1 in case of an error or 0 otherwise. + */ +static int cobalt_tx_bytes(struct cobalt_i2c_regs __iomem *regs, + struct i2c_adapter *adap, bool start, bool stop, + u8 *data, u16 len) +{ + unsigned long start_time; + int status; + int cmd; + int i; + + for (i = 0; i < len; i++) { + /* Setup data */ + iowrite8(data[i], ®s->txr_rxr); + + /* Setup command */ + if (i == 0 && start) { + /* Write + Start */ + cmd = M00018_CR_BITMAP_WR_MSK | + M00018_CR_BITMAP_STA_MSK; + } else if (i == len - 1 && stop) { + /* Write + Stop */ + cmd = M00018_CR_BITMAP_WR_MSK | + M00018_CR_BITMAP_STO_MSK; + } else { + /* Write only */ + cmd = M00018_CR_BITMAP_WR_MSK; + } + + /* Execute command */ + iowrite8(cmd, ®s->cr_sr); + + /* Wait for transfer to complete (TIP = 0) */ + start_time = jiffies; + status = ioread8(®s->cr_sr); + while (status & M00018_SR_BITMAP_TIP_MSK) { + if (time_after(jiffies, start_time + adap->timeout)) + return -ETIMEDOUT; + cond_resched(); + status = ioread8(®s->cr_sr); + } + + /* Verify ACK */ + if (status & M00018_SR_BITMAP_RXACK_MSK) { + /* NO ACK! */ + return -EIO; + } + + /* Verify arbitration */ + if (status & M00018_SR_BITMAP_AL_MSK) { + /* Arbitration lost! */ + return -EIO; + } + } + return 0; +} + +/* Do low-level i2c byte read. + * Returns -1 in case of an error or 0 otherwise. + */ +static int cobalt_rx_bytes(struct cobalt_i2c_regs __iomem *regs, + struct i2c_adapter *adap, bool start, bool stop, + u8 *data, u16 len) +{ + unsigned long start_time; + int status; + int cmd; + int i; + + for (i = 0; i < len; i++) { + /* Setup command */ + if (i == 0 && start) { + /* Read + Start */ + cmd = M00018_CR_BITMAP_RD_MSK | + M00018_CR_BITMAP_STA_MSK; + } else if (i == len - 1 && stop) { + /* Read + Stop */ + cmd = M00018_CR_BITMAP_RD_MSK | + M00018_CR_BITMAP_STO_MSK; + } else { + /* Read only */ + cmd = M00018_CR_BITMAP_RD_MSK; + } + + /* Last byte to read, no ACK */ + if (i == len - 1) + cmd |= M00018_CR_BITMAP_ACK_MSK; + + /* Execute command */ + iowrite8(cmd, ®s->cr_sr); + + /* Wait for transfer to complete (TIP = 0) */ + start_time = jiffies; + status = ioread8(®s->cr_sr); + while (status & M00018_SR_BITMAP_TIP_MSK) { + if (time_after(jiffies, start_time + adap->timeout)) + return -ETIMEDOUT; + cond_resched(); + status = ioread8(®s->cr_sr); + } + + /* Verify arbitration */ + if (status & M00018_SR_BITMAP_AL_MSK) { + /* Arbitration lost! */ + return -EIO; + } + + /* Store data */ + data[i] = ioread8(®s->txr_rxr); + } + return 0; +} + +/* Generate stop condition on i2c bus. + * The m00018 stop isn't doing the right thing (wrong timing). + * So instead send a start condition, 8 zeroes and a stop condition. + */ +static int cobalt_stop(struct cobalt_i2c_regs __iomem *regs, + struct i2c_adapter *adap) +{ + u8 data = 0; + + return cobalt_tx_bytes(regs, adap, true, true, &data, 1); +} + +static int cobalt_xfer(struct i2c_adapter *adap, + struct i2c_msg msgs[], int num) +{ + struct cobalt_i2c_data *data = adap->algo_data; + struct cobalt_i2c_regs __iomem *regs = data->regs; + struct i2c_msg *pmsg; + unsigned short flags; + int ret = 0; + int i, j; + + for (i = 0; i < num; i++) { + int stop = (i == num - 1); + + pmsg = &msgs[i]; + flags = pmsg->flags; + + if (!(pmsg->flags & I2C_M_NOSTART)) { + u8 addr = pmsg->addr << 1; + + if (flags & I2C_M_RD) + addr |= 1; + if (flags & I2C_M_REV_DIR_ADDR) + addr ^= 1; + for (j = 0; j < adap->retries; j++) { + ret = cobalt_tx_bytes(regs, adap, true, false, + &addr, 1); + if (!ret) + break; + cobalt_stop(regs, adap); + } + if (ret < 0) + return ret; + ret = 0; + } + if (pmsg->flags & I2C_M_RD) { + /* read bytes into buffer */ + ret = cobalt_rx_bytes(regs, adap, false, stop, + pmsg->buf, pmsg->len); + if (ret < 0) + goto bailout; + } else { + /* write bytes from buffer */ + ret = cobalt_tx_bytes(regs, adap, false, stop, + pmsg->buf, pmsg->len); + if (ret < 0) + goto bailout; + } + } + ret = i; + +bailout: + if (ret < 0) + cobalt_stop(regs, adap); + return ret; +} + +static u32 cobalt_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +/* template for i2c-bit-algo */ +static const struct i2c_adapter cobalt_i2c_adap_template = { + .name = "cobalt i2c driver", + .algo = NULL, /* set by i2c-algo-bit */ + .algo_data = NULL, /* filled from template */ + .owner = THIS_MODULE, +}; + +static const struct i2c_algorithm cobalt_algo = { + .master_xfer = cobalt_xfer, + .functionality = cobalt_func, +}; + +/* init + register i2c algo-bit adapter */ +int cobalt_i2c_init(struct cobalt *cobalt) +{ + int i, err; + int status; + int prescale; + unsigned long start_time; + + cobalt_dbg(1, "i2c init\n"); + + /* Define I2C clock prescaler */ + prescale = ((ALT_CPU_FREQ) / (5 * I2C_FREQUENCY)) - 1; + + for (i = 0; i < COBALT_NUM_ADAPTERS; i++) { + struct cobalt_i2c_regs __iomem *regs = + cobalt_i2c_regs(cobalt, i); + struct i2c_adapter *adap = &cobalt->i2c_adap[i]; + + /* Disable I2C */ + iowrite8(M00018_CTR_BITMAP_EN_MSK, ®s->cr_sr); + iowrite8(0, ®s->ctr); + iowrite8(0, ®s->cr_sr); + + start_time = jiffies; + do { + if (time_after(jiffies, start_time + HZ)) { + if (cobalt_ignore_err) { + adap->dev.parent = NULL; + return 0; + } + return -ETIMEDOUT; + } + status = ioread8(®s->cr_sr); + } while (status & M00018_SR_BITMAP_TIP_MSK); + + /* Disable I2C */ + iowrite8(0, ®s->ctr); + iowrite8(0, ®s->cr_sr); + + /* Calculate i2c prescaler */ + iowrite8(prescale & 0xff, ®s->prerlo); + iowrite8((prescale >> 8) & 0xff, ®s->prerhi); + /* Enable I2C, interrupts disabled */ + iowrite8(M00018_CTR_BITMAP_EN_MSK, ®s->ctr); + /* Setup algorithm for adapter */ + cobalt->i2c_data[i].cobalt = cobalt; + cobalt->i2c_data[i].regs = regs; + *adap = cobalt_i2c_adap_template; + adap->algo = &cobalt_algo; + adap->algo_data = &cobalt->i2c_data[i]; + adap->retries = 3; + sprintf(adap->name + strlen(adap->name), + " #%d-%d", cobalt->instance, i); + i2c_set_adapdata(adap, &cobalt->v4l2_dev); + adap->dev.parent = &cobalt->pci_dev->dev; + err = i2c_add_adapter(adap); + if (err) { + if (cobalt_ignore_err) { + adap->dev.parent = NULL; + return 0; + } + while (i--) + i2c_del_adapter(&cobalt->i2c_adap[i]); + return err; + } + cobalt_info("registered bus %s\n", adap->name); + } + return 0; +} + +void cobalt_i2c_exit(struct cobalt *cobalt) +{ + int i; + + cobalt_dbg(1, "i2c exit\n"); + + for (i = 0; i < COBALT_NUM_ADAPTERS; i++) { + cobalt_err("unregistered bus %s\n", cobalt->i2c_adap[i].name); + i2c_del_adapter(&cobalt->i2c_adap[i]); + } +} diff --git a/drivers/media/pci/cobalt/cobalt-i2c.h b/drivers/media/pci/cobalt/cobalt-i2c.h new file mode 100644 index 000000000..7a9057c8b --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-i2c.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * cobalt I2C functions + * + * Derived from cx18-i2c.h + * + * Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +/* init + register i2c algo-bit adapter */ +int cobalt_i2c_init(struct cobalt *cobalt); +void cobalt_i2c_exit(struct cobalt *cobalt); diff --git a/drivers/media/pci/cobalt/cobalt-irq.c b/drivers/media/pci/cobalt/cobalt-irq.c new file mode 100644 index 000000000..a518927ab --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-irq.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * cobalt interrupt handling + * + * Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#include + +#include "cobalt-driver.h" +#include "cobalt-irq.h" +#include "cobalt-omnitek.h" + +static void cobalt_dma_stream_queue_handler(struct cobalt_stream *s) +{ + struct cobalt *cobalt = s->cobalt; + int rx = s->video_channel; + struct m00473_freewheel_regmap __iomem *fw = + COBALT_CVI_FREEWHEEL(s->cobalt, rx); + struct m00233_video_measure_regmap __iomem *vmr = + COBALT_CVI_VMR(s->cobalt, rx); + struct m00389_cvi_regmap __iomem *cvi = + COBALT_CVI(s->cobalt, rx); + struct m00479_clk_loss_detector_regmap __iomem *clkloss = + COBALT_CVI_CLK_LOSS(s->cobalt, rx); + struct cobalt_buffer *cb; + bool skip = false; + + spin_lock(&s->irqlock); + + if (list_empty(&s->bufs)) { + pr_err("no buffers!\n"); + spin_unlock(&s->irqlock); + return; + } + + /* Give the fresh filled up buffer to the user. + * Note that the interrupt is only sent if the DMA can continue + * with a new buffer, so it is always safe to return this buffer + * to userspace. */ + cb = list_first_entry(&s->bufs, struct cobalt_buffer, list); + list_del(&cb->list); + spin_unlock(&s->irqlock); + + if (s->is_audio || s->is_output) + goto done; + + if (s->unstable_frame) { + uint32_t stat = ioread32(&vmr->irq_status); + + iowrite32(stat, &vmr->irq_status); + if (!(ioread32(&vmr->status) & + M00233_STATUS_BITMAP_INIT_DONE_MSK)) { + cobalt_dbg(1, "!init_done\n"); + if (s->enable_freewheel) + goto restart_fw; + goto done; + } + + if (ioread32(&clkloss->status) & + M00479_STATUS_BITMAP_CLOCK_MISSING_MSK) { + iowrite32(0, &clkloss->ctrl); + iowrite32(M00479_CTRL_BITMAP_ENABLE_MSK, &clkloss->ctrl); + cobalt_dbg(1, "no clock\n"); + if (s->enable_freewheel) + goto restart_fw; + goto done; + } + if ((stat & (M00233_IRQ_STATUS_BITMAP_VACTIVE_AREA_MSK | + M00233_IRQ_STATUS_BITMAP_HACTIVE_AREA_MSK)) || + ioread32(&vmr->vactive_area) != s->timings.bt.height || + ioread32(&vmr->hactive_area) != s->timings.bt.width) { + cobalt_dbg(1, "unstable\n"); + if (s->enable_freewheel) + goto restart_fw; + goto done; + } + if (!s->enable_cvi) { + s->enable_cvi = true; + iowrite32(M00389_CONTROL_BITMAP_ENABLE_MSK, &cvi->control); + goto done; + } + if (!(ioread32(&cvi->status) & M00389_STATUS_BITMAP_LOCK_MSK)) { + cobalt_dbg(1, "cvi no lock\n"); + if (s->enable_freewheel) + goto restart_fw; + goto done; + } + if (!s->enable_freewheel) { + cobalt_dbg(1, "stable\n"); + s->enable_freewheel = true; + iowrite32(0, &fw->ctrl); + goto done; + } + cobalt_dbg(1, "enabled fw\n"); + iowrite32(M00233_CONTROL_BITMAP_ENABLE_MEASURE_MSK | + M00233_CONTROL_BITMAP_ENABLE_INTERRUPT_MSK, + &vmr->control); + iowrite32(M00473_CTRL_BITMAP_ENABLE_MSK, &fw->ctrl); + s->enable_freewheel = false; + s->unstable_frame = false; + s->skip_first_frames = 2; + skip = true; + goto done; + } + if (ioread32(&fw->status) & M00473_STATUS_BITMAP_FREEWHEEL_MODE_MSK) { +restart_fw: + cobalt_dbg(1, "lost lock\n"); + iowrite32(M00233_CONTROL_BITMAP_ENABLE_MEASURE_MSK, + &vmr->control); + iowrite32(M00473_CTRL_BITMAP_ENABLE_MSK | + M00473_CTRL_BITMAP_FORCE_FREEWHEEL_MODE_MSK, + &fw->ctrl); + iowrite32(0, &cvi->control); + s->unstable_frame = true; + s->enable_freewheel = false; + s->enable_cvi = false; + } +done: + if (s->skip_first_frames) { + skip = true; + s->skip_first_frames--; + } + cb->vb.vb2_buf.timestamp = ktime_get_ns(); + /* TODO: the sequence number should be read from the FPGA so we + also know about dropped frames. */ + cb->vb.sequence = s->sequence++; + vb2_buffer_done(&cb->vb.vb2_buf, + (skip || s->unstable_frame) ? + VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); +} + +irqreturn_t cobalt_irq_handler(int irq, void *dev_id) +{ + struct cobalt *cobalt = (struct cobalt *)dev_id; + u32 dma_interrupt = + cobalt_read_bar0(cobalt, DMA_INTERRUPT_STATUS_REG) & 0xffff; + u32 mask = cobalt_read_bar1(cobalt, COBALT_SYS_STAT_MASK); + u32 edge = cobalt_read_bar1(cobalt, COBALT_SYS_STAT_EDGE); + int i; + + /* Clear DMA interrupt */ + cobalt_write_bar0(cobalt, DMA_INTERRUPT_STATUS_REG, dma_interrupt); + cobalt_write_bar1(cobalt, COBALT_SYS_STAT_MASK, mask & ~edge); + cobalt_write_bar1(cobalt, COBALT_SYS_STAT_EDGE, edge); + + for (i = 0; i < COBALT_NUM_STREAMS; i++) { + struct cobalt_stream *s = &cobalt->streams[i]; + unsigned dma_fifo_mask = s->dma_fifo_mask; + + if (dma_interrupt & (1 << s->dma_channel)) { + cobalt->irq_dma[i]++; + /* Give fresh buffer to user and chain newly + * queued buffers */ + cobalt_dma_stream_queue_handler(s); + if (!s->is_audio) { + edge &= ~dma_fifo_mask; + cobalt_write_bar1(cobalt, COBALT_SYS_STAT_MASK, + mask & ~edge); + } + } + if (s->is_audio) + continue; + if (edge & s->adv_irq_mask) + set_bit(COBALT_STREAM_FL_ADV_IRQ, &s->flags); + if ((edge & mask & dma_fifo_mask) && vb2_is_streaming(&s->q)) { + cobalt_info("full rx FIFO %d\n", i); + cobalt->irq_full_fifo++; + } + } + + queue_work(cobalt->irq_work_queues, &cobalt->irq_work_queue); + + if (edge & mask & (COBALT_SYSSTAT_VI0_INT1_MSK | + COBALT_SYSSTAT_VI1_INT1_MSK | + COBALT_SYSSTAT_VI2_INT1_MSK | + COBALT_SYSSTAT_VI3_INT1_MSK | + COBALT_SYSSTAT_VIHSMA_INT1_MSK | + COBALT_SYSSTAT_VOHSMA_INT1_MSK)) + cobalt->irq_adv1++; + if (edge & mask & (COBALT_SYSSTAT_VI0_INT2_MSK | + COBALT_SYSSTAT_VI1_INT2_MSK | + COBALT_SYSSTAT_VI2_INT2_MSK | + COBALT_SYSSTAT_VI3_INT2_MSK | + COBALT_SYSSTAT_VIHSMA_INT2_MSK)) + cobalt->irq_adv2++; + if (edge & mask & COBALT_SYSSTAT_VOHSMA_INT1_MSK) + cobalt->irq_advout++; + if (dma_interrupt) + cobalt->irq_dma_tot++; + if (!(edge & mask) && !dma_interrupt) + cobalt->irq_none++; + dma_interrupt = cobalt_read_bar0(cobalt, DMA_INTERRUPT_STATUS_REG); + + return IRQ_HANDLED; +} + +void cobalt_irq_work_handler(struct work_struct *work) +{ + struct cobalt *cobalt = + container_of(work, struct cobalt, irq_work_queue); + int i; + + for (i = 0; i < COBALT_NUM_NODES; i++) { + struct cobalt_stream *s = &cobalt->streams[i]; + + if (test_and_clear_bit(COBALT_STREAM_FL_ADV_IRQ, &s->flags)) { + u32 mask; + + v4l2_subdev_call(cobalt->streams[i].sd, core, + interrupt_service_routine, 0, NULL); + mask = cobalt_read_bar1(cobalt, COBALT_SYS_STAT_MASK); + cobalt_write_bar1(cobalt, COBALT_SYS_STAT_MASK, + mask | s->adv_irq_mask); + } + } +} + +void cobalt_irq_log_status(struct cobalt *cobalt) +{ + u32 mask; + int i; + + cobalt_info("irq: adv1=%u adv2=%u advout=%u none=%u full=%u\n", + cobalt->irq_adv1, cobalt->irq_adv2, cobalt->irq_advout, + cobalt->irq_none, cobalt->irq_full_fifo); + cobalt_info("irq: dma_tot=%u (", cobalt->irq_dma_tot); + for (i = 0; i < COBALT_NUM_STREAMS; i++) + pr_cont("%s%u", i ? "/" : "", cobalt->irq_dma[i]); + pr_cont(")\n"); + cobalt->irq_dma_tot = cobalt->irq_adv1 = cobalt->irq_adv2 = 0; + cobalt->irq_advout = cobalt->irq_none = cobalt->irq_full_fifo = 0; + memset(cobalt->irq_dma, 0, sizeof(cobalt->irq_dma)); + + mask = cobalt_read_bar1(cobalt, COBALT_SYS_STAT_MASK); + cobalt_write_bar1(cobalt, COBALT_SYS_STAT_MASK, + mask | + COBALT_SYSSTAT_VI0_LOST_DATA_MSK | + COBALT_SYSSTAT_VI1_LOST_DATA_MSK | + COBALT_SYSSTAT_VI2_LOST_DATA_MSK | + COBALT_SYSSTAT_VI3_LOST_DATA_MSK | + COBALT_SYSSTAT_VIHSMA_LOST_DATA_MSK | + COBALT_SYSSTAT_VOHSMA_LOST_DATA_MSK | + COBALT_SYSSTAT_AUD_IN_LOST_DATA_MSK | + COBALT_SYSSTAT_AUD_OUT_LOST_DATA_MSK); +} diff --git a/drivers/media/pci/cobalt/cobalt-irq.h b/drivers/media/pci/cobalt/cobalt-irq.h new file mode 100644 index 000000000..0b4078ce6 --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-irq.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * cobalt interrupt handling + * + * Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#include + +irqreturn_t cobalt_irq_handler(int irq, void *dev_id); +void cobalt_irq_work_handler(struct work_struct *work); +void cobalt_irq_log_status(struct cobalt *cobalt); diff --git a/drivers/media/pci/cobalt/cobalt-omnitek.c b/drivers/media/pci/cobalt/cobalt-omnitek.c new file mode 100644 index 000000000..01b82a2e8 --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-omnitek.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Omnitek Scatter-Gather DMA Controller + * + * Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#include +#include +#include +#include + +#include "cobalt-driver.h" +#include "cobalt-omnitek.h" + +/* descriptor */ +#define END_OF_CHAIN (1 << 1) +#define INTERRUPT_ENABLE (1 << 2) +#define WRITE_TO_PCI (1 << 3) +#define READ_FROM_PCI (0 << 3) +#define DESCRIPTOR_FLAG_MSK (END_OF_CHAIN | INTERRUPT_ENABLE | WRITE_TO_PCI) +#define NEXT_ADRS_MSK 0xffffffe0 + +/* control/status register */ +#define ENABLE (1 << 0) +#define START (1 << 1) +#define ABORT (1 << 2) +#define DONE (1 << 4) +#define SG_INTERRUPT (1 << 5) +#define EVENT_INTERRUPT (1 << 6) +#define SCATTER_GATHER_MODE (1 << 8) +#define DISABLE_VIDEO_RESYNC (1 << 9) +#define EVENT_INTERRUPT_ENABLE (1 << 10) +#define DIRECTIONAL_MSK (3 << 16) +#define INPUT_ONLY (0 << 16) +#define OUTPUT_ONLY (1 << 16) +#define BIDIRECTIONAL (2 << 16) +#define DMA_TYPE_MEMORY (0 << 18) +#define DMA_TYPE_FIFO (1 << 18) + +#define BASE (cobalt->bar0) +#define CAPABILITY_HEADER (BASE) +#define CAPABILITY_REGISTER (BASE + 0x04) +#define PCI_64BIT (1 << 8) +#define LOCAL_64BIT (1 << 9) +#define INTERRUPT_STATUS (BASE + 0x08) +#define PCI(c) (BASE + 0x40 + ((c) * 0x40)) +#define SIZE(c) (BASE + 0x58 + ((c) * 0x40)) +#define DESCRIPTOR(c) (BASE + 0x50 + ((c) * 0x40)) +#define CS_REG(c) (BASE + 0x60 + ((c) * 0x40)) +#define BYTES_TRANSFERRED(c) (BASE + 0x64 + ((c) * 0x40)) + + +static char *get_dma_direction(u32 status) +{ + switch (status & DIRECTIONAL_MSK) { + case INPUT_ONLY: return "Input"; + case OUTPUT_ONLY: return "Output"; + case BIDIRECTIONAL: return "Bidirectional"; + } + return ""; +} + +static void show_dma_capability(struct cobalt *cobalt) +{ + u32 header = ioread32(CAPABILITY_HEADER); + u32 capa = ioread32(CAPABILITY_REGISTER); + u32 i; + + cobalt_info("Omnitek DMA capability: ID 0x%02x Version 0x%02x Next 0x%x Size 0x%x\n", + header & 0xff, (header >> 8) & 0xff, + (header >> 16) & 0xffff, (capa >> 24) & 0xff); + + switch ((capa >> 8) & 0x3) { + case 0: + cobalt_info("Omnitek DMA: 32 bits PCIe and Local\n"); + break; + case 1: + cobalt_info("Omnitek DMA: 64 bits PCIe, 32 bits Local\n"); + break; + case 3: + cobalt_info("Omnitek DMA: 64 bits PCIe and Local\n"); + break; + } + + for (i = 0; i < (capa & 0xf); i++) { + u32 status = ioread32(CS_REG(i)); + + cobalt_info("Omnitek DMA channel #%d: %s %s\n", i, + status & DMA_TYPE_FIFO ? "FIFO" : "MEMORY", + get_dma_direction(status)); + } +} + +void omni_sg_dma_start(struct cobalt_stream *s, struct sg_dma_desc_info *desc) +{ + struct cobalt *cobalt = s->cobalt; + + iowrite32((u32)((u64)desc->bus >> 32), DESCRIPTOR(s->dma_channel) + 4); + iowrite32((u32)desc->bus & NEXT_ADRS_MSK, DESCRIPTOR(s->dma_channel)); + iowrite32(ENABLE | SCATTER_GATHER_MODE | START, CS_REG(s->dma_channel)); +} + +bool is_dma_done(struct cobalt_stream *s) +{ + struct cobalt *cobalt = s->cobalt; + + if (ioread32(CS_REG(s->dma_channel)) & DONE) + return true; + + return false; +} + +void omni_sg_dma_abort_channel(struct cobalt_stream *s) +{ + struct cobalt *cobalt = s->cobalt; + + if (!is_dma_done(s)) + iowrite32(ABORT, CS_REG(s->dma_channel)); +} + +int omni_sg_dma_init(struct cobalt *cobalt) +{ + u32 capa = ioread32(CAPABILITY_REGISTER); + int i; + + cobalt->first_fifo_channel = 0; + cobalt->dma_channels = capa & 0xf; + if (capa & PCI_64BIT) + cobalt->pci_32_bit = false; + else + cobalt->pci_32_bit = true; + + for (i = 0; i < cobalt->dma_channels; i++) { + u32 status = ioread32(CS_REG(i)); + u32 ctrl = ioread32(CS_REG(i)); + + if (!(ctrl & DONE)) + iowrite32(ABORT, CS_REG(i)); + + if (!(status & DMA_TYPE_FIFO)) + cobalt->first_fifo_channel++; + } + show_dma_capability(cobalt); + return 0; +} + +int descriptor_list_create(struct cobalt *cobalt, + struct scatterlist *scatter_list, bool to_pci, unsigned sglen, + unsigned size, unsigned width, unsigned stride, + struct sg_dma_desc_info *desc) +{ + struct sg_dma_descriptor *d = (struct sg_dma_descriptor *)desc->virt; + dma_addr_t next = desc->bus; + unsigned offset = 0; + unsigned copy_bytes = width; + unsigned copied = 0; + bool first = true; + + /* Must be 4-byte aligned */ + WARN_ON(sg_dma_address(scatter_list) & 3); + WARN_ON(size & 3); + WARN_ON(next & 3); + WARN_ON(stride & 3); + WARN_ON(stride < width); + if (width >= stride) + copy_bytes = stride = size; + + while (size) { + dma_addr_t addr = sg_dma_address(scatter_list) + offset; + unsigned bytes; + + if (addr == 0) + return -EFAULT; + if (cobalt->pci_32_bit) { + WARN_ON((u64)addr >> 32); + if ((u64)addr >> 32) + return -EFAULT; + } + + /* PCIe address */ + d->pci_l = addr & 0xffffffff; + /* If dma_addr_t is 32 bits, then addr >> 32 is actually the + equivalent of addr >> 0 in gcc. So must cast to u64. */ + d->pci_h = (u64)addr >> 32; + + /* Sync to start of streaming frame */ + d->local = 0; + d->reserved0 = 0; + + /* Transfer bytes */ + bytes = min(sg_dma_len(scatter_list) - offset, + copy_bytes - copied); + + if (first) { + if (to_pci) + d->local = 0x11111111; + first = false; + if (sglen == 1) { + /* Make sure there are always at least two + * descriptors */ + d->bytes = (bytes / 2) & ~3; + d->reserved1 = 0; + size -= d->bytes; + copied += d->bytes; + offset += d->bytes; + addr += d->bytes; + next += sizeof(struct sg_dma_descriptor); + d->next_h = (u32)((u64)next >> 32); + d->next_l = (u32)next | + (to_pci ? WRITE_TO_PCI : 0); + bytes -= d->bytes; + d++; + /* PCIe address */ + d->pci_l = addr & 0xffffffff; + /* If dma_addr_t is 32 bits, then addr >> 32 + * is actually the equivalent of addr >> 0 in + * gcc. So must cast to u64. */ + d->pci_h = (u64)addr >> 32; + + /* Sync to start of streaming frame */ + d->local = 0; + d->reserved0 = 0; + } + } + + d->bytes = bytes; + d->reserved1 = 0; + size -= bytes; + copied += bytes; + offset += bytes; + + if (copied == copy_bytes) { + while (copied < stride) { + bytes = min(sg_dma_len(scatter_list) - offset, + stride - copied); + copied += bytes; + offset += bytes; + size -= bytes; + if (sg_dma_len(scatter_list) == offset) { + offset = 0; + scatter_list = sg_next(scatter_list); + } + } + copied = 0; + } else { + offset = 0; + scatter_list = sg_next(scatter_list); + } + + /* Next descriptor + control bits */ + next += sizeof(struct sg_dma_descriptor); + if (size == 0) { + /* Loopback to the first descriptor */ + d->next_h = (u32)((u64)desc->bus >> 32); + d->next_l = (u32)desc->bus | + (to_pci ? WRITE_TO_PCI : 0) | INTERRUPT_ENABLE; + if (!to_pci) + d->local = 0x22222222; + desc->last_desc_virt = d; + } else { + d->next_h = (u32)((u64)next >> 32); + d->next_l = (u32)next | (to_pci ? WRITE_TO_PCI : 0); + } + d++; + } + return 0; +} + +void descriptor_list_chain(struct sg_dma_desc_info *this, + struct sg_dma_desc_info *next) +{ + struct sg_dma_descriptor *d = this->last_desc_virt; + u32 direction = d->next_l & WRITE_TO_PCI; + + if (next == NULL) { + d->next_h = 0; + d->next_l = direction | INTERRUPT_ENABLE | END_OF_CHAIN; + } else { + d->next_h = (u32)((u64)next->bus >> 32); + d->next_l = (u32)next->bus | direction | INTERRUPT_ENABLE; + } +} + +void *descriptor_list_allocate(struct sg_dma_desc_info *desc, size_t bytes) +{ + desc->size = bytes; + desc->virt = dma_alloc_coherent(desc->dev, bytes, + &desc->bus, GFP_KERNEL); + return desc->virt; +} + +void descriptor_list_free(struct sg_dma_desc_info *desc) +{ + if (desc->virt) + dma_free_coherent(desc->dev, desc->size, + desc->virt, desc->bus); + desc->virt = NULL; +} + +void descriptor_list_interrupt_enable(struct sg_dma_desc_info *desc) +{ + struct sg_dma_descriptor *d = desc->last_desc_virt; + + d->next_l |= INTERRUPT_ENABLE; +} + +void descriptor_list_interrupt_disable(struct sg_dma_desc_info *desc) +{ + struct sg_dma_descriptor *d = desc->last_desc_virt; + + d->next_l &= ~INTERRUPT_ENABLE; +} + +void descriptor_list_loopback(struct sg_dma_desc_info *desc) +{ + struct sg_dma_descriptor *d = desc->last_desc_virt; + + d->next_h = (u32)((u64)desc->bus >> 32); + d->next_l = (u32)desc->bus | (d->next_l & DESCRIPTOR_FLAG_MSK); +} + +void descriptor_list_end_of_chain(struct sg_dma_desc_info *desc) +{ + struct sg_dma_descriptor *d = desc->last_desc_virt; + + d->next_l |= END_OF_CHAIN; +} diff --git a/drivers/media/pci/cobalt/cobalt-omnitek.h b/drivers/media/pci/cobalt/cobalt-omnitek.h new file mode 100644 index 000000000..129c5fccb --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-omnitek.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Omnitek Scatter-Gather DMA Controller + * + * Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#ifndef COBALT_OMNITEK_H +#define COBALT_OMNITEK_H + +#include +#include "cobalt-driver.h" + +struct sg_dma_descriptor { + u32 pci_l; + u32 pci_h; + + u32 local; + u32 reserved0; + + u32 next_l; + u32 next_h; + + u32 bytes; + u32 reserved1; +}; + +int omni_sg_dma_init(struct cobalt *cobalt); +void omni_sg_dma_abort_channel(struct cobalt_stream *s); +void omni_sg_dma_start(struct cobalt_stream *s, struct sg_dma_desc_info *desc); +bool is_dma_done(struct cobalt_stream *s); + +int descriptor_list_create(struct cobalt *cobalt, + struct scatterlist *scatter_list, bool to_pci, unsigned sglen, + unsigned size, unsigned width, unsigned stride, + struct sg_dma_desc_info *desc); + +void descriptor_list_chain(struct sg_dma_desc_info *this, + struct sg_dma_desc_info *next); +void descriptor_list_loopback(struct sg_dma_desc_info *desc); +void descriptor_list_end_of_chain(struct sg_dma_desc_info *desc); + +void *descriptor_list_allocate(struct sg_dma_desc_info *desc, size_t bytes); +void descriptor_list_free(struct sg_dma_desc_info *desc); + +void descriptor_list_interrupt_enable(struct sg_dma_desc_info *desc); +void descriptor_list_interrupt_disable(struct sg_dma_desc_info *desc); + +#endif diff --git a/drivers/media/pci/cobalt/cobalt-v4l2.c b/drivers/media/pci/cobalt/cobalt-v4l2.c new file mode 100644 index 000000000..51fd9576c --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-v4l2.c @@ -0,0 +1,1317 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * cobalt V4L2 API + * + * Derived from ivtv-ioctl.c and cx18-fileops.c + * + * Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "cobalt-alsa.h" +#include "cobalt-cpld.h" +#include "cobalt-driver.h" +#include "cobalt-v4l2.h" +#include "cobalt-irq.h" +#include "cobalt-omnitek.h" + +static const struct v4l2_dv_timings cea1080p60 = V4L2_DV_BT_CEA_1920X1080P60; + +/* vb2 DMA streaming ops */ + +static int cobalt_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct cobalt_stream *s = q->drv_priv; + unsigned size = s->stride * s->height; + + if (*num_buffers < 3) + *num_buffers = 3; + if (*num_buffers > NR_BUFS) + *num_buffers = NR_BUFS; + if (*num_planes) + return sizes[0] < size ? -EINVAL : 0; + *num_planes = 1; + sizes[0] = size; + return 0; +} + +static int cobalt_buf_init(struct vb2_buffer *vb) +{ + struct cobalt_stream *s = vb->vb2_queue->drv_priv; + struct cobalt *cobalt = s->cobalt; + const size_t max_pages_per_line = + (COBALT_MAX_WIDTH * COBALT_MAX_BPP) / PAGE_SIZE + 2; + const size_t bytes = + COBALT_MAX_HEIGHT * max_pages_per_line * 0x20; + const size_t audio_bytes = ((1920 * 4) / PAGE_SIZE + 1) * 0x20; + struct sg_dma_desc_info *desc = &s->dma_desc_info[vb->index]; + struct sg_table *sg_desc = vb2_dma_sg_plane_desc(vb, 0); + unsigned size; + int ret; + + size = s->stride * s->height; + if (vb2_plane_size(vb, 0) < size) { + cobalt_info("data will not fit into plane (%lu < %u)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + if (desc->virt == NULL) { + desc->dev = &cobalt->pci_dev->dev; + descriptor_list_allocate(desc, + s->is_audio ? audio_bytes : bytes); + if (desc->virt == NULL) + return -ENOMEM; + } + ret = descriptor_list_create(cobalt, sg_desc->sgl, + !s->is_output, sg_desc->nents, size, + s->width * s->bpp, s->stride, desc); + if (ret) + descriptor_list_free(desc); + return ret; +} + +static void cobalt_buf_cleanup(struct vb2_buffer *vb) +{ + struct cobalt_stream *s = vb->vb2_queue->drv_priv; + struct sg_dma_desc_info *desc = &s->dma_desc_info[vb->index]; + + descriptor_list_free(desc); +} + +static int cobalt_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cobalt_stream *s = vb->vb2_queue->drv_priv; + + vb2_set_plane_payload(vb, 0, s->stride * s->height); + vbuf->field = V4L2_FIELD_NONE; + return 0; +} + +static void chain_all_buffers(struct cobalt_stream *s) +{ + struct sg_dma_desc_info *desc[NR_BUFS]; + struct cobalt_buffer *cb; + int i = 0; + + list_for_each_entry(cb, &s->bufs, list) { + desc[i] = &s->dma_desc_info[cb->vb.vb2_buf.index]; + if (i > 0) + descriptor_list_chain(desc[i-1], desc[i]); + i++; + } +} + +static void cobalt_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vb2_queue *q = vb->vb2_queue; + struct cobalt_stream *s = q->drv_priv; + struct cobalt_buffer *cb = to_cobalt_buffer(vbuf); + struct sg_dma_desc_info *desc = &s->dma_desc_info[vb->index]; + unsigned long flags; + + /* Prepare new buffer */ + descriptor_list_loopback(desc); + descriptor_list_interrupt_disable(desc); + + spin_lock_irqsave(&s->irqlock, flags); + list_add_tail(&cb->list, &s->bufs); + chain_all_buffers(s); + spin_unlock_irqrestore(&s->irqlock, flags); +} + +static void cobalt_enable_output(struct cobalt_stream *s) +{ + struct cobalt *cobalt = s->cobalt; + struct v4l2_bt_timings *bt = &s->timings.bt; + struct m00514_syncgen_flow_evcnt_regmap __iomem *vo = + COBALT_TX_BASE(cobalt); + unsigned fmt = s->pixfmt != V4L2_PIX_FMT_BGR32 ? + M00514_CONTROL_BITMAP_FORMAT_16_BPP_MSK : 0; + struct v4l2_subdev_format sd_fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + u64 clk = bt->pixelclock; + + if (bt->flags & V4L2_DV_FL_REDUCED_FPS) + clk = div_u64(clk * 1000ULL, 1001); + if (!cobalt_cpld_set_freq(cobalt, clk)) { + cobalt_err("pixelclock out of range\n"); + return; + } + + sd_fmt.format.colorspace = s->colorspace; + sd_fmt.format.xfer_func = s->xfer_func; + sd_fmt.format.ycbcr_enc = s->ycbcr_enc; + sd_fmt.format.quantization = s->quantization; + sd_fmt.format.width = bt->width; + sd_fmt.format.height = bt->height; + + /* Set up FDMA packer */ + switch (s->pixfmt) { + case V4L2_PIX_FMT_YUYV: + sd_fmt.format.code = MEDIA_BUS_FMT_UYVY8_1X16; + break; + case V4L2_PIX_FMT_BGR32: + sd_fmt.format.code = MEDIA_BUS_FMT_RGB888_1X24; + break; + } + v4l2_subdev_call(s->sd, pad, set_fmt, NULL, &sd_fmt); + + iowrite32(0, &vo->control); + /* 1080p60 */ + iowrite32(bt->hsync, &vo->sync_generator_h_sync_length); + iowrite32(bt->hbackporch, &vo->sync_generator_h_backporch_length); + iowrite32(bt->width, &vo->sync_generator_h_active_length); + iowrite32(bt->hfrontporch, &vo->sync_generator_h_frontporch_length); + iowrite32(bt->vsync, &vo->sync_generator_v_sync_length); + iowrite32(bt->vbackporch, &vo->sync_generator_v_backporch_length); + iowrite32(bt->height, &vo->sync_generator_v_active_length); + iowrite32(bt->vfrontporch, &vo->sync_generator_v_frontporch_length); + iowrite32(0x9900c1, &vo->error_color); + + iowrite32(M00514_CONTROL_BITMAP_SYNC_GENERATOR_LOAD_PARAM_MSK | fmt, + &vo->control); + iowrite32(M00514_CONTROL_BITMAP_EVCNT_CLEAR_MSK | fmt, &vo->control); + iowrite32(M00514_CONTROL_BITMAP_SYNC_GENERATOR_ENABLE_MSK | + M00514_CONTROL_BITMAP_FLOW_CTRL_OUTPUT_ENABLE_MSK | + fmt, &vo->control); +} + +static void cobalt_enable_input(struct cobalt_stream *s) +{ + struct cobalt *cobalt = s->cobalt; + int ch = (int)s->video_channel; + struct m00235_fdma_packer_regmap __iomem *packer; + struct v4l2_subdev_format sd_fmt_yuyv = { + .pad = s->pad_source, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .format.code = MEDIA_BUS_FMT_YUYV8_1X16, + }; + struct v4l2_subdev_format sd_fmt_rgb = { + .pad = s->pad_source, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .format.code = MEDIA_BUS_FMT_RGB888_1X24, + }; + + cobalt_dbg(1, "video_channel %d (%s, %s)\n", + s->video_channel, + s->input == 0 ? "hdmi" : "generator", + "YUYV"); + + packer = COBALT_CVI_PACKER(cobalt, ch); + + /* Set up FDMA packer */ + switch (s->pixfmt) { + case V4L2_PIX_FMT_YUYV: + iowrite32(M00235_CONTROL_BITMAP_ENABLE_MSK | + (1 << M00235_CONTROL_BITMAP_PACK_FORMAT_OFST), + &packer->control); + v4l2_subdev_call(s->sd, pad, set_fmt, NULL, + &sd_fmt_yuyv); + break; + case V4L2_PIX_FMT_RGB24: + iowrite32(M00235_CONTROL_BITMAP_ENABLE_MSK | + (2 << M00235_CONTROL_BITMAP_PACK_FORMAT_OFST), + &packer->control); + v4l2_subdev_call(s->sd, pad, set_fmt, NULL, + &sd_fmt_rgb); + break; + case V4L2_PIX_FMT_BGR32: + iowrite32(M00235_CONTROL_BITMAP_ENABLE_MSK | + M00235_CONTROL_BITMAP_ENDIAN_FORMAT_MSK | + (3 << M00235_CONTROL_BITMAP_PACK_FORMAT_OFST), + &packer->control); + v4l2_subdev_call(s->sd, pad, set_fmt, NULL, + &sd_fmt_rgb); + break; + } +} + +static void cobalt_dma_start_streaming(struct cobalt_stream *s) +{ + struct cobalt *cobalt = s->cobalt; + int rx = s->video_channel; + struct m00460_evcnt_regmap __iomem *evcnt = + COBALT_CVI_EVCNT(cobalt, rx); + struct cobalt_buffer *cb; + unsigned long flags; + + spin_lock_irqsave(&s->irqlock, flags); + if (!s->is_output) { + iowrite32(M00460_CONTROL_BITMAP_CLEAR_MSK, &evcnt->control); + iowrite32(M00460_CONTROL_BITMAP_ENABLE_MSK, &evcnt->control); + } else { + struct m00514_syncgen_flow_evcnt_regmap __iomem *vo = + COBALT_TX_BASE(cobalt); + u32 ctrl = ioread32(&vo->control); + + ctrl &= ~(M00514_CONTROL_BITMAP_EVCNT_ENABLE_MSK | + M00514_CONTROL_BITMAP_EVCNT_CLEAR_MSK); + iowrite32(ctrl | M00514_CONTROL_BITMAP_EVCNT_CLEAR_MSK, + &vo->control); + iowrite32(ctrl | M00514_CONTROL_BITMAP_EVCNT_ENABLE_MSK, + &vo->control); + } + cb = list_first_entry(&s->bufs, struct cobalt_buffer, list); + omni_sg_dma_start(s, &s->dma_desc_info[cb->vb.vb2_buf.index]); + spin_unlock_irqrestore(&s->irqlock, flags); +} + +static int cobalt_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct cobalt_stream *s = q->drv_priv; + struct cobalt *cobalt = s->cobalt; + struct m00233_video_measure_regmap __iomem *vmr; + struct m00473_freewheel_regmap __iomem *fw; + struct m00479_clk_loss_detector_regmap __iomem *clkloss; + int rx = s->video_channel; + struct m00389_cvi_regmap __iomem *cvi = COBALT_CVI(cobalt, rx); + struct m00460_evcnt_regmap __iomem *evcnt = COBALT_CVI_EVCNT(cobalt, rx); + struct v4l2_bt_timings *bt = &s->timings.bt; + u64 tot_size; + u32 clk_freq; + + if (s->is_audio) + goto done; + if (s->is_output) { + s->unstable_frame = false; + cobalt_enable_output(s); + goto done; + } + + cobalt_enable_input(s); + + fw = COBALT_CVI_FREEWHEEL(cobalt, rx); + vmr = COBALT_CVI_VMR(cobalt, rx); + clkloss = COBALT_CVI_CLK_LOSS(cobalt, rx); + + iowrite32(M00460_CONTROL_BITMAP_CLEAR_MSK, &evcnt->control); + iowrite32(M00460_CONTROL_BITMAP_ENABLE_MSK, &evcnt->control); + iowrite32(bt->width, &cvi->frame_width); + iowrite32(bt->height, &cvi->frame_height); + tot_size = V4L2_DV_BT_FRAME_WIDTH(bt) * V4L2_DV_BT_FRAME_HEIGHT(bt); + iowrite32(div_u64((u64)V4L2_DV_BT_FRAME_WIDTH(bt) * COBALT_CLK * 4, + bt->pixelclock), &vmr->hsync_timeout_val); + iowrite32(M00233_CONTROL_BITMAP_ENABLE_MEASURE_MSK, &vmr->control); + clk_freq = ioread32(&fw->clk_freq); + iowrite32(clk_freq / 1000000, &clkloss->ref_clk_cnt_val); + /* The lower bound for the clock frequency is 0.5% lower as is + * allowed by the spec */ + iowrite32(div_u64(bt->pixelclock * 995, 1000000000), + &clkloss->test_clk_cnt_val); + /* will be enabled after the first frame has been received */ + iowrite32(bt->width * bt->height, &fw->active_length); + iowrite32(div_u64((u64)clk_freq * tot_size, bt->pixelclock), + &fw->total_length); + iowrite32(M00233_IRQ_TRIGGERS_BITMAP_VACTIVE_AREA_MSK | + M00233_IRQ_TRIGGERS_BITMAP_HACTIVE_AREA_MSK, + &vmr->irq_triggers); + iowrite32(0, &cvi->control); + iowrite32(M00233_CONTROL_BITMAP_ENABLE_MEASURE_MSK, &vmr->control); + + iowrite32(0xff, &fw->output_color); + iowrite32(M00479_CTRL_BITMAP_ENABLE_MSK, &clkloss->ctrl); + iowrite32(M00473_CTRL_BITMAP_ENABLE_MSK | + M00473_CTRL_BITMAP_FORCE_FREEWHEEL_MODE_MSK, &fw->ctrl); + s->unstable_frame = true; + s->enable_freewheel = false; + s->enable_cvi = false; + s->skip_first_frames = 0; + +done: + s->sequence = 0; + cobalt_dma_start_streaming(s); + return 0; +} + +static void cobalt_dma_stop_streaming(struct cobalt_stream *s) +{ + struct cobalt *cobalt = s->cobalt; + struct sg_dma_desc_info *desc; + struct cobalt_buffer *cb; + unsigned long flags; + int timeout_msec = 100; + int rx = s->video_channel; + struct m00460_evcnt_regmap __iomem *evcnt = + COBALT_CVI_EVCNT(cobalt, rx); + + if (!s->is_output) { + iowrite32(0, &evcnt->control); + } else if (!s->is_audio) { + struct m00514_syncgen_flow_evcnt_regmap __iomem *vo = + COBALT_TX_BASE(cobalt); + + iowrite32(M00514_CONTROL_BITMAP_EVCNT_CLEAR_MSK, &vo->control); + iowrite32(0, &vo->control); + } + + /* Try to stop the DMA engine gracefully */ + spin_lock_irqsave(&s->irqlock, flags); + list_for_each_entry(cb, &s->bufs, list) { + desc = &s->dma_desc_info[cb->vb.vb2_buf.index]; + /* Stop DMA after this descriptor chain */ + descriptor_list_end_of_chain(desc); + } + spin_unlock_irqrestore(&s->irqlock, flags); + + /* Wait 100 millisecond for DMA to finish, abort on timeout. */ + if (!wait_event_timeout(s->q.done_wq, is_dma_done(s), + msecs_to_jiffies(timeout_msec))) { + omni_sg_dma_abort_channel(s); + pr_warn("aborted\n"); + } + cobalt_write_bar0(cobalt, DMA_INTERRUPT_STATUS_REG, + 1 << s->dma_channel); +} + +static void cobalt_stop_streaming(struct vb2_queue *q) +{ + struct cobalt_stream *s = q->drv_priv; + struct cobalt *cobalt = s->cobalt; + int rx = s->video_channel; + struct m00233_video_measure_regmap __iomem *vmr; + struct m00473_freewheel_regmap __iomem *fw; + struct m00479_clk_loss_detector_regmap __iomem *clkloss; + struct cobalt_buffer *cb; + struct list_head *p, *safe; + unsigned long flags; + + cobalt_dma_stop_streaming(s); + + /* Return all buffers to user space */ + spin_lock_irqsave(&s->irqlock, flags); + list_for_each_safe(p, safe, &s->bufs) { + cb = list_entry(p, struct cobalt_buffer, list); + list_del(&cb->list); + vb2_buffer_done(&cb->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&s->irqlock, flags); + + if (s->is_audio || s->is_output) + return; + + fw = COBALT_CVI_FREEWHEEL(cobalt, rx); + vmr = COBALT_CVI_VMR(cobalt, rx); + clkloss = COBALT_CVI_CLK_LOSS(cobalt, rx); + iowrite32(0, &vmr->control); + iowrite32(M00233_CONTROL_BITMAP_ENABLE_MEASURE_MSK, &vmr->control); + iowrite32(0, &fw->ctrl); + iowrite32(0, &clkloss->ctrl); +} + +static const struct vb2_ops cobalt_qops = { + .queue_setup = cobalt_queue_setup, + .buf_init = cobalt_buf_init, + .buf_cleanup = cobalt_buf_cleanup, + .buf_prepare = cobalt_buf_prepare, + .buf_queue = cobalt_buf_queue, + .start_streaming = cobalt_start_streaming, + .stop_streaming = cobalt_stop_streaming, +}; + +/* V4L2 ioctls */ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int cobalt_cobaltc(struct cobalt *cobalt, unsigned int cmd, void *arg) +{ + struct v4l2_dbg_register *regs = arg; + void __iomem *adrs = cobalt->bar1 + regs->reg; + + cobalt_info("cobalt_cobaltc: adrs = %p\n", adrs); + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + regs->size = 4; + if (cmd == VIDIOC_DBG_S_REGISTER) + iowrite32(regs->val, adrs); + else + regs->val = ioread32(adrs); + return 0; +} + +static int cobalt_g_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct cobalt_stream *s = video_drvdata(file); + struct cobalt *cobalt = s->cobalt; + + return cobalt_cobaltc(cobalt, VIDIOC_DBG_G_REGISTER, reg); +} + +static int cobalt_s_register(struct file *file, void *priv, + const struct v4l2_dbg_register *reg) +{ + struct cobalt_stream *s = video_drvdata(file); + struct cobalt *cobalt = s->cobalt; + + return cobalt_cobaltc(cobalt, VIDIOC_DBG_S_REGISTER, + (struct v4l2_dbg_register *)reg); +} +#endif + +static int cobalt_querycap(struct file *file, void *priv, + struct v4l2_capability *vcap) +{ + struct cobalt_stream *s = video_drvdata(file); + struct cobalt *cobalt = s->cobalt; + + strscpy(vcap->driver, "cobalt", sizeof(vcap->driver)); + strscpy(vcap->card, "cobalt", sizeof(vcap->card)); + snprintf(vcap->bus_info, sizeof(vcap->bus_info), + "PCIe:%s", pci_name(cobalt->pci_dev)); + vcap->capabilities = V4L2_CAP_STREAMING | V4L2_CAP_READWRITE | + V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_DEVICE_CAPS; + if (cobalt->have_hsma_tx) + vcap->capabilities |= V4L2_CAP_VIDEO_OUTPUT; + return 0; +} + +static void cobalt_video_input_status_show(struct cobalt_stream *s) +{ + struct m00389_cvi_regmap __iomem *cvi; + struct m00233_video_measure_regmap __iomem *vmr; + struct m00473_freewheel_regmap __iomem *fw; + struct m00479_clk_loss_detector_regmap __iomem *clkloss; + struct m00235_fdma_packer_regmap __iomem *packer; + int rx = s->video_channel; + struct cobalt *cobalt = s->cobalt; + u32 cvi_ctrl, cvi_stat; + u32 vmr_ctrl, vmr_stat; + + cvi = COBALT_CVI(cobalt, rx); + vmr = COBALT_CVI_VMR(cobalt, rx); + fw = COBALT_CVI_FREEWHEEL(cobalt, rx); + clkloss = COBALT_CVI_CLK_LOSS(cobalt, rx); + packer = COBALT_CVI_PACKER(cobalt, rx); + cvi_ctrl = ioread32(&cvi->control); + cvi_stat = ioread32(&cvi->status); + vmr_ctrl = ioread32(&vmr->control); + vmr_stat = ioread32(&vmr->status); + cobalt_info("rx%d: cvi resolution: %dx%d\n", rx, + ioread32(&cvi->frame_width), ioread32(&cvi->frame_height)); + cobalt_info("rx%d: cvi control: %s%s%s\n", rx, + (cvi_ctrl & M00389_CONTROL_BITMAP_ENABLE_MSK) ? + "enable " : "disable ", + (cvi_ctrl & M00389_CONTROL_BITMAP_HSYNC_POLARITY_LOW_MSK) ? + "HSync- " : "HSync+ ", + (cvi_ctrl & M00389_CONTROL_BITMAP_VSYNC_POLARITY_LOW_MSK) ? + "VSync- " : "VSync+ "); + cobalt_info("rx%d: cvi status: %s%s\n", rx, + (cvi_stat & M00389_STATUS_BITMAP_LOCK_MSK) ? + "lock " : "no-lock ", + (cvi_stat & M00389_STATUS_BITMAP_ERROR_MSK) ? + "error " : "no-error "); + + cobalt_info("rx%d: Measurements: %s%s%s%s%s%s%s\n", rx, + (vmr_ctrl & M00233_CONTROL_BITMAP_HSYNC_POLARITY_LOW_MSK) ? + "HSync- " : "HSync+ ", + (vmr_ctrl & M00233_CONTROL_BITMAP_VSYNC_POLARITY_LOW_MSK) ? + "VSync- " : "VSync+ ", + (vmr_ctrl & M00233_CONTROL_BITMAP_ENABLE_MEASURE_MSK) ? + "enabled " : "disabled ", + (vmr_ctrl & M00233_CONTROL_BITMAP_ENABLE_INTERRUPT_MSK) ? + "irq-enabled " : "irq-disabled ", + (vmr_ctrl & M00233_CONTROL_BITMAP_UPDATE_ON_HSYNC_MSK) ? + "update-on-hsync " : "", + (vmr_stat & M00233_STATUS_BITMAP_HSYNC_TIMEOUT_MSK) ? + "hsync-timeout " : "", + (vmr_stat & M00233_STATUS_BITMAP_INIT_DONE_MSK) ? + "init-done" : ""); + cobalt_info("rx%d: irq_status: 0x%02x irq_triggers: 0x%02x\n", rx, + ioread32(&vmr->irq_status) & 0xff, + ioread32(&vmr->irq_triggers) & 0xff); + cobalt_info("rx%d: vsync: %d\n", rx, ioread32(&vmr->vsync_time)); + cobalt_info("rx%d: vbp: %d\n", rx, ioread32(&vmr->vback_porch)); + cobalt_info("rx%d: vact: %d\n", rx, ioread32(&vmr->vactive_area)); + cobalt_info("rx%d: vfb: %d\n", rx, ioread32(&vmr->vfront_porch)); + cobalt_info("rx%d: hsync: %d\n", rx, ioread32(&vmr->hsync_time)); + cobalt_info("rx%d: hbp: %d\n", rx, ioread32(&vmr->hback_porch)); + cobalt_info("rx%d: hact: %d\n", rx, ioread32(&vmr->hactive_area)); + cobalt_info("rx%d: hfb: %d\n", rx, ioread32(&vmr->hfront_porch)); + cobalt_info("rx%d: Freewheeling: %s%s%s\n", rx, + (ioread32(&fw->ctrl) & M00473_CTRL_BITMAP_ENABLE_MSK) ? + "enabled " : "disabled ", + (ioread32(&fw->ctrl) & M00473_CTRL_BITMAP_FORCE_FREEWHEEL_MODE_MSK) ? + "forced " : "", + (ioread32(&fw->status) & M00473_STATUS_BITMAP_FREEWHEEL_MODE_MSK) ? + "freewheeling " : "video-passthrough "); + iowrite32(0xff, &vmr->irq_status); + cobalt_info("rx%d: Clock Loss Detection: %s%s\n", rx, + (ioread32(&clkloss->ctrl) & M00479_CTRL_BITMAP_ENABLE_MSK) ? + "enabled " : "disabled ", + (ioread32(&clkloss->status) & M00479_STATUS_BITMAP_CLOCK_MISSING_MSK) ? + "clock-missing " : "found-clock "); + cobalt_info("rx%d: Packer: %x\n", rx, ioread32(&packer->control)); +} + +static int cobalt_log_status(struct file *file, void *priv) +{ + struct cobalt_stream *s = video_drvdata(file); + struct cobalt *cobalt = s->cobalt; + struct m00514_syncgen_flow_evcnt_regmap __iomem *vo = + COBALT_TX_BASE(cobalt); + u8 stat; + + cobalt_info("%s", cobalt->hdl_info); + cobalt_info("sysctrl: %08x, sysstat: %08x\n", + cobalt_g_sysctrl(cobalt), + cobalt_g_sysstat(cobalt)); + cobalt_info("dma channel: %d, video channel: %d\n", + s->dma_channel, s->video_channel); + cobalt_pcie_status_show(cobalt); + cobalt_cpld_status(cobalt); + cobalt_irq_log_status(cobalt); + v4l2_subdev_call(s->sd, core, log_status); + if (!s->is_output) { + cobalt_video_input_status_show(s); + return 0; + } + + stat = ioread32(&vo->rd_status); + + cobalt_info("tx: status: %s%s\n", + (stat & M00514_RD_STATUS_BITMAP_FLOW_CTRL_NO_DATA_ERROR_MSK) ? + "no_data " : "", + (stat & M00514_RD_STATUS_BITMAP_READY_BUFFER_FULL_MSK) ? + "ready_buffer_full " : ""); + cobalt_info("tx: evcnt: %d\n", ioread32(&vo->rd_evcnt_count)); + return 0; +} + +static int cobalt_enum_dv_timings(struct file *file, void *priv, + struct v4l2_enum_dv_timings *timings) +{ + struct cobalt_stream *s = video_drvdata(file); + + if (s->input == 1) { + if (timings->index) + return -EINVAL; + memset(timings->reserved, 0, sizeof(timings->reserved)); + timings->timings = cea1080p60; + return 0; + } + timings->pad = 0; + return v4l2_subdev_call(s->sd, + pad, enum_dv_timings, timings); +} + +static int cobalt_s_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct cobalt_stream *s = video_drvdata(file); + int err; + + if (s->input == 1) { + *timings = cea1080p60; + return 0; + } + + if (v4l2_match_dv_timings(timings, &s->timings, 0, true)) + return 0; + + if (vb2_is_busy(&s->q)) + return -EBUSY; + + err = v4l2_subdev_call(s->sd, + pad, s_dv_timings, 0, timings); + if (!err) { + s->timings = *timings; + s->width = timings->bt.width; + s->height = timings->bt.height; + s->stride = timings->bt.width * s->bpp; + } + return err; +} + +static int cobalt_g_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct cobalt_stream *s = video_drvdata(file); + + if (s->input == 1) { + *timings = cea1080p60; + return 0; + } + return v4l2_subdev_call(s->sd, + pad, g_dv_timings, 0, timings); +} + +static int cobalt_query_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct cobalt_stream *s = video_drvdata(file); + + if (s->input == 1) { + *timings = cea1080p60; + return 0; + } + return v4l2_subdev_call(s->sd, + pad, query_dv_timings, 0, timings); +} + +static int cobalt_dv_timings_cap(struct file *file, void *priv, + struct v4l2_dv_timings_cap *cap) +{ + struct cobalt_stream *s = video_drvdata(file); + + cap->pad = 0; + return v4l2_subdev_call(s->sd, + pad, dv_timings_cap, cap); +} + +static int cobalt_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + switch (f->index) { + case 0: + f->pixelformat = V4L2_PIX_FMT_YUYV; + break; + case 1: + f->pixelformat = V4L2_PIX_FMT_RGB24; + break; + case 2: + f->pixelformat = V4L2_PIX_FMT_BGR32; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cobalt_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cobalt_stream *s = video_drvdata(file); + struct v4l2_pix_format *pix = &f->fmt.pix; + + pix->width = s->width; + pix->height = s->height; + pix->bytesperline = s->stride; + pix->field = V4L2_FIELD_NONE; + + if (s->input == 1) { + pix->colorspace = V4L2_COLORSPACE_SRGB; + } else { + struct v4l2_subdev_format sd_fmt = { + .pad = s->pad_source, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + v4l2_subdev_call(s->sd, pad, get_fmt, NULL, &sd_fmt); + v4l2_fill_pix_format(pix, &sd_fmt.format); + } + + pix->pixelformat = s->pixfmt; + pix->sizeimage = pix->bytesperline * pix->height; + + return 0; +} + +static int cobalt_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cobalt_stream *s = video_drvdata(file); + struct v4l2_pix_format *pix = &f->fmt.pix; + + /* Check for min (QCIF) and max (Full HD) size */ + if ((pix->width < 176) || (pix->height < 144)) { + pix->width = 176; + pix->height = 144; + } + + if ((pix->width > 1920) || (pix->height > 1080)) { + pix->width = 1920; + pix->height = 1080; + } + + /* Make width multiple of 4 */ + pix->width &= ~0x3; + + /* Make height multiple of 2 */ + pix->height &= ~0x1; + + if (s->input == 1) { + /* Generator => fixed format only */ + pix->width = 1920; + pix->height = 1080; + pix->colorspace = V4L2_COLORSPACE_SRGB; + } else { + struct v4l2_subdev_format sd_fmt = { + .pad = s->pad_source, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + v4l2_subdev_call(s->sd, pad, get_fmt, NULL, &sd_fmt); + v4l2_fill_pix_format(pix, &sd_fmt.format); + } + + switch (pix->pixelformat) { + case V4L2_PIX_FMT_YUYV: + default: + pix->bytesperline = max(pix->bytesperline & ~0x3, + pix->width * COBALT_BYTES_PER_PIXEL_YUYV); + pix->pixelformat = V4L2_PIX_FMT_YUYV; + break; + case V4L2_PIX_FMT_RGB24: + pix->bytesperline = max(pix->bytesperline & ~0x3, + pix->width * COBALT_BYTES_PER_PIXEL_RGB24); + break; + case V4L2_PIX_FMT_BGR32: + pix->bytesperline = max(pix->bytesperline & ~0x3, + pix->width * COBALT_BYTES_PER_PIXEL_RGB32); + break; + } + + pix->sizeimage = pix->bytesperline * pix->height; + pix->field = V4L2_FIELD_NONE; + + return 0; +} + +static int cobalt_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cobalt_stream *s = video_drvdata(file); + struct v4l2_pix_format *pix = &f->fmt.pix; + + if (vb2_is_busy(&s->q)) + return -EBUSY; + + if (cobalt_try_fmt_vid_cap(file, priv, f)) + return -EINVAL; + + s->width = pix->width; + s->height = pix->height; + s->stride = pix->bytesperline; + switch (pix->pixelformat) { + case V4L2_PIX_FMT_YUYV: + s->bpp = COBALT_BYTES_PER_PIXEL_YUYV; + break; + case V4L2_PIX_FMT_RGB24: + s->bpp = COBALT_BYTES_PER_PIXEL_RGB24; + break; + case V4L2_PIX_FMT_BGR32: + s->bpp = COBALT_BYTES_PER_PIXEL_RGB32; + break; + default: + return -EINVAL; + } + s->pixfmt = pix->pixelformat; + cobalt_enable_input(s); + + return 0; +} + +static int cobalt_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct v4l2_pix_format *pix = &f->fmt.pix; + + /* Check for min (QCIF) and max (Full HD) size */ + if ((pix->width < 176) || (pix->height < 144)) { + pix->width = 176; + pix->height = 144; + } + + if ((pix->width > 1920) || (pix->height > 1080)) { + pix->width = 1920; + pix->height = 1080; + } + + /* Make width multiple of 4 */ + pix->width &= ~0x3; + + /* Make height multiple of 2 */ + pix->height &= ~0x1; + + switch (pix->pixelformat) { + case V4L2_PIX_FMT_YUYV: + default: + pix->bytesperline = max(pix->bytesperline & ~0x3, + pix->width * COBALT_BYTES_PER_PIXEL_YUYV); + pix->pixelformat = V4L2_PIX_FMT_YUYV; + break; + case V4L2_PIX_FMT_BGR32: + pix->bytesperline = max(pix->bytesperline & ~0x3, + pix->width * COBALT_BYTES_PER_PIXEL_RGB32); + break; + } + + pix->sizeimage = pix->bytesperline * pix->height; + pix->field = V4L2_FIELD_NONE; + + return 0; +} + +static int cobalt_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cobalt_stream *s = video_drvdata(file); + struct v4l2_pix_format *pix = &f->fmt.pix; + + pix->width = s->width; + pix->height = s->height; + pix->bytesperline = s->stride; + pix->field = V4L2_FIELD_NONE; + pix->pixelformat = s->pixfmt; + pix->colorspace = s->colorspace; + pix->xfer_func = s->xfer_func; + pix->ycbcr_enc = s->ycbcr_enc; + pix->quantization = s->quantization; + pix->sizeimage = pix->bytesperline * pix->height; + + return 0; +} + +static int cobalt_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + switch (f->index) { + case 0: + f->pixelformat = V4L2_PIX_FMT_YUYV; + break; + case 1: + f->pixelformat = V4L2_PIX_FMT_BGR32; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cobalt_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cobalt_stream *s = video_drvdata(file); + struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_subdev_format sd_fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + u32 code; + + if (cobalt_try_fmt_vid_out(file, priv, f)) + return -EINVAL; + + if (vb2_is_busy(&s->q) && (pix->pixelformat != s->pixfmt || + pix->width != s->width || pix->height != s->height || + pix->bytesperline != s->stride)) + return -EBUSY; + + switch (pix->pixelformat) { + case V4L2_PIX_FMT_YUYV: + s->bpp = COBALT_BYTES_PER_PIXEL_YUYV; + code = MEDIA_BUS_FMT_UYVY8_1X16; + break; + case V4L2_PIX_FMT_BGR32: + s->bpp = COBALT_BYTES_PER_PIXEL_RGB32; + code = MEDIA_BUS_FMT_RGB888_1X24; + break; + default: + return -EINVAL; + } + s->width = pix->width; + s->height = pix->height; + s->stride = pix->bytesperline; + s->pixfmt = pix->pixelformat; + s->colorspace = pix->colorspace; + s->xfer_func = pix->xfer_func; + s->ycbcr_enc = pix->ycbcr_enc; + s->quantization = pix->quantization; + v4l2_fill_mbus_format(&sd_fmt.format, pix, code); + v4l2_subdev_call(s->sd, pad, set_fmt, NULL, &sd_fmt); + return 0; +} + +static int cobalt_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + struct cobalt_stream *s = video_drvdata(file); + + if (inp->index > 1) + return -EINVAL; + if (inp->index == 0) + snprintf(inp->name, sizeof(inp->name), + "HDMI-%d", s->video_channel); + else + snprintf(inp->name, sizeof(inp->name), + "Generator-%d", s->video_channel); + inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->capabilities = V4L2_IN_CAP_DV_TIMINGS; + if (inp->index == 1) + return 0; + return v4l2_subdev_call(s->sd, + video, g_input_status, &inp->status); +} + +static int cobalt_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct cobalt_stream *s = video_drvdata(file); + + *i = s->input; + return 0; +} + +static int cobalt_s_input(struct file *file, void *priv, unsigned int i) +{ + struct cobalt_stream *s = video_drvdata(file); + + if (i >= 2) + return -EINVAL; + if (vb2_is_busy(&s->q)) + return -EBUSY; + s->input = i; + + cobalt_enable_input(s); + + if (s->input == 1) /* Test Pattern Generator */ + return 0; + + return v4l2_subdev_call(s->sd, video, s_routing, + ADV76XX_PAD_HDMI_PORT_A, 0, 0); +} + +static int cobalt_enum_output(struct file *file, void *priv, + struct v4l2_output *out) +{ + if (out->index) + return -EINVAL; + snprintf(out->name, sizeof(out->name), "HDMI-%d", out->index); + out->type = V4L2_OUTPUT_TYPE_ANALOG; + out->capabilities = V4L2_OUT_CAP_DV_TIMINGS; + return 0; +} + +static int cobalt_g_output(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int cobalt_s_output(struct file *file, void *priv, unsigned int i) +{ + return i ? -EINVAL : 0; +} + +static int cobalt_g_edid(struct file *file, void *priv, struct v4l2_edid *edid) +{ + struct cobalt_stream *s = video_drvdata(file); + u32 pad = edid->pad; + int ret; + + if (edid->pad >= (s->is_output ? 1 : 2)) + return -EINVAL; + edid->pad = 0; + ret = v4l2_subdev_call(s->sd, pad, get_edid, edid); + edid->pad = pad; + return ret; +} + +static int cobalt_s_edid(struct file *file, void *priv, struct v4l2_edid *edid) +{ + struct cobalt_stream *s = video_drvdata(file); + u32 pad = edid->pad; + int ret; + + if (edid->pad >= 2) + return -EINVAL; + edid->pad = 0; + ret = v4l2_subdev_call(s->sd, pad, set_edid, edid); + edid->pad = pad; + return ret; +} + +static int cobalt_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_event_subscribe(fh, sub, 4, NULL); + } + return v4l2_ctrl_subscribe_event(fh, sub); +} + +static int cobalt_g_parm(struct file *file, void *priv, struct v4l2_streamparm *a) +{ + struct cobalt_stream *s = video_drvdata(file); + struct v4l2_fract fps; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + fps = v4l2_calc_timeperframe(&s->timings); + a->parm.capture.timeperframe.numerator = fps.numerator; + a->parm.capture.timeperframe.denominator = fps.denominator; + a->parm.capture.readbuffers = 3; + return 0; +} + +static int cobalt_g_pixelaspect(struct file *file, void *priv, + int type, struct v4l2_fract *f) +{ + struct cobalt_stream *s = video_drvdata(file); + struct v4l2_dv_timings timings; + int err = 0; + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (s->input == 1) + timings = cea1080p60; + else + err = v4l2_subdev_call(s->sd, pad, g_dv_timings, 0, &timings); + if (!err) + *f = v4l2_dv_timings_aspect_ratio(&timings); + return err; +} + +static int cobalt_g_selection(struct file *file, void *priv, + struct v4l2_selection *sel) +{ + struct cobalt_stream *s = video_drvdata(file); + struct v4l2_dv_timings timings; + int err = 0; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (s->input == 1) + timings = cea1080p60; + else + err = v4l2_subdev_call(s->sd, pad, g_dv_timings, 0, &timings); + + if (err) + return err; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = timings.bt.width; + sel->r.height = timings.bt.height; + break; + default: + return -EINVAL; + } + return 0; +} + +static const struct v4l2_ioctl_ops cobalt_ioctl_ops = { + .vidioc_querycap = cobalt_querycap, + .vidioc_g_parm = cobalt_g_parm, + .vidioc_log_status = cobalt_log_status, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_g_pixelaspect = cobalt_g_pixelaspect, + .vidioc_g_selection = cobalt_g_selection, + .vidioc_enum_input = cobalt_enum_input, + .vidioc_g_input = cobalt_g_input, + .vidioc_s_input = cobalt_s_input, + .vidioc_enum_fmt_vid_cap = cobalt_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = cobalt_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = cobalt_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = cobalt_try_fmt_vid_cap, + .vidioc_enum_output = cobalt_enum_output, + .vidioc_g_output = cobalt_g_output, + .vidioc_s_output = cobalt_s_output, + .vidioc_enum_fmt_vid_out = cobalt_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = cobalt_g_fmt_vid_out, + .vidioc_s_fmt_vid_out = cobalt_s_fmt_vid_out, + .vidioc_try_fmt_vid_out = cobalt_try_fmt_vid_out, + .vidioc_s_dv_timings = cobalt_s_dv_timings, + .vidioc_g_dv_timings = cobalt_g_dv_timings, + .vidioc_query_dv_timings = cobalt_query_dv_timings, + .vidioc_enum_dv_timings = cobalt_enum_dv_timings, + .vidioc_dv_timings_cap = cobalt_dv_timings_cap, + .vidioc_g_edid = cobalt_g_edid, + .vidioc_s_edid = cobalt_s_edid, + .vidioc_subscribe_event = cobalt_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = cobalt_g_register, + .vidioc_s_register = cobalt_s_register, +#endif +}; + +static const struct v4l2_ioctl_ops cobalt_ioctl_empty_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = cobalt_g_register, + .vidioc_s_register = cobalt_s_register, +#endif +}; + +/* Register device nodes */ + +static const struct v4l2_file_operations cobalt_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .unlocked_ioctl = video_ioctl2, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .read = vb2_fop_read, +}; + +static const struct v4l2_file_operations cobalt_out_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .unlocked_ioctl = video_ioctl2, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .write = vb2_fop_write, +}; + +static const struct v4l2_file_operations cobalt_empty_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .unlocked_ioctl = video_ioctl2, + .release = v4l2_fh_release, +}; + +static int cobalt_node_register(struct cobalt *cobalt, int node) +{ + static const struct v4l2_dv_timings dv1080p60 = + V4L2_DV_BT_CEA_1920X1080P60; + struct cobalt_stream *s = cobalt->streams + node; + struct video_device *vdev = &s->vdev; + struct vb2_queue *q = &s->q; + int ret; + + mutex_init(&s->lock); + spin_lock_init(&s->irqlock); + + snprintf(vdev->name, sizeof(vdev->name), + "%s-%d", cobalt->v4l2_dev.name, node); + s->width = 1920; + /* Audio frames are just 4 lines of 1920 bytes */ + s->height = s->is_audio ? 4 : 1080; + + if (s->is_audio) { + s->bpp = 1; + s->pixfmt = V4L2_PIX_FMT_GREY; + } else if (s->is_output) { + s->bpp = COBALT_BYTES_PER_PIXEL_RGB32; + s->pixfmt = V4L2_PIX_FMT_BGR32; + } else { + s->bpp = COBALT_BYTES_PER_PIXEL_YUYV; + s->pixfmt = V4L2_PIX_FMT_YUYV; + } + s->colorspace = V4L2_COLORSPACE_SRGB; + s->stride = s->width * s->bpp; + + if (!s->is_audio) { + if (s->is_dummy) + cobalt_warn("Setting up dummy video node %d\n", node); + vdev->v4l2_dev = &cobalt->v4l2_dev; + if (s->is_dummy) + vdev->fops = &cobalt_empty_fops; + else + vdev->fops = s->is_output ? &cobalt_out_fops : + &cobalt_fops; + vdev->release = video_device_release_empty; + vdev->vfl_dir = s->is_output ? VFL_DIR_TX : VFL_DIR_RX; + vdev->lock = &s->lock; + if (s->sd) + vdev->ctrl_handler = s->sd->ctrl_handler; + s->timings = dv1080p60; + v4l2_subdev_call(s->sd, pad, s_dv_timings, 0, &s->timings); + if (!s->is_output && s->sd) + cobalt_enable_input(s); + vdev->ioctl_ops = s->is_dummy ? &cobalt_ioctl_empty_ops : + &cobalt_ioctl_ops; + } + + INIT_LIST_HEAD(&s->bufs); + q->type = s->is_output ? V4L2_BUF_TYPE_VIDEO_OUTPUT : + V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + q->io_modes |= s->is_output ? VB2_WRITE : VB2_READ; + q->drv_priv = s; + q->buf_struct_size = sizeof(struct cobalt_buffer); + q->ops = &cobalt_qops; + q->mem_ops = &vb2_dma_sg_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->min_queued_buffers = 2; + q->lock = &s->lock; + q->dev = &cobalt->pci_dev->dev; + vdev->queue = q; + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; + if (s->is_output) + vdev->device_caps |= V4L2_CAP_VIDEO_OUTPUT; + else + vdev->device_caps |= V4L2_CAP_VIDEO_CAPTURE; + + video_set_drvdata(vdev, s); + ret = vb2_queue_init(q); + if (!s->is_audio && ret == 0) + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + else if (!s->is_dummy) + ret = cobalt_alsa_init(s); + + if (ret < 0) { + if (!s->is_audio) + cobalt_err("couldn't register v4l2 device node %d\n", + node); + return ret; + } + cobalt_info("registered node %d\n", node); + return 0; +} + +/* Initialize v4l2 variables and register v4l2 devices */ +int cobalt_nodes_register(struct cobalt *cobalt) +{ + int node, ret; + + /* Setup V4L2 Devices */ + for (node = 0; node < COBALT_NUM_STREAMS; node++) { + ret = cobalt_node_register(cobalt, node); + if (ret) + return ret; + } + return 0; +} + +/* Unregister v4l2 devices */ +void cobalt_nodes_unregister(struct cobalt *cobalt) +{ + int node; + + /* Teardown all streams */ + for (node = 0; node < COBALT_NUM_STREAMS; node++) { + struct cobalt_stream *s = cobalt->streams + node; + struct video_device *vdev = &s->vdev; + + if (!s->is_audio) + video_unregister_device(vdev); + else if (!s->is_dummy) + cobalt_alsa_exit(s); + } +} diff --git a/drivers/media/pci/cobalt/cobalt-v4l2.h b/drivers/media/pci/cobalt/cobalt-v4l2.h new file mode 100644 index 000000000..dc43974b2 --- /dev/null +++ b/drivers/media/pci/cobalt/cobalt-v4l2.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * cobalt V4L2 API + * + * Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +int cobalt_nodes_register(struct cobalt *cobalt); +void cobalt_nodes_unregister(struct cobalt *cobalt); diff --git a/drivers/media/pci/cobalt/m00233_video_measure_memmap_package.h b/drivers/media/pci/cobalt/m00233_video_measure_memmap_package.h new file mode 100644 index 000000000..4c6ad1cee --- /dev/null +++ b/drivers/media/pci/cobalt/m00233_video_measure_memmap_package.h @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#ifndef M00233_VIDEO_MEASURE_MEMMAP_PACKAGE_H +#define M00233_VIDEO_MEASURE_MEMMAP_PACKAGE_H + +/******************************************************************* + * Register Block + * M00233_VIDEO_MEASURE_MEMMAP_PACKAGE_VHD_REGMAP + *******************************************************************/ +struct m00233_video_measure_regmap { + uint32_t irq_status; /* Reg 0x0000 */ + /* The vertical counter starts on rising edge of vsync */ + uint32_t vsync_time; /* Reg 0x0004 */ + uint32_t vback_porch; /* Reg 0x0008 */ + uint32_t vactive_area; /* Reg 0x000c */ + uint32_t vfront_porch; /* Reg 0x0010 */ + /* The horizontal counter starts on rising edge of hsync. */ + uint32_t hsync_time; /* Reg 0x0014 */ + uint32_t hback_porch; /* Reg 0x0018 */ + uint32_t hactive_area; /* Reg 0x001c */ + uint32_t hfront_porch; /* Reg 0x0020 */ + uint32_t control; /* Reg 0x0024, Default=0x0 */ + uint32_t irq_triggers; /* Reg 0x0028, Default=0xff */ + /* Value is given in number of register bus clock periods between */ + /* falling and rising edge of hsync. Must be non-zero. */ + uint32_t hsync_timeout_val; /* Reg 0x002c, Default=0x1fff */ + uint32_t status; /* Reg 0x0030 */ +}; + +#define M00233_VIDEO_MEASURE_REG_IRQ_STATUS_OFST 0 +#define M00233_VIDEO_MEASURE_REG_VSYNC_TIME_OFST 4 +#define M00233_VIDEO_MEASURE_REG_VBACK_PORCH_OFST 8 +#define M00233_VIDEO_MEASURE_REG_VACTIVE_AREA_OFST 12 +#define M00233_VIDEO_MEASURE_REG_VFRONT_PORCH_OFST 16 +#define M00233_VIDEO_MEASURE_REG_HSYNC_TIME_OFST 20 +#define M00233_VIDEO_MEASURE_REG_HBACK_PORCH_OFST 24 +#define M00233_VIDEO_MEASURE_REG_HACTIVE_AREA_OFST 28 +#define M00233_VIDEO_MEASURE_REG_HFRONT_PORCH_OFST 32 +#define M00233_VIDEO_MEASURE_REG_CONTROL_OFST 36 +#define M00233_VIDEO_MEASURE_REG_IRQ_TRIGGERS_OFST 40 +#define M00233_VIDEO_MEASURE_REG_HSYNC_TIMEOUT_VAL_OFST 44 +#define M00233_VIDEO_MEASURE_REG_STATUS_OFST 48 + +/******************************************************************* + * Bit Mask for register + * M00233_VIDEO_MEASURE_MEMMAP_PACKAGE_VHD_BITMAP + *******************************************************************/ +/* irq_status [7:0] */ +#define M00233_IRQ_STATUS_BITMAP_VSYNC_TIME_OFST (0) +#define M00233_IRQ_STATUS_BITMAP_VSYNC_TIME_MSK (0x1 << M00233_IRQ_STATUS_BITMAP_VSYNC_TIME_OFST) +#define M00233_IRQ_STATUS_BITMAP_VBACK_PORCH_OFST (1) +#define M00233_IRQ_STATUS_BITMAP_VBACK_PORCH_MSK (0x1 << M00233_IRQ_STATUS_BITMAP_VBACK_PORCH_OFST) +#define M00233_IRQ_STATUS_BITMAP_VACTIVE_AREA_OFST (2) +#define M00233_IRQ_STATUS_BITMAP_VACTIVE_AREA_MSK (0x1 << M00233_IRQ_STATUS_BITMAP_VACTIVE_AREA_OFST) +#define M00233_IRQ_STATUS_BITMAP_VFRONT_PORCH_OFST (3) +#define M00233_IRQ_STATUS_BITMAP_VFRONT_PORCH_MSK (0x1 << M00233_IRQ_STATUS_BITMAP_VFRONT_PORCH_OFST) +#define M00233_IRQ_STATUS_BITMAP_HSYNC_TIME_OFST (4) +#define M00233_IRQ_STATUS_BITMAP_HSYNC_TIME_MSK (0x1 << M00233_IRQ_STATUS_BITMAP_HSYNC_TIME_OFST) +#define M00233_IRQ_STATUS_BITMAP_HBACK_PORCH_OFST (5) +#define M00233_IRQ_STATUS_BITMAP_HBACK_PORCH_MSK (0x1 << M00233_IRQ_STATUS_BITMAP_HBACK_PORCH_OFST) +#define M00233_IRQ_STATUS_BITMAP_HACTIVE_AREA_OFST (6) +#define M00233_IRQ_STATUS_BITMAP_HACTIVE_AREA_MSK (0x1 << M00233_IRQ_STATUS_BITMAP_HACTIVE_AREA_OFST) +#define M00233_IRQ_STATUS_BITMAP_HFRONT_PORCH_OFST (7) +#define M00233_IRQ_STATUS_BITMAP_HFRONT_PORCH_MSK (0x1 << M00233_IRQ_STATUS_BITMAP_HFRONT_PORCH_OFST) +/* control [4:0] */ +#define M00233_CONTROL_BITMAP_HSYNC_POLARITY_LOW_OFST (0) +#define M00233_CONTROL_BITMAP_HSYNC_POLARITY_LOW_MSK (0x1 << M00233_CONTROL_BITMAP_HSYNC_POLARITY_LOW_OFST) +#define M00233_CONTROL_BITMAP_VSYNC_POLARITY_LOW_OFST (1) +#define M00233_CONTROL_BITMAP_VSYNC_POLARITY_LOW_MSK (0x1 << M00233_CONTROL_BITMAP_VSYNC_POLARITY_LOW_OFST) +#define M00233_CONTROL_BITMAP_ENABLE_MEASURE_OFST (2) +#define M00233_CONTROL_BITMAP_ENABLE_MEASURE_MSK (0x1 << M00233_CONTROL_BITMAP_ENABLE_MEASURE_OFST) +#define M00233_CONTROL_BITMAP_ENABLE_INTERRUPT_OFST (3) +#define M00233_CONTROL_BITMAP_ENABLE_INTERRUPT_MSK (0x1 << M00233_CONTROL_BITMAP_ENABLE_INTERRUPT_OFST) +#define M00233_CONTROL_BITMAP_UPDATE_ON_HSYNC_OFST (4) +#define M00233_CONTROL_BITMAP_UPDATE_ON_HSYNC_MSK (0x1 << M00233_CONTROL_BITMAP_UPDATE_ON_HSYNC_OFST) +/* irq_triggers [7:0] */ +#define M00233_IRQ_TRIGGERS_BITMAP_VSYNC_TIME_OFST (0) +#define M00233_IRQ_TRIGGERS_BITMAP_VSYNC_TIME_MSK (0x1 << M00233_IRQ_TRIGGERS_BITMAP_VSYNC_TIME_OFST) +#define M00233_IRQ_TRIGGERS_BITMAP_VBACK_PORCH_OFST (1) +#define M00233_IRQ_TRIGGERS_BITMAP_VBACK_PORCH_MSK (0x1 << M00233_IRQ_TRIGGERS_BITMAP_VBACK_PORCH_OFST) +#define M00233_IRQ_TRIGGERS_BITMAP_VACTIVE_AREA_OFST (2) +#define M00233_IRQ_TRIGGERS_BITMAP_VACTIVE_AREA_MSK (0x1 << M00233_IRQ_TRIGGERS_BITMAP_VACTIVE_AREA_OFST) +#define M00233_IRQ_TRIGGERS_BITMAP_VFRONT_PORCH_OFST (3) +#define M00233_IRQ_TRIGGERS_BITMAP_VFRONT_PORCH_MSK (0x1 << M00233_IRQ_TRIGGERS_BITMAP_VFRONT_PORCH_OFST) +#define M00233_IRQ_TRIGGERS_BITMAP_HSYNC_TIME_OFST (4) +#define M00233_IRQ_TRIGGERS_BITMAP_HSYNC_TIME_MSK (0x1 << M00233_IRQ_TRIGGERS_BITMAP_HSYNC_TIME_OFST) +#define M00233_IRQ_TRIGGERS_BITMAP_HBACK_PORCH_OFST (5) +#define M00233_IRQ_TRIGGERS_BITMAP_HBACK_PORCH_MSK (0x1 << M00233_IRQ_TRIGGERS_BITMAP_HBACK_PORCH_OFST) +#define M00233_IRQ_TRIGGERS_BITMAP_HACTIVE_AREA_OFST (6) +#define M00233_IRQ_TRIGGERS_BITMAP_HACTIVE_AREA_MSK (0x1 << M00233_IRQ_TRIGGERS_BITMAP_HACTIVE_AREA_OFST) +#define M00233_IRQ_TRIGGERS_BITMAP_HFRONT_PORCH_OFST (7) +#define M00233_IRQ_TRIGGERS_BITMAP_HFRONT_PORCH_MSK (0x1 << M00233_IRQ_TRIGGERS_BITMAP_HFRONT_PORCH_OFST) +/* status [1:0] */ +#define M00233_STATUS_BITMAP_HSYNC_TIMEOUT_OFST (0) +#define M00233_STATUS_BITMAP_HSYNC_TIMEOUT_MSK (0x1 << M00233_STATUS_BITMAP_HSYNC_TIMEOUT_OFST) +#define M00233_STATUS_BITMAP_INIT_DONE_OFST (1) +#define M00233_STATUS_BITMAP_INIT_DONE_MSK (0x1 << M00233_STATUS_BITMAP_INIT_DONE_OFST) + +#endif /*M00233_VIDEO_MEASURE_MEMMAP_PACKAGE_H*/ diff --git a/drivers/media/pci/cobalt/m00235_fdma_packer_memmap_package.h b/drivers/media/pci/cobalt/m00235_fdma_packer_memmap_package.h new file mode 100644 index 000000000..6cc1ad7d9 --- /dev/null +++ b/drivers/media/pci/cobalt/m00235_fdma_packer_memmap_package.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#ifndef M00235_FDMA_PACKER_MEMMAP_PACKAGE_H +#define M00235_FDMA_PACKER_MEMMAP_PACKAGE_H + +/******************************************************************* + * Register Block + * M00235_FDMA_PACKER_MEMMAP_PACKAGE_VHD_REGMAP + *******************************************************************/ +struct m00235_fdma_packer_regmap { + uint32_t control; /* Reg 0x0000, Default=0x0 */ +}; + +#define M00235_FDMA_PACKER_REG_CONTROL_OFST 0 + +/******************************************************************* + * Bit Mask for register + * M00235_FDMA_PACKER_MEMMAP_PACKAGE_VHD_BITMAP + *******************************************************************/ +/* control [3:0] */ +#define M00235_CONTROL_BITMAP_ENABLE_OFST (0) +#define M00235_CONTROL_BITMAP_ENABLE_MSK (0x1 << M00235_CONTROL_BITMAP_ENABLE_OFST) +#define M00235_CONTROL_BITMAP_PACK_FORMAT_OFST (1) +#define M00235_CONTROL_BITMAP_PACK_FORMAT_MSK (0x3 << M00235_CONTROL_BITMAP_PACK_FORMAT_OFST) +#define M00235_CONTROL_BITMAP_ENDIAN_FORMAT_OFST (3) +#define M00235_CONTROL_BITMAP_ENDIAN_FORMAT_MSK (0x1 << M00235_CONTROL_BITMAP_ENDIAN_FORMAT_OFST) + +#endif /*M00235_FDMA_PACKER_MEMMAP_PACKAGE_H*/ diff --git a/drivers/media/pci/cobalt/m00389_cvi_memmap_package.h b/drivers/media/pci/cobalt/m00389_cvi_memmap_package.h new file mode 100644 index 000000000..f0c6fe304 --- /dev/null +++ b/drivers/media/pci/cobalt/m00389_cvi_memmap_package.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#ifndef M00389_CVI_MEMMAP_PACKAGE_H +#define M00389_CVI_MEMMAP_PACKAGE_H + +/******************************************************************* + * Register Block + * M00389_CVI_MEMMAP_PACKAGE_VHD_REGMAP + *******************************************************************/ +struct m00389_cvi_regmap { + uint32_t control; /* Reg 0x0000, Default=0x0 */ + uint32_t frame_width; /* Reg 0x0004, Default=0x10 */ + uint32_t frame_height; /* Reg 0x0008, Default=0xc */ + uint32_t freewheel_period; /* Reg 0x000c, Default=0x0 */ + uint32_t error_color; /* Reg 0x0010, Default=0x0 */ + uint32_t status; /* Reg 0x0014 */ +}; + +#define M00389_CVI_REG_CONTROL_OFST 0 +#define M00389_CVI_REG_FRAME_WIDTH_OFST 4 +#define M00389_CVI_REG_FRAME_HEIGHT_OFST 8 +#define M00389_CVI_REG_FREEWHEEL_PERIOD_OFST 12 +#define M00389_CVI_REG_ERROR_COLOR_OFST 16 +#define M00389_CVI_REG_STATUS_OFST 20 + +/******************************************************************* + * Bit Mask for register + * M00389_CVI_MEMMAP_PACKAGE_VHD_BITMAP + *******************************************************************/ +/* control [2:0] */ +#define M00389_CONTROL_BITMAP_ENABLE_OFST (0) +#define M00389_CONTROL_BITMAP_ENABLE_MSK (0x1 << M00389_CONTROL_BITMAP_ENABLE_OFST) +#define M00389_CONTROL_BITMAP_HSYNC_POLARITY_LOW_OFST (1) +#define M00389_CONTROL_BITMAP_HSYNC_POLARITY_LOW_MSK (0x1 << M00389_CONTROL_BITMAP_HSYNC_POLARITY_LOW_OFST) +#define M00389_CONTROL_BITMAP_VSYNC_POLARITY_LOW_OFST (2) +#define M00389_CONTROL_BITMAP_VSYNC_POLARITY_LOW_MSK (0x1 << M00389_CONTROL_BITMAP_VSYNC_POLARITY_LOW_OFST) +/* status [1:0] */ +#define M00389_STATUS_BITMAP_LOCK_OFST (0) +#define M00389_STATUS_BITMAP_LOCK_MSK (0x1 << M00389_STATUS_BITMAP_LOCK_OFST) +#define M00389_STATUS_BITMAP_ERROR_OFST (1) +#define M00389_STATUS_BITMAP_ERROR_MSK (0x1 << M00389_STATUS_BITMAP_ERROR_OFST) + +#endif /*M00389_CVI_MEMMAP_PACKAGE_H*/ diff --git a/drivers/media/pci/cobalt/m00460_evcnt_memmap_package.h b/drivers/media/pci/cobalt/m00460_evcnt_memmap_package.h new file mode 100644 index 000000000..27f05aca6 --- /dev/null +++ b/drivers/media/pci/cobalt/m00460_evcnt_memmap_package.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#ifndef M00460_EVCNT_MEMMAP_PACKAGE_H +#define M00460_EVCNT_MEMMAP_PACKAGE_H + +/******************************************************************* + * Register Block + * M00460_EVCNT_MEMMAP_PACKAGE_VHD_REGMAP + *******************************************************************/ +struct m00460_evcnt_regmap { + uint32_t control; /* Reg 0x0000, Default=0x0 */ + uint32_t count; /* Reg 0x0004 */ +}; + +#define M00460_EVCNT_REG_CONTROL_OFST 0 +#define M00460_EVCNT_REG_COUNT_OFST 4 + +/******************************************************************* + * Bit Mask for register + * M00460_EVCNT_MEMMAP_PACKAGE_VHD_BITMAP + *******************************************************************/ +/* control [1:0] */ +#define M00460_CONTROL_BITMAP_ENABLE_OFST (0) +#define M00460_CONTROL_BITMAP_ENABLE_MSK (0x1 << M00460_CONTROL_BITMAP_ENABLE_OFST) +#define M00460_CONTROL_BITMAP_CLEAR_OFST (1) +#define M00460_CONTROL_BITMAP_CLEAR_MSK (0x1 << M00460_CONTROL_BITMAP_CLEAR_OFST) + +#endif /*M00460_EVCNT_MEMMAP_PACKAGE_H*/ diff --git a/drivers/media/pci/cobalt/m00473_freewheel_memmap_package.h b/drivers/media/pci/cobalt/m00473_freewheel_memmap_package.h new file mode 100644 index 000000000..8a5bf0087 --- /dev/null +++ b/drivers/media/pci/cobalt/m00473_freewheel_memmap_package.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#ifndef M00473_FREEWHEEL_MEMMAP_PACKAGE_H +#define M00473_FREEWHEEL_MEMMAP_PACKAGE_H + +/******************************************************************* + * Register Block + * M00473_FREEWHEEL_MEMMAP_PACKAGE_VHD_REGMAP + *******************************************************************/ +struct m00473_freewheel_regmap { + uint32_t ctrl; /* Reg 0x0000, Default=0x0 */ + uint32_t status; /* Reg 0x0004 */ + uint32_t active_length; /* Reg 0x0008, Default=0x1fa400 */ + uint32_t total_length; /* Reg 0x000c, Default=0x31151b */ + uint32_t data_width; /* Reg 0x0010 */ + uint32_t output_color; /* Reg 0x0014, Default=0xffff */ + uint32_t clk_freq; /* Reg 0x0018 */ +}; + +#define M00473_FREEWHEEL_REG_CTRL_OFST 0 +#define M00473_FREEWHEEL_REG_STATUS_OFST 4 +#define M00473_FREEWHEEL_REG_ACTIVE_LENGTH_OFST 8 +#define M00473_FREEWHEEL_REG_TOTAL_LENGTH_OFST 12 +#define M00473_FREEWHEEL_REG_DATA_WIDTH_OFST 16 +#define M00473_FREEWHEEL_REG_OUTPUT_COLOR_OFST 20 +#define M00473_FREEWHEEL_REG_CLK_FREQ_OFST 24 + +/******************************************************************* + * Bit Mask for register + * M00473_FREEWHEEL_MEMMAP_PACKAGE_VHD_BITMAP + *******************************************************************/ +/* ctrl [1:0] */ +#define M00473_CTRL_BITMAP_ENABLE_OFST (0) +#define M00473_CTRL_BITMAP_ENABLE_MSK (0x1 << M00473_CTRL_BITMAP_ENABLE_OFST) +#define M00473_CTRL_BITMAP_FORCE_FREEWHEEL_MODE_OFST (1) +#define M00473_CTRL_BITMAP_FORCE_FREEWHEEL_MODE_MSK (0x1 << M00473_CTRL_BITMAP_FORCE_FREEWHEEL_MODE_OFST) +/* status [0:0] */ +#define M00473_STATUS_BITMAP_FREEWHEEL_MODE_OFST (0) +#define M00473_STATUS_BITMAP_FREEWHEEL_MODE_MSK (0x1 << M00473_STATUS_BITMAP_FREEWHEEL_MODE_OFST) + +#endif /*M00473_FREEWHEEL_MEMMAP_PACKAGE_H*/ diff --git a/drivers/media/pci/cobalt/m00479_clk_loss_detector_memmap_package.h b/drivers/media/pci/cobalt/m00479_clk_loss_detector_memmap_package.h new file mode 100644 index 000000000..18bd4fcd2 --- /dev/null +++ b/drivers/media/pci/cobalt/m00479_clk_loss_detector_memmap_package.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#ifndef M00479_CLK_LOSS_DETECTOR_MEMMAP_PACKAGE_H +#define M00479_CLK_LOSS_DETECTOR_MEMMAP_PACKAGE_H + +/******************************************************************* + * Register Block + * M00479_CLK_LOSS_DETECTOR_MEMMAP_PACKAGE_VHD_REGMAP + *******************************************************************/ +struct m00479_clk_loss_detector_regmap { + /* Control module */ + uint32_t ctrl; /* Reg 0x0000, Default=0x0 */ + uint32_t status; /* Reg 0x0004 */ + /* Number of ref clk cycles before checking the clock under test */ + uint32_t ref_clk_cnt_val; /* Reg 0x0008, Default=0xc4 */ + /* Number of test clk cycles required in the ref_clk_cnt_val period + * to ensure that the test clock is performing as expected */ + uint32_t test_clk_cnt_val; /* Reg 0x000c, Default=0xa */ +}; + +#define M00479_CLK_LOSS_DETECTOR_REG_CTRL_OFST 0 +#define M00479_CLK_LOSS_DETECTOR_REG_STATUS_OFST 4 +#define M00479_CLK_LOSS_DETECTOR_REG_REF_CLK_CNT_VAL_OFST 8 +#define M00479_CLK_LOSS_DETECTOR_REG_TEST_CLK_CNT_VAL_OFST 12 + +/******************************************************************* + * Bit Mask for register + * M00479_CLK_LOSS_DETECTOR_MEMMAP_PACKAGE_VHD_BITMAP + *******************************************************************/ +/* ctrl [0:0] */ +#define M00479_CTRL_BITMAP_ENABLE_OFST (0) +#define M00479_CTRL_BITMAP_ENABLE_MSK (0x1 << M00479_CTRL_BITMAP_ENABLE_OFST) +/* status [0:0] */ +#define M00479_STATUS_BITMAP_CLOCK_MISSING_OFST (0) +#define M00479_STATUS_BITMAP_CLOCK_MISSING_MSK (0x1 << M00479_STATUS_BITMAP_CLOCK_MISSING_OFST) + +#endif /*M00479_CLK_LOSS_DETECTOR_MEMMAP_PACKAGE_H*/ diff --git a/drivers/media/pci/cobalt/m00514_syncgen_flow_evcnt_memmap_package.h b/drivers/media/pci/cobalt/m00514_syncgen_flow_evcnt_memmap_package.h new file mode 100644 index 000000000..86da49033 --- /dev/null +++ b/drivers/media/pci/cobalt/m00514_syncgen_flow_evcnt_memmap_package.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + */ + +#ifndef M00514_SYNCGEN_FLOW_EVCNT_MEMMAP_PACKAGE_H +#define M00514_SYNCGEN_FLOW_EVCNT_MEMMAP_PACKAGE_H + +/******************************************************************* + * Register Block + * M00514_SYNCGEN_FLOW_EVCNT_MEMMAP_PACKAGE_VHD_REGMAP + *******************************************************************/ +struct m00514_syncgen_flow_evcnt_regmap { + uint32_t control; /* Reg 0x0000, Default=0x0 */ + uint32_t sync_generator_h_sync_length; /* Reg 0x0004, Default=0x0 */ + uint32_t sync_generator_h_backporch_length; /* Reg 0x0008, Default=0x0 */ + uint32_t sync_generator_h_active_length; /* Reg 0x000c, Default=0x0 */ + uint32_t sync_generator_h_frontporch_length; /* Reg 0x0010, Default=0x0 */ + uint32_t sync_generator_v_sync_length; /* Reg 0x0014, Default=0x0 */ + uint32_t sync_generator_v_backporch_length; /* Reg 0x0018, Default=0x0 */ + uint32_t sync_generator_v_active_length; /* Reg 0x001c, Default=0x0 */ + uint32_t sync_generator_v_frontporch_length; /* Reg 0x0020, Default=0x0 */ + uint32_t error_color; /* Reg 0x0024, Default=0x0 */ + uint32_t rd_status; /* Reg 0x0028 */ + uint32_t rd_evcnt_count; /* Reg 0x002c */ +}; + +#define M00514_SYNCGEN_FLOW_EVCNT_REG_CONTROL_OFST 0 +#define M00514_SYNCGEN_FLOW_EVCNT_REG_SYNC_GENERATOR_H_SYNC_LENGTH_OFST 4 +#define M00514_SYNCGEN_FLOW_EVCNT_REG_SYNC_GENERATOR_H_BACKPORCH_LENGTH_OFST 8 +#define M00514_SYNCGEN_FLOW_EVCNT_REG_SYNC_GENERATOR_H_ACTIVE_LENGTH_OFST 12 +#define M00514_SYNCGEN_FLOW_EVCNT_REG_SYNC_GENERATOR_H_FRONTPORCH_LENGTH_OFST 16 +#define M00514_SYNCGEN_FLOW_EVCNT_REG_SYNC_GENERATOR_V_SYNC_LENGTH_OFST 20 +#define M00514_SYNCGEN_FLOW_EVCNT_REG_SYNC_GENERATOR_V_BACKPORCH_LENGTH_OFST 24 +#define M00514_SYNCGEN_FLOW_EVCNT_REG_SYNC_GENERATOR_V_ACTIVE_LENGTH_OFST 28 +#define M00514_SYNCGEN_FLOW_EVCNT_REG_SYNC_GENERATOR_V_FRONTPORCH_LENGTH_OFST 32 +#define M00514_SYNCGEN_FLOW_EVCNT_REG_ERROR_COLOR_OFST 36 +#define M00514_SYNCGEN_FLOW_EVCNT_REG_RD_STATUS_OFST 40 +#define M00514_SYNCGEN_FLOW_EVCNT_REG_RD_EVCNT_COUNT_OFST 44 + +/******************************************************************* + * Bit Mask for register + * M00514_SYNCGEN_FLOW_EVCNT_MEMMAP_PACKAGE_VHD_BITMAP + *******************************************************************/ +/* control [7:0] */ +#define M00514_CONTROL_BITMAP_SYNC_GENERATOR_LOAD_PARAM_OFST (0) +#define M00514_CONTROL_BITMAP_SYNC_GENERATOR_LOAD_PARAM_MSK (0x1 << M00514_CONTROL_BITMAP_SYNC_GENERATOR_LOAD_PARAM_OFST) +#define M00514_CONTROL_BITMAP_SYNC_GENERATOR_ENABLE_OFST (1) +#define M00514_CONTROL_BITMAP_SYNC_GENERATOR_ENABLE_MSK (0x1 << M00514_CONTROL_BITMAP_SYNC_GENERATOR_ENABLE_OFST) +#define M00514_CONTROL_BITMAP_FLOW_CTRL_OUTPUT_ENABLE_OFST (2) +#define M00514_CONTROL_BITMAP_FLOW_CTRL_OUTPUT_ENABLE_MSK (0x1 << M00514_CONTROL_BITMAP_FLOW_CTRL_OUTPUT_ENABLE_OFST) +#define M00514_CONTROL_BITMAP_HSYNC_POLARITY_LOW_OFST (3) +#define M00514_CONTROL_BITMAP_HSYNC_POLARITY_LOW_MSK (0x1 << M00514_CONTROL_BITMAP_HSYNC_POLARITY_LOW_OFST) +#define M00514_CONTROL_BITMAP_VSYNC_POLARITY_LOW_OFST (4) +#define M00514_CONTROL_BITMAP_VSYNC_POLARITY_LOW_MSK (0x1 << M00514_CONTROL_BITMAP_VSYNC_POLARITY_LOW_OFST) +#define M00514_CONTROL_BITMAP_EVCNT_ENABLE_OFST (5) +#define M00514_CONTROL_BITMAP_EVCNT_ENABLE_MSK (0x1 << M00514_CONTROL_BITMAP_EVCNT_ENABLE_OFST) +#define M00514_CONTROL_BITMAP_EVCNT_CLEAR_OFST (6) +#define M00514_CONTROL_BITMAP_EVCNT_CLEAR_MSK (0x1 << M00514_CONTROL_BITMAP_EVCNT_CLEAR_OFST) +#define M00514_CONTROL_BITMAP_FORMAT_16_BPP_OFST (7) +#define M00514_CONTROL_BITMAP_FORMAT_16_BPP_MSK (0x1 << M00514_CONTROL_BITMAP_FORMAT_16_BPP_OFST) +/* error_color [23:0] */ +#define M00514_ERROR_COLOR_BITMAP_BLUE_OFST (0) +#define M00514_ERROR_COLOR_BITMAP_BLUE_MSK (0xff << M00514_ERROR_COLOR_BITMAP_BLUE_OFST) +#define M00514_ERROR_COLOR_BITMAP_GREEN_OFST (8) +#define M00514_ERROR_COLOR_BITMAP_GREEN_MSK (0xff << M00514_ERROR_COLOR_BITMAP_GREEN_OFST) +#define M00514_ERROR_COLOR_BITMAP_RED_OFST (16) +#define M00514_ERROR_COLOR_BITMAP_RED_MSK (0xff << M00514_ERROR_COLOR_BITMAP_RED_OFST) +/* rd_status [1:0] */ +#define M00514_RD_STATUS_BITMAP_FLOW_CTRL_NO_DATA_ERROR_OFST (0) +#define M00514_RD_STATUS_BITMAP_FLOW_CTRL_NO_DATA_ERROR_MSK (0x1 << M00514_RD_STATUS_BITMAP_FLOW_CTRL_NO_DATA_ERROR_OFST) +#define M00514_RD_STATUS_BITMAP_READY_BUFFER_FULL_OFST (1) +#define M00514_RD_STATUS_BITMAP_READY_BUFFER_FULL_MSK (0x1 << M00514_RD_STATUS_BITMAP_READY_BUFFER_FULL_OFST) + +#endif /*M00514_SYNCGEN_FLOW_EVCNT_MEMMAP_PACKAGE_H*/ diff --git a/drivers/media/pci/cx18/Kconfig b/drivers/media/pci/cx18/Kconfig new file mode 100644 index 000000000..5c959bb5e --- /dev/null +++ b/drivers/media/pci/cx18/Kconfig @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_CX18 + tristate "Conexant cx23418 MPEG encoder support" + depends on VIDEO_DEV && DVB_CORE && PCI && I2C + select I2C_ALGOBIT + select VIDEOBUF2_VMALLOC + depends on RC_CORE + select VIDEO_TUNER + select VIDEO_TVEEPROM + select VIDEO_CX2341X + select VIDEO_CS5345 + select DVB_S5H1409 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MXL5005S if MEDIA_SUBDRV_AUTOSELECT + select DVB_S5H1411 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA8290 if MEDIA_SUBDRV_AUTOSELECT + help + This is a video4linux driver for Conexant cx23418 based + PCI combo video recorder devices. + + This is used in devices such as the Hauppauge HVR-1600 + cards. + + To compile this driver as a module, choose M here: the + module will be called cx18. + +config VIDEO_CX18_ALSA + tristate "Conexant 23418 DMA audio support" + depends on VIDEO_CX18 && SND + select SND_PCM + help + This is a video4linux driver for direct (DMA) audio on + Conexant 23418 based TV cards using ALSA. + + To compile this driver as a module, choose M here: the + module will be called cx18-alsa. diff --git a/drivers/media/pci/cx18/Makefile b/drivers/media/pci/cx18/Makefile new file mode 100644 index 000000000..df00ef8b4 --- /dev/null +++ b/drivers/media/pci/cx18/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +cx18-objs := cx18-driver.o cx18-cards.o cx18-i2c.o cx18-firmware.o cx18-gpio.o \ + cx18-queue.o cx18-streams.o cx18-fileops.o cx18-ioctl.o cx18-controls.o \ + cx18-mailbox.o cx18-vbi.o cx18-audio.o cx18-video.o cx18-irq.o \ + cx18-av-core.o cx18-av-audio.o cx18-av-firmware.o cx18-av-vbi.o cx18-scb.o \ + cx18-dvb.o cx18-io.o +cx18-alsa-objs := cx18-alsa-main.o cx18-alsa-pcm.o + +obj-$(CONFIG_VIDEO_CX18) += cx18.o +obj-$(CONFIG_VIDEO_CX18_ALSA) += cx18-alsa.o + +ccflags-y += -I $(srctree)/drivers/media/dvb-frontends +ccflags-y += -I $(srctree)/drivers/media/tuners diff --git a/drivers/media/pci/cx18/cx18-alsa-main.c b/drivers/media/pci/cx18/cx18-alsa-main.c new file mode 100644 index 000000000..b77f5375f --- /dev/null +++ b/drivers/media/pci/cx18/cx18-alsa-main.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA interface to cx18 PCM capture streams + * + * Copyright (C) 2009 Andy Walls + * Copyright (C) 2009 Devin Heitmueller + * + * Portions of this work were sponsored by ONELAN Limited. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "cx18-driver.h" +#include "cx18-version.h" +#include "cx18-alsa.h" +#include "cx18-alsa-pcm.h" + +int cx18_alsa_debug; + +#define CX18_DEBUG_ALSA_INFO(fmt, arg...) \ + do { \ + if (cx18_alsa_debug & 2) \ + printk(KERN_INFO "%s: " fmt, "cx18-alsa", ## arg); \ + } while (0); + +module_param_named(debug, cx18_alsa_debug, int, 0644); +MODULE_PARM_DESC(debug, + "Debug level (bitmask). Default: 0\n" + "\t\t\t 1/0x0001: warning\n" + "\t\t\t 2/0x0002: info\n"); + +MODULE_AUTHOR("Andy Walls"); +MODULE_DESCRIPTION("CX23418 ALSA Interface"); +MODULE_LICENSE("GPL"); + +MODULE_VERSION(CX18_VERSION); + +static inline +struct snd_cx18_card *to_snd_cx18_card(struct v4l2_device *v4l2_dev) +{ + return to_cx18(v4l2_dev)->alsa; +} + +static void snd_cx18_card_free(struct snd_cx18_card *cxsc) +{ + if (cxsc == NULL) + return; + + if (cxsc->v4l2_dev != NULL) + to_cx18(cxsc->v4l2_dev)->alsa = NULL; + + /* FIXME - take any other stopping actions needed */ + + kfree(cxsc); +} + +static void snd_cx18_card_private_free(struct snd_card *sc) +{ + if (sc == NULL) + return; + snd_cx18_card_free(sc->private_data); + sc->private_data = NULL; + sc->private_free = NULL; +} + +static int snd_cx18_card_create(struct v4l2_device *v4l2_dev, + struct snd_card *sc, + struct snd_cx18_card **cxsc) +{ + *cxsc = kzalloc_obj(struct snd_cx18_card); + if (*cxsc == NULL) + return -ENOMEM; + + (*cxsc)->v4l2_dev = v4l2_dev; + (*cxsc)->sc = sc; + + sc->private_data = *cxsc; + sc->private_free = snd_cx18_card_private_free; + + return 0; +} + +static int snd_cx18_card_set_names(struct snd_cx18_card *cxsc) +{ + struct cx18 *cx = to_cx18(cxsc->v4l2_dev); + struct snd_card *sc = cxsc->sc; + + /* sc->driver is used by alsa-lib's configurator: simple, unique */ + strscpy(sc->driver, "CX23418", sizeof(sc->driver)); + + /* sc->shortname is a symlink in /proc/asound: CX18-M -> cardN */ + snprintf(sc->shortname, sizeof(sc->shortname), "CX18-%d", + cx->instance); + + /* sc->longname is read from /proc/asound/cards */ + snprintf(sc->longname, sizeof(sc->longname), + "CX23418 #%d %s TV/FM Radio/Line-In Capture", + cx->instance, cx->card_name); + + return 0; +} + +static int snd_cx18_init(struct v4l2_device *v4l2_dev) +{ + struct cx18 *cx = to_cx18(v4l2_dev); + struct snd_card *sc = NULL; + struct snd_cx18_card *cxsc; + int ret; + + /* Numbrs steps from "Writing an ALSA Driver" by Takashi Iwai */ + + /* (1) Check and increment the device index */ + /* This is a no-op for us. We'll use the cx->instance */ + + /* (2) Create a card instance */ + ret = snd_card_new(&cx->pci_dev->dev, + SNDRV_DEFAULT_IDX1, /* use first available id */ + SNDRV_DEFAULT_STR1, /* xid from end of shortname*/ + THIS_MODULE, 0, &sc); + if (ret) { + CX18_ALSA_ERR("%s: snd_card_new() failed with err %d\n", + __func__, ret); + goto err_exit; + } + + /* (3) Create a main component */ + ret = snd_cx18_card_create(v4l2_dev, sc, &cxsc); + if (ret) { + CX18_ALSA_ERR("%s: snd_cx18_card_create() failed with err %d\n", + __func__, ret); + goto err_exit_free; + } + + /* (4) Set the driver ID and name strings */ + snd_cx18_card_set_names(cxsc); + + + ret = snd_cx18_pcm_create(cxsc); + if (ret) { + CX18_ALSA_ERR("%s: snd_cx18_pcm_create() failed with err %d\n", + __func__, ret); + goto err_exit_free; + } + /* FIXME - proc files */ + + /* (7) Set the driver data and return 0 */ + /* We do this out of normal order for PCI drivers to avoid races */ + cx->alsa = cxsc; + + /* (6) Register the card instance */ + ret = snd_card_register(sc); + if (ret) { + cx->alsa = NULL; + CX18_ALSA_ERR("%s: snd_card_register() failed with err %d\n", + __func__, ret); + goto err_exit_free; + } + + return 0; + +err_exit_free: + if (sc != NULL) + snd_card_free(sc); + kfree(cxsc); +err_exit: + return ret; +} + +static int cx18_alsa_load(struct cx18 *cx) +{ + struct v4l2_device *v4l2_dev = &cx->v4l2_dev; + struct cx18_stream *s; + + if (v4l2_dev == NULL) { + printk(KERN_ERR "cx18-alsa: %s: struct v4l2_device * is NULL\n", + __func__); + return 0; + } + + cx = to_cx18(v4l2_dev); + if (cx == NULL) { + printk(KERN_ERR "cx18-alsa cx is NULL\n"); + return 0; + } + + s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM]; + if (s->video_dev.v4l2_dev == NULL) { + CX18_DEBUG_ALSA_INFO("%s: PCM stream for card is disabled - skipping\n", + __func__); + return 0; + } + + if (cx->alsa != NULL) { + CX18_ALSA_ERR("%s: struct snd_cx18_card * already exists\n", + __func__); + return 0; + } + + if (snd_cx18_init(v4l2_dev)) { + CX18_ALSA_ERR("%s: failed to create struct snd_cx18_card\n", + __func__); + } else { + CX18_DEBUG_ALSA_INFO("%s: created cx18 ALSA interface instance\n", + __func__); + } + return 0; +} + +static int __init cx18_alsa_init(void) +{ + printk(KERN_INFO "cx18-alsa: module loading...\n"); + cx18_ext_init = &cx18_alsa_load; + return 0; +} + +static void __exit snd_cx18_exit(struct snd_cx18_card *cxsc) +{ + struct cx18 *cx = to_cx18(cxsc->v4l2_dev); + + /* FIXME - pointer checks & shutdown cxsc */ + + snd_card_free(cxsc->sc); + cx->alsa = NULL; +} + +static int __exit cx18_alsa_exit_callback(struct device *dev, void *data) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); + struct snd_cx18_card *cxsc; + + if (v4l2_dev == NULL) { + printk(KERN_ERR "cx18-alsa: %s: struct v4l2_device * is NULL\n", + __func__); + return 0; + } + + cxsc = to_snd_cx18_card(v4l2_dev); + if (cxsc == NULL) { + CX18_ALSA_WARN("%s: struct snd_cx18_card * is NULL\n", + __func__); + return 0; + } + + snd_cx18_exit(cxsc); + return 0; +} + +static void __exit cx18_alsa_exit(void) +{ + struct device_driver *drv; + int ret; + + printk(KERN_INFO "cx18-alsa: module unloading...\n"); + + drv = driver_find("cx18", &pci_bus_type); + ret = driver_for_each_device(drv, NULL, NULL, cx18_alsa_exit_callback); + (void)ret; /* suppress compiler warning */ + + cx18_ext_init = NULL; + printk(KERN_INFO "cx18-alsa: module unload complete\n"); +} + +module_init(cx18_alsa_init); +module_exit(cx18_alsa_exit); diff --git a/drivers/media/pci/cx18/cx18-alsa-pcm.c b/drivers/media/pci/cx18/cx18-alsa-pcm.c new file mode 100644 index 000000000..bed28b4b4 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-alsa-pcm.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA PCM device for the + * ALSA interface to cx18 PCM capture streams + * + * Copyright (C) 2009 Andy Walls + * Copyright (C) 2009 Devin Heitmueller + * + * Portions of this work were sponsored by ONELAN Limited. + */ + +#include +#include + +#include + +#include +#include + +#include "cx18-driver.h" +#include "cx18-queue.h" +#include "cx18-streams.h" +#include "cx18-fileops.h" +#include "cx18-alsa.h" +#include "cx18-alsa-pcm.h" + +static unsigned int pcm_debug; +module_param(pcm_debug, int, 0644); +MODULE_PARM_DESC(pcm_debug, "enable debug messages for pcm"); + +#define dprintk(fmt, arg...) do { \ + if (pcm_debug) \ + printk(KERN_INFO "cx18-alsa-pcm %s: " fmt, \ + __func__, ##arg); \ + } while (0) + +static const struct snd_pcm_hardware snd_cx18_hw_capture = { + .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID, + + .formats = SNDRV_PCM_FMTBIT_S16_LE, + + .rates = SNDRV_PCM_RATE_48000, + + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 62720 * 8, /* just about the value in usbaudio.c */ + .period_bytes_min = 64, /* 12544/2, */ + .period_bytes_max = 12544, + .periods_min = 2, + .periods_max = 98, /* 12544, */ +}; + +void cx18_alsa_announce_pcm_data(struct snd_cx18_card *cxsc, u8 *pcm_data, + size_t num_bytes) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int oldptr; + unsigned int stride; + int period_elapsed = 0; + int length; + + dprintk("cx18 alsa announce ptr=%p data=%p num_bytes=%zu\n", cxsc, + pcm_data, num_bytes); + + substream = cxsc->capture_pcm_substream; + if (substream == NULL) { + dprintk("substream was NULL\n"); + return; + } + + runtime = substream->runtime; + if (runtime == NULL) { + dprintk("runtime was NULL\n"); + return; + } + + stride = runtime->frame_bits >> 3; + if (stride == 0) { + dprintk("stride is zero\n"); + return; + } + + length = num_bytes / stride; + if (length == 0) { + dprintk("%s: length was zero\n", __func__); + return; + } + + if (runtime->dma_area == NULL) { + dprintk("dma area was NULL - ignoring\n"); + return; + } + + oldptr = cxsc->hwptr_done_capture; + if (oldptr + length >= runtime->buffer_size) { + unsigned int cnt = + runtime->buffer_size - oldptr; + memcpy(runtime->dma_area + oldptr * stride, pcm_data, + cnt * stride); + memcpy(runtime->dma_area, pcm_data + cnt * stride, + length * stride - cnt * stride); + } else { + memcpy(runtime->dma_area + oldptr * stride, pcm_data, + length * stride); + } + snd_pcm_stream_lock(substream); + + cxsc->hwptr_done_capture += length; + if (cxsc->hwptr_done_capture >= + runtime->buffer_size) + cxsc->hwptr_done_capture -= + runtime->buffer_size; + + cxsc->capture_transfer_done += length; + if (cxsc->capture_transfer_done >= + runtime->period_size) { + cxsc->capture_transfer_done -= + runtime->period_size; + period_elapsed = 1; + } + + snd_pcm_stream_unlock(substream); + + if (period_elapsed) + snd_pcm_period_elapsed(substream); +} + +static int snd_cx18_pcm_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; + struct cx18 *cx = to_cx18(v4l2_dev); + struct cx18_stream *s; + struct cx18_open_id item; + int ret; + + /* Instruct the cx18 to start sending packets */ + snd_cx18_lock(cxsc); + s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM]; + + item.cx = cx; + item.type = s->type; + item.open_id = cx->open_id++; + + /* See if the stream is available */ + if (cx18_claim_stream(&item, item.type)) { + /* No, it's already in use */ + snd_cx18_unlock(cxsc); + return -EBUSY; + } + + if (test_bit(CX18_F_S_STREAMOFF, &s->s_flags) || + test_and_set_bit(CX18_F_S_STREAMING, &s->s_flags)) { + /* We're already streaming. No additional action required */ + snd_cx18_unlock(cxsc); + return 0; + } + + + runtime->hw = snd_cx18_hw_capture; + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + cxsc->capture_pcm_substream = substream; + runtime->private_data = cx; + + cx->pcm_announce_callback = cx18_alsa_announce_pcm_data; + + /* Not currently streaming, so start it up */ + set_bit(CX18_F_S_STREAMING, &s->s_flags); + ret = cx18_start_v4l2_encode_stream(s); + snd_cx18_unlock(cxsc); + + return ret; +} + +static int snd_cx18_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); + struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; + struct cx18 *cx = to_cx18(v4l2_dev); + struct cx18_stream *s; + + /* Instruct the cx18 to stop sending packets */ + snd_cx18_lock(cxsc); + s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM]; + cx18_stop_v4l2_encode_stream(s, 0); + clear_bit(CX18_F_S_STREAMING, &s->s_flags); + + cx18_release_stream(s); + + cx->pcm_announce_callback = NULL; + snd_cx18_unlock(cxsc); + + return 0; +} + +static int snd_cx18_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); + + cxsc->hwptr_done_capture = 0; + cxsc->capture_transfer_done = 0; + + return 0; +} + +static int snd_cx18_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + return 0; +} + +static +snd_pcm_uframes_t snd_cx18_pcm_pointer(struct snd_pcm_substream *substream) +{ + unsigned long flags; + snd_pcm_uframes_t hwptr_done; + struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); + + spin_lock_irqsave(&cxsc->slock, flags); + hwptr_done = cxsc->hwptr_done_capture; + spin_unlock_irqrestore(&cxsc->slock, flags); + + return hwptr_done; +} + +static const struct snd_pcm_ops snd_cx18_pcm_capture_ops = { + .open = snd_cx18_pcm_capture_open, + .close = snd_cx18_pcm_capture_close, + .prepare = snd_cx18_pcm_prepare, + .trigger = snd_cx18_pcm_trigger, + .pointer = snd_cx18_pcm_pointer, +}; + +int snd_cx18_pcm_create(struct snd_cx18_card *cxsc) +{ + struct snd_pcm *sp; + struct snd_card *sc = cxsc->sc; + struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; + struct cx18 *cx = to_cx18(v4l2_dev); + int ret; + + ret = snd_pcm_new(sc, "CX23418 PCM", + 0, /* PCM device 0, the only one for this card */ + 0, /* 0 playback substreams */ + 1, /* 1 capture substream */ + &sp); + if (ret) { + CX18_ALSA_ERR("%s: snd_cx18_pcm_create() failed with err %d\n", + __func__, ret); + goto err_exit; + } + + spin_lock_init(&cxsc->slock); + + snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_CAPTURE, + &snd_cx18_pcm_capture_ops); + snd_pcm_set_managed_buffer_all(sp, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); + sp->info_flags = 0; + sp->private_data = cxsc; + strscpy(sp->name, cx->card_name, sizeof(sp->name)); + + return 0; + +err_exit: + return ret; +} diff --git a/drivers/media/pci/cx18/cx18-alsa-pcm.h b/drivers/media/pci/cx18/cx18-alsa-pcm.h new file mode 100644 index 000000000..6160b2532 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-alsa-pcm.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA PCM device for the + * ALSA interface to cx18 PCM capture streams + * + * Copyright (C) 2009 Andy Walls + */ + +int snd_cx18_pcm_create(struct snd_cx18_card *cxsc); + +/* Used by cx18-mailbox to announce the PCM data to the module */ +void cx18_alsa_announce_pcm_data(struct snd_cx18_card *card, u8 *pcm_data, + size_t num_bytes); diff --git a/drivers/media/pci/cx18/cx18-alsa.h b/drivers/media/pci/cx18/cx18-alsa.h new file mode 100644 index 000000000..6d8685fe3 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-alsa.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA interface to cx18 PCM capture streams + * + * Copyright (C) 2009 Andy Walls + */ + +struct snd_card; + +struct snd_cx18_card { + struct v4l2_device *v4l2_dev; + struct snd_card *sc; + unsigned int capture_transfer_done; + unsigned int hwptr_done_capture; + struct snd_pcm_substream *capture_pcm_substream; + spinlock_t slock; +}; + +extern int cx18_alsa_debug; + +/* + * File operations that manipulate the encoder or video or audio subdevices + * need to be serialized. Use the same lock we use for v4l2 file ops. + */ +static inline void snd_cx18_lock(struct snd_cx18_card *cxsc) +{ + struct cx18 *cx = to_cx18(cxsc->v4l2_dev); + mutex_lock(&cx->serialize_lock); +} + +static inline void snd_cx18_unlock(struct snd_cx18_card *cxsc) +{ + struct cx18 *cx = to_cx18(cxsc->v4l2_dev); + mutex_unlock(&cx->serialize_lock); +} + +#define CX18_ALSA_DBGFLG_WARN (1 << 0) +#define CX18_ALSA_DBGFLG_INFO (1 << 1) + +#define CX18_ALSA_DEBUG(x, type, fmt, args...) \ + do { \ + if ((x) & cx18_alsa_debug) \ + printk(KERN_INFO "%s-alsa: " type ": " fmt, \ + v4l2_dev->name , ## args); \ + } while (0) + +#define CX18_ALSA_DEBUG_WARN(fmt, args...) \ + CX18_ALSA_DEBUG(CX18_ALSA_DBGFLG_WARN, "warning", fmt , ## args) + +#define CX18_ALSA_DEBUG_INFO(fmt, args...) \ + CX18_ALSA_DEBUG(CX18_ALSA_DBGFLG_INFO, "info", fmt , ## args) + +#define CX18_ALSA_ERR(fmt, args...) \ + printk(KERN_ERR "%s-alsa: " fmt, v4l2_dev->name , ## args) + +#define CX18_ALSA_WARN(fmt, args...) \ + printk(KERN_WARNING "%s-alsa: " fmt, v4l2_dev->name , ## args) + +#define CX18_ALSA_INFO(fmt, args...) \ + printk(KERN_INFO "%s-alsa: " fmt, v4l2_dev->name , ## args) diff --git a/drivers/media/pci/cx18/cx18-audio.c b/drivers/media/pci/cx18/cx18-audio.c new file mode 100644 index 000000000..146479561 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-audio.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 audio-related functions + * + * Derived from ivtv-audio.c + * + * Copyright (C) 2007 Hans Verkuil + */ + +#include "cx18-driver.h" +#include "cx18-io.h" +#include "cx18-cards.h" +#include "cx18-audio.h" + +#define CX18_AUDIO_ENABLE 0xc72014 +#define CX18_AI1_MUX_MASK 0x30 +#define CX18_AI1_MUX_I2S1 0x00 +#define CX18_AI1_MUX_I2S2 0x10 +#define CX18_AI1_MUX_843_I2S 0x20 + +/* Selects the audio input and output according to the current + settings. */ +int cx18_audio_set_io(struct cx18 *cx) +{ + const struct cx18_card_audio_input *in; + u32 u, v; + int err; + + /* Determine which input to use */ + if (test_bit(CX18_F_I_RADIO_USER, &cx->i_flags)) + in = &cx->card->radio_input; + else + in = &cx->card->audio_inputs[cx->audio_input]; + + /* handle muxer chips */ + v4l2_subdev_call(cx->sd_extmux, audio, s_routing, + (u32) in->muxer_input, 0, 0); + + err = cx18_call_hw_err(cx, cx->card->hw_audio_ctrl, + audio, s_routing, in->audio_input, 0, 0); + if (err) + return err; + + /* FIXME - this internal mux should be abstracted to a subdev */ + u = cx18_read_reg(cx, CX18_AUDIO_ENABLE); + v = u & ~CX18_AI1_MUX_MASK; + switch (in->audio_input) { + case CX18_AV_AUDIO_SERIAL1: + v |= CX18_AI1_MUX_I2S1; + break; + case CX18_AV_AUDIO_SERIAL2: + v |= CX18_AI1_MUX_I2S2; + break; + default: + v |= CX18_AI1_MUX_843_I2S; + break; + } + if (v == u) { + /* force a toggle of some AI1 MUX control bits */ + u &= ~CX18_AI1_MUX_MASK; + switch (in->audio_input) { + case CX18_AV_AUDIO_SERIAL1: + u |= CX18_AI1_MUX_843_I2S; + break; + case CX18_AV_AUDIO_SERIAL2: + u |= CX18_AI1_MUX_843_I2S; + break; + default: + u |= CX18_AI1_MUX_I2S1; + break; + } + cx18_write_reg_expect(cx, u | 0xb00, CX18_AUDIO_ENABLE, + u, CX18_AI1_MUX_MASK); + } + cx18_write_reg_expect(cx, v | 0xb00, CX18_AUDIO_ENABLE, + v, CX18_AI1_MUX_MASK); + return 0; +} diff --git a/drivers/media/pci/cx18/cx18-audio.h b/drivers/media/pci/cx18/cx18-audio.h new file mode 100644 index 000000000..29cf89d38 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-audio.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 audio-related functions + * + * Derived from ivtv-audio.c + * + * Copyright (C) 2007 Hans Verkuil + */ + +int cx18_audio_set_io(struct cx18 *cx); diff --git a/drivers/media/pci/cx18/cx18-av-audio.c b/drivers/media/pci/cx18/cx18-av-audio.c new file mode 100644 index 000000000..644d8ca45 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-av-audio.c @@ -0,0 +1,457 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 ADEC audio functions + * + * Derived from cx25840-audio.c + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#include "cx18-driver.h" + +static int set_audclk_freq(struct cx18 *cx, u32 freq) +{ + struct cx18_av_state *state = &cx->av_state; + + if (freq != 32000 && freq != 44100 && freq != 48000) + return -EINVAL; + + /* + * The PLL parameters are based on the external crystal frequency that + * would ideally be: + * + * NTSC Color subcarrier freq * 8 = + * 4.5 MHz/286 * 455/2 * 8 = 28.63636363... MHz + * + * The accidents of history and rationale that explain from where this + * combination of magic numbers originate can be found in: + * + * [1] Abrahams, I. C., "Choice of Chrominance Subcarrier Frequency in + * the NTSC Standards", Proceedings of the I-R-E, January 1954, pp 79-80 + * + * [2] Abrahams, I. C., "The 'Frequency Interleaving' Principle in the + * NTSC Standards", Proceedings of the I-R-E, January 1954, pp 81-83 + * + * As Mike Bradley has rightly pointed out, it's not the exact crystal + * frequency that matters, only that all parts of the driver and + * firmware are using the same value (close to the ideal value). + * + * Since I have a strong suspicion that, if the firmware ever assumes a + * crystal value at all, it will assume 28.636360 MHz, the crystal + * freq used in calculations in this driver will be: + * + * xtal_freq = 28.636360 MHz + * + * an error of less than 0.13 ppm which is way, way better than any off + * the shelf crystal will have for accuracy anyway. + * + * Below I aim to run the PLLs' VCOs near 400 MHz to minimize error. + * + * Many thanks to Jeff Campbell and Mike Bradley for their extensive + * investigation, experimentation, testing, and suggested solutions of + * audio/video sync problems with SVideo and CVBS captures. + */ + + if (state->aud_input > CX18_AV_AUDIO_SERIAL2) { + switch (freq) { + case 32000: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x0d, AUX PLL Post Divider = 0x20 + */ + cx18_av_write4(cx, 0x108, 0x200d040f); + + /* VID_PLL Fraction = 0x2be2fe */ + /* xtal * 0xf.15f17f0/4 = 108 MHz: 432 MHz pre-postdiv*/ + cx18_av_write4(cx, 0x10c, 0x002be2fe); + + /* AUX_PLL Fraction = 0x176740c */ + /* xtal * 0xd.bb3a060/0x20 = 32000 * 384: 393 MHz p-pd*/ + cx18_av_write4(cx, 0x110, 0x0176740c); + + /* src3/4/6_ctl */ + /* 0x1.f77f = (4 * xtal/8*2/455) / 32000 */ + cx18_av_write4(cx, 0x900, 0x0801f77f); + cx18_av_write4(cx, 0x904, 0x0801f77f); + cx18_av_write4(cx, 0x90c, 0x0801f77f); + + /* SA_MCLK_SEL=1, SA_MCLK_DIV=0x20 */ + cx18_av_write(cx, 0x127, 0x60); + + /* AUD_COUNT = 0x2fff = 8 samples * 4 * 384 - 1 */ + cx18_av_write4(cx, 0x12c, 0x11202fff); + + /* + * EN_AV_LOCK = 0 + * VID_COUNT = 0x0d2ef8 = 107999.000 * 8 = + * ((8 samples/32,000) * (13,500,000 * 8) * 4 - 1) * 8 + */ + cx18_av_write4(cx, 0x128, 0xa00d2ef8); + break; + + case 44100: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x0e, AUX PLL Post Divider = 0x18 + */ + cx18_av_write4(cx, 0x108, 0x180e040f); + + /* VID_PLL Fraction = 0x2be2fe */ + /* xtal * 0xf.15f17f0/4 = 108 MHz: 432 MHz pre-postdiv*/ + cx18_av_write4(cx, 0x10c, 0x002be2fe); + + /* AUX_PLL Fraction = 0x062a1f2 */ + /* xtal * 0xe.3150f90/0x18 = 44100 * 384: 406 MHz p-pd*/ + cx18_av_write4(cx, 0x110, 0x0062a1f2); + + /* src3/4/6_ctl */ + /* 0x1.6d59 = (4 * xtal/8*2/455) / 44100 */ + cx18_av_write4(cx, 0x900, 0x08016d59); + cx18_av_write4(cx, 0x904, 0x08016d59); + cx18_av_write4(cx, 0x90c, 0x08016d59); + + /* SA_MCLK_SEL=1, SA_MCLK_DIV=0x18 */ + cx18_av_write(cx, 0x127, 0x58); + + /* AUD_COUNT = 0x92ff = 49 samples * 2 * 384 - 1 */ + cx18_av_write4(cx, 0x12c, 0x112092ff); + + /* + * EN_AV_LOCK = 0 + * VID_COUNT = 0x1d4bf8 = 239999.000 * 8 = + * ((49 samples/44,100) * (13,500,000 * 8) * 2 - 1) * 8 + */ + cx18_av_write4(cx, 0x128, 0xa01d4bf8); + break; + + case 48000: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x0e, AUX PLL Post Divider = 0x16 + */ + cx18_av_write4(cx, 0x108, 0x160e040f); + + /* VID_PLL Fraction = 0x2be2fe */ + /* xtal * 0xf.15f17f0/4 = 108 MHz: 432 MHz pre-postdiv*/ + cx18_av_write4(cx, 0x10c, 0x002be2fe); + + /* AUX_PLL Fraction = 0x05227ad */ + /* xtal * 0xe.2913d68/0x16 = 48000 * 384: 406 MHz p-pd*/ + cx18_av_write4(cx, 0x110, 0x005227ad); + + /* src3/4/6_ctl */ + /* 0x1.4faa = (4 * xtal/8*2/455) / 48000 */ + cx18_av_write4(cx, 0x900, 0x08014faa); + cx18_av_write4(cx, 0x904, 0x08014faa); + cx18_av_write4(cx, 0x90c, 0x08014faa); + + /* SA_MCLK_SEL=1, SA_MCLK_DIV=0x16 */ + cx18_av_write(cx, 0x127, 0x56); + + /* AUD_COUNT = 0x5fff = 4 samples * 16 * 384 - 1 */ + cx18_av_write4(cx, 0x12c, 0x11205fff); + + /* + * EN_AV_LOCK = 0 + * VID_COUNT = 0x1193f8 = 143999.000 * 8 = + * ((4 samples/48,000) * (13,500,000 * 8) * 16 - 1) * 8 + */ + cx18_av_write4(cx, 0x128, 0xa01193f8); + break; + } + } else { + switch (freq) { + case 32000: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x0d, AUX PLL Post Divider = 0x30 + */ + cx18_av_write4(cx, 0x108, 0x300d040f); + + /* VID_PLL Fraction = 0x2be2fe */ + /* xtal * 0xf.15f17f0/4 = 108 MHz: 432 MHz pre-postdiv*/ + cx18_av_write4(cx, 0x10c, 0x002be2fe); + + /* AUX_PLL Fraction = 0x176740c */ + /* xtal * 0xd.bb3a060/0x30 = 32000 * 256: 393 MHz p-pd*/ + cx18_av_write4(cx, 0x110, 0x0176740c); + + /* src1_ctl */ + /* 0x1.0000 = 32000/32000 */ + cx18_av_write4(cx, 0x8f8, 0x08010000); + + /* src3/4/6_ctl */ + /* 0x2.0000 = 2 * (32000/32000) */ + cx18_av_write4(cx, 0x900, 0x08020000); + cx18_av_write4(cx, 0x904, 0x08020000); + cx18_av_write4(cx, 0x90c, 0x08020000); + + /* SA_MCLK_SEL=1, SA_MCLK_DIV=0x30 */ + cx18_av_write(cx, 0x127, 0x70); + + /* AUD_COUNT = 0x1fff = 8 samples * 4 * 256 - 1 */ + cx18_av_write4(cx, 0x12c, 0x11201fff); + + /* + * EN_AV_LOCK = 0 + * VID_COUNT = 0x0d2ef8 = 107999.000 * 8 = + * ((8 samples/32,000) * (13,500,000 * 8) * 4 - 1) * 8 + */ + cx18_av_write4(cx, 0x128, 0xa00d2ef8); + break; + + case 44100: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x0e, AUX PLL Post Divider = 0x24 + */ + cx18_av_write4(cx, 0x108, 0x240e040f); + + /* VID_PLL Fraction = 0x2be2fe */ + /* xtal * 0xf.15f17f0/4 = 108 MHz: 432 MHz pre-postdiv*/ + cx18_av_write4(cx, 0x10c, 0x002be2fe); + + /* AUX_PLL Fraction = 0x062a1f2 */ + /* xtal * 0xe.3150f90/0x24 = 44100 * 256: 406 MHz p-pd*/ + cx18_av_write4(cx, 0x110, 0x0062a1f2); + + /* src1_ctl */ + /* 0x1.60cd = 44100/32000 */ + cx18_av_write4(cx, 0x8f8, 0x080160cd); + + /* src3/4/6_ctl */ + /* 0x1.7385 = 2 * (32000/44100) */ + cx18_av_write4(cx, 0x900, 0x08017385); + cx18_av_write4(cx, 0x904, 0x08017385); + cx18_av_write4(cx, 0x90c, 0x08017385); + + /* SA_MCLK_SEL=1, SA_MCLK_DIV=0x24 */ + cx18_av_write(cx, 0x127, 0x64); + + /* AUD_COUNT = 0x61ff = 49 samples * 2 * 256 - 1 */ + cx18_av_write4(cx, 0x12c, 0x112061ff); + + /* + * EN_AV_LOCK = 0 + * VID_COUNT = 0x1d4bf8 = 239999.000 * 8 = + * ((49 samples/44,100) * (13,500,000 * 8) * 2 - 1) * 8 + */ + cx18_av_write4(cx, 0x128, 0xa01d4bf8); + break; + + case 48000: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x0d, AUX PLL Post Divider = 0x20 + */ + cx18_av_write4(cx, 0x108, 0x200d040f); + + /* VID_PLL Fraction = 0x2be2fe */ + /* xtal * 0xf.15f17f0/4 = 108 MHz: 432 MHz pre-postdiv*/ + cx18_av_write4(cx, 0x10c, 0x002be2fe); + + /* AUX_PLL Fraction = 0x176740c */ + /* xtal * 0xd.bb3a060/0x20 = 48000 * 256: 393 MHz p-pd*/ + cx18_av_write4(cx, 0x110, 0x0176740c); + + /* src1_ctl */ + /* 0x1.8000 = 48000/32000 */ + cx18_av_write4(cx, 0x8f8, 0x08018000); + + /* src3/4/6_ctl */ + /* 0x1.5555 = 2 * (32000/48000) */ + cx18_av_write4(cx, 0x900, 0x08015555); + cx18_av_write4(cx, 0x904, 0x08015555); + cx18_av_write4(cx, 0x90c, 0x08015555); + + /* SA_MCLK_SEL=1, SA_MCLK_DIV=0x20 */ + cx18_av_write(cx, 0x127, 0x60); + + /* AUD_COUNT = 0x3fff = 4 samples * 16 * 256 - 1 */ + cx18_av_write4(cx, 0x12c, 0x11203fff); + + /* + * EN_AV_LOCK = 0 + * VID_COUNT = 0x1193f8 = 143999.000 * 8 = + * ((4 samples/48,000) * (13,500,000 * 8) * 16 - 1) * 8 + */ + cx18_av_write4(cx, 0x128, 0xa01193f8); + break; + } + } + + state->audclk_freq = freq; + + return 0; +} + +void cx18_av_audio_set_path(struct cx18 *cx) +{ + struct cx18_av_state *state = &cx->av_state; + u8 v; + + /* stop microcontroller */ + v = cx18_av_read(cx, 0x803) & ~0x10; + cx18_av_write_expect(cx, 0x803, v, v, 0x1f); + + /* assert soft reset */ + v = cx18_av_read(cx, 0x810) | 0x01; + cx18_av_write_expect(cx, 0x810, v, v, 0x0f); + + /* Mute everything to prevent the PFFT! */ + cx18_av_write(cx, 0x8d3, 0x1f); + + if (state->aud_input <= CX18_AV_AUDIO_SERIAL2) { + /* Set Path1 to Serial Audio Input */ + cx18_av_write4(cx, 0x8d0, 0x01011012); + + /* The microcontroller should not be started for the + * non-tuner inputs: autodetection is specific for + * TV audio. */ + } else { + /* Set Path1 to Analog Demod Main Channel */ + cx18_av_write4(cx, 0x8d0, 0x1f063870); + } + + set_audclk_freq(cx, state->audclk_freq); + + /* deassert soft reset */ + v = cx18_av_read(cx, 0x810) & ~0x01; + cx18_av_write_expect(cx, 0x810, v, v, 0x0f); + + if (state->aud_input > CX18_AV_AUDIO_SERIAL2) { + /* When the microcontroller detects the + * audio format, it will unmute the lines */ + v = cx18_av_read(cx, 0x803) | 0x10; + cx18_av_write_expect(cx, 0x803, v, v, 0x1f); + } +} + +static void set_volume(struct cx18 *cx, int volume) +{ + /* First convert the volume to msp3400 values (0-127) */ + int vol = volume >> 9; + /* now scale it up to cx18_av values + * -114dB to -96dB maps to 0 + * this should be 19, but in my testing that was 4dB too loud */ + if (vol <= 23) + vol = 0; + else + vol -= 23; + + /* PATH1_VOLUME */ + cx18_av_write(cx, 0x8d4, 228 - (vol * 2)); +} + +static void set_bass(struct cx18 *cx, int bass) +{ + /* PATH1_EQ_BASS_VOL */ + cx18_av_and_or(cx, 0x8d9, ~0x3f, 48 - (bass * 48 / 0xffff)); +} + +static void set_treble(struct cx18 *cx, int treble) +{ + /* PATH1_EQ_TREBLE_VOL */ + cx18_av_and_or(cx, 0x8db, ~0x3f, 48 - (treble * 48 / 0xffff)); +} + +static void set_balance(struct cx18 *cx, int balance) +{ + int bal = balance >> 8; + if (bal > 0x80) { + /* PATH1_BAL_LEFT */ + cx18_av_and_or(cx, 0x8d5, 0x7f, 0x80); + /* PATH1_BAL_LEVEL */ + cx18_av_and_or(cx, 0x8d5, ~0x7f, bal & 0x7f); + } else { + /* PATH1_BAL_LEFT */ + cx18_av_and_or(cx, 0x8d5, 0x7f, 0x00); + /* PATH1_BAL_LEVEL */ + cx18_av_and_or(cx, 0x8d5, ~0x7f, 0x80 - bal); + } +} + +static void set_mute(struct cx18 *cx, int mute) +{ + struct cx18_av_state *state = &cx->av_state; + u8 v; + + if (state->aud_input > CX18_AV_AUDIO_SERIAL2) { + /* Must turn off microcontroller in order to mute sound. + * Not sure if this is the best method, but it does work. + * If the microcontroller is running, then it will undo any + * changes to the mute register. */ + v = cx18_av_read(cx, 0x803); + if (mute) { + /* disable microcontroller */ + v &= ~0x10; + cx18_av_write_expect(cx, 0x803, v, v, 0x1f); + cx18_av_write(cx, 0x8d3, 0x1f); + } else { + /* enable microcontroller */ + v |= 0x10; + cx18_av_write_expect(cx, 0x803, v, v, 0x1f); + } + } else { + /* SRC1_MUTE_EN */ + cx18_av_and_or(cx, 0x8d3, ~0x2, mute ? 0x02 : 0x00); + } +} + +int cx18_av_s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + struct cx18_av_state *state = &cx->av_state; + int retval; + u8 v; + + if (state->aud_input > CX18_AV_AUDIO_SERIAL2) { + v = cx18_av_read(cx, 0x803) & ~0x10; + cx18_av_write_expect(cx, 0x803, v, v, 0x1f); + cx18_av_write(cx, 0x8d3, 0x1f); + } + v = cx18_av_read(cx, 0x810) | 0x1; + cx18_av_write_expect(cx, 0x810, v, v, 0x0f); + + retval = set_audclk_freq(cx, freq); + + v = cx18_av_read(cx, 0x810) & ~0x1; + cx18_av_write_expect(cx, 0x810, v, v, 0x0f); + if (state->aud_input > CX18_AV_AUDIO_SERIAL2) { + v = cx18_av_read(cx, 0x803) | 0x10; + cx18_av_write_expect(cx, 0x803, v, v, 0x1f); + } + return retval; +} + +static int cx18_av_audio_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct cx18 *cx = v4l2_get_subdevdata(sd); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_VOLUME: + set_volume(cx, ctrl->val); + break; + case V4L2_CID_AUDIO_BASS: + set_bass(cx, ctrl->val); + break; + case V4L2_CID_AUDIO_TREBLE: + set_treble(cx, ctrl->val); + break; + case V4L2_CID_AUDIO_BALANCE: + set_balance(cx, ctrl->val); + break; + case V4L2_CID_AUDIO_MUTE: + set_mute(cx, ctrl->val); + break; + default: + return -EINVAL; + } + return 0; +} + +const struct v4l2_ctrl_ops cx18_av_audio_ctrl_ops = { + .s_ctrl = cx18_av_audio_s_ctrl, +}; diff --git a/drivers/media/pci/cx18/cx18-av-core.c b/drivers/media/pci/cx18/cx18-av-core.c new file mode 100644 index 000000000..4fb19d26e --- /dev/null +++ b/drivers/media/pci/cx18/cx18-av-core.c @@ -0,0 +1,1358 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 ADEC audio functions + * + * Derived from cx25840-core.c + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#include "cx18-driver.h" +#include "cx18-io.h" +#include "cx18-cards.h" + +int cx18_av_write(struct cx18 *cx, u16 addr, u8 value) +{ + u32 reg = 0xc40000 + (addr & ~3); + u32 mask = 0xff; + int shift = (addr & 3) * 8; + u32 x = cx18_read_reg(cx, reg); + + x = (x & ~(mask << shift)) | ((u32)value << shift); + cx18_write_reg(cx, x, reg); + return 0; +} + +int cx18_av_write_expect(struct cx18 *cx, u16 addr, u8 value, u8 eval, u8 mask) +{ + u32 reg = 0xc40000 + (addr & ~3); + int shift = (addr & 3) * 8; + u32 x = cx18_read_reg(cx, reg); + + x = (x & ~((u32)0xff << shift)) | ((u32)value << shift); + cx18_write_reg_expect(cx, x, reg, + ((u32)eval << shift), ((u32)mask << shift)); + return 0; +} + +int cx18_av_write4(struct cx18 *cx, u16 addr, u32 value) +{ + cx18_write_reg(cx, value, 0xc40000 + addr); + return 0; +} + +int +cx18_av_write4_expect(struct cx18 *cx, u16 addr, u32 value, u32 eval, u32 mask) +{ + cx18_write_reg_expect(cx, value, 0xc40000 + addr, eval, mask); + return 0; +} + +int cx18_av_write4_noretry(struct cx18 *cx, u16 addr, u32 value) +{ + cx18_write_reg_noretry(cx, value, 0xc40000 + addr); + return 0; +} + +u8 cx18_av_read(struct cx18 *cx, u16 addr) +{ + u32 x = cx18_read_reg(cx, 0xc40000 + (addr & ~3)); + int shift = (addr & 3) * 8; + + return (x >> shift) & 0xff; +} + +u32 cx18_av_read4(struct cx18 *cx, u16 addr) +{ + return cx18_read_reg(cx, 0xc40000 + addr); +} + +int cx18_av_and_or(struct cx18 *cx, u16 addr, unsigned and_mask, + u8 or_value) +{ + return cx18_av_write(cx, addr, + (cx18_av_read(cx, addr) & and_mask) | + or_value); +} + +int cx18_av_and_or4(struct cx18 *cx, u16 addr, u32 and_mask, + u32 or_value) +{ + return cx18_av_write4(cx, addr, + (cx18_av_read4(cx, addr) & and_mask) | + or_value); +} + +static void cx18_av_init(struct cx18 *cx) +{ + /* + * The crystal freq used in calculations in this driver will be + * 28.636360 MHz. + * Aim to run the PLLs' VCOs near 400 MHz to minimize errors. + */ + + /* + * VDCLK Integer = 0x0f, Post Divider = 0x04 + * AIMCLK Integer = 0x0e, Post Divider = 0x16 + */ + cx18_av_write4(cx, CXADEC_PLL_CTRL1, 0x160e040f); + + /* VDCLK Fraction = 0x2be2fe */ + /* xtal * 0xf.15f17f0/4 = 108 MHz: 432 MHz before post divide */ + cx18_av_write4(cx, CXADEC_VID_PLL_FRAC, 0x002be2fe); + + /* AIMCLK Fraction = 0x05227ad */ + /* xtal * 0xe.2913d68/0x16 = 48000 * 384: 406 MHz pre post-div*/ + cx18_av_write4(cx, CXADEC_AUX_PLL_FRAC, 0x005227ad); + + /* SA_MCLK_SEL=1, SA_MCLK_DIV=0x16 */ + cx18_av_write(cx, CXADEC_I2S_MCLK, 0x56); +} + +static void cx18_av_initialize(struct v4l2_subdev *sd) +{ + struct cx18_av_state *state = to_cx18_av_state(sd); + struct cx18 *cx = v4l2_get_subdevdata(sd); + int default_volume; + u32 v; + + cx18_av_loadfw(cx); + /* Stop 8051 code execution */ + cx18_av_write4_expect(cx, CXADEC_DL_CTL, 0x03000000, + 0x03000000, 0x13000000); + + /* initialize the PLL by toggling sleep bit */ + v = cx18_av_read4(cx, CXADEC_HOST_REG1); + /* enable sleep mode - register appears to be read only... */ + cx18_av_write4_expect(cx, CXADEC_HOST_REG1, v | 1, v, 0xfffe); + /* disable sleep mode */ + cx18_av_write4_expect(cx, CXADEC_HOST_REG1, v & 0xfffe, + v & 0xfffe, 0xffff); + + /* initialize DLLs */ + v = cx18_av_read4(cx, CXADEC_DLL1_DIAG_CTRL) & 0xE1FFFEFF; + /* disable FLD */ + cx18_av_write4(cx, CXADEC_DLL1_DIAG_CTRL, v); + /* enable FLD */ + cx18_av_write4(cx, CXADEC_DLL1_DIAG_CTRL, v | 0x10000100); + + v = cx18_av_read4(cx, CXADEC_DLL2_DIAG_CTRL) & 0xE1FFFEFF; + /* disable FLD */ + cx18_av_write4(cx, CXADEC_DLL2_DIAG_CTRL, v); + /* enable FLD */ + cx18_av_write4(cx, CXADEC_DLL2_DIAG_CTRL, v | 0x06000100); + + /* set analog bias currents. Set Vreg to 1.20V. */ + cx18_av_write4(cx, CXADEC_AFE_DIAG_CTRL1, 0x000A1802); + + v = cx18_av_read4(cx, CXADEC_AFE_DIAG_CTRL3) | 1; + /* enable TUNE_FIL_RST */ + cx18_av_write4_expect(cx, CXADEC_AFE_DIAG_CTRL3, v, v, 0x03009F0F); + /* disable TUNE_FIL_RST */ + cx18_av_write4_expect(cx, CXADEC_AFE_DIAG_CTRL3, + v & 0xFFFFFFFE, v & 0xFFFFFFFE, 0x03009F0F); + + /* enable 656 output */ + cx18_av_and_or4(cx, CXADEC_PIN_CTRL1, ~0, 0x040C00); + + /* video output drive strength */ + cx18_av_and_or4(cx, CXADEC_PIN_CTRL2, ~0, 0x2); + + /* reset video */ + cx18_av_write4(cx, CXADEC_SOFT_RST_CTRL, 0x8000); + cx18_av_write4(cx, CXADEC_SOFT_RST_CTRL, 0); + + /* + * Disable Video Auto-config of the Analog Front End and Video PLL. + * + * Since we only use BT.656 pixel mode, which works for both 525 and 625 + * line systems, it's just easier for us to set registers + * 0x102 (CXADEC_CHIP_CTRL), 0x104-0x106 (CXADEC_AFE_CTRL), + * 0x108-0x109 (CXADEC_PLL_CTRL1), and 0x10c-0x10f (CXADEC_VID_PLL_FRAC) + * ourselves, than to run around cleaning up after the auto-config. + * + * (Note: my CX23418 chip doesn't seem to let the ACFG_DIS bit + * get set to 1, but OTOH, it doesn't seem to do AFE and VID PLL + * autoconfig either.) + * + * As a default, also turn off Dual mode for ADC2 and set ADC2 to CH3. + */ + cx18_av_and_or4(cx, CXADEC_CHIP_CTRL, 0xFFFBFFFF, 0x00120000); + + /* Setup the Video and Aux/Audio PLLs */ + cx18_av_init(cx); + + /* set video to auto-detect */ + /* Clear bits 11-12 to enable slow locking mode. Set autodetect mode */ + /* set the comb notch = 1 */ + cx18_av_and_or4(cx, CXADEC_MODE_CTRL, 0xFFF7E7F0, 0x02040800); + + /* Enable wtw_en in CRUSH_CTRL (Set bit 22) */ + /* Enable maj_sel in CRUSH_CTRL (Set bit 20) */ + cx18_av_and_or4(cx, CXADEC_CRUSH_CTRL, ~0, 0x00500000); + + /* Set VGA_TRACK_RANGE to 0x20 */ + cx18_av_and_or4(cx, CXADEC_DFE_CTRL2, 0xFFFF00FF, 0x00002000); + + /* + * Initial VBI setup + * VIP-1.1, 10 bit mode, enable Raw, disable sliced, + * don't clamp raw samples when codes are in use, 1 byte user D-words, + * IDID0 has line #, RP code V bit transition on VBLANK, data during + * blanking intervals + */ + cx18_av_write4(cx, CXADEC_OUT_CTRL1, 0x4013252e); + + /* Set the video input. + The setting in MODE_CTRL gets lost when we do the above setup */ + /* EncSetSignalStd(dwDevNum, pEnc->dwSigStd); */ + /* EncSetVideoInput(dwDevNum, pEnc->VidIndSelection); */ + + /* + * Analog Front End (AFE) + * Default to luma on ch1/ADC1, chroma on ch2/ADC2, SIF on ch3/ADC2 + * bypass_ch[1-3] use filter + * droop_comp_ch[1-3] disable + * clamp_en_ch[1-3] disable + * aud_in_sel ADC2 + * luma_in_sel ADC1 + * chroma_in_sel ADC2 + * clamp_sel_ch[2-3] midcode + * clamp_sel_ch1 video decoder + * vga_sel_ch3 audio decoder + * vga_sel_ch[1-2] video decoder + * half_bw_ch[1-3] disable + * +12db_ch[1-3] disable + */ + cx18_av_and_or4(cx, CXADEC_AFE_CTRL, 0xFF000000, 0x00005D00); + +/* if(dwEnable && dw3DCombAvailable) { */ +/* CxDevWrReg(CXADEC_SRC_COMB_CFG, 0x7728021F); */ +/* } else { */ +/* CxDevWrReg(CXADEC_SRC_COMB_CFG, 0x6628021F); */ +/* } */ + cx18_av_write4(cx, CXADEC_SRC_COMB_CFG, 0x6628021F); + default_volume = cx18_av_read(cx, 0x8d4); + /* + * Enforce the legacy volume scale mapping limits to avoid + * -ERANGE errors when initializing the volume control + */ + if (default_volume > 228) { + /* Bottom out at -96 dB, v4l2 vol range 0x2e00-0x2fff */ + default_volume = 228; + cx18_av_write(cx, 0x8d4, 228); + } else if (default_volume < 20) { + /* Top out at + 8 dB, v4l2 vol range 0xfe00-0xffff */ + default_volume = 20; + cx18_av_write(cx, 0x8d4, 20); + } + default_volume = (((228 - default_volume) >> 1) + 23) << 9; + state->volume->cur.val = state->volume->default_value = default_volume; + v4l2_ctrl_handler_setup(&state->hdl); +} + +static int cx18_av_reset(struct v4l2_subdev *sd, u32 val) +{ + cx18_av_initialize(sd); + return 0; +} + +static int cx18_av_load_fw(struct v4l2_subdev *sd) +{ + struct cx18_av_state *state = to_cx18_av_state(sd); + + if (!state->is_initialized) { + /* initialize on first use */ + state->is_initialized = 1; + cx18_av_initialize(sd); + } + return 0; +} + +void cx18_av_std_setup(struct cx18 *cx) +{ + struct cx18_av_state *state = &cx->av_state; + struct v4l2_subdev *sd = &state->sd; + v4l2_std_id std = state->std; + + /* + * Video ADC crystal clock to pixel clock SRC decimation ratio + * 28.636360 MHz/13.5 Mpps * 256 = 0x21f.07b + */ + const int src_decimation = 0x21f; + + int hblank, hactive, burst, vblank, vactive, sc; + int vblank656; + int luma_lpf, uv_lpf, comb; + u32 pll_int, pll_frac, pll_post; + + /* datasheet startup, step 8d */ + if (std & ~V4L2_STD_NTSC) + cx18_av_write(cx, 0x49f, 0x11); + else + cx18_av_write(cx, 0x49f, 0x14); + + /* + * Note: At the end of a field, there are 3 sets of half line duration + * (double horizontal rate) pulses: + * + * 5 (625) or 6 (525) half-lines to blank for the vertical retrace + * 5 (625) or 6 (525) vertical sync pulses of half line duration + * 5 (625) or 6 (525) half-lines of equalization pulses + */ + if (std & V4L2_STD_625_50) { + /* + * The following relationships of half line counts should hold: + * 625 = vblank656 + vactive + * 10 = vblank656 - vblank = vsync pulses + equalization pulses + * + * vblank656: half lines after line 625/mid-313 of blanked video + * vblank: half lines, after line 5/317, of blanked video + * vactive: half lines of active video + + * 5 half lines after the end of active video + * + * As far as I can tell: + * vblank656 starts counting from the falling edge of the first + * vsync pulse (start of line 1 or mid-313) + * vblank starts counting from the after the 5 vsync pulses and + * 5 or 4 equalization pulses (start of line 6 or 318) + * + * For 625 line systems the driver will extract VBI information + * from lines 6-23 and lines 318-335 (but the slicer can only + * handle 17 lines, not the 18 in the vblank region). + * In addition, we need vblank656 and vblank to be one whole + * line longer, to cover line 24 and 336, so the SAV/EAV RP + * codes get generated such that the encoder can actually + * extract line 23 & 335 (WSS). We'll lose 1 line in each field + * at the top of the screen. + * + * It appears the 5 half lines that happen after active + * video must be included in vactive (579 instead of 574), + * otherwise the colors get badly displayed in various regions + * of the screen. I guess the chroma comb filter gets confused + * without them (at least when a PVR-350 is the PAL source). + */ + vblank656 = 48; /* lines 1 - 24 & 313 - 336 */ + vblank = 38; /* lines 6 - 24 & 318 - 336 */ + vactive = 579; /* lines 24 - 313 & 337 - 626 */ + + /* + * For a 13.5 Mpps clock and 15,625 Hz line rate, a line is + * 864 pixels = 720 active + 144 blanking. ITU-R BT.601 + * specifies 12 luma clock periods or ~ 0.9 * 13.5 Mpps after + * the end of active video to start a horizontal line, so that + * leaves 132 pixels of hblank to ignore. + */ + hblank = 132; + hactive = 720; + + /* + * Burst gate delay (for 625 line systems) + * Hsync leading edge to color burst rise = 5.6 us + * Color burst width = 2.25 us + * Gate width = 4 pixel clocks + * (5.6 us + 2.25/2 us) * 13.5 Mpps + 4/2 clocks = 92.79 clocks + */ + burst = 93; + luma_lpf = 2; + if (std & V4L2_STD_PAL) { + uv_lpf = 1; + comb = 0x20; + /* sc = 4433618.75 * src_decimation/28636360 * 2^13 */ + sc = 688700; + } else if (std == V4L2_STD_PAL_Nc) { + uv_lpf = 1; + comb = 0x20; + /* sc = 3582056.25 * src_decimation/28636360 * 2^13 */ + sc = 556422; + } else { /* SECAM */ + uv_lpf = 0; + comb = 0; + /* (fr + fb)/2 = (4406260 + 4250000)/2 = 4328130 */ + /* sc = 4328130 * src_decimation/28636360 * 2^13 */ + sc = 672314; + } + } else { + /* + * The following relationships of half line counts should hold: + * 525 = prevsync + vblank656 + vactive + * 12 = vblank656 - vblank = vsync pulses + equalization pulses + * + * prevsync: 6 half-lines before the vsync pulses + * vblank656: half lines, after line 3/mid-266, of blanked video + * vblank: half lines, after line 9/272, of blanked video + * vactive: half lines of active video + * + * As far as I can tell: + * vblank656 starts counting from the falling edge of the first + * vsync pulse (start of line 4 or mid-266) + * vblank starts counting from the after the 6 vsync pulses and + * 6 or 5 equalization pulses (start of line 10 or 272) + * + * For 525 line systems the driver will extract VBI information + * from lines 10-21 and lines 273-284. + */ + vblank656 = 38; /* lines 4 - 22 & 266 - 284 */ + vblank = 26; /* lines 10 - 22 & 272 - 284 */ + vactive = 481; /* lines 23 - 263 & 285 - 525 */ + + /* + * For a 13.5 Mpps clock and 15,734.26 Hz line rate, a line is + * 858 pixels = 720 active + 138 blanking. The Hsync leading + * edge should happen 1.2 us * 13.5 Mpps ~= 16 pixels after the + * end of active video, leaving 122 pixels of hblank to ignore + * before active video starts. + */ + hactive = 720; + hblank = 122; + luma_lpf = 1; + uv_lpf = 1; + + /* + * Burst gate delay (for 525 line systems) + * Hsync leading edge to color burst rise = 5.3 us + * Color burst width = 2.5 us + * Gate width = 4 pixel clocks + * (5.3 us + 2.5/2 us) * 13.5 Mpps + 4/2 clocks = 90.425 clocks + */ + if (std == V4L2_STD_PAL_60) { + burst = 90; + luma_lpf = 2; + comb = 0x20; + /* sc = 4433618.75 * src_decimation/28636360 * 2^13 */ + sc = 688700; + } else if (std == V4L2_STD_PAL_M) { + /* The 97 needs to be verified against PAL-M timings */ + burst = 97; + comb = 0x20; + /* sc = 3575611.49 * src_decimation/28636360 * 2^13 */ + sc = 555421; + } else { + burst = 90; + comb = 0x66; + /* sc = 3579545.45.. * src_decimation/28636360 * 2^13 */ + sc = 556032; + } + } + + /* DEBUG: Displays configured PLL frequency */ + pll_int = cx18_av_read(cx, 0x108); + pll_frac = cx18_av_read4(cx, 0x10c) & 0x1ffffff; + pll_post = cx18_av_read(cx, 0x109); + CX18_DEBUG_INFO_DEV(sd, "PLL regs = int: %u, frac: %u, post: %u\n", + pll_int, pll_frac, pll_post); + + if (pll_post) { + int fsc, pll; + u64 tmp; + + pll = (28636360L * ((((u64)pll_int) << 25) + pll_frac)) >> 25; + pll /= pll_post; + CX18_DEBUG_INFO_DEV(sd, "Video PLL = %d.%06d MHz\n", + pll / 1000000, pll % 1000000); + CX18_DEBUG_INFO_DEV(sd, "Pixel rate = %d.%06d Mpixel/sec\n", + pll / 8000000, (pll / 8) % 1000000); + + CX18_DEBUG_INFO_DEV(sd, "ADC XTAL/pixel clock decimation ratio = %d.%03d\n", + src_decimation / 256, + ((src_decimation % 256) * 1000) / 256); + + tmp = 28636360 * (u64) sc; + do_div(tmp, src_decimation); + fsc = tmp >> 13; + CX18_DEBUG_INFO_DEV(sd, + "Chroma sub-carrier initial freq = %d.%06d MHz\n", + fsc / 1000000, fsc % 1000000); + + CX18_DEBUG_INFO_DEV(sd, + "hblank %i, hactive %i, vblank %i, vactive %i, vblank656 %i, src_dec %i, burst 0x%02x, luma_lpf %i, uv_lpf %i, comb 0x%02x, sc 0x%06x\n", + hblank, hactive, vblank, vactive, vblank656, + src_decimation, burst, luma_lpf, uv_lpf, + comb, sc); + } + + /* Sets horizontal blanking delay and active lines */ + cx18_av_write(cx, 0x470, hblank); + cx18_av_write(cx, 0x471, + (((hblank >> 8) & 0x3) | (hactive << 4)) & 0xff); + cx18_av_write(cx, 0x472, hactive >> 4); + + /* Sets burst gate delay */ + cx18_av_write(cx, 0x473, burst); + + /* Sets vertical blanking delay and active duration */ + cx18_av_write(cx, 0x474, vblank); + cx18_av_write(cx, 0x475, + (((vblank >> 8) & 0x3) | (vactive << 4)) & 0xff); + cx18_av_write(cx, 0x476, vactive >> 4); + cx18_av_write(cx, 0x477, vblank656); + + /* Sets src decimation rate */ + cx18_av_write(cx, 0x478, src_decimation & 0xff); + cx18_av_write(cx, 0x479, (src_decimation >> 8) & 0xff); + + /* Sets Luma and UV Low pass filters */ + cx18_av_write(cx, 0x47a, luma_lpf << 6 | ((uv_lpf << 4) & 0x30)); + + /* Enables comb filters */ + cx18_av_write(cx, 0x47b, comb); + + /* Sets SC Step*/ + cx18_av_write(cx, 0x47c, sc); + cx18_av_write(cx, 0x47d, (sc >> 8) & 0xff); + cx18_av_write(cx, 0x47e, (sc >> 16) & 0xff); + + if (std & V4L2_STD_625_50) { + state->slicer_line_delay = 1; + state->slicer_line_offset = (6 + state->slicer_line_delay - 2); + } else { + state->slicer_line_delay = 0; + state->slicer_line_offset = (10 + state->slicer_line_delay - 2); + } + cx18_av_write(cx, 0x47f, state->slicer_line_delay); +} + +static void input_change(struct cx18 *cx) +{ + struct cx18_av_state *state = &cx->av_state; + v4l2_std_id std = state->std; + u8 v; + + /* Follow step 8c and 8d of section 3.16 in the cx18_av datasheet */ + cx18_av_write(cx, 0x49f, (std & V4L2_STD_NTSC) ? 0x14 : 0x11); + cx18_av_and_or(cx, 0x401, ~0x60, 0); + cx18_av_and_or(cx, 0x401, ~0x60, 0x60); + + if (std & V4L2_STD_525_60) { + if (std == V4L2_STD_NTSC_M_JP) { + /* Japan uses EIAJ audio standard */ + cx18_av_write_expect(cx, 0x808, 0xf7, 0xf7, 0xff); + cx18_av_write_expect(cx, 0x80b, 0x02, 0x02, 0x3f); + } else if (std == V4L2_STD_NTSC_M_KR) { + /* South Korea uses A2 audio standard */ + cx18_av_write_expect(cx, 0x808, 0xf8, 0xf8, 0xff); + cx18_av_write_expect(cx, 0x80b, 0x03, 0x03, 0x3f); + } else { + /* Others use the BTSC audio standard */ + cx18_av_write_expect(cx, 0x808, 0xf6, 0xf6, 0xff); + cx18_av_write_expect(cx, 0x80b, 0x01, 0x01, 0x3f); + } + } else if (std & V4L2_STD_PAL) { + /* Follow tuner change procedure for PAL */ + cx18_av_write_expect(cx, 0x808, 0xff, 0xff, 0xff); + cx18_av_write_expect(cx, 0x80b, 0x03, 0x03, 0x3f); + } else if (std & V4L2_STD_SECAM) { + /* Select autodetect for SECAM */ + cx18_av_write_expect(cx, 0x808, 0xff, 0xff, 0xff); + cx18_av_write_expect(cx, 0x80b, 0x03, 0x03, 0x3f); + } + + v = cx18_av_read(cx, 0x803); + if (v & 0x10) { + /* restart audio decoder microcontroller */ + v &= ~0x10; + cx18_av_write_expect(cx, 0x803, v, v, 0x1f); + v |= 0x10; + cx18_av_write_expect(cx, 0x803, v, v, 0x1f); + } +} + +static int cx18_av_s_frequency(struct v4l2_subdev *sd, + const struct v4l2_frequency *freq) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + input_change(cx); + return 0; +} + +static int set_input(struct cx18 *cx, enum cx18_av_video_input vid_input, + enum cx18_av_audio_input aud_input) +{ + struct cx18_av_state *state = &cx->av_state; + struct v4l2_subdev *sd = &state->sd; + + enum analog_signal_type { + NONE, CVBS, Y, C, SIF, Pb, Pr + } ch[3] = {NONE, NONE, NONE}; + + u8 afe_mux_cfg; + u8 adc2_cfg; + u8 input_mode; + u32 afe_cfg; + int i; + + CX18_DEBUG_INFO_DEV(sd, "decoder set video input %d, audio input %d\n", + vid_input, aud_input); + + if (vid_input >= CX18_AV_COMPOSITE1 && + vid_input <= CX18_AV_COMPOSITE8) { + afe_mux_cfg = 0xf0 + (vid_input - CX18_AV_COMPOSITE1); + ch[0] = CVBS; + input_mode = 0x0; + } else if (vid_input >= CX18_AV_COMPONENT_LUMA1) { + int luma = vid_input & 0xf000; + int r_chroma = vid_input & 0xf0000; + int b_chroma = vid_input & 0xf00000; + + if ((vid_input & ~0xfff000) || + luma < CX18_AV_COMPONENT_LUMA1 || + luma > CX18_AV_COMPONENT_LUMA8 || + r_chroma < CX18_AV_COMPONENT_R_CHROMA4 || + r_chroma > CX18_AV_COMPONENT_R_CHROMA6 || + b_chroma < CX18_AV_COMPONENT_B_CHROMA7 || + b_chroma > CX18_AV_COMPONENT_B_CHROMA8) { + CX18_ERR_DEV(sd, "0x%06x is not a valid video input!\n", + vid_input); + return -EINVAL; + } + afe_mux_cfg = (luma - CX18_AV_COMPONENT_LUMA1) >> 12; + ch[0] = Y; + afe_mux_cfg |= (r_chroma - CX18_AV_COMPONENT_R_CHROMA4) >> 12; + ch[1] = Pr; + afe_mux_cfg |= (b_chroma - CX18_AV_COMPONENT_B_CHROMA7) >> 14; + ch[2] = Pb; + input_mode = 0x6; + } else { + int luma = vid_input & 0xf0; + int chroma = vid_input & 0xf00; + + if ((vid_input & ~0xff0) || + luma < CX18_AV_SVIDEO_LUMA1 || + luma > CX18_AV_SVIDEO_LUMA8 || + chroma < CX18_AV_SVIDEO_CHROMA4 || + chroma > CX18_AV_SVIDEO_CHROMA8) { + CX18_ERR_DEV(sd, "0x%06x is not a valid video input!\n", + vid_input); + return -EINVAL; + } + afe_mux_cfg = 0xf0 + ((luma - CX18_AV_SVIDEO_LUMA1) >> 4); + ch[0] = Y; + if (chroma >= CX18_AV_SVIDEO_CHROMA7) { + afe_mux_cfg &= 0x3f; + afe_mux_cfg |= (chroma - CX18_AV_SVIDEO_CHROMA7) >> 2; + ch[2] = C; + } else { + afe_mux_cfg &= 0xcf; + afe_mux_cfg |= (chroma - CX18_AV_SVIDEO_CHROMA4) >> 4; + ch[1] = C; + } + input_mode = 0x2; + } + + switch (aud_input) { + case CX18_AV_AUDIO_SERIAL1: + case CX18_AV_AUDIO_SERIAL2: + /* do nothing, use serial audio input */ + break; + case CX18_AV_AUDIO4: + afe_mux_cfg &= ~0x30; + ch[1] = SIF; + break; + case CX18_AV_AUDIO5: + afe_mux_cfg = (afe_mux_cfg & ~0x30) | 0x10; + ch[1] = SIF; + break; + case CX18_AV_AUDIO6: + afe_mux_cfg = (afe_mux_cfg & ~0x30) | 0x20; + ch[1] = SIF; + break; + case CX18_AV_AUDIO7: + afe_mux_cfg &= ~0xc0; + ch[2] = SIF; + break; + case CX18_AV_AUDIO8: + afe_mux_cfg = (afe_mux_cfg & ~0xc0) | 0x40; + ch[2] = SIF; + break; + + default: + CX18_ERR_DEV(sd, "0x%04x is not a valid audio input!\n", + aud_input); + return -EINVAL; + } + + /* Set up analog front end multiplexers */ + cx18_av_write_expect(cx, 0x103, afe_mux_cfg, afe_mux_cfg, 0xf7); + /* Set INPUT_MODE to Composite, S-Video, or Component */ + cx18_av_and_or(cx, 0x401, ~0x6, input_mode); + + /* Set CH_SEL_ADC2 to 1 if input comes from CH3 */ + adc2_cfg = cx18_av_read(cx, 0x102); + if (ch[2] == NONE) + adc2_cfg &= ~0x2; /* No sig on CH3, set ADC2 to CH2 for input */ + else + adc2_cfg |= 0x2; /* Signal on CH3, set ADC2 to CH3 for input */ + + /* Set DUAL_MODE_ADC2 to 1 if input comes from both CH2 and CH3 */ + if (ch[1] != NONE && ch[2] != NONE) + adc2_cfg |= 0x4; /* Set dual mode */ + else + adc2_cfg &= ~0x4; /* Clear dual mode */ + cx18_av_write_expect(cx, 0x102, adc2_cfg, adc2_cfg, 0x17); + + /* Configure the analog front end */ + afe_cfg = cx18_av_read4(cx, CXADEC_AFE_CTRL); + afe_cfg &= 0xff000000; + afe_cfg |= 0x00005000; /* CHROMA_IN, AUD_IN: ADC2; LUMA_IN: ADC1 */ + if (ch[1] != NONE && ch[2] != NONE) + afe_cfg |= 0x00000030; /* half_bw_ch[2-3] since in dual mode */ + + for (i = 0; i < 3; i++) { + switch (ch[i]) { + default: + case NONE: + /* CLAMP_SEL = Fixed to midcode clamp level */ + afe_cfg |= (0x00000200 << i); + break; + case CVBS: + case Y: + if (i > 0) + afe_cfg |= 0x00002000; /* LUMA_IN_SEL: ADC2 */ + break; + case C: + case Pb: + case Pr: + /* CLAMP_SEL = Fixed to midcode clamp level */ + afe_cfg |= (0x00000200 << i); + if (i == 0 && ch[i] == C) + afe_cfg &= ~0x00001000; /* CHROMA_IN_SEL ADC1 */ + break; + case SIF: + /* + * VGA_GAIN_SEL = Audio Decoder + * CLAMP_SEL = Fixed to midcode clamp level + */ + afe_cfg |= (0x00000240 << i); + if (i == 0) + afe_cfg &= ~0x00004000; /* AUD_IN_SEL ADC1 */ + break; + } + } + + cx18_av_write4(cx, CXADEC_AFE_CTRL, afe_cfg); + + state->vid_input = vid_input; + state->aud_input = aud_input; + cx18_av_audio_set_path(cx); + input_change(cx); + return 0; +} + +static int cx18_av_s_video_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct cx18_av_state *state = to_cx18_av_state(sd); + struct cx18 *cx = v4l2_get_subdevdata(sd); + return set_input(cx, input, state->aud_input); +} + +static int cx18_av_s_audio_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct cx18_av_state *state = to_cx18_av_state(sd); + struct cx18 *cx = v4l2_get_subdevdata(sd); + return set_input(cx, state->vid_input, input); +} + +static int cx18_av_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct cx18_av_state *state = to_cx18_av_state(sd); + struct cx18 *cx = v4l2_get_subdevdata(sd); + u8 vpres; + u8 mode; + int val = 0; + + if (state->radio) + return 0; + + vpres = cx18_av_read(cx, 0x40e) & 0x20; + vt->signal = vpres ? 0xffff : 0x0; + + vt->capability |= + V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 | + V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP; + + mode = cx18_av_read(cx, 0x804); + + /* get rxsubchans and audmode */ + if ((mode & 0xf) == 1) + val |= V4L2_TUNER_SUB_STEREO; + else + val |= V4L2_TUNER_SUB_MONO; + + if (mode == 2 || mode == 4) + val = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + + if (mode & 0x10) + val |= V4L2_TUNER_SUB_SAP; + + vt->rxsubchans = val; + vt->audmode = state->audmode; + return 0; +} + +static int cx18_av_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt) +{ + struct cx18_av_state *state = to_cx18_av_state(sd); + struct cx18 *cx = v4l2_get_subdevdata(sd); + u8 v; + + if (state->radio) + return 0; + + v = cx18_av_read(cx, 0x809); + v &= ~0xf; + + switch (vt->audmode) { + case V4L2_TUNER_MODE_MONO: + /* mono -> mono + stereo -> mono + bilingual -> lang1 */ + break; + case V4L2_TUNER_MODE_STEREO: + case V4L2_TUNER_MODE_LANG1: + /* mono -> mono + stereo -> stereo + bilingual -> lang1 */ + v |= 0x4; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + /* mono -> mono + stereo -> stereo + bilingual -> lang1/lang2 */ + v |= 0x7; + break; + case V4L2_TUNER_MODE_LANG2: + /* mono -> mono + stereo -> stereo + bilingual -> lang2 */ + v |= 0x1; + break; + default: + return -EINVAL; + } + cx18_av_write_expect(cx, 0x809, v, v, 0xff); + state->audmode = vt->audmode; + return 0; +} + +static int cx18_av_s_std(struct v4l2_subdev *sd, v4l2_std_id norm) +{ + struct cx18_av_state *state = to_cx18_av_state(sd); + struct cx18 *cx = v4l2_get_subdevdata(sd); + + u8 fmt = 0; /* zero is autodetect */ + u8 pal_m = 0; + + if (state->radio == 0 && state->std == norm) + return 0; + + state->radio = 0; + state->std = norm; + + /* First tests should be against specific std */ + if (state->std == V4L2_STD_NTSC_M_JP) { + fmt = 0x2; + } else if (state->std == V4L2_STD_NTSC_443) { + fmt = 0x3; + } else if (state->std == V4L2_STD_PAL_M) { + pal_m = 1; + fmt = 0x5; + } else if (state->std == V4L2_STD_PAL_N) { + fmt = 0x6; + } else if (state->std == V4L2_STD_PAL_Nc) { + fmt = 0x7; + } else if (state->std == V4L2_STD_PAL_60) { + fmt = 0x8; + } else { + /* Then, test against generic ones */ + if (state->std & V4L2_STD_NTSC) + fmt = 0x1; + else if (state->std & V4L2_STD_PAL) + fmt = 0x4; + else if (state->std & V4L2_STD_SECAM) + fmt = 0xc; + } + + CX18_DEBUG_INFO_DEV(sd, "changing video std to fmt %i\n", fmt); + + /* Follow step 9 of section 3.16 in the cx18_av datasheet. + Without this PAL may display a vertical ghosting effect. + This happens for example with the Yuan MPC622. */ + if (fmt >= 4 && fmt < 8) { + /* Set format to NTSC-M */ + cx18_av_and_or(cx, 0x400, ~0xf, 1); + /* Turn off LCOMB */ + cx18_av_and_or(cx, 0x47b, ~6, 0); + } + cx18_av_and_or(cx, 0x400, ~0x2f, fmt | 0x20); + cx18_av_and_or(cx, 0x403, ~0x3, pal_m); + cx18_av_std_setup(cx); + input_change(cx); + return 0; +} + +static int cx18_av_s_radio(struct v4l2_subdev *sd) +{ + struct cx18_av_state *state = to_cx18_av_state(sd); + state->radio = 1; + return 0; +} + +static int cx18_av_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct cx18 *cx = v4l2_get_subdevdata(sd); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + cx18_av_write(cx, 0x414, ctrl->val - 128); + break; + + case V4L2_CID_CONTRAST: + cx18_av_write(cx, 0x415, ctrl->val << 1); + break; + + case V4L2_CID_SATURATION: + cx18_av_write(cx, 0x420, ctrl->val << 1); + cx18_av_write(cx, 0x421, ctrl->val << 1); + break; + + case V4L2_CID_HUE: + cx18_av_write(cx, 0x422, ctrl->val); + break; + + default: + return -EINVAL; + } + return 0; +} + +static int cx18_av_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *fmt = &format->format; + struct cx18_av_state *state = to_cx18_av_state(sd); + struct cx18 *cx = v4l2_get_subdevdata(sd); + int HSC, VSC, Vsrc, Hsrc, filter, Vlines; + int is_50Hz = !(state->std & V4L2_STD_525_60); + + if (format->pad || fmt->code != MEDIA_BUS_FMT_FIXED) + return -EINVAL; + + fmt->field = V4L2_FIELD_INTERLACED; + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + + Vsrc = (cx18_av_read(cx, 0x476) & 0x3f) << 4; + Vsrc |= (cx18_av_read(cx, 0x475) & 0xf0) >> 4; + + Hsrc = (cx18_av_read(cx, 0x472) & 0x3f) << 4; + Hsrc |= (cx18_av_read(cx, 0x471) & 0xf0) >> 4; + + /* + * This adjustment reflects the excess of vactive, set in + * cx18_av_std_setup(), above standard values: + * + * 480 + 1 for 60 Hz systems + * 576 + 3 for 50 Hz systems + */ + Vlines = fmt->height + (is_50Hz ? 3 : 1); + + /* + * Invalid height and width scaling requests are: + * 1. width less than 1/16 of the source width + * 2. width greater than the source width + * 3. height less than 1/8 of the source height + * 4. height greater than the source height + */ + if ((fmt->width * 16 < Hsrc) || (Hsrc < fmt->width) || + (Vlines * 8 < Vsrc) || (Vsrc < Vlines)) { + CX18_ERR_DEV(sd, "%dx%d is not a valid size!\n", + fmt->width, fmt->height); + return -ERANGE; + } + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + return 0; + + HSC = (Hsrc * (1 << 20)) / fmt->width - (1 << 20); + VSC = (1 << 16) - (Vsrc * (1 << 9) / Vlines - (1 << 9)); + VSC &= 0x1fff; + + if (fmt->width >= 385) + filter = 0; + else if (fmt->width > 192) + filter = 1; + else if (fmt->width > 96) + filter = 2; + else + filter = 3; + + CX18_DEBUG_INFO_DEV(sd, + "decoder set size %dx%d -> scale %ux%u\n", + fmt->width, fmt->height, HSC, VSC); + + /* HSCALE=HSC */ + cx18_av_write(cx, 0x418, HSC & 0xff); + cx18_av_write(cx, 0x419, (HSC >> 8) & 0xff); + cx18_av_write(cx, 0x41a, HSC >> 16); + /* VSCALE=VSC */ + cx18_av_write(cx, 0x41c, VSC & 0xff); + cx18_av_write(cx, 0x41d, VSC >> 8); + /* VS_INTRLACE=1 VFILT=filter */ + cx18_av_write(cx, 0x41e, 0x8 | filter); + return 0; +} + +static int cx18_av_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + + CX18_DEBUG_INFO_DEV(sd, "%s output\n", enable ? "enable" : "disable"); + if (enable) { + cx18_av_write(cx, 0x115, 0x8c); + cx18_av_write(cx, 0x116, 0x07); + } else { + cx18_av_write(cx, 0x115, 0x00); + cx18_av_write(cx, 0x116, 0x00); + } + return 0; +} + +static void log_video_status(struct cx18 *cx) +{ + static const char *const fmt_strs[] = { + "0x0", + "NTSC-M", "NTSC-J", "NTSC-4.43", + "PAL-BDGHI", "PAL-M", "PAL-N", "PAL-Nc", "PAL-60", + "0x9", "0xA", "0xB", + "SECAM", + "0xD", "0xE", "0xF" + }; + + struct cx18_av_state *state = &cx->av_state; + struct v4l2_subdev *sd = &state->sd; + u8 vidfmt_sel = cx18_av_read(cx, 0x400) & 0xf; + u8 gen_stat1 = cx18_av_read(cx, 0x40d); + u8 gen_stat2 = cx18_av_read(cx, 0x40e); + int vid_input = state->vid_input; + + CX18_INFO_DEV(sd, "Video signal: %spresent\n", + (gen_stat2 & 0x20) ? "" : "not "); + CX18_INFO_DEV(sd, "Detected format: %s\n", + fmt_strs[gen_stat1 & 0xf]); + + CX18_INFO_DEV(sd, "Specified standard: %s\n", + vidfmt_sel ? fmt_strs[vidfmt_sel] + : "automatic detection"); + + if (vid_input >= CX18_AV_COMPOSITE1 && + vid_input <= CX18_AV_COMPOSITE8) { + CX18_INFO_DEV(sd, "Specified video input: Composite %d\n", + vid_input - CX18_AV_COMPOSITE1 + 1); + } else { + CX18_INFO_DEV(sd, "Specified video input: S-Video (Luma In%d, Chroma In%d)\n", + (vid_input & 0xf0) >> 4, + (vid_input & 0xf00) >> 8); + } + + CX18_INFO_DEV(sd, "Specified audioclock freq: %d Hz\n", + state->audclk_freq); +} + +static void log_audio_status(struct cx18 *cx) +{ + struct cx18_av_state *state = &cx->av_state; + struct v4l2_subdev *sd = &state->sd; + u8 download_ctl = cx18_av_read(cx, 0x803); + u8 mod_det_stat0 = cx18_av_read(cx, 0x804); + u8 mod_det_stat1 = cx18_av_read(cx, 0x805); + u8 audio_config = cx18_av_read(cx, 0x808); + u8 pref_mode = cx18_av_read(cx, 0x809); + u8 afc0 = cx18_av_read(cx, 0x80b); + u8 mute_ctl = cx18_av_read(cx, 0x8d3); + int aud_input = state->aud_input; + char *p; + + switch (mod_det_stat0) { + case 0x00: p = "mono"; break; + case 0x01: p = "stereo"; break; + case 0x02: p = "dual"; break; + case 0x04: p = "tri"; break; + case 0x10: p = "mono with SAP"; break; + case 0x11: p = "stereo with SAP"; break; + case 0x12: p = "dual with SAP"; break; + case 0x14: p = "tri with SAP"; break; + case 0xfe: p = "forced mode"; break; + default: p = "not defined"; break; + } + CX18_INFO_DEV(sd, "Detected audio mode: %s\n", p); + + switch (mod_det_stat1) { + case 0x00: p = "not defined"; break; + case 0x01: p = "EIAJ"; break; + case 0x02: p = "A2-M"; break; + case 0x03: p = "A2-BG"; break; + case 0x04: p = "A2-DK1"; break; + case 0x05: p = "A2-DK2"; break; + case 0x06: p = "A2-DK3"; break; + case 0x07: p = "A1 (6.0 MHz FM Mono)"; break; + case 0x08: p = "AM-L"; break; + case 0x09: p = "NICAM-BG"; break; + case 0x0a: p = "NICAM-DK"; break; + case 0x0b: p = "NICAM-I"; break; + case 0x0c: p = "NICAM-L"; break; + case 0x0d: p = "BTSC/EIAJ/A2-M Mono (4.5 MHz FMMono)"; break; + case 0x0e: p = "IF FM Radio"; break; + case 0x0f: p = "BTSC"; break; + case 0x10: p = "detected chrominance"; break; + case 0xfd: p = "unknown audio standard"; break; + case 0xfe: p = "forced audio standard"; break; + case 0xff: p = "no detected audio standard"; break; + default: p = "not defined"; break; + } + CX18_INFO_DEV(sd, "Detected audio standard: %s\n", p); + CX18_INFO_DEV(sd, "Audio muted: %s\n", + (mute_ctl & 0x2) ? "yes" : "no"); + CX18_INFO_DEV(sd, "Audio microcontroller: %s\n", + (download_ctl & 0x10) ? "running" : "stopped"); + + switch (audio_config >> 4) { + case 0x00: p = "undefined"; break; + case 0x01: p = "BTSC"; break; + case 0x02: p = "EIAJ"; break; + case 0x03: p = "A2-M"; break; + case 0x04: p = "A2-BG"; break; + case 0x05: p = "A2-DK1"; break; + case 0x06: p = "A2-DK2"; break; + case 0x07: p = "A2-DK3"; break; + case 0x08: p = "A1 (6.0 MHz FM Mono)"; break; + case 0x09: p = "AM-L"; break; + case 0x0a: p = "NICAM-BG"; break; + case 0x0b: p = "NICAM-DK"; break; + case 0x0c: p = "NICAM-I"; break; + case 0x0d: p = "NICAM-L"; break; + case 0x0e: p = "FM radio"; break; + case 0x0f: p = "automatic detection"; break; + default: p = "undefined"; break; + } + CX18_INFO_DEV(sd, "Configured audio standard: %s\n", p); + + if ((audio_config >> 4) < 0xF) { + switch (audio_config & 0xF) { + case 0x00: p = "MONO1 (LANGUAGE A/Mono L+R channel for BTSC, EIAJ, A2)"; break; + case 0x01: p = "MONO2 (LANGUAGE B)"; break; + case 0x02: p = "MONO3 (STEREO forced MONO)"; break; + case 0x03: p = "MONO4 (NICAM ANALOG-Language C/Analog Fallback)"; break; + case 0x04: p = "STEREO"; break; + case 0x05: p = "DUAL1 (AC)"; break; + case 0x06: p = "DUAL2 (BC)"; break; + case 0x07: p = "DUAL3 (AB)"; break; + default: p = "undefined"; + } + CX18_INFO_DEV(sd, "Configured audio mode: %s\n", p); + } else { + switch (audio_config & 0xF) { + case 0x00: p = "BG"; break; + case 0x01: p = "DK1"; break; + case 0x02: p = "DK2"; break; + case 0x03: p = "DK3"; break; + case 0x04: p = "I"; break; + case 0x05: p = "L"; break; + case 0x06: p = "BTSC"; break; + case 0x07: p = "EIAJ"; break; + case 0x08: p = "A2-M"; break; + case 0x09: p = "FM Radio (4.5 MHz)"; break; + case 0x0a: p = "FM Radio (5.5 MHz)"; break; + case 0x0b: p = "S-Video"; break; + case 0x0f: p = "automatic standard and mode detection"; break; + default: p = "undefined"; break; + } + CX18_INFO_DEV(sd, "Configured audio system: %s\n", p); + } + + if (aud_input) + CX18_INFO_DEV(sd, "Specified audio input: Tuner (In%d)\n", + aud_input); + else + CX18_INFO_DEV(sd, "Specified audio input: External\n"); + + switch (pref_mode & 0xf) { + case 0: p = "mono/language A"; break; + case 1: p = "language B"; break; + case 2: p = "language C"; break; + case 3: p = "analog fallback"; break; + case 4: p = "stereo"; break; + case 5: p = "language AC"; break; + case 6: p = "language BC"; break; + case 7: p = "language AB"; break; + default: p = "undefined"; break; + } + CX18_INFO_DEV(sd, "Preferred audio mode: %s\n", p); + + if ((audio_config & 0xf) == 0xf) { + switch ((afc0 >> 3) & 0x1) { + case 0: p = "system DK"; break; + case 1: p = "system L"; break; + } + CX18_INFO_DEV(sd, "Selected 65 MHz format: %s\n", p); + + switch (afc0 & 0x7) { + case 0: p = "Chroma"; break; + case 1: p = "BTSC"; break; + case 2: p = "EIAJ"; break; + case 3: p = "A2-M"; break; + case 4: p = "autodetect"; break; + default: p = "undefined"; break; + } + CX18_INFO_DEV(sd, "Selected 45 MHz format: %s\n", p); + } +} + +static int cx18_av_log_status(struct v4l2_subdev *sd) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + log_video_status(cx); + log_audio_status(cx); + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int cx18_av_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + + if ((reg->reg & 0x3) != 0) + return -EINVAL; + reg->size = 4; + reg->val = cx18_av_read4(cx, reg->reg & 0x00000ffc); + return 0; +} + +static int cx18_av_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + + if ((reg->reg & 0x3) != 0) + return -EINVAL; + cx18_av_write4(cx, reg->reg & 0x00000ffc, reg->val); + return 0; +} +#endif + +static const struct v4l2_ctrl_ops cx18_av_ctrl_ops = { + .s_ctrl = cx18_av_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops cx18_av_general_ops = { + .log_status = cx18_av_log_status, + .load_fw = cx18_av_load_fw, + .reset = cx18_av_reset, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = cx18_av_g_register, + .s_register = cx18_av_s_register, +#endif +}; + +static const struct v4l2_subdev_tuner_ops cx18_av_tuner_ops = { + .s_radio = cx18_av_s_radio, + .s_frequency = cx18_av_s_frequency, + .g_tuner = cx18_av_g_tuner, + .s_tuner = cx18_av_s_tuner, +}; + +static const struct v4l2_subdev_audio_ops cx18_av_audio_ops = { + .s_clock_freq = cx18_av_s_clock_freq, + .s_routing = cx18_av_s_audio_routing, +}; + +static const struct v4l2_subdev_video_ops cx18_av_video_ops = { + .s_std = cx18_av_s_std, + .s_routing = cx18_av_s_video_routing, + .s_stream = cx18_av_s_stream, +}; + +static const struct v4l2_subdev_vbi_ops cx18_av_vbi_ops = { + .decode_vbi_line = cx18_av_decode_vbi_line, + .g_sliced_fmt = cx18_av_g_sliced_fmt, + .s_sliced_fmt = cx18_av_s_sliced_fmt, + .s_raw_fmt = cx18_av_s_raw_fmt, +}; + +static const struct v4l2_subdev_pad_ops cx18_av_pad_ops = { + .set_fmt = cx18_av_set_fmt, +}; + +static const struct v4l2_subdev_ops cx18_av_ops = { + .core = &cx18_av_general_ops, + .tuner = &cx18_av_tuner_ops, + .audio = &cx18_av_audio_ops, + .video = &cx18_av_video_ops, + .vbi = &cx18_av_vbi_ops, + .pad = &cx18_av_pad_ops, +}; + +int cx18_av_probe(struct cx18 *cx) +{ + struct cx18_av_state *state = &cx->av_state; + struct v4l2_subdev *sd; + int err; + + state->rev = cx18_av_read4(cx, CXADEC_CHIP_CTRL) & 0xffff; + + state->vid_input = CX18_AV_COMPOSITE7; + state->aud_input = CX18_AV_AUDIO8; + state->audclk_freq = 48000; + state->audmode = V4L2_TUNER_MODE_LANG1; + state->slicer_line_delay = 0; + state->slicer_line_offset = (10 + state->slicer_line_delay - 2); + + sd = &state->sd; + v4l2_subdev_init(sd, &cx18_av_ops); + v4l2_set_subdevdata(sd, cx); + snprintf(sd->name, sizeof(sd->name), + "%s %03x", cx->v4l2_dev.name, (state->rev >> 4)); + sd->grp_id = CX18_HW_418_AV; + v4l2_ctrl_handler_init(&state->hdl, 9); + v4l2_ctrl_new_std(&state->hdl, &cx18_av_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(&state->hdl, &cx18_av_ctrl_ops, + V4L2_CID_CONTRAST, 0, 127, 1, 64); + v4l2_ctrl_new_std(&state->hdl, &cx18_av_ctrl_ops, + V4L2_CID_SATURATION, 0, 127, 1, 64); + v4l2_ctrl_new_std(&state->hdl, &cx18_av_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + + state->volume = v4l2_ctrl_new_std(&state->hdl, + &cx18_av_audio_ctrl_ops, V4L2_CID_AUDIO_VOLUME, + 0, 65535, 65535 / 100, 0); + v4l2_ctrl_new_std(&state->hdl, + &cx18_av_audio_ctrl_ops, V4L2_CID_AUDIO_MUTE, + 0, 1, 1, 0); + v4l2_ctrl_new_std(&state->hdl, &cx18_av_audio_ctrl_ops, + V4L2_CID_AUDIO_BALANCE, + 0, 65535, 65535 / 100, 32768); + v4l2_ctrl_new_std(&state->hdl, &cx18_av_audio_ctrl_ops, + V4L2_CID_AUDIO_BASS, + 0, 65535, 65535 / 100, 32768); + v4l2_ctrl_new_std(&state->hdl, &cx18_av_audio_ctrl_ops, + V4L2_CID_AUDIO_TREBLE, + 0, 65535, 65535 / 100, 32768); + sd->ctrl_handler = &state->hdl; + if (state->hdl.error) { + int err = state->hdl.error; + + v4l2_ctrl_handler_free(&state->hdl); + return err; + } + err = v4l2_device_register_subdev(&cx->v4l2_dev, sd); + if (err) + v4l2_ctrl_handler_free(&state->hdl); + else + cx18_av_init(cx); + return err; +} diff --git a/drivers/media/pci/cx18/cx18-av-core.h b/drivers/media/pci/cx18/cx18-av-core.h new file mode 100644 index 000000000..71ac9d7af --- /dev/null +++ b/drivers/media/pci/cx18/cx18-av-core.h @@ -0,0 +1,376 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 ADEC header + * + * Derived from cx25840-core.h + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#ifndef _CX18_AV_CORE_H_ +#define _CX18_AV_CORE_H_ + +#include +#include + +struct cx18; + +enum cx18_av_video_input { + /* Composite video inputs In1-In8 */ + CX18_AV_COMPOSITE1 = 1, + CX18_AV_COMPOSITE2, + CX18_AV_COMPOSITE3, + CX18_AV_COMPOSITE4, + CX18_AV_COMPOSITE5, + CX18_AV_COMPOSITE6, + CX18_AV_COMPOSITE7, + CX18_AV_COMPOSITE8, + + /* S-Video inputs consist of one luma input (In1-In8) ORed with one + chroma input (In5-In8) */ + CX18_AV_SVIDEO_LUMA1 = 0x10, + CX18_AV_SVIDEO_LUMA2 = 0x20, + CX18_AV_SVIDEO_LUMA3 = 0x30, + CX18_AV_SVIDEO_LUMA4 = 0x40, + CX18_AV_SVIDEO_LUMA5 = 0x50, + CX18_AV_SVIDEO_LUMA6 = 0x60, + CX18_AV_SVIDEO_LUMA7 = 0x70, + CX18_AV_SVIDEO_LUMA8 = 0x80, + CX18_AV_SVIDEO_CHROMA4 = 0x400, + CX18_AV_SVIDEO_CHROMA5 = 0x500, + CX18_AV_SVIDEO_CHROMA6 = 0x600, + CX18_AV_SVIDEO_CHROMA7 = 0x700, + CX18_AV_SVIDEO_CHROMA8 = 0x800, + + /* S-Video aliases for common luma/chroma combinations */ + CX18_AV_SVIDEO1 = 0x510, + CX18_AV_SVIDEO2 = 0x620, + CX18_AV_SVIDEO3 = 0x730, + CX18_AV_SVIDEO4 = 0x840, + + /* Component Video inputs consist of one luma input (In1-In8) ORed + with a red chroma (In4-In6) and blue chroma input (In7-In8) */ + CX18_AV_COMPONENT_LUMA1 = 0x1000, + CX18_AV_COMPONENT_LUMA2 = 0x2000, + CX18_AV_COMPONENT_LUMA3 = 0x3000, + CX18_AV_COMPONENT_LUMA4 = 0x4000, + CX18_AV_COMPONENT_LUMA5 = 0x5000, + CX18_AV_COMPONENT_LUMA6 = 0x6000, + CX18_AV_COMPONENT_LUMA7 = 0x7000, + CX18_AV_COMPONENT_LUMA8 = 0x8000, + CX18_AV_COMPONENT_R_CHROMA4 = 0x40000, + CX18_AV_COMPONENT_R_CHROMA5 = 0x50000, + CX18_AV_COMPONENT_R_CHROMA6 = 0x60000, + CX18_AV_COMPONENT_B_CHROMA7 = 0x700000, + CX18_AV_COMPONENT_B_CHROMA8 = 0x800000, + + /* Component Video aliases for common combinations */ + CX18_AV_COMPONENT1 = 0x861000, +}; + +enum cx18_av_audio_input { + /* Audio inputs: serial or In4-In8 */ + CX18_AV_AUDIO_SERIAL1, + CX18_AV_AUDIO_SERIAL2, + CX18_AV_AUDIO4 = 4, + CX18_AV_AUDIO5, + CX18_AV_AUDIO6, + CX18_AV_AUDIO7, + CX18_AV_AUDIO8, +}; + +struct cx18_av_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *volume; + int radio; + v4l2_std_id std; + enum cx18_av_video_input vid_input; + enum cx18_av_audio_input aud_input; + u32 audclk_freq; + int audmode; + u32 rev; + int is_initialized; + + /* + * The VBI slicer starts operating and counting lines, beginning at + * slicer line count of 1, at D lines after the deassertion of VRESET. + * This staring field line, S, is 6 (& 319) or 10 (& 273) for 625 or 525 + * line systems respectively. Sliced ancillary data captured on VBI + * slicer line M is inserted after the VBI slicer is done with line M, + * when VBI slicer line count is N = M+1. Thus when the VBI slicer + * reports a VBI slicer line number with ancillary data, the IDID0 byte + * indicates VBI slicer line N. The actual field line that the captured + * data comes from is + * + * L = M+(S+D-1) = N-1+(S+D-1) = N + (S+D-2). + * + * L is the line in the field, not frame, from which the VBI data came. + * N is the line reported by the slicer in the ancillary data. + * D is the slicer_line_delay value programmed into register 0x47f. + * S is 6 for 625 line systems or 10 for 525 line systems + * (S+D-2) is the slicer_line_offset used to convert slicer reported + * line counts to actual field lines. + */ + int slicer_line_delay; + int slicer_line_offset; +}; + + +/* Registers */ +#define CXADEC_CHIP_TYPE_TIGER 0x837 +#define CXADEC_CHIP_TYPE_MAKO 0x843 + +#define CXADEC_HOST_REG1 0x000 +#define CXADEC_HOST_REG2 0x001 + +#define CXADEC_CHIP_CTRL 0x100 +#define CXADEC_AFE_CTRL 0x104 +#define CXADEC_PLL_CTRL1 0x108 +#define CXADEC_VID_PLL_FRAC 0x10C +#define CXADEC_AUX_PLL_FRAC 0x110 +#define CXADEC_PIN_CTRL1 0x114 +#define CXADEC_PIN_CTRL2 0x118 +#define CXADEC_PIN_CFG1 0x11C +#define CXADEC_PIN_CFG2 0x120 + +#define CXADEC_PIN_CFG3 0x124 +#define CXADEC_I2S_MCLK 0x127 + +#define CXADEC_AUD_LOCK1 0x128 +#define CXADEC_AUD_LOCK2 0x12C +#define CXADEC_POWER_CTRL 0x130 +#define CXADEC_AFE_DIAG_CTRL1 0x134 +#define CXADEC_AFE_DIAG_CTRL2 0x138 +#define CXADEC_AFE_DIAG_CTRL3 0x13C +#define CXADEC_PLL_DIAG_CTRL 0x140 +#define CXADEC_TEST_CTRL1 0x144 +#define CXADEC_TEST_CTRL2 0x148 +#define CXADEC_BIST_STAT 0x14C +#define CXADEC_DLL1_DIAG_CTRL 0x158 +#define CXADEC_DLL2_DIAG_CTRL 0x15C + +/* IR registers */ +#define CXADEC_IR_CTRL_REG 0x200 +#define CXADEC_IR_TXCLK_REG 0x204 +#define CXADEC_IR_RXCLK_REG 0x208 +#define CXADEC_IR_CDUTY_REG 0x20C +#define CXADEC_IR_STAT_REG 0x210 +#define CXADEC_IR_IRQEN_REG 0x214 +#define CXADEC_IR_FILTER_REG 0x218 +#define CXADEC_IR_FIFO_REG 0x21C + +/* Video Registers */ +#define CXADEC_MODE_CTRL 0x400 +#define CXADEC_OUT_CTRL1 0x404 +#define CXADEC_OUT_CTRL2 0x408 +#define CXADEC_GEN_STAT 0x40C +#define CXADEC_INT_STAT_MASK 0x410 +#define CXADEC_LUMA_CTRL 0x414 + +#define CXADEC_BRIGHTNESS_CTRL_BYTE 0x414 +#define CXADEC_CONTRAST_CTRL_BYTE 0x415 +#define CXADEC_LUMA_CTRL_BYTE_3 0x416 + +#define CXADEC_HSCALE_CTRL 0x418 +#define CXADEC_VSCALE_CTRL 0x41C + +#define CXADEC_CHROMA_CTRL 0x420 + +#define CXADEC_USAT_CTRL_BYTE 0x420 +#define CXADEC_VSAT_CTRL_BYTE 0x421 +#define CXADEC_HUE_CTRL_BYTE 0x422 + +#define CXADEC_VBI_LINE_CTRL1 0x424 +#define CXADEC_VBI_LINE_CTRL2 0x428 +#define CXADEC_VBI_LINE_CTRL3 0x42C +#define CXADEC_VBI_LINE_CTRL4 0x430 +#define CXADEC_VBI_LINE_CTRL5 0x434 +#define CXADEC_VBI_FC_CFG 0x438 +#define CXADEC_VBI_MISC_CFG1 0x43C +#define CXADEC_VBI_MISC_CFG2 0x440 +#define CXADEC_VBI_PAY1 0x444 +#define CXADEC_VBI_PAY2 0x448 +#define CXADEC_VBI_CUST1_CFG1 0x44C +#define CXADEC_VBI_CUST1_CFG2 0x450 +#define CXADEC_VBI_CUST1_CFG3 0x454 +#define CXADEC_VBI_CUST2_CFG1 0x458 +#define CXADEC_VBI_CUST2_CFG2 0x45C +#define CXADEC_VBI_CUST2_CFG3 0x460 +#define CXADEC_VBI_CUST3_CFG1 0x464 +#define CXADEC_VBI_CUST3_CFG2 0x468 +#define CXADEC_VBI_CUST3_CFG3 0x46C +#define CXADEC_HORIZ_TIM_CTRL 0x470 +#define CXADEC_VERT_TIM_CTRL 0x474 +#define CXADEC_SRC_COMB_CFG 0x478 +#define CXADEC_CHROMA_VBIOFF_CFG 0x47C +#define CXADEC_FIELD_COUNT 0x480 +#define CXADEC_MISC_TIM_CTRL 0x484 +#define CXADEC_DFE_CTRL1 0x488 +#define CXADEC_DFE_CTRL2 0x48C +#define CXADEC_DFE_CTRL3 0x490 +#define CXADEC_PLL_CTRL2 0x494 +#define CXADEC_HTL_CTRL 0x498 +#define CXADEC_COMB_CTRL 0x49C +#define CXADEC_CRUSH_CTRL 0x4A0 +#define CXADEC_SOFT_RST_CTRL 0x4A4 +#define CXADEC_MV_DT_CTRL2 0x4A8 +#define CXADEC_MV_DT_CTRL3 0x4AC +#define CXADEC_MISC_DIAG_CTRL 0x4B8 + +#define CXADEC_DL_CTL 0x800 +#define CXADEC_DL_CTL_ADDRESS_LOW 0x800 /* Byte 1 in DL_CTL */ +#define CXADEC_DL_CTL_ADDRESS_HIGH 0x801 /* Byte 2 in DL_CTL */ +#define CXADEC_DL_CTL_DATA 0x802 /* Byte 3 in DL_CTL */ +#define CXADEC_DL_CTL_CONTROL 0x803 /* Byte 4 in DL_CTL */ + +#define CXADEC_STD_DET_STATUS 0x804 + +#define CXADEC_STD_DET_CTL 0x808 +#define CXADEC_STD_DET_CTL_AUD_CTL 0x808 /* Byte 1 in STD_DET_CTL */ +#define CXADEC_STD_DET_CTL_PREF_MODE 0x809 /* Byte 2 in STD_DET_CTL */ + +#define CXADEC_DW8051_INT 0x80C +#define CXADEC_GENERAL_CTL 0x810 +#define CXADEC_AAGC_CTL 0x814 +#define CXADEC_IF_SRC_CTL 0x818 +#define CXADEC_ANLOG_DEMOD_CTL 0x81C +#define CXADEC_ROT_FREQ_CTL 0x820 +#define CXADEC_FM1_CTL 0x824 +#define CXADEC_PDF_CTL 0x828 +#define CXADEC_DFT1_CTL1 0x82C +#define CXADEC_DFT1_CTL2 0x830 +#define CXADEC_DFT_STATUS 0x834 +#define CXADEC_DFT2_CTL1 0x838 +#define CXADEC_DFT2_CTL2 0x83C +#define CXADEC_DFT2_STATUS 0x840 +#define CXADEC_DFT3_CTL1 0x844 +#define CXADEC_DFT3_CTL2 0x848 +#define CXADEC_DFT3_STATUS 0x84C +#define CXADEC_DFT4_CTL1 0x850 +#define CXADEC_DFT4_CTL2 0x854 +#define CXADEC_DFT4_STATUS 0x858 +#define CXADEC_AM_MTS_DET 0x85C +#define CXADEC_ANALOG_MUX_CTL 0x860 +#define CXADEC_DIG_PLL_CTL1 0x864 +#define CXADEC_DIG_PLL_CTL2 0x868 +#define CXADEC_DIG_PLL_CTL3 0x86C +#define CXADEC_DIG_PLL_CTL4 0x870 +#define CXADEC_DIG_PLL_CTL5 0x874 +#define CXADEC_DEEMPH_GAIN_CTL 0x878 +#define CXADEC_DEEMPH_COEF1 0x87C +#define CXADEC_DEEMPH_COEF2 0x880 +#define CXADEC_DBX1_CTL1 0x884 +#define CXADEC_DBX1_CTL2 0x888 +#define CXADEC_DBX1_STATUS 0x88C +#define CXADEC_DBX2_CTL1 0x890 +#define CXADEC_DBX2_CTL2 0x894 +#define CXADEC_DBX2_STATUS 0x898 +#define CXADEC_AM_FM_DIFF 0x89C + +/* NICAM registers go here */ +#define CXADEC_NICAM_STATUS 0x8C8 +#define CXADEC_DEMATRIX_CTL 0x8CC + +#define CXADEC_PATH1_CTL1 0x8D0 +#define CXADEC_PATH1_VOL_CTL 0x8D4 +#define CXADEC_PATH1_EQ_CTL 0x8D8 +#define CXADEC_PATH1_SC_CTL 0x8DC + +#define CXADEC_PATH2_CTL1 0x8E0 +#define CXADEC_PATH2_VOL_CTL 0x8E4 +#define CXADEC_PATH2_EQ_CTL 0x8E8 +#define CXADEC_PATH2_SC_CTL 0x8EC + +#define CXADEC_SRC_CTL 0x8F0 +#define CXADEC_SRC_LF_COEF 0x8F4 +#define CXADEC_SRC1_CTL 0x8F8 +#define CXADEC_SRC2_CTL 0x8FC +#define CXADEC_SRC3_CTL 0x900 +#define CXADEC_SRC4_CTL 0x904 +#define CXADEC_SRC5_CTL 0x908 +#define CXADEC_SRC6_CTL 0x90C + +#define CXADEC_BASEBAND_OUT_SEL 0x910 +#define CXADEC_I2S_IN_CTL 0x914 +#define CXADEC_I2S_OUT_CTL 0x918 +#define CXADEC_AC97_CTL 0x91C +#define CXADEC_QAM_PDF 0x920 +#define CXADEC_QAM_CONST_DEC 0x924 +#define CXADEC_QAM_ROTATOR_FREQ 0x948 + +/* Bit definitions / settings used in Mako Audio */ +#define CXADEC_PREF_MODE_MONO_LANGA 0 +#define CXADEC_PREF_MODE_MONO_LANGB 1 +#define CXADEC_PREF_MODE_MONO_LANGC 2 +#define CXADEC_PREF_MODE_FALLBACK 3 +#define CXADEC_PREF_MODE_STEREO 4 +#define CXADEC_PREF_MODE_DUAL_LANG_AC 5 +#define CXADEC_PREF_MODE_DUAL_LANG_BC 6 +#define CXADEC_PREF_MODE_DUAL_LANG_AB 7 + + +#define CXADEC_DETECT_STEREO 1 +#define CXADEC_DETECT_DUAL 2 +#define CXADEC_DETECT_TRI 4 +#define CXADEC_DETECT_SAP 0x10 +#define CXADEC_DETECT_NO_SIGNAL 0xFF + +#define CXADEC_SELECT_AUDIO_STANDARD_BG 0xF0 /* NICAM BG and A2 BG */ +#define CXADEC_SELECT_AUDIO_STANDARD_DK1 0xF1 /* NICAM DK and A2 DK */ +#define CXADEC_SELECT_AUDIO_STANDARD_DK2 0xF2 +#define CXADEC_SELECT_AUDIO_STANDARD_DK3 0xF3 +#define CXADEC_SELECT_AUDIO_STANDARD_I 0xF4 /* NICAM I and A1 */ +#define CXADEC_SELECT_AUDIO_STANDARD_L 0xF5 /* NICAM L and System L AM */ +#define CXADEC_SELECT_AUDIO_STANDARD_BTSC 0xF6 +#define CXADEC_SELECT_AUDIO_STANDARD_EIAJ 0xF7 +#define CXADEC_SELECT_AUDIO_STANDARD_A2_M 0xF8 /* A2 M */ +#define CXADEC_SELECT_AUDIO_STANDARD_FM 0xF9 /* FM radio */ +#define CXADEC_SELECT_AUDIO_STANDARD_AUTO 0xFF /* Auto detect */ + +static inline struct cx18_av_state *to_cx18_av_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct cx18_av_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct cx18_av_state, hdl)->sd; +} + +/* ----------------------------------------------------------------------- */ +/* cx18_av-core.c */ +int cx18_av_write(struct cx18 *cx, u16 addr, u8 value); +int cx18_av_write4(struct cx18 *cx, u16 addr, u32 value); +int cx18_av_write4_noretry(struct cx18 *cx, u16 addr, u32 value); +int cx18_av_write_expect(struct cx18 *cx, u16 addr, u8 value, u8 eval, u8 mask); +int cx18_av_write4_expect(struct cx18 *cx, u16 addr, u32 value, u32 eval, + u32 mask); +u8 cx18_av_read(struct cx18 *cx, u16 addr); +u32 cx18_av_read4(struct cx18 *cx, u16 addr); +int cx18_av_and_or(struct cx18 *cx, u16 addr, unsigned mask, u8 value); +int cx18_av_and_or4(struct cx18 *cx, u16 addr, u32 mask, u32 value); +void cx18_av_std_setup(struct cx18 *cx); + +int cx18_av_probe(struct cx18 *cx); + +/* ----------------------------------------------------------------------- */ +/* cx18_av-firmware.c */ +int cx18_av_loadfw(struct cx18 *cx); + +/* ----------------------------------------------------------------------- */ +/* cx18_av-audio.c */ +int cx18_av_s_clock_freq(struct v4l2_subdev *sd, u32 freq); +void cx18_av_audio_set_path(struct cx18 *cx); +extern const struct v4l2_ctrl_ops cx18_av_audio_ctrl_ops; + +/* ----------------------------------------------------------------------- */ +/* cx18_av-vbi.c */ +int cx18_av_decode_vbi_line(struct v4l2_subdev *sd, + struct v4l2_decode_vbi_line *vbi); +int cx18_av_s_raw_fmt(struct v4l2_subdev *sd, struct v4l2_vbi_format *fmt); +int cx18_av_g_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *fmt); +int cx18_av_s_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *fmt); + +#endif diff --git a/drivers/media/pci/cx18/cx18-av-firmware.c b/drivers/media/pci/cx18/cx18-av-firmware.c new file mode 100644 index 000000000..02dde685a --- /dev/null +++ b/drivers/media/pci/cx18/cx18-av-firmware.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 ADEC firmware functions + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#include "cx18-driver.h" +#include "cx18-io.h" +#include + +#define CX18_AUDIO_ENABLE 0xc72014 +#define CX18_AI1_MUX_MASK 0x30 +#define CX18_AI1_MUX_I2S1 0x00 +#define CX18_AI1_MUX_I2S2 0x10 +#define CX18_AI1_MUX_843_I2S 0x20 +#define CX18_AI1_MUX_INVALID 0x30 + +#define FWFILE "v4l-cx23418-dig.fw" + +static int cx18_av_verifyfw(struct cx18 *cx, const struct firmware *fw) +{ + struct v4l2_subdev *sd = &cx->av_state.sd; + int ret = 0; + const u8 *data; + u32 size; + int addr; + u32 expected, dl_control; + + /* Ensure we put the 8051 in reset and enable firmware upload mode */ + dl_control = cx18_av_read4(cx, CXADEC_DL_CTL); + do { + dl_control &= 0x00ffffff; + dl_control |= 0x0f000000; + cx18_av_write4_noretry(cx, CXADEC_DL_CTL, dl_control); + dl_control = cx18_av_read4(cx, CXADEC_DL_CTL); + } while ((dl_control & 0xff000000) != 0x0f000000); + + /* Read and auto increment until at address 0x0000 */ + while (dl_control & 0x3fff) + dl_control = cx18_av_read4(cx, CXADEC_DL_CTL); + + data = fw->data; + size = fw->size; + for (addr = 0; addr < size; addr++) { + dl_control &= 0xffff3fff; /* ignore top 2 bits of address */ + expected = 0x0f000000 | ((u32)data[addr] << 16) | addr; + if (expected != dl_control) { + CX18_ERR_DEV(sd, "verification of %s firmware load failed: expected %#010x got %#010x\n", + FWFILE, expected, dl_control); + ret = -EIO; + break; + } + dl_control = cx18_av_read4(cx, CXADEC_DL_CTL); + } + if (ret == 0) + CX18_INFO_DEV(sd, "verified load of %s firmware (%d bytes)\n", + FWFILE, size); + return ret; +} + +int cx18_av_loadfw(struct cx18 *cx) +{ + struct v4l2_subdev *sd = &cx->av_state.sd; + const struct firmware *fw = NULL; + u32 size; + u32 u, v; + const u8 *ptr; + int i; + int retries1 = 0; + + if (request_firmware(&fw, FWFILE, &cx->pci_dev->dev) != 0) { + CX18_ERR_DEV(sd, "unable to open firmware %s\n", FWFILE); + return -EINVAL; + } + + /* The firmware load often has byte errors, so allow for several + retries, both at byte level and at the firmware load level. */ + while (retries1 < 5) { + cx18_av_write4_expect(cx, CXADEC_CHIP_CTRL, 0x00010000, + 0x00008430, 0xffffffff); /* cx25843 */ + cx18_av_write_expect(cx, CXADEC_STD_DET_CTL, 0xf6, 0xf6, 0xff); + + /* Reset the Mako core, Register is alias of CXADEC_CHIP_CTRL */ + cx18_av_write4_expect(cx, 0x8100, 0x00010000, + 0x00008430, 0xffffffff); /* cx25843 */ + + /* Put the 8051 in reset and enable firmware upload */ + cx18_av_write4_noretry(cx, CXADEC_DL_CTL, 0x0F000000); + + ptr = fw->data; + size = fw->size; + + for (i = 0; i < size; i++) { + u32 dl_control = 0x0F000000 | i | ((u32)ptr[i] << 16); + u32 value = 0; + int retries2; + int unrec_err = 0; + + for (retries2 = 0; retries2 < CX18_MAX_MMIO_WR_RETRIES; + retries2++) { + cx18_av_write4_noretry(cx, CXADEC_DL_CTL, + dl_control); + udelay(10); + value = cx18_av_read4(cx, CXADEC_DL_CTL); + if (value == dl_control) + break; + /* Check if we can correct the byte by changing + the address. We can only write the lower + address byte of the address. */ + if ((value & 0x3F00) != (dl_control & 0x3F00)) { + unrec_err = 1; + break; + } + } + if (unrec_err || retries2 >= CX18_MAX_MMIO_WR_RETRIES) + break; + } + if (i == size) + break; + retries1++; + } + if (retries1 >= 5) { + CX18_ERR_DEV(sd, "unable to load firmware %s\n", FWFILE); + release_firmware(fw); + return -EIO; + } + + cx18_av_write4_expect(cx, CXADEC_DL_CTL, + 0x03000000 | fw->size, 0x03000000, 0x13000000); + + CX18_INFO_DEV(sd, "loaded %s firmware (%d bytes)\n", FWFILE, size); + + if (cx18_av_verifyfw(cx, fw) == 0) + cx18_av_write4_expect(cx, CXADEC_DL_CTL, + 0x13000000 | fw->size, 0x13000000, 0x13000000); + + /* Output to the 416 */ + cx18_av_and_or4(cx, CXADEC_PIN_CTRL1, ~0, 0x78000); + + /* Audio input control 1 set to Sony mode */ + /* Audio output input 2 is 0 for slave operation input */ + /* 0xC4000914[5]: 0 = left sample on WS=0, 1 = left sample on WS=1 */ + /* 0xC4000914[7]: 0 = Philips mode, 1 = Sony mode (1st SCK rising edge + after WS transition for first bit of audio word. */ + cx18_av_write4(cx, CXADEC_I2S_IN_CTL, 0x000000A0); + + /* Audio output control 1 is set to Sony mode */ + /* Audio output control 2 is set to 1 for master mode */ + /* 0xC4000918[5]: 0 = left sample on WS=0, 1 = left sample on WS=1 */ + /* 0xC4000918[7]: 0 = Philips mode, 1 = Sony mode (1st SCK rising edge + after WS transition for first bit of audio word. */ + /* 0xC4000918[8]: 0 = slave operation, 1 = master (SCK_OUT and WS_OUT + are generated) */ + cx18_av_write4(cx, CXADEC_I2S_OUT_CTL, 0x000001A0); + + /* set alt I2s master clock to /0x16 and enable alt divider i2s + passthrough */ + cx18_av_write4(cx, CXADEC_PIN_CFG3, 0x5600B687); + + cx18_av_write4_expect(cx, CXADEC_STD_DET_CTL, 0x000000F6, 0x000000F6, + 0x3F00FFFF); + /* CxDevWrReg(CXADEC_STD_DET_CTL, 0x000000FF); */ + + /* Set bit 0 in register 0x9CC to signify that this is MiniMe. */ + /* Register 0x09CC is defined by the Merlin firmware, and doesn't + have a name in the spec. */ + cx18_av_write4(cx, 0x09CC, 1); + + v = cx18_read_reg(cx, CX18_AUDIO_ENABLE); + /* If bit 11 is 1, clear bit 10 */ + if (v & 0x800) + cx18_write_reg_expect(cx, v & 0xFFFFFBFF, CX18_AUDIO_ENABLE, + 0, 0x400); + + /* Toggle the AI1 MUX */ + v = cx18_read_reg(cx, CX18_AUDIO_ENABLE); + u = v & CX18_AI1_MUX_MASK; + v &= ~CX18_AI1_MUX_MASK; + if (u == CX18_AI1_MUX_843_I2S || u == CX18_AI1_MUX_INVALID) { + /* Switch to I2S1 */ + v |= CX18_AI1_MUX_I2S1; + cx18_write_reg_expect(cx, v | 0xb00, CX18_AUDIO_ENABLE, + v, CX18_AI1_MUX_MASK); + /* Switch back to the A/V decoder core I2S output */ + v = (v & ~CX18_AI1_MUX_MASK) | CX18_AI1_MUX_843_I2S; + } else { + /* Switch to the A/V decoder core I2S output */ + v |= CX18_AI1_MUX_843_I2S; + cx18_write_reg_expect(cx, v | 0xb00, CX18_AUDIO_ENABLE, + v, CX18_AI1_MUX_MASK); + /* Switch back to I2S1 or I2S2 */ + v = (v & ~CX18_AI1_MUX_MASK) | u; + } + cx18_write_reg_expect(cx, v | 0xb00, CX18_AUDIO_ENABLE, + v, CX18_AI1_MUX_MASK); + + /* Enable WW auto audio standard detection */ + v = cx18_av_read4(cx, CXADEC_STD_DET_CTL); + v |= 0xFF; /* Auto by default */ + v |= 0x400; /* Stereo by default */ + v |= 0x14000000; + cx18_av_write4_expect(cx, CXADEC_STD_DET_CTL, v, v, 0x3F00FFFF); + + release_firmware(fw); + return 0; +} + +MODULE_FIRMWARE(FWFILE); diff --git a/drivers/media/pci/cx18/cx18-av-vbi.c b/drivers/media/pci/cx18/cx18-av-vbi.c new file mode 100644 index 000000000..f9beeaeaa --- /dev/null +++ b/drivers/media/pci/cx18/cx18-av-vbi.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 ADEC VBI functions + * + * Derived from cx25840-vbi.c + * + * Copyright (C) 2007 Hans Verkuil + */ + + +#include +#include "cx18-driver.h" + +/* + * For sliced VBI output, we set up to use VIP-1.1, 8-bit mode, + * NN counts 1 byte Dwords, an IDID with the VBI line # in it. + * Thus, according to the VIP-2 Spec, our VBI ancillary data lines + * (should!) look like: + * 4 byte EAV code: 0xff 0x00 0x00 0xRP + * unknown number of possible idle bytes + * 3 byte Anc data preamble: 0x00 0xff 0xff + * 1 byte data identifier: ne010iii (parity bits, 010, DID bits) + * 1 byte secondary data id: nessssss (parity bits, SDID bits) + * 1 byte data word count: necccccc (parity bits, NN Dword count) + * 2 byte Internal DID: VBI-line-# 0x80 + * NN data bytes + * 1 byte checksum + * Fill bytes needed to fil out to 4*NN bytes of payload + * + * The RP codes for EAVs when in VIP-1.1 mode, not in raw mode, & + * in the vertical blanking interval are: + * 0xb0 (Task 0 VerticalBlank HorizontalBlank 0 0 0 0) + * 0xf0 (Task EvenField VerticalBlank HorizontalBlank 0 0 0 0) + * + * Since the V bit is only allowed to toggle in the EAV RP code, just + * before the first active region line and for active lines, they are: + * 0x90 (Task 0 0 HorizontalBlank 0 0 0 0) + * 0xd0 (Task EvenField 0 HorizontalBlank 0 0 0 0) + * + * The user application DID bytes we care about are: + * 0x91 (1 0 010 0 !ActiveLine AncDataPresent) + * 0x55 (0 1 010 2ndField !ActiveLine AncDataPresent) + * + */ +static const u8 sliced_vbi_did[2] = { 0x91, 0x55 }; + +struct vbi_anc_data { + /* u8 eav[4]; */ + /* u8 idle[]; Variable number of idle bytes */ + u8 preamble[3]; + u8 did; + u8 sdid; + u8 data_count; + u8 idid[2]; + u8 payload[]; /* data_count of payload */ + /* u8 checksum; */ + /* u8 fill[]; Variable number of fill bytes */ +}; + +static int decode_vps(u8 *dst, u8 *p) +{ + static const u8 biphase_tbl[] = { + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xc3, 0x4b, 0x43, 0xc3, 0x87, 0x0f, 0x07, 0x87, + 0x83, 0x0b, 0x03, 0x83, 0xc3, 0x4b, 0x43, 0xc3, + 0xc1, 0x49, 0x41, 0xc1, 0x85, 0x0d, 0x05, 0x85, + 0x81, 0x09, 0x01, 0x81, 0xc1, 0x49, 0x41, 0xc1, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xc2, 0x4a, 0x42, 0xc2, 0x86, 0x0e, 0x06, 0x86, + 0x82, 0x0a, 0x02, 0x82, 0xc2, 0x4a, 0x42, 0xc2, + 0xc0, 0x48, 0x40, 0xc0, 0x84, 0x0c, 0x04, 0x84, + 0x80, 0x08, 0x00, 0x80, 0xc0, 0x48, 0x40, 0xc0, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + }; + + u8 c, err = 0; + int i; + + for (i = 0; i < 2 * 13; i += 2) { + err |= biphase_tbl[p[i]] | biphase_tbl[p[i + 1]]; + c = (biphase_tbl[p[i + 1]] & 0xf) | + ((biphase_tbl[p[i]] & 0xf) << 4); + dst[i / 2] = c; + } + + return err & 0xf0; +} + +int cx18_av_g_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *svbi) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + struct cx18_av_state *state = &cx->av_state; + static const u16 lcr2vbi[] = { + 0, V4L2_SLICED_TELETEXT_B, 0, /* 1 */ + 0, V4L2_SLICED_WSS_625, 0, /* 4 */ + V4L2_SLICED_CAPTION_525, /* 6 */ + 0, 0, V4L2_SLICED_VPS, 0, 0, /* 9 */ + 0, 0, 0, 0 + }; + int is_pal = !(state->std & V4L2_STD_525_60); + int i; + + memset(svbi->service_lines, 0, sizeof(svbi->service_lines)); + svbi->service_set = 0; + + /* we're done if raw VBI is active */ + if ((cx18_av_read(cx, 0x404) & 0x10) == 0) + return 0; + + if (is_pal) { + for (i = 7; i <= 23; i++) { + u8 v = cx18_av_read(cx, 0x424 + i - 7); + + svbi->service_lines[0][i] = lcr2vbi[v >> 4]; + svbi->service_lines[1][i] = lcr2vbi[v & 0xf]; + svbi->service_set |= svbi->service_lines[0][i] | + svbi->service_lines[1][i]; + } + } else { + for (i = 10; i <= 21; i++) { + u8 v = cx18_av_read(cx, 0x424 + i - 10); + + svbi->service_lines[0][i] = lcr2vbi[v >> 4]; + svbi->service_lines[1][i] = lcr2vbi[v & 0xf]; + svbi->service_set |= svbi->service_lines[0][i] | + svbi->service_lines[1][i]; + } + } + return 0; +} + +int cx18_av_s_raw_fmt(struct v4l2_subdev *sd, struct v4l2_vbi_format *fmt) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + struct cx18_av_state *state = &cx->av_state; + + /* Setup standard */ + cx18_av_std_setup(cx); + + /* VBI Offset */ + cx18_av_write(cx, 0x47f, state->slicer_line_delay); + cx18_av_write(cx, 0x404, 0x2e); + return 0; +} + +int cx18_av_s_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *svbi) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + struct cx18_av_state *state = &cx->av_state; + int is_pal = !(state->std & V4L2_STD_525_60); + int i, x; + u8 lcr[24]; + + for (x = 0; x <= 23; x++) + lcr[x] = 0x00; + + /* Setup standard */ + cx18_av_std_setup(cx); + + /* Sliced VBI */ + cx18_av_write(cx, 0x404, 0x32); /* Ancillary data */ + cx18_av_write(cx, 0x406, 0x13); + cx18_av_write(cx, 0x47f, state->slicer_line_delay); + + /* Force impossible lines to 0 */ + if (is_pal) { + for (i = 0; i <= 6; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + } else { + for (i = 0; i <= 9; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + + for (i = 22; i <= 23; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + } + + /* Build register values for requested service lines */ + for (i = 7; i <= 23; i++) { + for (x = 0; x <= 1; x++) { + switch (svbi->service_lines[1-x][i]) { + case V4L2_SLICED_TELETEXT_B: + lcr[i] |= 1 << (4 * x); + break; + case V4L2_SLICED_WSS_625: + lcr[i] |= 4 << (4 * x); + break; + case V4L2_SLICED_CAPTION_525: + lcr[i] |= 6 << (4 * x); + break; + case V4L2_SLICED_VPS: + lcr[i] |= 9 << (4 * x); + break; + } + } + } + + if (is_pal) { + for (x = 1, i = 0x424; i <= 0x434; i++, x++) + cx18_av_write(cx, i, lcr[6 + x]); + } else { + for (x = 1, i = 0x424; i <= 0x430; i++, x++) + cx18_av_write(cx, i, lcr[9 + x]); + for (i = 0x431; i <= 0x434; i++) + cx18_av_write(cx, i, 0); + } + + cx18_av_write(cx, 0x43c, 0x16); + /* Should match vblank set in cx18_av_std_setup() */ + cx18_av_write(cx, 0x474, is_pal ? 38 : 26); + return 0; +} + +int cx18_av_decode_vbi_line(struct v4l2_subdev *sd, + struct v4l2_decode_vbi_line *vbi) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + struct cx18_av_state *state = &cx->av_state; + struct vbi_anc_data *anc = (struct vbi_anc_data *)vbi->p; + u8 *p; + int did, sdid, l, err = 0; + + /* + * Check for the ancillary data header for sliced VBI + */ + if (anc->preamble[0] || + anc->preamble[1] != 0xff || anc->preamble[2] != 0xff || + (anc->did != sliced_vbi_did[0] && + anc->did != sliced_vbi_did[1])) { + vbi->line = vbi->type = 0; + return 0; + } + + did = anc->did; + sdid = anc->sdid & 0xf; + l = anc->idid[0] & 0x3f; + l += state->slicer_line_offset; + p = anc->payload; + + /* Decode the SDID set by the slicer */ + switch (sdid) { + case 1: + sdid = V4L2_SLICED_TELETEXT_B; + break; + case 4: + sdid = V4L2_SLICED_WSS_625; + break; + case 6: + sdid = V4L2_SLICED_CAPTION_525; + err = !parity8(p[0]) || !parity8(p[1]); + break; + case 9: + sdid = V4L2_SLICED_VPS; + if (decode_vps(p, p) != 0) + err = 1; + break; + default: + sdid = 0; + err = 1; + break; + } + + vbi->type = err ? 0 : sdid; + vbi->line = err ? 0 : l; + vbi->is_second_field = err ? 0 : (did == sliced_vbi_did[1]); + vbi->p = p; + return 0; +} diff --git a/drivers/media/pci/cx18/cx18-cards.c b/drivers/media/pci/cx18/cx18-cards.c new file mode 100644 index 000000000..bddb9e0ff --- /dev/null +++ b/drivers/media/pci/cx18/cx18-cards.c @@ -0,0 +1,624 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 functions to query card hardware + * + * Derived from ivtv-cards.c + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#include "cx18-driver.h" +#include "cx18-cards.h" +#include "cx18-av-core.h" +#include "cx18-i2c.h" +#include + +#define V4L2_STD_PAL_SECAM (V4L2_STD_PAL|V4L2_STD_SECAM) + +/********************** card configuration *******************************/ + +/* usual i2c tuner addresses to probe */ +static struct cx18_card_tuner_i2c cx18_i2c_std = { + .radio = { I2C_CLIENT_END }, + .demod = { 0x43, I2C_CLIENT_END }, + .tv = { 0x61, 0x60, I2C_CLIENT_END }, +}; + +/* + * usual i2c tuner addresses to probe with additional demod address for + * an NXP TDA8295 at 0x42 (N.B. it can possibly be at 0x4b or 0x4c too). + */ +static struct cx18_card_tuner_i2c cx18_i2c_nxp = { + .radio = { I2C_CLIENT_END }, + .demod = { 0x42, 0x43, I2C_CLIENT_END }, + .tv = { 0x61, 0x60, I2C_CLIENT_END }, +}; + +/* Please add new PCI IDs to: https://pci-ids.ucw.cz/ + This keeps the PCI ID database up to date. Note that the entries + must be added under vendor 0x4444 (Conexant) as subsystem IDs. + New vendor IDs should still be added to the vendor ID list. */ + +/* Hauppauge HVR-1600 cards */ + +/* Note: for Hauppauge cards the tveeprom information is used instead + of PCI IDs */ +static const struct cx18_card cx18_card_hvr1600_esmt = { + .type = CX18_CARD_HVR_1600_ESMT, + .name = "Hauppauge HVR-1600", + .comment = "Simultaneous Digital and Analog TV capture supported\n", + .v4l2_capabilities = CX18_CAP_ENCODER, + .hw_audio_ctrl = CX18_HW_418_AV, + .hw_muxer = CX18_HW_CS5345, + .hw_all = CX18_HW_TVEEPROM | CX18_HW_418_AV | CX18_HW_TUNER | + CX18_HW_CS5345 | CX18_HW_DVB | CX18_HW_GPIO_RESET_CTRL | + CX18_HW_Z8F0811_IR_HAUP, + .video_inputs = { + { CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE7 }, + { CX18_CARD_INPUT_SVIDEO1, 1, CX18_AV_SVIDEO1 }, + { CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE3 }, + { CX18_CARD_INPUT_SVIDEO2, 2, CX18_AV_SVIDEO2 }, + { CX18_CARD_INPUT_COMPOSITE2, 2, CX18_AV_COMPOSITE4 }, + }, + .audio_inputs = { + { CX18_CARD_INPUT_AUD_TUNER, + CX18_AV_AUDIO8, CS5345_IN_1 | CS5345_MCLK_1_5 }, + { CX18_CARD_INPUT_LINE_IN1, + CX18_AV_AUDIO_SERIAL1, CS5345_IN_2 }, + { CX18_CARD_INPUT_LINE_IN2, + CX18_AV_AUDIO_SERIAL1, CS5345_IN_3 }, + }, + .radio_input = { CX18_CARD_INPUT_AUD_TUNER, + CX18_AV_AUDIO_SERIAL1, CS5345_IN_4 }, + .ddr = { + /* ESMT M13S128324A-5B memory */ + .chip_config = 0x003, + .refresh = 0x30c, + .timing1 = 0x44220e82, + .timing2 = 0x08, + .tune_lane = 0, + .initial_emrs = 0, + }, + .gpio_init.initial_value = 0x3001, + .gpio_init.direction = 0x3001, + .gpio_i2c_slave_reset = { + .active_lo_mask = 0x3001, + .msecs_asserted = 10, + .msecs_recovery = 40, + .ir_reset_mask = 0x0001, + }, + .i2c = &cx18_i2c_std, +}; + +static const struct cx18_card cx18_card_hvr1600_s5h1411 = { + .type = CX18_CARD_HVR_1600_S5H1411, + .name = "Hauppauge HVR-1600", + .comment = "Simultaneous Digital and Analog TV capture supported\n", + .v4l2_capabilities = CX18_CAP_ENCODER, + .hw_audio_ctrl = CX18_HW_418_AV, + .hw_muxer = CX18_HW_CS5345, + .hw_all = CX18_HW_TVEEPROM | CX18_HW_418_AV | CX18_HW_TUNER | + CX18_HW_CS5345 | CX18_HW_DVB | CX18_HW_GPIO_RESET_CTRL | + CX18_HW_Z8F0811_IR_HAUP, + .video_inputs = { + { CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE7 }, + { CX18_CARD_INPUT_SVIDEO1, 1, CX18_AV_SVIDEO1 }, + { CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE3 }, + { CX18_CARD_INPUT_SVIDEO2, 2, CX18_AV_SVIDEO2 }, + { CX18_CARD_INPUT_COMPOSITE2, 2, CX18_AV_COMPOSITE4 }, + }, + .audio_inputs = { + { CX18_CARD_INPUT_AUD_TUNER, + CX18_AV_AUDIO8, CS5345_IN_1 | CS5345_MCLK_1_5 }, + { CX18_CARD_INPUT_LINE_IN1, + CX18_AV_AUDIO_SERIAL1, CS5345_IN_2 }, + { CX18_CARD_INPUT_LINE_IN2, + CX18_AV_AUDIO_SERIAL1, CS5345_IN_3 }, + }, + .radio_input = { CX18_CARD_INPUT_AUD_TUNER, + CX18_AV_AUDIO_SERIAL1, CS5345_IN_4 }, + .ddr = { + /* ESMT M13S128324A-5B memory */ + .chip_config = 0x003, + .refresh = 0x30c, + .timing1 = 0x44220e82, + .timing2 = 0x08, + .tune_lane = 0, + .initial_emrs = 0, + }, + .gpio_init.initial_value = 0x3801, + .gpio_init.direction = 0x3801, + .gpio_i2c_slave_reset = { + .active_lo_mask = 0x3801, + .msecs_asserted = 10, + .msecs_recovery = 40, + .ir_reset_mask = 0x0001, + }, + .i2c = &cx18_i2c_nxp, +}; + +static const struct cx18_card cx18_card_hvr1600_samsung = { + .type = CX18_CARD_HVR_1600_SAMSUNG, + .name = "Hauppauge HVR-1600 (Preproduction)", + .comment = "Simultaneous Digital and Analog TV capture supported\n", + .v4l2_capabilities = CX18_CAP_ENCODER, + .hw_audio_ctrl = CX18_HW_418_AV, + .hw_muxer = CX18_HW_CS5345, + .hw_all = CX18_HW_TVEEPROM | CX18_HW_418_AV | CX18_HW_TUNER | + CX18_HW_CS5345 | CX18_HW_DVB | CX18_HW_GPIO_RESET_CTRL | + CX18_HW_Z8F0811_IR_HAUP, + .video_inputs = { + { CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE7 }, + { CX18_CARD_INPUT_SVIDEO1, 1, CX18_AV_SVIDEO1 }, + { CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE3 }, + { CX18_CARD_INPUT_SVIDEO2, 2, CX18_AV_SVIDEO2 }, + { CX18_CARD_INPUT_COMPOSITE2, 2, CX18_AV_COMPOSITE4 }, + }, + .audio_inputs = { + { CX18_CARD_INPUT_AUD_TUNER, + CX18_AV_AUDIO8, CS5345_IN_1 | CS5345_MCLK_1_5 }, + { CX18_CARD_INPUT_LINE_IN1, + CX18_AV_AUDIO_SERIAL1, CS5345_IN_2 }, + { CX18_CARD_INPUT_LINE_IN2, + CX18_AV_AUDIO_SERIAL1, CS5345_IN_3 }, + }, + .radio_input = { CX18_CARD_INPUT_AUD_TUNER, + CX18_AV_AUDIO_SERIAL1, CS5345_IN_4 }, + .ddr = { + /* Samsung K4D263238G-VC33 memory */ + .chip_config = 0x003, + .refresh = 0x30c, + .timing1 = 0x23230b73, + .timing2 = 0x08, + .tune_lane = 0, + .initial_emrs = 2, + }, + .gpio_init.initial_value = 0x3001, + .gpio_init.direction = 0x3001, + .gpio_i2c_slave_reset = { + .active_lo_mask = 0x3001, + .msecs_asserted = 10, + .msecs_recovery = 40, + .ir_reset_mask = 0x0001, + }, + .i2c = &cx18_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Compro VideoMate H900: note that this card is analog only! */ + +static const struct cx18_card_pci_info cx18_pci_h900[] = { + { PCI_DEVICE_ID_CX23418, CX18_PCI_ID_COMPRO, 0xe100 }, + { 0, 0, 0 } +}; + +static const struct cx18_card cx18_card_h900 = { + .type = CX18_CARD_COMPRO_H900, + .name = "Compro VideoMate H900", + .comment = "Analog TV capture supported\n", + .v4l2_capabilities = CX18_CAP_ENCODER, + .hw_audio_ctrl = CX18_HW_418_AV, + .hw_all = CX18_HW_418_AV | CX18_HW_TUNER | CX18_HW_GPIO_RESET_CTRL, + .video_inputs = { + { CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE2 }, + { CX18_CARD_INPUT_SVIDEO1, 1, + CX18_AV_SVIDEO_LUMA3 | CX18_AV_SVIDEO_CHROMA4 }, + { CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE1 }, + }, + .audio_inputs = { + { CX18_CARD_INPUT_AUD_TUNER, + CX18_AV_AUDIO5, 0 }, + { CX18_CARD_INPUT_LINE_IN1, + CX18_AV_AUDIO_SERIAL1, 0 }, + }, + .radio_input = { CX18_CARD_INPUT_AUD_TUNER, + CX18_AV_AUDIO_SERIAL1, 0 }, + .tuners = { + { .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 }, + }, + .ddr = { + /* EtronTech EM6A9160TS-5G memory */ + .chip_config = 0x50003, + .refresh = 0x753, + .timing1 = 0x24330e84, + .timing2 = 0x1f, + .tune_lane = 0, + .initial_emrs = 0, + }, + .xceive_pin = 15, + .pci_list = cx18_pci_h900, + .i2c = &cx18_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Yuan MPC718: not working at the moment! */ + +static const struct cx18_card_pci_info cx18_pci_mpc718[] = { + { PCI_DEVICE_ID_CX23418, CX18_PCI_ID_YUAN, 0x0718 }, + { 0, 0, 0 } +}; + +static const struct cx18_card cx18_card_mpc718 = { + .type = CX18_CARD_YUAN_MPC718, + .name = "Yuan MPC718 MiniPCI DVB-T/Analog", + .comment = "Experimenters needed for device to work well.\n" + "\tTo help, mail the linux-media list (www.linuxtv.org).\n", + .v4l2_capabilities = CX18_CAP_ENCODER, + .hw_audio_ctrl = CX18_HW_418_AV, + .hw_muxer = CX18_HW_GPIO_MUX, + .hw_all = CX18_HW_TVEEPROM | CX18_HW_418_AV | CX18_HW_TUNER | + CX18_HW_GPIO_MUX | CX18_HW_DVB | CX18_HW_GPIO_RESET_CTRL, + .video_inputs = { + { CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE2 }, + { CX18_CARD_INPUT_SVIDEO1, 1, + CX18_AV_SVIDEO_LUMA3 | CX18_AV_SVIDEO_CHROMA4 }, + { CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE1 }, + { CX18_CARD_INPUT_SVIDEO2, 2, + CX18_AV_SVIDEO_LUMA7 | CX18_AV_SVIDEO_CHROMA8 }, + { CX18_CARD_INPUT_COMPOSITE2, 2, CX18_AV_COMPOSITE6 }, + }, + .audio_inputs = { + { CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 0 }, + { CX18_CARD_INPUT_LINE_IN1, CX18_AV_AUDIO_SERIAL1, 1 }, + { CX18_CARD_INPUT_LINE_IN2, CX18_AV_AUDIO_SERIAL2, 1 }, + }, + .tuners = { + /* XC3028 tuner */ + { .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 }, + }, + /* FIXME - the FM radio is just a guess and driver doesn't use SIF */ + .radio_input = { CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 2 }, + .ddr = { + /* Hynix HY5DU283222B DDR RAM */ + .chip_config = 0x303, + .refresh = 0x3bd, + .timing1 = 0x36320966, + .timing2 = 0x1f, + .tune_lane = 0, + .initial_emrs = 2, + }, + .gpio_init.initial_value = 0x1, + .gpio_init.direction = 0x3, + /* FIXME - these GPIO's are just guesses */ + .gpio_audio_input = { .mask = 0x3, + .tuner = 0x1, + .linein = 0x3, + .radio = 0x1 }, + .xceive_pin = 0, + .pci_list = cx18_pci_mpc718, + .i2c = &cx18_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* GoTView PCI */ + +static const struct cx18_card_pci_info cx18_pci_gotview_dvd3[] = { + { PCI_DEVICE_ID_CX23418, CX18_PCI_ID_GOTVIEW, 0x3343 }, + { 0, 0, 0 } +}; + +static const struct cx18_card cx18_card_gotview_dvd3 = { + .type = CX18_CARD_GOTVIEW_PCI_DVD3, + .name = "GoTView PCI DVD3 Hybrid", + .comment = "Experimenters needed for device to work well.\n" + "\tTo help, mail the linux-media list (www.linuxtv.org).\n", + .v4l2_capabilities = CX18_CAP_ENCODER, + .hw_audio_ctrl = CX18_HW_418_AV, + .hw_muxer = CX18_HW_GPIO_MUX, + .hw_all = CX18_HW_TVEEPROM | CX18_HW_418_AV | CX18_HW_TUNER | + CX18_HW_GPIO_MUX | CX18_HW_DVB | CX18_HW_GPIO_RESET_CTRL, + .video_inputs = { + { CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE2 }, + { CX18_CARD_INPUT_SVIDEO1, 1, + CX18_AV_SVIDEO_LUMA3 | CX18_AV_SVIDEO_CHROMA4 }, + { CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE1 }, + { CX18_CARD_INPUT_SVIDEO2, 2, + CX18_AV_SVIDEO_LUMA7 | CX18_AV_SVIDEO_CHROMA8 }, + { CX18_CARD_INPUT_COMPOSITE2, 2, CX18_AV_COMPOSITE6 }, + }, + .audio_inputs = { + { CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 0 }, + { CX18_CARD_INPUT_LINE_IN1, CX18_AV_AUDIO_SERIAL1, 1 }, + { CX18_CARD_INPUT_LINE_IN2, CX18_AV_AUDIO_SERIAL2, 1 }, + }, + .tuners = { + /* XC3028 tuner */ + { .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 }, + }, + /* FIXME - the FM radio is just a guess and driver doesn't use SIF */ + .radio_input = { CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 2 }, + .ddr = { + /* Hynix HY5DU283222B DDR RAM */ + .chip_config = 0x303, + .refresh = 0x3bd, + .timing1 = 0x36320966, + .timing2 = 0x1f, + .tune_lane = 0, + .initial_emrs = 2, + }, + .gpio_init.initial_value = 0x1, + .gpio_init.direction = 0x3, + + .gpio_audio_input = { .mask = 0x3, + .tuner = 0x1, + .linein = 0x2, + .radio = 0x1 }, + .xceive_pin = 0, + .pci_list = cx18_pci_gotview_dvd3, + .i2c = &cx18_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Conexant Raptor PAL/SECAM: note that this card is analog only! */ + +static const struct cx18_card_pci_info cx18_pci_cnxt_raptor_pal[] = { + { PCI_DEVICE_ID_CX23418, CX18_PCI_ID_CONEXANT, 0x0009 }, + { 0, 0, 0 } +}; + +static const struct cx18_card cx18_card_cnxt_raptor_pal = { + .type = CX18_CARD_CNXT_RAPTOR_PAL, + .name = "Conexant Raptor PAL/SECAM", + .comment = "Analog TV capture supported\n", + .v4l2_capabilities = CX18_CAP_ENCODER, + .hw_audio_ctrl = CX18_HW_418_AV, + .hw_muxer = CX18_HW_GPIO_MUX, + .hw_all = CX18_HW_418_AV | CX18_HW_TUNER | CX18_HW_GPIO_MUX, + .video_inputs = { + { CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE2 }, + { CX18_CARD_INPUT_SVIDEO1, 1, + CX18_AV_SVIDEO_LUMA3 | CX18_AV_SVIDEO_CHROMA4 }, + { CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE1 }, + { CX18_CARD_INPUT_SVIDEO2, 2, + CX18_AV_SVIDEO_LUMA7 | CX18_AV_SVIDEO_CHROMA8 }, + { CX18_CARD_INPUT_COMPOSITE2, 2, CX18_AV_COMPOSITE6 }, + }, + .audio_inputs = { + { CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 0 }, + { CX18_CARD_INPUT_LINE_IN1, CX18_AV_AUDIO_SERIAL1, 1 }, + { CX18_CARD_INPUT_LINE_IN2, CX18_AV_AUDIO_SERIAL2, 1 }, + }, + .tuners = { + { .std = V4L2_STD_PAL_SECAM, .tuner = TUNER_PHILIPS_FM1216ME_MK3 }, + }, + .radio_input = { CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO_SERIAL1, 2 }, + .ddr = { + /* MT 46V16M16 memory */ + .chip_config = 0x50306, + .refresh = 0x753, + .timing1 = 0x33220953, + .timing2 = 0x09, + .tune_lane = 0, + .initial_emrs = 0, + }, + .gpio_init.initial_value = 0x1002, + .gpio_init.direction = 0xf002, + .gpio_audio_input = { .mask = 0xf002, + .tuner = 0x1002, /* LED D1 Tuner AF */ + .linein = 0x2000, /* LED D2 Line In 1 */ + .radio = 0x4002 }, /* LED D3 Tuner AF */ + .pci_list = cx18_pci_cnxt_raptor_pal, + .i2c = &cx18_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Toshiba Qosmio laptop internal DVB-T/Analog Hybrid Tuner */ + +static const struct cx18_card_pci_info cx18_pci_toshiba_qosmio_dvbt[] = { + { PCI_DEVICE_ID_CX23418, CX18_PCI_ID_TOSHIBA, 0x0110 }, + { 0, 0, 0 } +}; + +static const struct cx18_card cx18_card_toshiba_qosmio_dvbt = { + .type = CX18_CARD_TOSHIBA_QOSMIO_DVBT, + .name = "Toshiba Qosmio DVB-T/Analog", + .comment = "Experimenters and photos needed for device to work well.\n" + "\tTo help, mail the linux-media list (www.linuxtv.org).\n", + .v4l2_capabilities = CX18_CAP_ENCODER, + .hw_audio_ctrl = CX18_HW_418_AV, + .hw_all = CX18_HW_418_AV | CX18_HW_TUNER | CX18_HW_GPIO_RESET_CTRL, + .video_inputs = { + { CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE6 }, + { CX18_CARD_INPUT_SVIDEO1, 1, + CX18_AV_SVIDEO_LUMA3 | CX18_AV_SVIDEO_CHROMA4 }, + { CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE1 }, + }, + .audio_inputs = { + { CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 0 }, + { CX18_CARD_INPUT_LINE_IN1, CX18_AV_AUDIO_SERIAL1, 1 }, + }, + .tuners = { + { .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 }, + }, + .ddr = { + .chip_config = 0x202, + .refresh = 0x3bb, + .timing1 = 0x33320a63, + .timing2 = 0x0a, + .tune_lane = 0, + .initial_emrs = 0x42, + }, + .xceive_pin = 15, + .pci_list = cx18_pci_toshiba_qosmio_dvbt, + .i2c = &cx18_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Leadtek WinFast PVR2100 */ + +static const struct cx18_card_pci_info cx18_pci_leadtek_pvr2100[] = { + { PCI_DEVICE_ID_CX23418, CX18_PCI_ID_LEADTEK, 0x6f27 }, /* PVR2100 */ + { 0, 0, 0 } +}; + +static const struct cx18_card cx18_card_leadtek_pvr2100 = { + .type = CX18_CARD_LEADTEK_PVR2100, + .name = "Leadtek WinFast PVR2100", + .comment = "Experimenters and photos needed for device to work well.\n" + "\tTo help, mail the linux-media list (www.linuxtv.org).\n", + .v4l2_capabilities = CX18_CAP_ENCODER, + .hw_audio_ctrl = CX18_HW_418_AV, + .hw_muxer = CX18_HW_GPIO_MUX, + .hw_all = CX18_HW_418_AV | CX18_HW_TUNER | CX18_HW_GPIO_MUX | + CX18_HW_GPIO_RESET_CTRL, + .video_inputs = { + { CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE2 }, + { CX18_CARD_INPUT_SVIDEO1, 1, + CX18_AV_SVIDEO_LUMA3 | CX18_AV_SVIDEO_CHROMA4 }, + { CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE7 }, + { CX18_CARD_INPUT_COMPONENT1, 1, CX18_AV_COMPONENT1 }, + }, + .audio_inputs = { + { CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 0 }, + { CX18_CARD_INPUT_LINE_IN1, CX18_AV_AUDIO_SERIAL1, 1 }, + }, + .tuners = { + /* XC2028 tuner */ + { .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 }, + }, + .radio_input = { CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 2 }, + .ddr = { + /* Pointer to proper DDR config values provided by Terry Wu */ + .chip_config = 0x303, + .refresh = 0x3bb, + .timing1 = 0x24220e83, + .timing2 = 0x1f, + .tune_lane = 0, + .initial_emrs = 0x2, + }, + .gpio_init.initial_value = 0x6, + .gpio_init.direction = 0x7, + .gpio_audio_input = { .mask = 0x7, + .tuner = 0x6, .linein = 0x2, .radio = 0x2 }, + .xceive_pin = 1, + .pci_list = cx18_pci_leadtek_pvr2100, + .i2c = &cx18_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Leadtek WinFast DVR3100 H */ + +static const struct cx18_card_pci_info cx18_pci_leadtek_dvr3100h[] = { + { PCI_DEVICE_ID_CX23418, CX18_PCI_ID_LEADTEK, 0x6690 }, /* DVR3100 H */ + { 0, 0, 0 } +}; + +static const struct cx18_card cx18_card_leadtek_dvr3100h = { + .type = CX18_CARD_LEADTEK_DVR3100H, + .name = "Leadtek WinFast DVR3100 H", + .comment = "Simultaneous DVB-T and Analog capture supported,\n" + "\texcept when capturing Analog from the antenna input.\n", + .v4l2_capabilities = CX18_CAP_ENCODER, + .hw_audio_ctrl = CX18_HW_418_AV, + .hw_muxer = CX18_HW_GPIO_MUX, + .hw_all = CX18_HW_418_AV | CX18_HW_TUNER | CX18_HW_GPIO_MUX | + CX18_HW_DVB | CX18_HW_GPIO_RESET_CTRL, + .video_inputs = { + { CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE2 }, + { CX18_CARD_INPUT_SVIDEO1, 1, + CX18_AV_SVIDEO_LUMA3 | CX18_AV_SVIDEO_CHROMA4 }, + { CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE7 }, + { CX18_CARD_INPUT_COMPONENT1, 1, CX18_AV_COMPONENT1 }, + }, + .audio_inputs = { + { CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 0 }, + { CX18_CARD_INPUT_LINE_IN1, CX18_AV_AUDIO_SERIAL1, 1 }, + }, + .tuners = { + /* XC3028 tuner */ + { .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 }, + }, + .radio_input = { CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 2 }, + .ddr = { + /* Pointer to proper DDR config values provided by Terry Wu */ + .chip_config = 0x303, + .refresh = 0x3bb, + .timing1 = 0x24220e83, + .timing2 = 0x1f, + .tune_lane = 0, + .initial_emrs = 0x2, + }, + .gpio_init.initial_value = 0x6, + .gpio_init.direction = 0x7, + .gpio_audio_input = { .mask = 0x7, + .tuner = 0x6, .linein = 0x2, .radio = 0x2 }, + .xceive_pin = 1, + .pci_list = cx18_pci_leadtek_dvr3100h, + .i2c = &cx18_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +static const struct cx18_card *cx18_card_list[] = { + &cx18_card_hvr1600_esmt, + &cx18_card_hvr1600_samsung, + &cx18_card_h900, + &cx18_card_mpc718, + &cx18_card_cnxt_raptor_pal, + &cx18_card_toshiba_qosmio_dvbt, + &cx18_card_leadtek_pvr2100, + &cx18_card_leadtek_dvr3100h, + &cx18_card_gotview_dvd3, + &cx18_card_hvr1600_s5h1411 +}; + +const struct cx18_card *cx18_get_card(u16 index) +{ + if (index >= ARRAY_SIZE(cx18_card_list)) + return NULL; + return cx18_card_list[index]; +} + +int cx18_get_input(struct cx18 *cx, u16 index, struct v4l2_input *input) +{ + const struct cx18_card_video_input *card_input = + cx->card->video_inputs + index; + static const char * const input_strs[] = { + "Tuner 1", + "S-Video 1", + "S-Video 2", + "Composite 1", + "Composite 2", + "Component 1" + }; + + if (index >= cx->nof_inputs) + return -EINVAL; + input->index = index; + strscpy(input->name, input_strs[card_input->video_type - 1], + sizeof(input->name)); + input->type = (card_input->video_type == CX18_CARD_INPUT_VID_TUNER ? + V4L2_INPUT_TYPE_TUNER : V4L2_INPUT_TYPE_CAMERA); + input->audioset = (1 << cx->nof_audio_inputs) - 1; + input->std = (input->type == V4L2_INPUT_TYPE_TUNER) ? + cx->tuner_std : V4L2_STD_ALL; + return 0; +} + +int cx18_get_audio_input(struct cx18 *cx, u16 index, struct v4l2_audio *audio) +{ + const struct cx18_card_audio_input *aud_input = + cx->card->audio_inputs + index; + static const char * const input_strs[] = { + "Tuner 1", + "Line In 1", + "Line In 2" + }; + + memset(audio, 0, sizeof(*audio)); + if (index >= cx->nof_audio_inputs) + return -EINVAL; + strscpy(audio->name, input_strs[aud_input->audio_type - 1], + sizeof(audio->name)); + audio->index = index; + audio->capability = V4L2_AUDCAP_STEREO; + return 0; +} diff --git a/drivers/media/pci/cx18/cx18-cards.h b/drivers/media/pci/cx18/cx18-cards.h new file mode 100644 index 000000000..511123b74 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-cards.h @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 functions to query card hardware + * + * Derived from ivtv-cards.c + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +/* hardware flags */ +#define CX18_HW_TUNER (1 << 0) +#define CX18_HW_TVEEPROM (1 << 1) +#define CX18_HW_CS5345 (1 << 2) +#define CX18_HW_DVB (1 << 3) +#define CX18_HW_418_AV (1 << 4) +#define CX18_HW_GPIO_MUX (1 << 5) +#define CX18_HW_GPIO_RESET_CTRL (1 << 6) +#define CX18_HW_Z8F0811_IR_HAUP (1 << 7) + +/* video inputs */ +#define CX18_CARD_INPUT_VID_TUNER 1 +#define CX18_CARD_INPUT_SVIDEO1 2 +#define CX18_CARD_INPUT_SVIDEO2 3 +#define CX18_CARD_INPUT_COMPOSITE1 4 +#define CX18_CARD_INPUT_COMPOSITE2 5 +#define CX18_CARD_INPUT_COMPONENT1 6 + +/* audio inputs */ +#define CX18_CARD_INPUT_AUD_TUNER 1 +#define CX18_CARD_INPUT_LINE_IN1 2 +#define CX18_CARD_INPUT_LINE_IN2 3 + +#define CX18_CARD_MAX_VIDEO_INPUTS 6 +#define CX18_CARD_MAX_AUDIO_INPUTS 3 +#define CX18_CARD_MAX_TUNERS 2 + +/* V4L2 capability aliases */ +#define CX18_CAP_ENCODER (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER | \ + V4L2_CAP_AUDIO | V4L2_CAP_READWRITE | \ + V4L2_CAP_STREAMING | V4L2_CAP_VBI_CAPTURE | \ + V4L2_CAP_SLICED_VBI_CAPTURE) + +struct cx18_card_video_input { + u8 video_type; /* video input type */ + u8 audio_index; /* index in cx18_card_audio_input array */ + u32 video_input; /* hardware video input */ +}; + +struct cx18_card_audio_input { + u8 audio_type; /* audio input type */ + u32 audio_input; /* hardware audio input */ + u16 muxer_input; /* hardware muxer input for boards with a + multiplexer chip */ +}; + +struct cx18_card_pci_info { + u16 device; + u16 subsystem_vendor; + u16 subsystem_device; +}; + +/* GPIO definitions */ + +/* The mask is the set of bits used by the operation */ + +struct cx18_gpio_init { /* set initial GPIO DIR and OUT values */ + u32 direction; /* DIR setting. Leave to 0 if no init is needed */ + u32 initial_value; +}; + +struct cx18_gpio_i2c_slave_reset { + u32 active_lo_mask; /* GPIO outputs that reset i2c chips when low */ + u32 active_hi_mask; /* GPIO outputs that reset i2c chips when high */ + int msecs_asserted; /* time period reset must remain asserted */ + int msecs_recovery; /* time after deassert for chips to be ready */ + u32 ir_reset_mask; /* GPIO to reset the Zilog Z8F0811 IR controller */ +}; + +struct cx18_gpio_audio_input { /* select tuner/line in input */ + u32 mask; /* leave to 0 if not supported */ + u32 tuner; + u32 linein; + u32 radio; +}; + +struct cx18_card_tuner { + v4l2_std_id std; /* standard for which the tuner is suitable */ + int tuner; /* tuner ID (from tuner.h) */ +}; + +struct cx18_card_tuner_i2c { + unsigned short radio[2];/* radio tuner i2c address to probe */ + unsigned short demod[3];/* demodulator i2c address to probe */ + unsigned short tv[4]; /* tv tuner i2c addresses to probe */ +}; + +struct cx18_ddr { /* DDR config data */ + u32 chip_config; + u32 refresh; + u32 timing1; + u32 timing2; + u32 tune_lane; + u32 initial_emrs; +}; + +/* for card information/parameters */ +struct cx18_card { + int type; + char *name; + char *comment; + u32 v4l2_capabilities; + u32 hw_audio_ctrl; /* hardware used for the V4L2 controls (only + 1 dev allowed currently) */ + u32 hw_muxer; /* hardware used to multiplex audio input */ + u32 hw_all; /* all hardware used by the board */ + struct cx18_card_video_input video_inputs[CX18_CARD_MAX_VIDEO_INPUTS]; + struct cx18_card_audio_input audio_inputs[CX18_CARD_MAX_AUDIO_INPUTS]; + struct cx18_card_audio_input radio_input; + + /* GPIO card-specific settings */ + u8 xceive_pin; /* XCeive tuner GPIO reset pin */ + struct cx18_gpio_init gpio_init; + struct cx18_gpio_i2c_slave_reset gpio_i2c_slave_reset; + struct cx18_gpio_audio_input gpio_audio_input; + + struct cx18_card_tuner tuners[CX18_CARD_MAX_TUNERS]; + struct cx18_card_tuner_i2c *i2c; + + struct cx18_ddr ddr; + + /* list of device and subsystem vendor/devices that + correspond to this card type. */ + const struct cx18_card_pci_info *pci_list; +}; + +int cx18_get_input(struct cx18 *cx, u16 index, struct v4l2_input *input); +int cx18_get_audio_input(struct cx18 *cx, u16 index, struct v4l2_audio *input); +const struct cx18_card *cx18_get_card(u16 index); diff --git a/drivers/media/pci/cx18/cx18-controls.c b/drivers/media/pci/cx18/cx18-controls.c new file mode 100644 index 000000000..78eadad8b --- /dev/null +++ b/drivers/media/pci/cx18/cx18-controls.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 ioctl control functions + * + * Derived from ivtv-controls.c + * + * Copyright (C) 2007 Hans Verkuil + */ +#include +#include + +#include "cx18-driver.h" +#include "cx18-cards.h" +#include "cx18-ioctl.h" +#include "cx18-audio.h" +#include "cx18-mailbox.h" +#include "cx18-controls.h" + +static int cx18_s_stream_vbi_fmt(struct cx2341x_handler *cxhdl, u32 fmt) +{ + struct cx18 *cx = container_of(cxhdl, struct cx18, cxhdl); + int type = cxhdl->stream_type->val; + + if (atomic_read(&cx->ana_capturing) > 0) + return -EBUSY; + + if (fmt != V4L2_MPEG_STREAM_VBI_FMT_IVTV || + !(type == V4L2_MPEG_STREAM_TYPE_MPEG2_PS || + type == V4L2_MPEG_STREAM_TYPE_MPEG2_DVD || + type == V4L2_MPEG_STREAM_TYPE_MPEG2_SVCD)) { + /* Only IVTV fmt VBI insertion & only MPEG-2 PS type streams */ + cx->vbi.insert_mpeg = V4L2_MPEG_STREAM_VBI_FMT_NONE; + CX18_DEBUG_INFO("disabled insertion of sliced VBI data into the MPEG stream\n"); + return 0; + } + + /* Allocate sliced VBI buffers if needed. */ + if (cx->vbi.sliced_mpeg_data[0] == NULL) { + int i; + + for (i = 0; i < CX18_VBI_FRAMES; i++) { + cx->vbi.sliced_mpeg_data[i] = + kmalloc(CX18_SLICED_MPEG_DATA_BUFSZ, GFP_KERNEL); + if (cx->vbi.sliced_mpeg_data[i] == NULL) { + while (--i >= 0) { + kfree(cx->vbi.sliced_mpeg_data[i]); + cx->vbi.sliced_mpeg_data[i] = NULL; + } + cx->vbi.insert_mpeg = + V4L2_MPEG_STREAM_VBI_FMT_NONE; + CX18_WARN("Unable to allocate buffers for sliced VBI data insertion\n"); + return -ENOMEM; + } + } + } + + cx->vbi.insert_mpeg = fmt; + CX18_DEBUG_INFO("enabled insertion of sliced VBI data into the MPEG PS,when sliced VBI is enabled\n"); + + /* + * If our current settings have no lines set for capture, store a valid, + * default set of service lines to capture, in our current settings. + */ + if (cx18_get_service_set(cx->vbi.sliced_in) == 0) { + if (cx->is_60hz) + cx->vbi.sliced_in->service_set = + V4L2_SLICED_CAPTION_525; + else + cx->vbi.sliced_in->service_set = V4L2_SLICED_WSS_625; + cx18_expand_service_set(cx->vbi.sliced_in, cx->is_50hz); + } + return 0; +} + +static int cx18_s_video_encoding(struct cx2341x_handler *cxhdl, u32 val) +{ + struct cx18 *cx = container_of(cxhdl, struct cx18, cxhdl); + int is_mpeg1 = val == V4L2_MPEG_VIDEO_ENCODING_MPEG_1; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_mbus_framefmt *fmt = &format.format; + + /* fix videodecoder resolution */ + fmt->width = cxhdl->width / (is_mpeg1 ? 2 : 1); + fmt->height = cxhdl->height; + fmt->code = MEDIA_BUS_FMT_FIXED; + v4l2_subdev_call(cx->sd_av, pad, set_fmt, NULL, &format); + return 0; +} + +static int cx18_s_audio_sampling_freq(struct cx2341x_handler *cxhdl, u32 idx) +{ + static const u32 freqs[3] = { 44100, 48000, 32000 }; + struct cx18 *cx = container_of(cxhdl, struct cx18, cxhdl); + + /* The audio clock of the digitizer must match the codec sample + rate otherwise you get some very strange effects. */ + if (idx < ARRAY_SIZE(freqs)) + cx18_call_all(cx, audio, s_clock_freq, freqs[idx]); + return 0; +} + +static int cx18_s_audio_mode(struct cx2341x_handler *cxhdl, u32 val) +{ + struct cx18 *cx = container_of(cxhdl, struct cx18, cxhdl); + + cx->dualwatch_stereo_mode = val; + return 0; +} + +const struct cx2341x_handler_ops cx18_cxhdl_ops = { + .s_audio_mode = cx18_s_audio_mode, + .s_audio_sampling_freq = cx18_s_audio_sampling_freq, + .s_video_encoding = cx18_s_video_encoding, + .s_stream_vbi_fmt = cx18_s_stream_vbi_fmt, +}; diff --git a/drivers/media/pci/cx18/cx18-controls.h b/drivers/media/pci/cx18/cx18-controls.h new file mode 100644 index 000000000..99de78878 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-controls.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 ioctl control functions + * + * Derived from ivtv-controls.h + * + * Copyright (C) 2007 Hans Verkuil + + */ + +extern const struct cx2341x_handler_ops cx18_cxhdl_ops; diff --git a/drivers/media/pci/cx18/cx18-driver.c b/drivers/media/pci/cx18/cx18-driver.c new file mode 100644 index 000000000..214fac7af --- /dev/null +++ b/drivers/media/pci/cx18/cx18-driver.c @@ -0,0 +1,1339 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 driver initialization and card probing + * + * Derived from ivtv-driver.c + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#include "cx18-driver.h" +#include "cx18-io.h" +#include "cx18-version.h" +#include "cx18-cards.h" +#include "cx18-i2c.h" +#include "cx18-irq.h" +#include "cx18-gpio.h" +#include "cx18-firmware.h" +#include "cx18-queue.h" +#include "cx18-streams.h" +#include "cx18-av-core.h" +#include "cx18-scb.h" +#include "cx18-mailbox.h" +#include "cx18-ioctl.h" +#include "cx18-controls.h" +#include "xc2028.h" +#include +#include + +/* If you have already X v4l cards, then set this to X. This way + the device numbers stay matched. Example: you have a WinTV card + without radio and a Compro H900 with. Normally this would give a + video1 device together with a radio0 device for the Compro. By + setting this to 1 you ensure that radio0 is now also radio1. */ +int cx18_first_minor; + +/* Callback for registering extensions */ +int (*cx18_ext_init)(struct cx18 *); +EXPORT_SYMBOL(cx18_ext_init); + +/* add your revision and whatnot here */ +static const struct pci_device_id cx18_pci_tbl[] = { + { PCI_VDEVICE(CX, PCI_DEVICE_ID_CX23418) }, + { } +}; + +MODULE_DEVICE_TABLE(pci, cx18_pci_tbl); + +static atomic_t cx18_instance = ATOMIC_INIT(0); + +/* Parameter declarations */ +static int cardtype[CX18_MAX_CARDS]; +static int tuner[CX18_MAX_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; +static int radio[CX18_MAX_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; +static unsigned cardtype_c = 1; +static unsigned tuner_c = 1; +static unsigned radio_c = 1; +static char pal[] = "--"; +static char secam[] = "--"; +static char ntsc[] = "-"; + +/* Buffers */ +static int enc_ts_buffers = CX18_DEFAULT_ENC_TS_BUFFERS; +static int enc_mpg_buffers = CX18_DEFAULT_ENC_MPG_BUFFERS; +static int enc_idx_buffers = CX18_DEFAULT_ENC_IDX_BUFFERS; +static int enc_yuv_buffers = CX18_DEFAULT_ENC_YUV_BUFFERS; +static int enc_vbi_buffers = CX18_DEFAULT_ENC_VBI_BUFFERS; +static int enc_pcm_buffers = CX18_DEFAULT_ENC_PCM_BUFFERS; + +static int enc_ts_bufsize = CX18_DEFAULT_ENC_TS_BUFSIZE; +static int enc_mpg_bufsize = CX18_DEFAULT_ENC_MPG_BUFSIZE; +static int enc_idx_bufsize = CX18_DEFAULT_ENC_IDX_BUFSIZE; +static int enc_yuv_bufsize = CX18_DEFAULT_ENC_YUV_BUFSIZE; +static int enc_pcm_bufsize = CX18_DEFAULT_ENC_PCM_BUFSIZE; + +static int enc_ts_bufs = -1; +static int enc_mpg_bufs = -1; +static int enc_idx_bufs = CX18_MAX_FW_MDLS_PER_STREAM; +static int enc_yuv_bufs = -1; +static int enc_vbi_bufs = -1; +static int enc_pcm_bufs = -1; + + +static int cx18_pci_latency = 1; + +static int mmio_ndelay; +static int retry_mmio = 1; + +int cx18_debug; + +module_param_array(tuner, int, &tuner_c, 0644); +module_param_array(radio, int, &radio_c, 0644); +module_param_array(cardtype, int, &cardtype_c, 0644); +module_param_string(pal, pal, sizeof(pal), 0644); +module_param_string(secam, secam, sizeof(secam), 0644); +module_param_string(ntsc, ntsc, sizeof(ntsc), 0644); +module_param_named(debug, cx18_debug, int, 0644); +module_param(mmio_ndelay, int, 0644); +module_param(retry_mmio, int, 0644); +module_param(cx18_pci_latency, int, 0644); +module_param(cx18_first_minor, int, 0644); + +module_param(enc_ts_buffers, int, 0644); +module_param(enc_mpg_buffers, int, 0644); +module_param(enc_idx_buffers, int, 0644); +module_param(enc_yuv_buffers, int, 0644); +module_param(enc_vbi_buffers, int, 0644); +module_param(enc_pcm_buffers, int, 0644); + +module_param(enc_ts_bufsize, int, 0644); +module_param(enc_mpg_bufsize, int, 0644); +module_param(enc_idx_bufsize, int, 0644); +module_param(enc_yuv_bufsize, int, 0644); +module_param(enc_pcm_bufsize, int, 0644); + +module_param(enc_ts_bufs, int, 0644); +module_param(enc_mpg_bufs, int, 0644); +module_param(enc_idx_bufs, int, 0644); +module_param(enc_yuv_bufs, int, 0644); +module_param(enc_vbi_bufs, int, 0644); +module_param(enc_pcm_bufs, int, 0644); + +MODULE_PARM_DESC(tuner, "Tuner type selection,\n" + "\t\t\tsee tuner.h for values"); +MODULE_PARM_DESC(radio, + "Enable or disable the radio. Use only if autodetection\n" + "\t\t\tfails. 0 = disable, 1 = enable"); +MODULE_PARM_DESC(cardtype, + "Only use this option if your card is not detected properly.\n" + "\t\tSpecify card type:\n" + "\t\t\t 1 = Hauppauge HVR 1600 (ESMT memory)\n" + "\t\t\t 2 = Hauppauge HVR 1600 (Samsung memory)\n" + "\t\t\t 3 = Compro VideoMate H900\n" + "\t\t\t 4 = Yuan MPC718\n" + "\t\t\t 5 = Conexant Raptor PAL/SECAM\n" + "\t\t\t 6 = Toshiba Qosmio DVB-T/Analog\n" + "\t\t\t 7 = Leadtek WinFast PVR2100\n" + "\t\t\t 8 = Leadtek WinFast DVR3100 H\n" + "\t\t\t 9 = GoTView PCI DVD3 Hybrid\n" + "\t\t\t 10 = Hauppauge HVR 1600 (S5H1411)\n" + "\t\t\t 0 = Autodetect (default)\n" + "\t\t\t-1 = Ignore this card\n\t\t"); +MODULE_PARM_DESC(pal, "Set PAL standard: B, G, H, D, K, I, M, N, Nc, 60"); +MODULE_PARM_DESC(secam, "Set SECAM standard: B, G, H, D, K, L, LC"); +MODULE_PARM_DESC(ntsc, "Set NTSC standard: M, J, K"); +MODULE_PARM_DESC(debug, + "Debug level (bitmask). Default: 0\n" + "\t\t\t 1/0x0001: warning\n" + "\t\t\t 2/0x0002: info\n" + "\t\t\t 4/0x0004: mailbox\n" + "\t\t\t 8/0x0008: dma\n" + "\t\t\t 16/0x0010: ioctl\n" + "\t\t\t 32/0x0020: file\n" + "\t\t\t 64/0x0040: i2c\n" + "\t\t\t128/0x0080: irq\n" + "\t\t\t256/0x0100: high volume\n"); +MODULE_PARM_DESC(cx18_pci_latency, + "Change the PCI latency to 64 if lower: 0 = No, 1 = Yes,\n" + "\t\t\tDefault: Yes"); +MODULE_PARM_DESC(retry_mmio, + "(Deprecated) MMIO writes are now always checked and retried\n" + "\t\t\tEffectively: 1 [Yes]"); +MODULE_PARM_DESC(mmio_ndelay, + "(Deprecated) MMIO accesses are now never purposely delayed\n" + "\t\t\tEffectively: 0 ns"); +MODULE_PARM_DESC(enc_ts_buffers, + "Encoder TS buffer memory (MB). (enc_ts_bufs can override)\n" + "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_TS_BUFFERS)); +MODULE_PARM_DESC(enc_ts_bufsize, + "Size of an encoder TS buffer (kB)\n" + "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_TS_BUFSIZE)); +MODULE_PARM_DESC(enc_ts_bufs, + "Number of encoder TS buffers\n" + "\t\t\tDefault is computed from other enc_ts_* parameters"); +MODULE_PARM_DESC(enc_mpg_buffers, + "Encoder MPG buffer memory (MB). (enc_mpg_bufs can override)\n" + "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_MPG_BUFFERS)); +MODULE_PARM_DESC(enc_mpg_bufsize, + "Size of an encoder MPG buffer (kB)\n" + "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_MPG_BUFSIZE)); +MODULE_PARM_DESC(enc_mpg_bufs, + "Number of encoder MPG buffers\n" + "\t\t\tDefault is computed from other enc_mpg_* parameters"); +MODULE_PARM_DESC(enc_idx_buffers, + "(Deprecated) Encoder IDX buffer memory (MB)\n" + "\t\t\tIgnored, except 0 disables IDX buffer allocations\n" + "\t\t\tDefault: 1 [Enabled]"); +MODULE_PARM_DESC(enc_idx_bufsize, + "Size of an encoder IDX buffer (kB)\n" + "\t\t\tAllowed values are multiples of 1.5 kB rounded up\n" + "\t\t\t(multiples of size required for 64 index entries)\n" + "\t\t\tDefault: 2"); +MODULE_PARM_DESC(enc_idx_bufs, + "Number of encoder IDX buffers\n" + "\t\t\tDefault: " __stringify(CX18_MAX_FW_MDLS_PER_STREAM)); +MODULE_PARM_DESC(enc_yuv_buffers, + "Encoder YUV buffer memory (MB). (enc_yuv_bufs can override)\n" + "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_YUV_BUFFERS)); +MODULE_PARM_DESC(enc_yuv_bufsize, + "Size of an encoder YUV buffer (kB)\n" + "\t\t\tAllowed values are multiples of 33.75 kB rounded up\n" + "\t\t\t(multiples of size required for 32 screen lines)\n" + "\t\t\tDefault: 102"); +MODULE_PARM_DESC(enc_yuv_bufs, + "Number of encoder YUV buffers\n" + "\t\t\tDefault is computed from other enc_yuv_* parameters"); +MODULE_PARM_DESC(enc_vbi_buffers, + "Encoder VBI buffer memory (MB). (enc_vbi_bufs can override)\n" + "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_VBI_BUFFERS)); +MODULE_PARM_DESC(enc_vbi_bufs, + "Number of encoder VBI buffers\n" + "\t\t\tDefault is computed from enc_vbi_buffers"); +MODULE_PARM_DESC(enc_pcm_buffers, + "Encoder PCM buffer memory (MB). (enc_pcm_bufs can override)\n" + "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_PCM_BUFFERS)); +MODULE_PARM_DESC(enc_pcm_bufsize, + "Size of an encoder PCM buffer (kB)\n" + "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_PCM_BUFSIZE)); +MODULE_PARM_DESC(enc_pcm_bufs, + "Number of encoder PCM buffers\n" + "\t\t\tDefault is computed from other enc_pcm_* parameters"); + +MODULE_PARM_DESC(cx18_first_minor, + "Set device node number assigned to first card"); + +MODULE_AUTHOR("Hans Verkuil"); +MODULE_DESCRIPTION("CX23418 driver"); +MODULE_LICENSE("GPL"); + +MODULE_VERSION(CX18_VERSION); + +#if defined(CONFIG_MODULES) && defined(MODULE) +static void request_module_async(struct work_struct *work) +{ + struct cx18 *dev = container_of(work, struct cx18, request_module_wk); + + /* Make sure cx18-alsa module is loaded */ + request_module("cx18-alsa"); + + /* Initialize cx18-alsa for this instance of the cx18 device */ + if (cx18_ext_init) + cx18_ext_init(dev); +} + +static void request_modules(struct cx18 *dev) +{ + INIT_WORK(&dev->request_module_wk, request_module_async); + schedule_work(&dev->request_module_wk); +} + +static void flush_request_modules(struct cx18 *dev) +{ + flush_work(&dev->request_module_wk); +} +#else +#define request_modules(dev) +#define flush_request_modules(dev) +#endif /* CONFIG_MODULES */ + +/* Generic utility functions */ +int cx18_msleep_timeout(unsigned int msecs, int intr) +{ + long int timeout = msecs_to_jiffies(msecs); + int sig; + + do { + set_current_state(intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE); + timeout = schedule_timeout(timeout); + sig = intr ? signal_pending(current) : 0; + } while (!sig && timeout); + return sig; +} + +/* Release ioremapped memory */ +static void cx18_iounmap(struct cx18 *cx) +{ + if (!cx) + return; + + /* Release io memory */ + if (cx->enc_mem) { + CX18_DEBUG_INFO("releasing enc_mem\n"); + iounmap(cx->enc_mem); + cx->enc_mem = NULL; + } +} + +static void cx18_eeprom_dump(struct cx18 *cx, unsigned char *eedata, int len) +{ + int i; + + CX18_INFO("eeprom dump:\n"); + for (i = 0; i < len; i++) { + if (0 == (i % 16)) + CX18_INFO("eeprom %02x:", i); + printk(KERN_CONT " %02x", eedata[i]); + if (15 == (i % 16)) + printk(KERN_CONT "\n"); + } +} + +/* Hauppauge card? get values from tveeprom */ +void cx18_read_eeprom(struct cx18 *cx, struct tveeprom *tv) +{ + struct i2c_client *c; + u8 eedata[256]; + + memset(tv, 0, sizeof(*tv)); + + c = kzalloc_obj(*c); + if (!c) + return; + + strscpy(c->name, "cx18 tveeprom tmp", sizeof(c->name)); + c->adapter = &cx->i2c_adap[0]; + c->addr = 0xa0 >> 1; + + if (tveeprom_read(c, eedata, sizeof(eedata))) + goto ret; + + switch (cx->card->type) { + case CX18_CARD_HVR_1600_ESMT: + case CX18_CARD_HVR_1600_SAMSUNG: + case CX18_CARD_HVR_1600_S5H1411: + tveeprom_hauppauge_analog(tv, eedata); + break; + case CX18_CARD_YUAN_MPC718: + case CX18_CARD_GOTVIEW_PCI_DVD3: + tv->model = 0x718; + cx18_eeprom_dump(cx, eedata, sizeof(eedata)); + CX18_INFO("eeprom PCI ID: %02x%02x:%02x%02x\n", + eedata[2], eedata[1], eedata[4], eedata[3]); + break; + default: + tv->model = 0xffffffff; + cx18_eeprom_dump(cx, eedata, sizeof(eedata)); + break; + } + +ret: + kfree(c); +} + +static void cx18_process_eeprom(struct cx18 *cx) +{ + struct tveeprom tv; + + cx18_read_eeprom(cx, &tv); + + /* Many thanks to Steven Toth from Hauppauge for providing the + model numbers */ + /* Note: the Samsung memory models cannot be reliably determined + from the model number. Use the cardtype module option if you + have one of these preproduction models. */ + switch (tv.model) { + case 74301: /* Retail models */ + case 74321: + case 74351: /* OEM models */ + case 74361: + /* Digital side is s5h1411/tda18271 */ + cx->card = cx18_get_card(CX18_CARD_HVR_1600_S5H1411); + break; + case 74021: /* Retail models */ + case 74031: + case 74041: + case 74141: + case 74541: /* OEM models */ + case 74551: + case 74591: + case 74651: + case 74691: + case 74751: + case 74891: + /* Digital side is s5h1409/mxl5005s */ + cx->card = cx18_get_card(CX18_CARD_HVR_1600_ESMT); + break; + case 0x718: + return; + case 0xffffffff: + CX18_INFO("Unknown EEPROM encoding\n"); + return; + case 0: + CX18_ERR("Invalid EEPROM\n"); + return; + default: + CX18_ERR("Unknown model %d, defaulting to original HVR-1600 (cardtype=1)\n", + tv.model); + cx->card = cx18_get_card(CX18_CARD_HVR_1600_ESMT); + break; + } + + cx->v4l2_cap = cx->card->v4l2_capabilities; + cx->card_name = cx->card->name; + cx->card_i2c = cx->card->i2c; + + CX18_INFO("Autodetected %s\n", cx->card_name); + + if (tv.tuner_type == TUNER_ABSENT) + CX18_ERR("tveeprom cannot autodetect tuner!\n"); + + if (cx->options.tuner == -1) + cx->options.tuner = tv.tuner_type; + if (cx->options.radio == -1) + cx->options.radio = (tv.has_radio != 0); + + if (cx->std != 0) + /* user specified tuner standard */ + return; + + /* autodetect tuner standard */ +#define TVEEPROM_TUNER_FORMAT_ALL (V4L2_STD_B | V4L2_STD_GH | \ + V4L2_STD_MN | \ + V4L2_STD_PAL_I | \ + V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC | \ + V4L2_STD_DK) + if ((tv.tuner_formats & TVEEPROM_TUNER_FORMAT_ALL) + == TVEEPROM_TUNER_FORMAT_ALL) { + CX18_DEBUG_INFO("Worldwide tuner detected\n"); + cx->std = V4L2_STD_ALL; + } else if (tv.tuner_formats & V4L2_STD_PAL) { + CX18_DEBUG_INFO("PAL tuner detected\n"); + cx->std |= V4L2_STD_PAL_BG | V4L2_STD_PAL_H; + } else if (tv.tuner_formats & V4L2_STD_NTSC) { + CX18_DEBUG_INFO("NTSC tuner detected\n"); + cx->std |= V4L2_STD_NTSC_M; + } else if (tv.tuner_formats & V4L2_STD_SECAM) { + CX18_DEBUG_INFO("SECAM tuner detected\n"); + cx->std |= V4L2_STD_SECAM_L; + } else { + CX18_INFO("No tuner detected, default to NTSC-M\n"); + cx->std |= V4L2_STD_NTSC_M; + } +} + +static v4l2_std_id cx18_parse_std(struct cx18 *cx) +{ + switch (pal[0]) { + case '6': + return V4L2_STD_PAL_60; + case 'b': + case 'B': + case 'g': + case 'G': + return V4L2_STD_PAL_BG; + case 'h': + case 'H': + return V4L2_STD_PAL_H; + case 'n': + case 'N': + if (pal[1] == 'c' || pal[1] == 'C') + return V4L2_STD_PAL_Nc; + return V4L2_STD_PAL_N; + case 'i': + case 'I': + return V4L2_STD_PAL_I; + case 'd': + case 'D': + case 'k': + case 'K': + return V4L2_STD_PAL_DK; + case 'M': + case 'm': + return V4L2_STD_PAL_M; + case '-': + break; + default: + CX18_WARN("pal= argument not recognised\n"); + return 0; + } + + switch (secam[0]) { + case 'b': + case 'B': + case 'g': + case 'G': + case 'h': + case 'H': + return V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H; + case 'd': + case 'D': + case 'k': + case 'K': + return V4L2_STD_SECAM_DK; + case 'l': + case 'L': + if (secam[1] == 'C' || secam[1] == 'c') + return V4L2_STD_SECAM_LC; + return V4L2_STD_SECAM_L; + case '-': + break; + default: + CX18_WARN("secam= argument not recognised\n"); + return 0; + } + + switch (ntsc[0]) { + case 'm': + case 'M': + return V4L2_STD_NTSC_M; + case 'j': + case 'J': + return V4L2_STD_NTSC_M_JP; + case 'k': + case 'K': + return V4L2_STD_NTSC_M_KR; + case '-': + break; + default: + CX18_WARN("ntsc= argument not recognised\n"); + return 0; + } + + /* no match found */ + return 0; +} + +static void cx18_process_options(struct cx18 *cx) +{ + int i, j; + + cx->options.megabytes[CX18_ENC_STREAM_TYPE_TS] = enc_ts_buffers; + cx->options.megabytes[CX18_ENC_STREAM_TYPE_MPG] = enc_mpg_buffers; + cx->options.megabytes[CX18_ENC_STREAM_TYPE_IDX] = enc_idx_buffers; + cx->options.megabytes[CX18_ENC_STREAM_TYPE_YUV] = enc_yuv_buffers; + cx->options.megabytes[CX18_ENC_STREAM_TYPE_VBI] = enc_vbi_buffers; + cx->options.megabytes[CX18_ENC_STREAM_TYPE_PCM] = enc_pcm_buffers; + cx->options.megabytes[CX18_ENC_STREAM_TYPE_RAD] = 0; /* control only */ + + cx->stream_buffers[CX18_ENC_STREAM_TYPE_TS] = enc_ts_bufs; + cx->stream_buffers[CX18_ENC_STREAM_TYPE_MPG] = enc_mpg_bufs; + cx->stream_buffers[CX18_ENC_STREAM_TYPE_IDX] = enc_idx_bufs; + cx->stream_buffers[CX18_ENC_STREAM_TYPE_YUV] = enc_yuv_bufs; + cx->stream_buffers[CX18_ENC_STREAM_TYPE_VBI] = enc_vbi_bufs; + cx->stream_buffers[CX18_ENC_STREAM_TYPE_PCM] = enc_pcm_bufs; + cx->stream_buffers[CX18_ENC_STREAM_TYPE_RAD] = 0; /* control, no data */ + + cx->stream_buf_size[CX18_ENC_STREAM_TYPE_TS] = enc_ts_bufsize; + cx->stream_buf_size[CX18_ENC_STREAM_TYPE_MPG] = enc_mpg_bufsize; + cx->stream_buf_size[CX18_ENC_STREAM_TYPE_IDX] = enc_idx_bufsize; + cx->stream_buf_size[CX18_ENC_STREAM_TYPE_YUV] = enc_yuv_bufsize; + cx->stream_buf_size[CX18_ENC_STREAM_TYPE_VBI] = VBI_ACTIVE_SAMPLES * 36; + cx->stream_buf_size[CX18_ENC_STREAM_TYPE_PCM] = enc_pcm_bufsize; + cx->stream_buf_size[CX18_ENC_STREAM_TYPE_RAD] = 0; /* control no data */ + + /* Ensure stream_buffers & stream_buf_size are valid */ + for (i = 0; i < CX18_MAX_STREAMS; i++) { + if (cx->stream_buffers[i] == 0 || /* User said 0 buffers */ + cx->options.megabytes[i] <= 0 || /* User said 0 MB total */ + cx->stream_buf_size[i] <= 0) { /* User said buf size 0 */ + cx->options.megabytes[i] = 0; + cx->stream_buffers[i] = 0; + cx->stream_buf_size[i] = 0; + continue; + } + /* + * YUV is a special case where the stream_buf_size needs to be + * an integral multiple of 33.75 kB (storage for 32 screens + * lines to maintain alignment in case of lost buffers). + * + * IDX is a special case where the stream_buf_size should be + * an integral multiple of 1.5 kB (storage for 64 index entries + * to maintain alignment in case of lost buffers). + * + */ + if (i == CX18_ENC_STREAM_TYPE_YUV) { + cx->stream_buf_size[i] *= 1024; + cx->stream_buf_size[i] -= + (cx->stream_buf_size[i] % CX18_UNIT_ENC_YUV_BUFSIZE); + + if (cx->stream_buf_size[i] < CX18_UNIT_ENC_YUV_BUFSIZE) + cx->stream_buf_size[i] = + CX18_UNIT_ENC_YUV_BUFSIZE; + } else if (i == CX18_ENC_STREAM_TYPE_IDX) { + cx->stream_buf_size[i] *= 1024; + cx->stream_buf_size[i] -= + (cx->stream_buf_size[i] % CX18_UNIT_ENC_IDX_BUFSIZE); + + if (cx->stream_buf_size[i] < CX18_UNIT_ENC_IDX_BUFSIZE) + cx->stream_buf_size[i] = + CX18_UNIT_ENC_IDX_BUFSIZE; + } + /* + * YUV and IDX are special cases where the stream_buf_size is + * now in bytes. + * VBI is a special case where the stream_buf_size is fixed + * and already in bytes + */ + if (i == CX18_ENC_STREAM_TYPE_VBI || + i == CX18_ENC_STREAM_TYPE_YUV || + i == CX18_ENC_STREAM_TYPE_IDX) { + if (cx->stream_buffers[i] < 0) { + cx->stream_buffers[i] = + cx->options.megabytes[i] * 1024 * 1024 + / cx->stream_buf_size[i]; + } else { + /* N.B. This might round down to 0 */ + cx->options.megabytes[i] = + cx->stream_buffers[i] + * cx->stream_buf_size[i]/(1024 * 1024); + } + } else { + /* All other streams have stream_buf_size in kB here */ + if (cx->stream_buffers[i] < 0) { + cx->stream_buffers[i] = + cx->options.megabytes[i] * 1024 + / cx->stream_buf_size[i]; + } else { + /* N.B. This might round down to 0 */ + cx->options.megabytes[i] = + cx->stream_buffers[i] + * cx->stream_buf_size[i] / 1024; + } + /* convert from kB to bytes */ + cx->stream_buf_size[i] *= 1024; + } + CX18_DEBUG_INFO("Stream type %d options: %d MB, %d buffers, %d bytes\n", + i, cx->options.megabytes[i], + cx->stream_buffers[i], cx->stream_buf_size[i]); + } + + cx->options.cardtype = cardtype[cx->instance]; + cx->options.tuner = tuner[cx->instance]; + cx->options.radio = radio[cx->instance]; + + cx->std = cx18_parse_std(cx); + if (cx->options.cardtype == -1) { + CX18_INFO("Ignore card\n"); + return; + } + cx->card = cx18_get_card(cx->options.cardtype - 1); + if (cx->card) + CX18_INFO("User specified %s card\n", cx->card->name); + else if (cx->options.cardtype != 0) + CX18_ERR("Unknown user specified type, trying to autodetect card\n"); + if (!cx->card) { + if (cx->pci_dev->subsystem_vendor == CX18_PCI_ID_HAUPPAUGE) { + cx->card = cx18_get_card(CX18_CARD_HVR_1600_ESMT); + CX18_INFO("Autodetected Hauppauge card\n"); + } + } + if (!cx->card) { + for (i = 0; (cx->card = cx18_get_card(i)); i++) { + if (!cx->card->pci_list) + continue; + for (j = 0; cx->card->pci_list[j].device; j++) { + if (cx->pci_dev->device != + cx->card->pci_list[j].device) + continue; + if (cx->pci_dev->subsystem_vendor != + cx->card->pci_list[j].subsystem_vendor) + continue; + if (cx->pci_dev->subsystem_device != + cx->card->pci_list[j].subsystem_device) + continue; + CX18_INFO("Autodetected %s card\n", cx->card->name); + goto done; + } + } + } +done: + + if (!cx->card) { + cx->card = cx18_get_card(CX18_CARD_HVR_1600_ESMT); + CX18_ERR("Unknown card: vendor/device: [%04x:%04x]\n", + cx->pci_dev->vendor, cx->pci_dev->device); + CX18_ERR(" subsystem vendor/device: [%04x:%04x]\n", + cx->pci_dev->subsystem_vendor, + cx->pci_dev->subsystem_device); + CX18_ERR("Defaulting to %s card\n", cx->card->name); + CX18_ERR("Please mail the vendor/device and subsystem vendor/device IDs and what kind of\n"); + CX18_ERR("card you have to the linux-media mailinglist (www.linuxtv.org)\n"); + CX18_ERR("Prefix your subject line with [UNKNOWN CX18 CARD].\n"); + } + cx->v4l2_cap = cx->card->v4l2_capabilities; + cx->card_name = cx->card->name; + cx->card_i2c = cx->card->i2c; +} + +static int cx18_create_in_workq(struct cx18 *cx) +{ + snprintf(cx->in_workq_name, sizeof(cx->in_workq_name), "%s-in", + cx->v4l2_dev.name); + cx->in_work_queue = alloc_ordered_workqueue("%s", 0, cx->in_workq_name); + if (!cx->in_work_queue) { + CX18_ERR("Unable to create incoming mailbox handler thread\n"); + return -ENOMEM; + } + return 0; +} + +static void cx18_init_in_work_orders(struct cx18 *cx) +{ + int i; + for (i = 0; i < CX18_MAX_IN_WORK_ORDERS; i++) { + cx->in_work_order[i].cx = cx; + cx->in_work_order[i].str = cx->epu_debug_str; + INIT_WORK(&cx->in_work_order[i].work, cx18_in_work_handler); + } +} + +/* Precondition: the cx18 structure has been memset to 0. Only + the dev and instance fields have been filled in. + No assumptions on the card type may be made here (see cx18_init_struct2 + for that). + */ +static int cx18_init_struct1(struct cx18 *cx) +{ + int ret; + + cx->base_addr = pci_resource_start(cx->pci_dev, 0); + + mutex_init(&cx->serialize_lock); + mutex_init(&cx->gpio_lock); + mutex_init(&cx->epu2apu_mb_lock); + mutex_init(&cx->epu2cpu_mb_lock); + + ret = cx18_create_in_workq(cx); + if (ret) + return ret; + + cx18_init_in_work_orders(cx); + + /* start counting open_id at 1 */ + cx->open_id = 1; + + /* Initial settings */ + cx->cxhdl.port = CX2341X_PORT_MEMORY; + cx->cxhdl.capabilities = CX2341X_CAP_HAS_TS | CX2341X_CAP_HAS_SLICED_VBI; + cx->cxhdl.ops = &cx18_cxhdl_ops; + cx->cxhdl.func = cx18_api_func; + cx->cxhdl.priv = &cx->streams[CX18_ENC_STREAM_TYPE_MPG]; + ret = cx2341x_handler_init(&cx->cxhdl, 50); + if (ret) + return ret; + cx->v4l2_dev.ctrl_handler = &cx->cxhdl.hdl; + + cx->temporal_strength = cx->cxhdl.video_temporal_filter->cur.val; + cx->spatial_strength = cx->cxhdl.video_spatial_filter->cur.val; + cx->filter_mode = cx->cxhdl.video_spatial_filter_mode->cur.val | + (cx->cxhdl.video_temporal_filter_mode->cur.val << 1) | + (cx->cxhdl.video_median_filter_type->cur.val << 2); + + init_waitqueue_head(&cx->cap_w); + init_waitqueue_head(&cx->mb_apu_waitq); + init_waitqueue_head(&cx->mb_cpu_waitq); + init_waitqueue_head(&cx->dma_waitq); + + /* VBI */ + cx->vbi.in.type = V4L2_BUF_TYPE_VBI_CAPTURE; + cx->vbi.sliced_in = &cx->vbi.in.fmt.sliced; + + /* IVTV style VBI insertion into MPEG streams */ + INIT_LIST_HEAD(&cx->vbi.sliced_mpeg_buf.list); + INIT_LIST_HEAD(&cx->vbi.sliced_mpeg_mdl.list); + INIT_LIST_HEAD(&cx->vbi.sliced_mpeg_mdl.buf_list); + list_add(&cx->vbi.sliced_mpeg_buf.list, + &cx->vbi.sliced_mpeg_mdl.buf_list); + return 0; +} + +/* Second initialization part. Here the card type has been + autodetected. */ +static void cx18_init_struct2(struct cx18 *cx) +{ + int i; + + for (i = 0; i < CX18_CARD_MAX_VIDEO_INPUTS; i++) + if (cx->card->video_inputs[i].video_type == 0) + break; + cx->nof_inputs = i; + for (i = 0; i < CX18_CARD_MAX_AUDIO_INPUTS; i++) + if (cx->card->audio_inputs[i].audio_type == 0) + break; + cx->nof_audio_inputs = i; + + /* Find tuner input */ + for (i = 0; i < cx->nof_inputs; i++) { + if (cx->card->video_inputs[i].video_type == + CX18_CARD_INPUT_VID_TUNER) + break; + } + if (i == cx->nof_inputs) + i = 0; + cx->active_input = i; + cx->audio_input = cx->card->video_inputs[i].audio_index; +} + +static int cx18_setup_pci(struct cx18 *cx, struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + u16 cmd; + unsigned char pci_latency; + + CX18_DEBUG_INFO("Enabling pci device\n"); + + if (pci_enable_device(pci_dev)) { + CX18_ERR("Can't enable device %d!\n", cx->instance); + return -EIO; + } + if (dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(32))) { + CX18_ERR("No suitable DMA available, card %d\n", cx->instance); + return -EIO; + } + if (!request_mem_region(cx->base_addr, CX18_MEM_SIZE, "cx18 encoder")) { + CX18_ERR("Cannot request encoder memory region, card %d\n", + cx->instance); + return -EIO; + } + + /* Enable bus mastering and memory mapped IO for the CX23418 */ + pci_read_config_word(pci_dev, PCI_COMMAND, &cmd); + cmd |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER; + pci_write_config_word(pci_dev, PCI_COMMAND, cmd); + + cx->card_rev = pci_dev->revision; + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &pci_latency); + + if (pci_latency < 64 && cx18_pci_latency) { + CX18_INFO("Unreasonably low latency timer, setting to 64 (was %d)\n", + pci_latency); + pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, 64); + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &pci_latency); + } + + CX18_DEBUG_INFO("cx%d (rev %d) at %02x:%02x.%x, irq: %d, latency: %d, memory: 0x%llx\n", + cx->pci_dev->device, cx->card_rev, pci_dev->bus->number, + PCI_SLOT(pci_dev->devfn), PCI_FUNC(pci_dev->devfn), + cx->pci_dev->irq, pci_latency, (u64)cx->base_addr); + + return 0; +} + +static void cx18_init_subdevs(struct cx18 *cx) +{ + u32 hw = cx->card->hw_all; + u32 device; + int i; + + for (i = 0, device = 1; i < 32; i++, device <<= 1) { + + if (!(device & hw)) + continue; + + switch (device) { + case CX18_HW_DVB: + case CX18_HW_TVEEPROM: + /* These subordinate devices do not use probing */ + cx->hw_flags |= device; + break; + case CX18_HW_418_AV: + /* The A/V decoder gets probed earlier to set PLLs */ + /* Just note that the card uses it (i.e. has analog) */ + cx->hw_flags |= device; + break; + case CX18_HW_GPIO_RESET_CTRL: + /* + * The Reset Controller gets probed and added to + * hw_flags earlier for i2c adapter/bus initialization + */ + break; + case CX18_HW_GPIO_MUX: + if (cx18_gpio_register(cx, device) == 0) + cx->hw_flags |= device; + break; + default: + if (cx18_i2c_register(cx, i) == 0) + cx->hw_flags |= device; + break; + } + } + + if (cx->hw_flags & CX18_HW_418_AV) + cx->sd_av = cx18_find_hw(cx, CX18_HW_418_AV); + + if (cx->card->hw_muxer != 0) + cx->sd_extmux = cx18_find_hw(cx, cx->card->hw_muxer); +} + +static int cx18_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + int retval = 0; + int i; + u32 devtype; + struct cx18 *cx; + + /* FIXME - module parameter arrays constrain max instances */ + i = atomic_inc_return(&cx18_instance) - 1; + if (i >= CX18_MAX_CARDS) { + printk(KERN_ERR "cx18: cannot manage card %d, driver has a limit of 0 - %d\n", + i, CX18_MAX_CARDS - 1); + return -ENOMEM; + } + + cx = kzalloc_obj(*cx); + if (!cx) + return -ENOMEM; + + cx->pci_dev = pci_dev; + cx->instance = i; + + retval = v4l2_device_register(&pci_dev->dev, &cx->v4l2_dev); + if (retval) { + printk(KERN_ERR "cx18: v4l2_device_register of card %d failed\n", + cx->instance); + kfree(cx); + return retval; + } + snprintf(cx->v4l2_dev.name, sizeof(cx->v4l2_dev.name), "cx18-%d", + cx->instance); + CX18_INFO("Initializing card %d\n", cx->instance); + + cx18_process_options(cx); + if (cx->options.cardtype == -1) { + retval = -ENODEV; + goto err; + } + + retval = cx18_init_struct1(cx); + if (retval) + goto err; + + CX18_DEBUG_INFO("base addr: 0x%llx\n", (u64)cx->base_addr); + + /* PCI Device Setup */ + retval = cx18_setup_pci(cx, pci_dev, pci_id); + if (retval != 0) + goto free_workqueues; + + /* map io memory */ + CX18_DEBUG_INFO("attempting ioremap at 0x%llx len 0x%08x\n", + (u64)cx->base_addr + CX18_MEM_OFFSET, CX18_MEM_SIZE); + cx->enc_mem = ioremap(cx->base_addr + CX18_MEM_OFFSET, + CX18_MEM_SIZE); + if (!cx->enc_mem) { + CX18_ERR("ioremap failed. Can't get a window into CX23418 memory and register space\n"); + CX18_ERR("Each capture card with a CX23418 needs 64 MB of vmalloc address space for the window\n"); + CX18_ERR("Check the output of 'grep Vmalloc /proc/meminfo'\n"); + CX18_ERR("Use the vmalloc= kernel command line option to set VmallocTotal to a larger value\n"); + retval = -ENOMEM; + goto free_mem; + } + cx->reg_mem = cx->enc_mem + CX18_REG_OFFSET; + devtype = cx18_read_reg(cx, 0xC72028); + switch (devtype & 0xff000000) { + case 0xff000000: + CX18_INFO("cx23418 revision %08x (A)\n", devtype); + break; + case 0x01000000: + CX18_INFO("cx23418 revision %08x (B)\n", devtype); + break; + default: + CX18_INFO("cx23418 revision %08x (Unknown)\n", devtype); + break; + } + + cx18_init_power(cx, 1); + cx18_init_memory(cx); + + cx->scb = (struct cx18_scb __iomem *)(cx->enc_mem + SCB_OFFSET); + cx18_init_scb(cx); + + cx18_gpio_init(cx); + + /* Initialize integrated A/V decoder early to set PLLs, just in case */ + retval = cx18_av_probe(cx); + if (retval) { + CX18_ERR("Could not register A/V decoder subdevice\n"); + goto free_map; + } + + /* Initialize GPIO Reset Controller to do chip resets during i2c init */ + if (cx->card->hw_all & CX18_HW_GPIO_RESET_CTRL) { + if (cx18_gpio_register(cx, CX18_HW_GPIO_RESET_CTRL) != 0) + CX18_WARN("Could not register GPIO reset controllersubdevice; proceeding anyway.\n"); + else + cx->hw_flags |= CX18_HW_GPIO_RESET_CTRL; + } + + /* active i2c */ + CX18_DEBUG_INFO("activating i2c...\n"); + retval = init_cx18_i2c(cx); + if (retval) { + CX18_ERR("Could not initialize i2c\n"); + goto free_map; + } + + if (cx->card->hw_all & CX18_HW_TVEEPROM) { + /* Based on the model number the cardtype may be changed. + The PCI IDs are not always reliable. */ + const struct cx18_card *orig_card = cx->card; + cx18_process_eeprom(cx); + + if (cx->card != orig_card) { + /* Changed the cardtype; re-reset the I2C chips */ + cx18_gpio_init(cx); + cx18_call_hw(cx, CX18_HW_GPIO_RESET_CTRL, + core, reset, (u32) CX18_GPIO_RESET_I2C); + } + } + if (cx->card->comment) + CX18_INFO("%s", cx->card->comment); + if (cx->card->v4l2_capabilities == 0) { + retval = -ENODEV; + goto free_i2c; + } + cx18_init_memory(cx); + cx18_init_scb(cx); + + /* Register IRQ */ + retval = request_irq(cx->pci_dev->irq, cx18_irq_handler, + IRQF_SHARED, cx->v4l2_dev.name, (void *)cx); + if (retval) { + CX18_ERR("Failed to register irq %d\n", retval); + goto free_i2c; + } + + if (cx->std == 0) + cx->std = V4L2_STD_NTSC_M; + + if (cx->options.tuner == -1) { + for (i = 0; i < CX18_CARD_MAX_TUNERS; i++) { + if ((cx->std & cx->card->tuners[i].std) == 0) + continue; + cx->options.tuner = cx->card->tuners[i].tuner; + break; + } + } + /* if no tuner was found, then pick the first tuner in the card list */ + if (cx->options.tuner == -1 && cx->card->tuners[0].std) { + cx->std = cx->card->tuners[0].std; + if (cx->std & V4L2_STD_PAL) + cx->std = V4L2_STD_PAL_BG | V4L2_STD_PAL_H; + else if (cx->std & V4L2_STD_NTSC) + cx->std = V4L2_STD_NTSC_M; + else if (cx->std & V4L2_STD_SECAM) + cx->std = V4L2_STD_SECAM_L; + cx->options.tuner = cx->card->tuners[0].tuner; + } + if (cx->options.radio == -1) + cx->options.radio = (cx->card->radio_input.audio_type != 0); + + /* The card is now fully identified, continue with card-specific + initialization. */ + cx18_init_struct2(cx); + + cx18_init_subdevs(cx); + + if (cx->std & V4L2_STD_525_60) + cx->is_60hz = 1; + else + cx->is_50hz = 1; + + cx2341x_handler_set_50hz(&cx->cxhdl, !cx->is_60hz); + + if (cx->options.radio > 0) + cx->v4l2_cap |= V4L2_CAP_RADIO; + + if (cx->options.tuner > -1) { + struct tuner_setup setup; + + setup.addr = ADDR_UNSET; + setup.type = cx->options.tuner; + setup.mode_mask = T_ANALOG_TV; /* matches TV tuners */ + setup.config = NULL; + if (cx->options.radio > 0) + setup.mode_mask |= T_RADIO; + setup.tuner_callback = (setup.type == TUNER_XC2028) ? + cx18_reset_tuner_gpio : NULL; + cx18_call_all(cx, tuner, s_type_addr, &setup); + if (setup.type == TUNER_XC2028) { + static struct xc2028_ctrl ctrl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + }; + struct v4l2_priv_tun_config cfg = { + .tuner = cx->options.tuner, + .priv = &ctrl, + }; + cx18_call_all(cx, tuner, s_config, &cfg); + } + } + + /* The tuner is fixed to the standard. The other inputs (e.g. S-Video) + are not. */ + cx->tuner_std = cx->std; + if (cx->std == V4L2_STD_ALL) + cx->std = V4L2_STD_NTSC_M; + + retval = cx18_streams_setup(cx); + if (retval) { + CX18_ERR("Error %d setting up streams\n", retval); + goto free_irq; + } + retval = cx18_streams_register(cx); + if (retval) { + CX18_ERR("Error %d registering devices\n", retval); + goto free_streams; + } + + CX18_INFO("Initialized card: %s\n", cx->card_name); + + /* Load cx18 submodules (cx18-alsa) */ + request_modules(cx); + return 0; + +free_streams: + cx18_streams_cleanup(cx, 1); +free_irq: + free_irq(cx->pci_dev->irq, (void *)cx); +free_i2c: + exit_cx18_i2c(cx); +free_map: + cx18_iounmap(cx); +free_mem: + release_mem_region(cx->base_addr, CX18_MEM_SIZE); +free_workqueues: + destroy_workqueue(cx->in_work_queue); +err: + CX18_ERR("Error %d on initialization\n", retval); + + v4l2_device_unregister(&cx->v4l2_dev); + kfree(cx); + return retval; +} + +int cx18_init_on_first_open(struct cx18 *cx) +{ + int video_input; + int fw_retry_count = 3; + struct v4l2_frequency vf; + v4l2_std_id std; + + if (test_bit(CX18_F_I_FAILED, &cx->i_flags)) + return -ENXIO; + + if (test_and_set_bit(CX18_F_I_INITED, &cx->i_flags)) + return 0; + + while (--fw_retry_count > 0) { + /* load firmware */ + if (cx18_firmware_init(cx) == 0) + break; + if (fw_retry_count > 1) + CX18_WARN("Retry loading firmware\n"); + } + + if (fw_retry_count == 0) { + set_bit(CX18_F_I_FAILED, &cx->i_flags); + return -ENXIO; + } + set_bit(CX18_F_I_LOADED_FW, &cx->i_flags); + + /* + * Init the firmware twice to work around a silicon bug + * with the digital TS. + * + * The second firmware load requires us to normalize the APU state, + * or the audio for the first analog capture will be badly incorrect. + * + * I can't seem to call APU_RESETAI and have it succeed without the + * APU capturing audio, so we start and stop it here to do the reset + */ + + /* MPEG Encoding, 224 kbps, MPEG Layer II, 48 ksps */ + cx18_vapi(cx, CX18_APU_START, 2, CX18_APU_ENCODING_METHOD_MPEG|0xb9, 0); + cx18_vapi(cx, CX18_APU_RESETAI, 0); + cx18_vapi(cx, CX18_APU_STOP, 1, CX18_APU_ENCODING_METHOD_MPEG); + + fw_retry_count = 3; + while (--fw_retry_count > 0) { + /* load firmware */ + if (cx18_firmware_init(cx) == 0) + break; + if (fw_retry_count > 1) + CX18_WARN("Retry loading firmware\n"); + } + + if (fw_retry_count == 0) { + set_bit(CX18_F_I_FAILED, &cx->i_flags); + return -ENXIO; + } + + /* + * The second firmware load requires us to normalize the APU state, + * or the audio for the first analog capture will be badly incorrect. + * + * I can't seem to call APU_RESETAI and have it succeed without the + * APU capturing audio, so we start and stop it here to do the reset + */ + + /* MPEG Encoding, 224 kbps, MPEG Layer II, 48 ksps */ + cx18_vapi(cx, CX18_APU_START, 2, CX18_APU_ENCODING_METHOD_MPEG|0xb9, 0); + cx18_vapi(cx, CX18_APU_RESETAI, 0); + cx18_vapi(cx, CX18_APU_STOP, 1, CX18_APU_ENCODING_METHOD_MPEG); + + /* Init the A/V decoder, if it hasn't been already */ + v4l2_subdev_call(cx->sd_av, core, load_fw); + + vf.tuner = 0; + vf.type = V4L2_TUNER_ANALOG_TV; + vf.frequency = 6400; /* the tuner 'baseline' frequency */ + + /* Set initial frequency. For PAL/SECAM broadcasts no + 'default' channel exists AFAIK. */ + if (cx->std == V4L2_STD_NTSC_M_JP) + vf.frequency = 1460; /* ch. 1 91250*16/1000 */ + else if (cx->std & V4L2_STD_NTSC_M) + vf.frequency = 1076; /* ch. 4 67250*16/1000 */ + + video_input = cx->active_input; + cx->active_input++; /* Force update of input */ + cx18_do_s_input(cx, video_input); + + /* Let the VIDIOC_S_STD ioctl do all the work, keeps the code + in one place. */ + cx->std++; /* Force full standard initialization */ + std = (cx->tuner_std == V4L2_STD_ALL) ? V4L2_STD_NTSC_M : cx->tuner_std; + cx18_do_s_std(cx, std); + cx18_do_s_frequency(cx, &vf); + return 0; +} + +static void cx18_cancel_in_work_orders(struct cx18 *cx) +{ + int i; + for (i = 0; i < CX18_MAX_IN_WORK_ORDERS; i++) + cancel_work_sync(&cx->in_work_order[i].work); +} + +static void cx18_cancel_out_work_orders(struct cx18 *cx) +{ + int i; + for (i = 0; i < CX18_MAX_STREAMS; i++) + if (cx->streams[i].video_dev.v4l2_dev) + cancel_work_sync(&cx->streams[i].out_work_order); +} + +static void cx18_remove(struct pci_dev *pci_dev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct cx18 *cx = to_cx18(v4l2_dev); + int i; + + CX18_DEBUG_INFO("Removing Card\n"); + + flush_request_modules(cx); + + /* Stop all captures */ + CX18_DEBUG_INFO("Stopping all streams\n"); + if (atomic_read(&cx->tot_capturing) > 0) + cx18_stop_all_captures(cx); + + /* Stop interrupts that cause incoming work to be queued */ + cx18_sw1_irq_disable(cx, IRQ_CPU_TO_EPU | IRQ_APU_TO_EPU); + + /* Incoming work can cause outgoing work, so clean up incoming first */ + cx18_cancel_in_work_orders(cx); + cx18_cancel_out_work_orders(cx); + + /* Stop ack interrupts that may have been needed for work to finish */ + cx18_sw2_irq_disable(cx, IRQ_CPU_TO_EPU_ACK | IRQ_APU_TO_EPU_ACK); + + cx18_halt_firmware(cx); + + destroy_workqueue(cx->in_work_queue); + + cx18_streams_cleanup(cx, 1); + + exit_cx18_i2c(cx); + + free_irq(cx->pci_dev->irq, (void *)cx); + + cx18_iounmap(cx); + + release_mem_region(cx->base_addr, CX18_MEM_SIZE); + + pci_disable_device(cx->pci_dev); + + if (cx->vbi.sliced_mpeg_data[0]) + for (i = 0; i < CX18_VBI_FRAMES; i++) + kfree(cx->vbi.sliced_mpeg_data[i]); + + v4l2_ctrl_handler_free(&cx->av_state.hdl); + + CX18_INFO("Removed %s\n", cx->card_name); + + v4l2_device_unregister(v4l2_dev); + kfree(cx); +} + + +/* define a pci_driver for card detection */ +static struct pci_driver cx18_pci_driver = { + .name = "cx18", + .id_table = cx18_pci_tbl, + .probe = cx18_probe, + .remove = cx18_remove, +}; + +static int __init module_start(void) +{ + printk(KERN_INFO "cx18: Start initialization, version %s\n", + CX18_VERSION); + + /* Validate parameters */ + if (cx18_first_minor < 0 || cx18_first_minor >= CX18_MAX_CARDS) { + printk(KERN_ERR "cx18: Exiting, cx18_first_minor must be between 0 and %d\n", + CX18_MAX_CARDS - 1); + return -1; + } + + if (cx18_debug < 0 || cx18_debug > 511) { + cx18_debug = 0; + printk(KERN_INFO "cx18: Debug value must be >= 0 and <= 511!\n"); + } + + if (pci_register_driver(&cx18_pci_driver)) { + printk(KERN_ERR "cx18: Error detecting PCI card\n"); + return -ENODEV; + } + printk(KERN_INFO "cx18: End initialization\n"); + return 0; +} + +static void __exit module_cleanup(void) +{ + pci_unregister_driver(&cx18_pci_driver); +} + +module_init(module_start); +module_exit(module_cleanup); +MODULE_FIRMWARE(XC2028_DEFAULT_FIRMWARE); diff --git a/drivers/media/pci/cx18/cx18-driver.h b/drivers/media/pci/cx18/cx18-driver.h new file mode 100644 index 000000000..ef3890370 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-driver.h @@ -0,0 +1,697 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 driver internal defines and structures + * + * Derived from ivtv-driver.h + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#ifndef CX18_DRIVER_H +#define CX18_DRIVER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "cx18-mailbox.h" +#include "cx18-av-core.h" +#include "cx23418.h" + +/* DVB */ +#include +#include +#include +#include +#include +#include + +/* vb2 YUV support */ +#include + +#ifndef CONFIG_PCI +# error "This driver requires kernel PCI support." +#endif + +#define CX18_MEM_OFFSET 0x00000000 +#define CX18_MEM_SIZE 0x04000000 +#define CX18_REG_OFFSET 0x02000000 + +/* Maximum cx18 driver instances. */ +#define CX18_MAX_CARDS 32 + +/* Supported cards */ +#define CX18_CARD_HVR_1600_ESMT 0 /* Hauppauge HVR 1600 (ESMT memory) */ +#define CX18_CARD_HVR_1600_SAMSUNG 1 /* Hauppauge HVR 1600 (Samsung memory) */ +#define CX18_CARD_COMPRO_H900 2 /* Compro VideoMate H900 */ +#define CX18_CARD_YUAN_MPC718 3 /* Yuan MPC718 */ +#define CX18_CARD_CNXT_RAPTOR_PAL 4 /* Conexant Raptor PAL */ +#define CX18_CARD_TOSHIBA_QOSMIO_DVBT 5 /* Toshiba Qosmio Interal DVB-T/Analog*/ +#define CX18_CARD_LEADTEK_PVR2100 6 /* Leadtek WinFast PVR2100 */ +#define CX18_CARD_LEADTEK_DVR3100H 7 /* Leadtek WinFast DVR3100 H */ +#define CX18_CARD_GOTVIEW_PCI_DVD3 8 /* GoTView PCI DVD3 Hybrid */ +#define CX18_CARD_HVR_1600_S5H1411 9 /* Hauppauge HVR 1600 s5h1411/tda18271*/ +#define CX18_CARD_LAST 9 + +#define CX18_ENC_STREAM_TYPE_MPG 0 +#define CX18_ENC_STREAM_TYPE_TS 1 +#define CX18_ENC_STREAM_TYPE_YUV 2 +#define CX18_ENC_STREAM_TYPE_VBI 3 +#define CX18_ENC_STREAM_TYPE_PCM 4 +#define CX18_ENC_STREAM_TYPE_IDX 5 +#define CX18_ENC_STREAM_TYPE_RAD 6 +#define CX18_MAX_STREAMS 7 + +/* system vendor and device IDs */ +#define PCI_VENDOR_ID_CX 0x14f1 +#define PCI_DEVICE_ID_CX23418 0x5b7a + +/* subsystem vendor ID */ +#define CX18_PCI_ID_HAUPPAUGE 0x0070 +#define CX18_PCI_ID_COMPRO 0x185b +#define CX18_PCI_ID_YUAN 0x12ab +#define CX18_PCI_ID_CONEXANT 0x14f1 +#define CX18_PCI_ID_TOSHIBA 0x1179 +#define CX18_PCI_ID_LEADTEK 0x107D +#define CX18_PCI_ID_GOTVIEW 0x5854 + +/* ======================================================================== */ +/* ========================== START USER SETTABLE DMA VARIABLES =========== */ +/* ======================================================================== */ + +/* DMA Buffers, Default size in MB allocated */ +#define CX18_DEFAULT_ENC_TS_BUFFERS 1 +#define CX18_DEFAULT_ENC_MPG_BUFFERS 2 +#define CX18_DEFAULT_ENC_IDX_BUFFERS 1 +#define CX18_DEFAULT_ENC_YUV_BUFFERS 2 +#define CX18_DEFAULT_ENC_VBI_BUFFERS 1 +#define CX18_DEFAULT_ENC_PCM_BUFFERS 1 + +/* Maximum firmware DMA buffers per stream */ +#define CX18_MAX_FW_MDLS_PER_STREAM 63 + +/* YUV buffer sizes in bytes to ensure integer # of frames per buffer */ +#define CX18_UNIT_ENC_YUV_BUFSIZE (720 * 32 * 3 / 2) /* bytes */ +#define CX18_625_LINE_ENC_YUV_BUFSIZE (CX18_UNIT_ENC_YUV_BUFSIZE * 576/32) +#define CX18_525_LINE_ENC_YUV_BUFSIZE (CX18_UNIT_ENC_YUV_BUFSIZE * 480/32) + +/* IDX buffer size should be a multiple of the index entry size from the chip */ +struct cx18_enc_idx_entry { + __le32 length; + __le32 offset_low; + __le32 offset_high; + __le32 flags; + __le32 pts_low; + __le32 pts_high; +} __attribute__ ((packed)); +#define CX18_UNIT_ENC_IDX_BUFSIZE \ + (sizeof(struct cx18_enc_idx_entry) * V4L2_ENC_IDX_ENTRIES) + +/* DMA buffer, default size in kB allocated */ +#define CX18_DEFAULT_ENC_TS_BUFSIZE 32 +#define CX18_DEFAULT_ENC_MPG_BUFSIZE 32 +#define CX18_DEFAULT_ENC_IDX_BUFSIZE (CX18_UNIT_ENC_IDX_BUFSIZE * 1 / 1024 + 1) +#define CX18_DEFAULT_ENC_YUV_BUFSIZE (CX18_UNIT_ENC_YUV_BUFSIZE * 3 / 1024 + 1) +#define CX18_DEFAULT_ENC_PCM_BUFSIZE 4 + +/* i2c stuff */ +#define I2C_CLIENTS_MAX 16 + +/* debugging */ + +/* Flag to turn on high volume debugging */ +#define CX18_DBGFLG_WARN (1 << 0) +#define CX18_DBGFLG_INFO (1 << 1) +#define CX18_DBGFLG_API (1 << 2) +#define CX18_DBGFLG_DMA (1 << 3) +#define CX18_DBGFLG_IOCTL (1 << 4) +#define CX18_DBGFLG_FILE (1 << 5) +#define CX18_DBGFLG_I2C (1 << 6) +#define CX18_DBGFLG_IRQ (1 << 7) +/* Flag to turn on high volume debugging */ +#define CX18_DBGFLG_HIGHVOL (1 << 8) + +/* NOTE: extra space before comma in 'fmt , ## args' is required for + gcc-2.95, otherwise it won't compile. */ +#define CX18_DEBUG(x, type, fmt, args...) \ + do { \ + if ((x) & cx18_debug) \ + v4l2_info(&cx->v4l2_dev, " " type ": " fmt , ## args); \ + } while (0) +#define CX18_DEBUG_WARN(fmt, args...) CX18_DEBUG(CX18_DBGFLG_WARN, "warning", fmt , ## args) +#define CX18_DEBUG_INFO(fmt, args...) CX18_DEBUG(CX18_DBGFLG_INFO, "info", fmt , ## args) +#define CX18_DEBUG_API(fmt, args...) CX18_DEBUG(CX18_DBGFLG_API, "api", fmt , ## args) +#define CX18_DEBUG_DMA(fmt, args...) CX18_DEBUG(CX18_DBGFLG_DMA, "dma", fmt , ## args) +#define CX18_DEBUG_IOCTL(fmt, args...) CX18_DEBUG(CX18_DBGFLG_IOCTL, "ioctl", fmt , ## args) +#define CX18_DEBUG_FILE(fmt, args...) CX18_DEBUG(CX18_DBGFLG_FILE, "file", fmt , ## args) +#define CX18_DEBUG_I2C(fmt, args...) CX18_DEBUG(CX18_DBGFLG_I2C, "i2c", fmt , ## args) +#define CX18_DEBUG_IRQ(fmt, args...) CX18_DEBUG(CX18_DBGFLG_IRQ, "irq", fmt , ## args) + +#define CX18_DEBUG_HIGH_VOL(x, type, fmt, args...) \ + do { \ + if (((x) & cx18_debug) && (cx18_debug & CX18_DBGFLG_HIGHVOL)) \ + v4l2_info(&cx->v4l2_dev, " " type ": " fmt , ## args); \ + } while (0) +#define CX18_DEBUG_HI_WARN(fmt, args...) CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_WARN, "warning", fmt , ## args) +#define CX18_DEBUG_HI_INFO(fmt, args...) CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_INFO, "info", fmt , ## args) +#define CX18_DEBUG_HI_API(fmt, args...) CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_API, "api", fmt , ## args) +#define CX18_DEBUG_HI_DMA(fmt, args...) CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_DMA, "dma", fmt , ## args) +#define CX18_DEBUG_HI_IOCTL(fmt, args...) CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_IOCTL, "ioctl", fmt , ## args) +#define CX18_DEBUG_HI_FILE(fmt, args...) CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_FILE, "file", fmt , ## args) +#define CX18_DEBUG_HI_I2C(fmt, args...) CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_I2C, "i2c", fmt , ## args) +#define CX18_DEBUG_HI_IRQ(fmt, args...) CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_IRQ, "irq", fmt , ## args) + +/* Standard kernel messages */ +#define CX18_ERR(fmt, args...) v4l2_err(&cx->v4l2_dev, fmt , ## args) +#define CX18_WARN(fmt, args...) v4l2_warn(&cx->v4l2_dev, fmt , ## args) +#define CX18_INFO(fmt, args...) v4l2_info(&cx->v4l2_dev, fmt , ## args) + +/* Messages for internal subdevs to use */ +#define CX18_DEBUG_DEV(x, dev, type, fmt, args...) \ + do { \ + if ((x) & cx18_debug) \ + v4l2_info(dev, " " type ": " fmt , ## args); \ + } while (0) +#define CX18_DEBUG_WARN_DEV(dev, fmt, args...) \ + CX18_DEBUG_DEV(CX18_DBGFLG_WARN, dev, "warning", fmt , ## args) +#define CX18_DEBUG_INFO_DEV(dev, fmt, args...) \ + CX18_DEBUG_DEV(CX18_DBGFLG_INFO, dev, "info", fmt , ## args) +#define CX18_DEBUG_API_DEV(dev, fmt, args...) \ + CX18_DEBUG_DEV(CX18_DBGFLG_API, dev, "api", fmt , ## args) +#define CX18_DEBUG_DMA_DEV(dev, fmt, args...) \ + CX18_DEBUG_DEV(CX18_DBGFLG_DMA, dev, "dma", fmt , ## args) +#define CX18_DEBUG_IOCTL_DEV(dev, fmt, args...) \ + CX18_DEBUG_DEV(CX18_DBGFLG_IOCTL, dev, "ioctl", fmt , ## args) +#define CX18_DEBUG_FILE_DEV(dev, fmt, args...) \ + CX18_DEBUG_DEV(CX18_DBGFLG_FILE, dev, "file", fmt , ## args) +#define CX18_DEBUG_I2C_DEV(dev, fmt, args...) \ + CX18_DEBUG_DEV(CX18_DBGFLG_I2C, dev, "i2c", fmt , ## args) +#define CX18_DEBUG_IRQ_DEV(dev, fmt, args...) \ + CX18_DEBUG_DEV(CX18_DBGFLG_IRQ, dev, "irq", fmt , ## args) + +#define CX18_DEBUG_HIGH_VOL_DEV(x, dev, type, fmt, args...) \ + do { \ + if (((x) & cx18_debug) && (cx18_debug & CX18_DBGFLG_HIGHVOL)) \ + v4l2_info(dev, " " type ": " fmt , ## args); \ + } while (0) +#define CX18_DEBUG_HI_WARN_DEV(dev, fmt, args...) \ + CX18_DEBUG_HIGH_VOL_DEV(CX18_DBGFLG_WARN, dev, "warning", fmt , ## args) +#define CX18_DEBUG_HI_INFO_DEV(dev, fmt, args...) \ + CX18_DEBUG_HIGH_VOL_DEV(CX18_DBGFLG_INFO, dev, "info", fmt , ## args) +#define CX18_DEBUG_HI_API_DEV(dev, fmt, args...) \ + CX18_DEBUG_HIGH_VOL_DEV(CX18_DBGFLG_API, dev, "api", fmt , ## args) +#define CX18_DEBUG_HI_DMA_DEV(dev, fmt, args...) \ + CX18_DEBUG_HIGH_VOL_DEV(CX18_DBGFLG_DMA, dev, "dma", fmt , ## args) +#define CX18_DEBUG_HI_IOCTL_DEV(dev, fmt, args...) \ + CX18_DEBUG_HIGH_VOL_DEV(CX18_DBGFLG_IOCTL, dev, "ioctl", fmt , ## args) +#define CX18_DEBUG_HI_FILE_DEV(dev, fmt, args...) \ + CX18_DEBUG_HIGH_VOL_DEV(CX18_DBGFLG_FILE, dev, "file", fmt , ## args) +#define CX18_DEBUG_HI_I2C_DEV(dev, fmt, args...) \ + CX18_DEBUG_HIGH_VOL_DEV(CX18_DBGFLG_I2C, dev, "i2c", fmt , ## args) +#define CX18_DEBUG_HI_IRQ_DEV(dev, fmt, args...) \ + CX18_DEBUG_HIGH_VOL_DEV(CX18_DBGFLG_IRQ, dev, "irq", fmt , ## args) + +#define CX18_ERR_DEV(dev, fmt, args...) v4l2_err(dev, fmt , ## args) +#define CX18_WARN_DEV(dev, fmt, args...) v4l2_warn(dev, fmt , ## args) +#define CX18_INFO_DEV(dev, fmt, args...) v4l2_info(dev, fmt , ## args) + +extern int cx18_debug; + +struct cx18_options { + int megabytes[CX18_MAX_STREAMS]; /* Size in megabytes of each stream */ + int cardtype; /* force card type on load */ + int tuner; /* set tuner on load */ + int radio; /* enable/disable radio */ +}; + +/* per-mdl bit flags */ +#define CX18_F_M_NEED_SWAP 0 /* mdl buffer data must be endianness swapped */ + +/* per-stream, s_flags */ +#define CX18_F_S_CLAIMED 3 /* this stream is claimed */ +#define CX18_F_S_STREAMING 4 /* the fw is decoding/encoding this stream */ +#define CX18_F_S_INTERNAL_USE 5 /* this stream is used internally (sliced VBI processing) */ +#define CX18_F_S_STREAMOFF 7 /* signal end of stream EOS */ +#define CX18_F_S_APPL_IO 8 /* this stream is used read/written by an application */ +#define CX18_F_S_STOPPING 9 /* telling the fw to stop capturing */ + +/* per-cx18, i_flags */ +#define CX18_F_I_LOADED_FW 0 /* Loaded firmware 1st time */ +#define CX18_F_I_EOS 4 /* End of encoder stream */ +#define CX18_F_I_RADIO_USER 5 /* radio tuner is selected */ +#define CX18_F_I_ENC_PAUSED 13 /* the encoder is paused */ +#define CX18_F_I_INITED 21 /* set after first open */ +#define CX18_F_I_FAILED 22 /* set if first open failed */ + +/* These are the VBI types as they appear in the embedded VBI private packets. */ +#define CX18_SLICED_TYPE_TELETEXT_B (1) +#define CX18_SLICED_TYPE_CAPTION_525 (4) +#define CX18_SLICED_TYPE_WSS_625 (5) +#define CX18_SLICED_TYPE_VPS (7) + +struct cx18_vb2_buffer { + /* Common video buffer sub-system struct */ + struct vb2_v4l2_buffer vb; + struct list_head list; + v4l2_std_id tvnorm; /* selected tv norm */ + u32 bytes_used; +}; + +struct cx18_buffer { + struct list_head list; + dma_addr_t dma_handle; + char *buf; + + u32 bytesused; + u32 readpos; +}; + +struct cx18_mdl { + struct list_head list; + u32 id; /* index into cx->scb->cpu_mdl[] of 1st cx18_mdl_ent */ + + unsigned int skipped; + unsigned long m_flags; + + struct list_head buf_list; + struct cx18_buffer *curr_buf; /* current buffer in list for reading */ + + u32 bytesused; + u32 readpos; +}; + +struct cx18_queue { + struct list_head list; + atomic_t depth; + u32 bytesused; + spinlock_t lock; +}; + +struct cx18_stream; /* forward reference */ + +struct cx18_dvb { + struct cx18_stream *stream; + struct dmx_frontend hw_frontend; + struct dmx_frontend mem_frontend; + struct dmxdev dmxdev; + struct dvb_adapter dvb_adapter; + struct dvb_demux demux; + struct dvb_frontend *fe; + struct dvb_net dvbnet; + int enabled; + int feeding; + struct mutex feedlock; +}; + +struct cx18; /* forward reference */ +struct cx18_scb; /* forward reference */ + + +#define CX18_MAX_MDL_ACKS 2 +#define CX18_MAX_IN_WORK_ORDERS (CX18_MAX_FW_MDLS_PER_STREAM + 7) +/* CPU_DE_RELEASE_MDL can burst CX18_MAX_FW_MDLS_PER_STREAM orders in a group */ + +#define CX18_F_EWO_MB_STALE_UPON_RECEIPT 0x1 +#define CX18_F_EWO_MB_STALE_WHILE_PROC 0x2 +#define CX18_F_EWO_MB_STALE \ + (CX18_F_EWO_MB_STALE_UPON_RECEIPT | CX18_F_EWO_MB_STALE_WHILE_PROC) + +struct cx18_in_work_order { + struct work_struct work; + atomic_t pending; + struct cx18 *cx; + unsigned long flags; + int rpu; + struct cx18_mailbox mb; + struct cx18_mdl_ack mdl_ack[CX18_MAX_MDL_ACKS]; + char *str; +}; + +#define CX18_INVALID_TASK_HANDLE 0xffffffff + +struct cx18_stream { + /* These first five fields are always set, even if the stream + is not actually created. */ + struct video_device video_dev; /* v4l2_dev is NULL when stream not created */ + struct cx18_dvb *dvb; /* DVB / Digital Transport */ + struct cx18 *cx; /* for ease of use */ + const char *name; /* name of the stream */ + int type; /* stream type */ + u32 handle; /* task handle */ + u32 v4l2_dev_caps; /* device capabilities */ + unsigned int mdl_base_idx; + + u32 id; + unsigned long s_flags; /* status flags, see above */ + int dma; /* can be PCI_DMA_TODEVICE, + PCI_DMA_FROMDEVICE or + PCI_DMA_NONE */ + wait_queue_head_t waitq; + + /* Buffers */ + struct list_head buf_pool; /* buffers not attached to an MDL */ + u32 buffers; /* total buffers owned by this stream */ + u32 buf_size; /* size in bytes of a single buffer */ + + /* MDL sizes - all stream MDLs are the same size */ + u32 bufs_per_mdl; + u32 mdl_size; /* total bytes in all buffers in a mdl */ + + /* MDL Queues */ + struct cx18_queue q_free; /* free - in rotation, not committed */ + struct cx18_queue q_busy; /* busy - in use by firmware */ + struct cx18_queue q_full; /* full - data for user apps */ + struct cx18_queue q_idle; /* idle - not in rotation */ + + struct work_struct out_work_order; + + /* Videobuf for YUV video */ + u32 pixelformat; + u32 vb_bytes_per_frame; + u32 vb_bytes_per_line; + struct list_head vb_capture; /* video capture queue */ + spinlock_t vb_lock; + struct timer_list vb_timeout; + u32 sequence; + + struct vb2_queue vidq; + enum v4l2_buf_type vb_type; +}; + +struct cx18_open_id { + struct v4l2_fh fh; + u32 open_id; + int type; + struct cx18 *cx; +}; + +static inline struct cx18_open_id *fh2id(struct v4l2_fh *fh) +{ + return container_of(fh, struct cx18_open_id, fh); +} + +static inline struct cx18_open_id *file2id(struct file *file) +{ + return fh2id(file_to_v4l2_fh(file)); +} + +/* forward declaration of struct defined in cx18-cards.h */ +struct cx18_card; + +/* + * A note about "sliced" VBI data as implemented in this driver: + * + * Currently we collect the sliced VBI in the form of Ancillary Data + * packets, inserted by the AV core decoder/digitizer/slicer in the + * horizontal blanking region of the VBI lines, in "raw" mode as far as + * the Encoder is concerned. We don't ever tell the Encoder itself + * to provide sliced VBI. (AV Core: sliced mode - Encoder: raw mode) + * + * We then process the ancillary data ourselves to send the sliced data + * to the user application directly or build up MPEG-2 private stream 1 + * packets to splice into (only!) MPEG-2 PS streams for the user app. + * + * (That's how ivtv essentially does it.) + * + * The Encoder should be able to extract certain sliced VBI data for + * us and provide it in a separate stream or splice it into any type of + * MPEG PS or TS stream, but this isn't implemented yet. + */ + +/* + * Number of "raw" VBI samples per horizontal line we tell the Encoder to + * grab from the decoder/digitizer/slicer output for raw or sliced VBI. + * It depends on the pixel clock and the horiz rate: + * + * (1/Fh)*(2*Fp) = Samples/line + * = 4 bytes EAV + Anc data in hblank + 4 bytes SAV + active samples + * + * Sliced VBI data is sent as ancillary data during horizontal blanking + * Raw VBI is sent as active video samples during vertcal blanking + * + * We use a BT.656 pxiel clock of 13.5 MHz and a BT.656 active line + * length of 720 pixels @ 4:2:2 sampling. Thus... + * + * For systems that use a 15.734 kHz horizontal rate, such as + * NTSC-M, PAL-M, PAL-60, and other 60 Hz/525 line systems, we have: + * + * (1/15.734 kHz) * 2 * 13.5 MHz = 1716 samples/line = + * 4 bytes SAV + 268 bytes anc data + 4 bytes SAV + 1440 active samples + * + * For systems that use a 15.625 kHz horizontal rate, such as + * PAL-B/G/H, PAL-I, SECAM-L and other 50 Hz/625 line systems, we have: + * + * (1/15.625 kHz) * 2 * 13.5 MHz = 1728 samples/line = + * 4 bytes SAV + 280 bytes anc data + 4 bytes SAV + 1440 active samples + */ +#define VBI_ACTIVE_SAMPLES 1444 /* 4 byte SAV + 720 Y + 720 U/V */ +#define VBI_HBLANK_SAMPLES_60HZ 272 /* 4 byte EAV + 268 anc/fill */ +#define VBI_HBLANK_SAMPLES_50HZ 284 /* 4 byte EAV + 280 anc/fill */ + +#define CX18_VBI_FRAMES 32 + +struct vbi_info { + /* Current state of v4l2 VBI settings for this device */ + struct v4l2_format in; + struct v4l2_sliced_vbi_format *sliced_in; /* pointer to in.fmt.sliced */ + u32 count; /* Count of VBI data lines: 60 Hz: 12 or 50 Hz: 18 */ + u32 start[2]; /* First VBI data line per field: 10 & 273 or 6 & 318 */ + + u32 frame; /* Count of VBI buffers/frames received from Encoder */ + + /* + * Vars for creation and insertion of MPEG Private Stream 1 packets + * of sliced VBI data into an MPEG PS + */ + + /* Boolean: create and insert Private Stream 1 packets into the PS */ + int insert_mpeg; + + /* + * Buffer for the maximum of 2 * 18 * packet_size sliced VBI lines. + * Used in cx18-vbi.c only for collecting sliced data, and as a source + * during conversion of sliced VBI data into MPEG Priv Stream 1 packets. + * We don't need to save state here, but the array may have been a bit + * too big (2304 bytes) to alloc from the stack. + */ + struct v4l2_sliced_vbi_data sliced_data[36]; + + /* + * A ring buffer of driver-generated MPEG-2 PS + * Program Pack/Private Stream 1 packets for sliced VBI data insertion + * into the MPEG PS stream. + * + * In each sliced_mpeg_data[] buffer is: + * 16 byte MPEG-2 PS Program Pack Header + * 16 byte MPEG-2 Private Stream 1 PES Header + * 4 byte magic number: "itv0" or "ITV0" + * 4 byte first field line mask, if "itv0" + * 4 byte second field line mask, if "itv0" + * 36 lines, if "ITV0"; or <36 lines, if "itv0"; of sliced VBI data + * + * Each line in the payload is + * 1 byte line header derived from the SDID (WSS, CC, VPS, etc.) + * 42 bytes of line data + * + * That's a maximum 1552 bytes of payload in the Private Stream 1 packet + * which is the payload size a PVR-350 (CX23415) MPEG decoder will + * accept for VBI data. So, including the headers, it's a maximum 1584 + * bytes total. + */ +#define CX18_SLICED_MPEG_DATA_MAXSZ 1584 + /* copy_vbi_buf() needs 8 temp bytes on the end for the worst case */ +#define CX18_SLICED_MPEG_DATA_BUFSZ (CX18_SLICED_MPEG_DATA_MAXSZ+8) + u8 *sliced_mpeg_data[CX18_VBI_FRAMES]; + u32 sliced_mpeg_size[CX18_VBI_FRAMES]; + + /* Count of Program Pack/Program Stream 1 packets inserted into PS */ + u32 inserted_frame; + + /* + * A dummy driver stream transfer mdl & buffer with a copy of the next + * sliced_mpeg_data[] buffer for output to userland apps. + * Only used in cx18-fileops.c, but its state needs to persist at times. + */ + struct cx18_mdl sliced_mpeg_mdl; + struct cx18_buffer sliced_mpeg_buf; +}; + +/* Per cx23418, per I2C bus private algo callback data */ +struct cx18_i2c_algo_callback_data { + struct cx18 *cx; + int bus_index; /* 0 or 1 for the cx23418's 1st or 2nd I2C bus */ +}; + +#define CX18_MAX_MMIO_WR_RETRIES 10 + +/* Struct to hold info about cx18 cards */ +struct cx18 { + int instance; + struct pci_dev *pci_dev; + struct v4l2_device v4l2_dev; + struct v4l2_subdev *sd_av; /* A/V decoder/digitizer sub-device */ + struct v4l2_subdev *sd_extmux; /* External multiplexer sub-dev */ + + const struct cx18_card *card; /* card information */ + const char *card_name; /* full name of the card */ + const struct cx18_card_tuner_i2c *card_i2c; /* i2c addresses to probe for tuner */ + u8 is_50hz; + u8 is_60hz; + u8 nof_inputs; /* number of video inputs */ + u8 nof_audio_inputs; /* number of audio inputs */ + u32 v4l2_cap; /* V4L2 capabilities of card */ + u32 hw_flags; /* Hardware description of the board */ + unsigned int free_mdl_idx; + struct cx18_scb __iomem *scb; /* pointer to SCB */ + struct mutex epu2apu_mb_lock; /* protect driver to chip mailbox in SCB*/ + struct mutex epu2cpu_mb_lock; /* protect driver to chip mailbox in SCB*/ + + struct cx18_av_state av_state; + + /* codec settings */ + struct cx2341x_handler cxhdl; + u32 filter_mode; + u32 temporal_strength; + u32 spatial_strength; + + /* dualwatch */ + unsigned long dualwatch_jiffies; + u32 dualwatch_stereo_mode; + + struct mutex serialize_lock; /* mutex used to serialize open/close/start/stop/ioctl operations */ + struct cx18_options options; /* User options */ + int stream_buffers[CX18_MAX_STREAMS]; /* # of buffers for each stream */ + int stream_buf_size[CX18_MAX_STREAMS]; /* Stream buffer size */ + struct cx18_stream streams[CX18_MAX_STREAMS]; /* Stream data */ + struct snd_cx18_card *alsa; /* ALSA interface for PCM capture stream */ + void (*pcm_announce_callback)(struct snd_cx18_card *card, u8 *pcm_data, + size_t num_bytes); + + unsigned long i_flags; /* global cx18 flags */ + atomic_t ana_capturing; /* count number of active analog capture streams */ + atomic_t tot_capturing; /* total count number of active capture streams */ + int search_pack_header; + + int open_id; /* incremented each time an open occurs, used as + unique ID. Starts at 1, so 0 can be used as + uninitialized value in the stream->id. */ + + resource_size_t base_addr; + + u8 card_rev; + void __iomem *enc_mem, *reg_mem; + + struct vbi_info vbi; + + u64 mpg_data_received; + u64 vbi_data_inserted; + + wait_queue_head_t mb_apu_waitq; + wait_queue_head_t mb_cpu_waitq; + wait_queue_head_t cap_w; + /* when the current DMA is finished this queue is woken up */ + wait_queue_head_t dma_waitq; + + u32 sw1_irq_mask; + u32 sw2_irq_mask; + u32 hw2_irq_mask; + + struct workqueue_struct *in_work_queue; + char in_workq_name[39]; /* "cx18-NN-in" */ + struct cx18_in_work_order in_work_order[CX18_MAX_IN_WORK_ORDERS]; + char epu_debug_str[256]; /* CX18_EPU_DEBUG is rare: use shared space */ + + /* i2c */ + struct i2c_adapter i2c_adap[2]; + struct i2c_algo_bit_data i2c_algo[2]; + struct cx18_i2c_algo_callback_data i2c_algo_cb_data[2]; + + struct IR_i2c_init_data ir_i2c_init_data; + + /* gpio */ + u32 gpio_dir; + u32 gpio_val; + struct mutex gpio_lock; + struct v4l2_subdev sd_gpiomux; + struct v4l2_subdev sd_resetctrl; + + /* v4l2 and User settings */ + + /* codec settings */ + u32 audio_input; + u32 active_input; + v4l2_std_id std; + v4l2_std_id tuner_std; /* The norm of the tuner (fixed) */ + + /* Used for cx18-alsa module loading */ + struct work_struct request_module_wk; +}; + +static inline struct cx18 *to_cx18(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct cx18, v4l2_dev); +} + +/* cx18 extensions to be loaded */ +extern int (*cx18_ext_init)(struct cx18 *); + +/* Globals */ +extern int cx18_first_minor; + +/*==============Prototypes==================*/ + +/* Return non-zero if a signal is pending */ +int cx18_msleep_timeout(unsigned int msecs, int intr); + +/* Read Hauppauge eeprom */ +struct tveeprom; /* forward reference */ +void cx18_read_eeprom(struct cx18 *cx, struct tveeprom *tv); + +/* First-open initialization: load firmware, etc. */ +int cx18_init_on_first_open(struct cx18 *cx); + +/* Test if the current VBI mode is raw (1) or sliced (0) */ +static inline int cx18_raw_vbi(const struct cx18 *cx) +{ + return cx->vbi.in.type == V4L2_BUF_TYPE_VBI_CAPTURE; +} + +/* Call the specified callback for all subdevs with a grp_id bit matching the + * mask in hw (if 0, then match them all). Ignore any errors. */ +#define cx18_call_hw(cx, hw, o, f, args...) \ + v4l2_device_mask_call_all(&(cx)->v4l2_dev, hw, o, f, ##args) + +#define cx18_call_all(cx, o, f, args...) cx18_call_hw(cx, 0, o, f , ##args) + +/* Call the specified callback for all subdevs with a grp_id bit matching the + * mask in hw (if 0, then match them all). If the callback returns an error + * other than 0 or -ENOIOCTLCMD, then return with that error code. */ +#define cx18_call_hw_err(cx, hw, o, f, args...) \ + v4l2_device_mask_call_until_err(&(cx)->v4l2_dev, hw, o, f, ##args) + +#define cx18_call_all_err(cx, o, f, args...) \ + cx18_call_hw_err(cx, 0, o, f , ##args) + +#endif /* CX18_DRIVER_H */ diff --git a/drivers/media/pci/cx18/cx18-dvb.c b/drivers/media/pci/cx18/cx18-dvb.c new file mode 100644 index 000000000..cf82360a5 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-dvb.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 functions for DVB support + * + * Copyright (c) 2008 Steven Toth + * Copyright (C) 2008 Andy Walls + */ + +#include "cx18-version.h" +#include "cx18-dvb.h" +#include "cx18-io.h" +#include "cx18-queue.h" +#include "cx18-streams.h" +#include "cx18-cards.h" +#include "cx18-gpio.h" +#include "s5h1409.h" +#include "mxl5005s.h" +#include "s5h1411.h" +#include "tda18271.h" +#include "zl10353.h" + +#include +#include "mt352.h" +#include "mt352_priv.h" +#include "xc2028.h" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +#define FWFILE "dvb-cx18-mpc718-mt352.fw" + +#define CX18_REG_DMUX_NUM_PORT_0_CONTROL 0xd5a000 +#define CX18_CLOCK_ENABLE2 0xc71024 +#define CX18_DMUX_CLK_MASK 0x0080 + +/* + * CX18_CARD_HVR_1600_ESMT + * CX18_CARD_HVR_1600_SAMSUNG + */ + +static struct mxl5005s_config hauppauge_hvr1600_tuner = { + .i2c_address = 0xC6 >> 1, + .if_freq = IF_FREQ_5380000HZ, + .xtal_freq = CRYSTAL_FREQ_16000000HZ, + .agc_mode = MXL_SINGLE_AGC, + .tracking_filter = MXL_TF_C_H, + .rssi_enable = MXL_RSSI_ENABLE, + .cap_select = MXL_CAP_SEL_ENABLE, + .div_out = MXL_DIV_OUT_4, + .clock_out = MXL_CLOCK_OUT_DISABLE, + .output_load = MXL5005S_IF_OUTPUT_LOAD_200_OHM, + .top = MXL5005S_TOP_25P2, + .mod_mode = MXL_DIGITAL_MODE, + .if_mode = MXL_ZERO_IF, + .qam_gain = 0x02, + .AgcMasterByte = 0x00, +}; + +static struct s5h1409_config hauppauge_hvr1600_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_ON, + .qam_if = 44000, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, + .hvr1600_opt = S5H1409_HVR1600_OPTIMIZE +}; + +/* + * CX18_CARD_HVR_1600_S5H1411 + */ +static struct s5h1411_config hcw_s5h1411_config = { + .output_mode = S5H1411_SERIAL_OUTPUT, + .gpio = S5H1411_GPIO_OFF, + .vsb_if = S5H1411_IF_44000, + .qam_if = S5H1411_IF_4000, + .inversion = S5H1411_INVERSION_ON, + .status_mode = S5H1411_DEMODLOCKING, + .mpeg_timing = S5H1411_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, +}; + +static struct tda18271_std_map hauppauge_tda18271_std_map = { + .atsc_6 = { .if_freq = 5380, .agc_mode = 3, .std = 3, + .if_lvl = 6, .rfagc_top = 0x37 }, + .qam_6 = { .if_freq = 4000, .agc_mode = 3, .std = 0, + .if_lvl = 6, .rfagc_top = 0x37 }, +}; + +static struct tda18271_config hauppauge_tda18271_config = { + .std_map = &hauppauge_tda18271_std_map, + .gate = TDA18271_GATE_DIGITAL, + .output_opt = TDA18271_OUTPUT_LT_OFF, +}; + +/* + * CX18_CARD_LEADTEK_DVR3100H + */ +/* Information/confirmation of proper config values provided by Terry Wu */ +static struct zl10353_config leadtek_dvr3100h_demod = { + .demod_address = 0x1e >> 1, /* Datasheet suggested straps */ + .if2 = 45600, /* 4.560 MHz IF from the XC3028 */ + .parallel_ts = 1, /* Not a serial TS */ + .no_tuner = 1, /* XC3028 is not behind the gate */ + .disable_i2c_gate_ctrl = 1, /* Disable the I2C gate */ +}; + +/* + * CX18_CARD_YUAN_MPC718 + */ +/* + * Due to + * + * 1. an absence of information on how to program the MT352 + * 2. the Linux mt352 module pushing MT352 initialization off onto us here + * + * We have to use an init sequence that *you* must extract from the Windows + * driver (yuanrap.sys) and which we load as a firmware. + * + * If someone can provide me with a Zarlink MT352 (Intel CE6352?) Design Manual + * with chip programming details, then I can remove this annoyance. + */ +static int yuan_mpc718_mt352_reqfw(struct cx18_stream *stream, + const struct firmware **fw) +{ + struct cx18 *cx = stream->cx; + const char *fn = FWFILE; + int ret; + + ret = request_firmware(fw, fn, &cx->pci_dev->dev); + if (ret) + CX18_ERR("Unable to open firmware file %s\n", fn); + else { + size_t sz = (*fw)->size; + if (sz < 2 || sz > 64 || (sz % 2) != 0) { + CX18_ERR("Firmware %s has a bad size: %lu bytes\n", + fn, (unsigned long) sz); + ret = -EILSEQ; + release_firmware(*fw); + *fw = NULL; + } + } + + if (ret) { + CX18_ERR("The MPC718 board variant with the MT352 DVB-T demodulator will not work without it\n"); + CX18_ERR("Run 'linux/scripts/get_dvb_firmware mpc718' if you need the firmware\n"); + } + return ret; +} + +static int yuan_mpc718_mt352_init(struct dvb_frontend *fe) +{ + struct cx18_dvb *dvb = container_of(fe->dvb, + struct cx18_dvb, dvb_adapter); + struct cx18_stream *stream = dvb->stream; + const struct firmware *fw = NULL; + int ret; + int i; + u8 buf[3]; + + ret = yuan_mpc718_mt352_reqfw(stream, &fw); + if (ret) + return ret; + + /* Loop through all the register-value pairs in the firmware file */ + for (i = 0; i < fw->size; i += 2) { + buf[0] = fw->data[i]; + /* Intercept a few registers we want to set ourselves */ + switch (buf[0]) { + case TRL_NOMINAL_RATE_0: + /* Set our custom OFDM bandwidth in the case below */ + break; + case TRL_NOMINAL_RATE_1: + /* 6 MHz: 64/7 * 6/8 / 20.48 * 2^16 = 0x55b6.db6 */ + /* 7 MHz: 64/7 * 7/8 / 20.48 * 2^16 = 0x6400 */ + /* 8 MHz: 64/7 * 8/8 / 20.48 * 2^16 = 0x7249.249 */ + buf[1] = 0x72; + buf[2] = 0x49; + mt352_write(fe, buf, 3); + break; + case INPUT_FREQ_0: + /* Set our custom IF in the case below */ + break; + case INPUT_FREQ_1: + /* 4.56 MHz IF: (20.48 - 4.56)/20.48 * 2^14 = 0x31c0 */ + buf[1] = 0x31; + buf[2] = 0xc0; + mt352_write(fe, buf, 3); + break; + default: + /* Pass through the register-value pair from the fw */ + buf[1] = fw->data[i+1]; + mt352_write(fe, buf, 2); + break; + } + } + + buf[0] = (u8) TUNER_GO; + buf[1] = 0x01; /* Go */ + mt352_write(fe, buf, 2); + release_firmware(fw); + return 0; +} + +static struct mt352_config yuan_mpc718_mt352_demod = { + .demod_address = 0x1e >> 1, + .adc_clock = 20480, /* 20.480 MHz */ + .if2 = 4560, /* 4.560 MHz */ + .no_tuner = 1, /* XC3028 is not behind the gate */ + .demod_init = yuan_mpc718_mt352_init, +}; + +static struct zl10353_config yuan_mpc718_zl10353_demod = { + .demod_address = 0x1e >> 1, /* Datasheet suggested straps */ + .if2 = 45600, /* 4.560 MHz IF from the XC3028 */ + .parallel_ts = 1, /* Not a serial TS */ + .no_tuner = 1, /* XC3028 is not behind the gate */ + .disable_i2c_gate_ctrl = 1, /* Disable the I2C gate */ +}; + +static struct zl10353_config gotview_dvd3_zl10353_demod = { + .demod_address = 0x1e >> 1, /* Datasheet suggested straps */ + .if2 = 45600, /* 4.560 MHz IF from the XC3028 */ + .parallel_ts = 1, /* Not a serial TS */ + .no_tuner = 1, /* XC3028 is not behind the gate */ + .disable_i2c_gate_ctrl = 1, /* Disable the I2C gate */ +}; + +static int dvb_register(struct cx18_stream *stream); + +/* Kernel DVB framework calls this when the feed needs to start. + * The CX18 framework should enable the transport DMA handling + * and queue processing. + */ +static int cx18_dvb_start_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct cx18_stream *stream = demux->priv; + struct cx18 *cx; + int ret; + u32 v; + + if (!stream) + return -EINVAL; + + cx = stream->cx; + CX18_DEBUG_INFO("Start feed: pid = 0x%x index = %d\n", + feed->pid, feed->index); + + mutex_lock(&cx->serialize_lock); + ret = cx18_init_on_first_open(cx); + mutex_unlock(&cx->serialize_lock); + if (ret) { + CX18_ERR("Failed to initialize firmware starting DVB feed\n"); + return ret; + } + ret = -EINVAL; + + switch (cx->card->type) { + case CX18_CARD_HVR_1600_ESMT: + case CX18_CARD_HVR_1600_SAMSUNG: + case CX18_CARD_HVR_1600_S5H1411: + v = cx18_read_reg(cx, CX18_REG_DMUX_NUM_PORT_0_CONTROL); + v |= 0x00400000; /* Serial Mode */ + v |= 0x00002000; /* Data Length - Byte */ + v |= 0x00010000; /* Error - Polarity */ + v |= 0x00020000; /* Error - Passthru */ + v |= 0x000c0000; /* Error - Ignore */ + cx18_write_reg(cx, v, CX18_REG_DMUX_NUM_PORT_0_CONTROL); + break; + + case CX18_CARD_LEADTEK_DVR3100H: + case CX18_CARD_YUAN_MPC718: + case CX18_CARD_GOTVIEW_PCI_DVD3: + default: + /* Assumption - Parallel transport - Signalling + * undefined or default. + */ + break; + } + + if (!demux->dmx.frontend) + return -EINVAL; + + mutex_lock(&stream->dvb->feedlock); + if (stream->dvb->feeding++ == 0) { + CX18_DEBUG_INFO("Starting Transport DMA\n"); + mutex_lock(&cx->serialize_lock); + set_bit(CX18_F_S_STREAMING, &stream->s_flags); + ret = cx18_start_v4l2_encode_stream(stream); + if (ret < 0) { + CX18_DEBUG_INFO("Failed to start Transport DMA\n"); + stream->dvb->feeding--; + if (stream->dvb->feeding == 0) + clear_bit(CX18_F_S_STREAMING, &stream->s_flags); + } + mutex_unlock(&cx->serialize_lock); + } else + ret = 0; + mutex_unlock(&stream->dvb->feedlock); + + return ret; +} + +/* Kernel DVB framework calls this when the feed needs to stop. */ +static int cx18_dvb_stop_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct cx18_stream *stream = demux->priv; + struct cx18 *cx; + int ret = -EINVAL; + + if (stream) { + cx = stream->cx; + CX18_DEBUG_INFO("Stop feed: pid = 0x%x index = %d\n", + feed->pid, feed->index); + + mutex_lock(&stream->dvb->feedlock); + if (--stream->dvb->feeding == 0) { + CX18_DEBUG_INFO("Stopping Transport DMA\n"); + mutex_lock(&cx->serialize_lock); + ret = cx18_stop_v4l2_encode_stream(stream, 0); + mutex_unlock(&cx->serialize_lock); + } else + ret = 0; + mutex_unlock(&stream->dvb->feedlock); + } + + return ret; +} + +int cx18_dvb_register(struct cx18_stream *stream) +{ + struct cx18 *cx = stream->cx; + struct cx18_dvb *dvb = stream->dvb; + struct dvb_adapter *dvb_adapter; + struct dvb_demux *dvbdemux; + struct dmx_demux *dmx; + int ret; + + if (!dvb) + return -EINVAL; + + dvb->enabled = 0; + dvb->stream = stream; + + ret = dvb_register_adapter(&dvb->dvb_adapter, + CX18_DRIVER_NAME, + THIS_MODULE, &cx->pci_dev->dev, adapter_nr); + if (ret < 0) + goto err_out; + + dvb_adapter = &dvb->dvb_adapter; + + dvbdemux = &dvb->demux; + + dvbdemux->priv = (void *)stream; + + dvbdemux->filternum = 256; + dvbdemux->feednum = 256; + dvbdemux->start_feed = cx18_dvb_start_feed; + dvbdemux->stop_feed = cx18_dvb_stop_feed; + dvbdemux->dmx.capabilities = (DMX_TS_FILTERING | + DMX_SECTION_FILTERING | DMX_MEMORY_BASED_FILTERING); + ret = dvb_dmx_init(dvbdemux); + if (ret < 0) + goto err_dvb_unregister_adapter; + + dmx = &dvbdemux->dmx; + + dvb->hw_frontend.source = DMX_FRONTEND_0; + dvb->mem_frontend.source = DMX_MEMORY_FE; + dvb->dmxdev.filternum = 256; + dvb->dmxdev.demux = dmx; + + ret = dvb_dmxdev_init(&dvb->dmxdev, dvb_adapter); + if (ret < 0) + goto err_dvb_dmx_release; + + ret = dmx->add_frontend(dmx, &dvb->hw_frontend); + if (ret < 0) + goto err_dvb_dmxdev_release; + + ret = dmx->add_frontend(dmx, &dvb->mem_frontend); + if (ret < 0) + goto err_remove_hw_frontend; + + ret = dmx->connect_frontend(dmx, &dvb->hw_frontend); + if (ret < 0) + goto err_remove_mem_frontend; + + ret = dvb_register(stream); + if (ret < 0) + goto err_disconnect_frontend; + + dvb_net_init(dvb_adapter, &dvb->dvbnet, dmx); + + CX18_INFO("DVB Frontend registered\n"); + CX18_INFO("Registered DVB adapter%d for %s (%d x %d.%02d kB)\n", + stream->dvb->dvb_adapter.num, stream->name, + stream->buffers, stream->buf_size/1024, + (stream->buf_size * 100 / 1024) % 100); + + mutex_init(&dvb->feedlock); + dvb->enabled = 1; + return ret; + +err_disconnect_frontend: + dmx->disconnect_frontend(dmx); +err_remove_mem_frontend: + dmx->remove_frontend(dmx, &dvb->mem_frontend); +err_remove_hw_frontend: + dmx->remove_frontend(dmx, &dvb->hw_frontend); +err_dvb_dmxdev_release: + dvb_dmxdev_release(&dvb->dmxdev); +err_dvb_dmx_release: + dvb_dmx_release(dvbdemux); +err_dvb_unregister_adapter: + dvb_unregister_adapter(dvb_adapter); +err_out: + return ret; +} + +void cx18_dvb_unregister(struct cx18_stream *stream) +{ + struct cx18 *cx = stream->cx; + struct cx18_dvb *dvb = stream->dvb; + struct dvb_adapter *dvb_adapter; + struct dvb_demux *dvbdemux; + struct dmx_demux *dmx; + + CX18_INFO("unregister DVB\n"); + + if (dvb == NULL || !dvb->enabled) + return; + + dvb_adapter = &dvb->dvb_adapter; + dvbdemux = &dvb->demux; + dmx = &dvbdemux->dmx; + + dmx->close(dmx); + dvb_net_release(&dvb->dvbnet); + dmx->remove_frontend(dmx, &dvb->mem_frontend); + dmx->remove_frontend(dmx, &dvb->hw_frontend); + dvb_dmxdev_release(&dvb->dmxdev); + dvb_dmx_release(dvbdemux); + dvb_unregister_frontend(dvb->fe); + dvb_frontend_detach(dvb->fe); + dvb_unregister_adapter(dvb_adapter); +} + +/* All the DVB attach calls go here, this function gets modified + * for each new card. cx18_dvb_start_feed() will also need changes. + */ +static int dvb_register(struct cx18_stream *stream) +{ + struct cx18_dvb *dvb = stream->dvb; + struct cx18 *cx = stream->cx; + int ret = 0; + + switch (cx->card->type) { + case CX18_CARD_HVR_1600_ESMT: + case CX18_CARD_HVR_1600_SAMSUNG: + dvb->fe = dvb_attach(s5h1409_attach, + &hauppauge_hvr1600_config, + &cx->i2c_adap[0]); + if (dvb->fe != NULL) { + dvb_attach(mxl5005s_attach, dvb->fe, + &cx->i2c_adap[0], + &hauppauge_hvr1600_tuner); + ret = 0; + } + break; + case CX18_CARD_HVR_1600_S5H1411: + dvb->fe = dvb_attach(s5h1411_attach, + &hcw_s5h1411_config, + &cx->i2c_adap[0]); + if (dvb->fe != NULL) + dvb_attach(tda18271_attach, dvb->fe, + 0x60, &cx->i2c_adap[0], + &hauppauge_tda18271_config); + break; + case CX18_CARD_LEADTEK_DVR3100H: + dvb->fe = dvb_attach(zl10353_attach, + &leadtek_dvr3100h_demod, + &cx->i2c_adap[1]); + if (dvb->fe != NULL) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &cx->i2c_adap[1], + .i2c_addr = 0xc2 >> 1, + .ctrl = NULL, + }; + static struct xc2028_ctrl ctrl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + .demod = XC3028_FE_ZARLINK456, + .type = XC2028_AUTO, + }; + + fe = dvb_attach(xc2028_attach, dvb->fe, &cfg); + if (fe != NULL && fe->ops.tuner_ops.set_config != NULL) + fe->ops.tuner_ops.set_config(fe, &ctrl); + } + break; + case CX18_CARD_YUAN_MPC718: + /* + * TODO + * Apparently, these cards also could instead have a + * DiBcom demod supported by one of the db7000 drivers + */ + dvb->fe = dvb_attach(mt352_attach, + &yuan_mpc718_mt352_demod, + &cx->i2c_adap[1]); + if (dvb->fe == NULL) + dvb->fe = dvb_attach(zl10353_attach, + &yuan_mpc718_zl10353_demod, + &cx->i2c_adap[1]); + if (dvb->fe != NULL) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &cx->i2c_adap[1], + .i2c_addr = 0xc2 >> 1, + .ctrl = NULL, + }; + static struct xc2028_ctrl ctrl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + .demod = XC3028_FE_ZARLINK456, + .type = XC2028_AUTO, + }; + + fe = dvb_attach(xc2028_attach, dvb->fe, &cfg); + if (fe != NULL && fe->ops.tuner_ops.set_config != NULL) + fe->ops.tuner_ops.set_config(fe, &ctrl); + } + break; + case CX18_CARD_GOTVIEW_PCI_DVD3: + dvb->fe = dvb_attach(zl10353_attach, + &gotview_dvd3_zl10353_demod, + &cx->i2c_adap[1]); + if (dvb->fe != NULL) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &cx->i2c_adap[1], + .i2c_addr = 0xc2 >> 1, + .ctrl = NULL, + }; + static struct xc2028_ctrl ctrl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + .demod = XC3028_FE_ZARLINK456, + .type = XC2028_AUTO, + }; + + fe = dvb_attach(xc2028_attach, dvb->fe, &cfg); + if (fe != NULL && fe->ops.tuner_ops.set_config != NULL) + fe->ops.tuner_ops.set_config(fe, &ctrl); + } + break; + default: + /* No Digital Tv Support */ + break; + } + + if (dvb->fe == NULL) { + CX18_ERR("frontend initialization failed\n"); + return -1; + } + + dvb->fe->callback = cx18_reset_tuner_gpio; + + ret = dvb_register_frontend(&dvb->dvb_adapter, dvb->fe); + if (ret < 0) { + if (dvb->fe->ops.release) + dvb->fe->ops.release(dvb->fe); + return ret; + } + + /* + * The firmware seems to enable the TS DMUX clock + * under various circumstances. However, since we know we + * might use it, let's just turn it on ourselves here. + */ + cx18_write_reg_expect(cx, + (CX18_DMUX_CLK_MASK << 16) | CX18_DMUX_CLK_MASK, + CX18_CLOCK_ENABLE2, + CX18_DMUX_CLK_MASK, + (CX18_DMUX_CLK_MASK << 16) | CX18_DMUX_CLK_MASK); + + return ret; +} + +MODULE_FIRMWARE(FWFILE); diff --git a/drivers/media/pci/cx18/cx18-dvb.h b/drivers/media/pci/cx18/cx18-dvb.h new file mode 100644 index 000000000..aa2677919 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-dvb.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 functions for DVB support + * + * Copyright (c) 2008 Steven Toth + */ + +#include "cx18-driver.h" + +int cx18_dvb_register(struct cx18_stream *stream); +void cx18_dvb_unregister(struct cx18_stream *stream); diff --git a/drivers/media/pci/cx18/cx18-fileops.c b/drivers/media/pci/cx18/cx18-fileops.c new file mode 100644 index 000000000..445afcd30 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-fileops.c @@ -0,0 +1,820 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 file operation functions + * + * Derived from ivtv-fileops.c + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#include "cx18-driver.h" +#include "cx18-fileops.h" +#include "cx18-i2c.h" +#include "cx18-queue.h" +#include "cx18-vbi.h" +#include "cx18-audio.h" +#include "cx18-mailbox.h" +#include "cx18-scb.h" +#include "cx18-streams.h" +#include "cx18-controls.h" +#include "cx18-ioctl.h" +#include "cx18-cards.h" +#include + +/* This function tries to claim the stream for a specific file descriptor. + If no one else is using this stream then the stream is claimed and + associated VBI and IDX streams are also automatically claimed. + Possible error returns: -EBUSY if someone else has claimed + the stream or 0 on success. */ +int cx18_claim_stream(struct cx18_open_id *id, int type) +{ + struct cx18 *cx = id->cx; + struct cx18_stream *s = &cx->streams[type]; + struct cx18_stream *s_assoc; + + /* Nothing should ever try to directly claim the IDX stream */ + if (type == CX18_ENC_STREAM_TYPE_IDX) { + CX18_WARN("MPEG Index stream cannot be claimed directly, but something tried.\n"); + return -EINVAL; + } + + if (test_and_set_bit(CX18_F_S_CLAIMED, &s->s_flags)) { + /* someone already claimed this stream */ + if (s->id == id->open_id) { + /* yes, this file descriptor did. So that's OK. */ + return 0; + } + if (s->id == -1 && type == CX18_ENC_STREAM_TYPE_VBI) { + /* VBI is handled already internally, now also assign + the file descriptor to this stream for external + reading of the stream. */ + s->id = id->open_id; + CX18_DEBUG_INFO("Start Read VBI\n"); + return 0; + } + /* someone else is using this stream already */ + CX18_DEBUG_INFO("Stream %d is busy\n", type); + return -EBUSY; + } + s->id = id->open_id; + + /* + * CX18_ENC_STREAM_TYPE_MPG needs to claim: + * CX18_ENC_STREAM_TYPE_VBI, if VBI insertion is on for sliced VBI, or + * CX18_ENC_STREAM_TYPE_IDX, if VBI insertion is off for sliced VBI + * (We don't yet fix up MPEG Index entries for our inserted packets). + * + * For all other streams we're done. + */ + if (type != CX18_ENC_STREAM_TYPE_MPG) + return 0; + + s_assoc = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; + if (cx->vbi.insert_mpeg && !cx18_raw_vbi(cx)) + s_assoc = &cx->streams[CX18_ENC_STREAM_TYPE_VBI]; + else if (!cx18_stream_enabled(s_assoc)) + return 0; + + set_bit(CX18_F_S_CLAIMED, &s_assoc->s_flags); + + /* mark that it is used internally */ + set_bit(CX18_F_S_INTERNAL_USE, &s_assoc->s_flags); + return 0; +} +EXPORT_SYMBOL(cx18_claim_stream); + +/* This function releases a previously claimed stream. It will take into + account associated VBI streams. */ +void cx18_release_stream(struct cx18_stream *s) +{ + struct cx18 *cx = s->cx; + struct cx18_stream *s_assoc; + + s->id = -1; + if (s->type == CX18_ENC_STREAM_TYPE_IDX) { + /* + * The IDX stream is only used internally, and can + * only be indirectly unclaimed by unclaiming the MPG stream. + */ + return; + } + + if (s->type == CX18_ENC_STREAM_TYPE_VBI && + test_bit(CX18_F_S_INTERNAL_USE, &s->s_flags)) { + /* this stream is still in use internally */ + return; + } + if (!test_and_clear_bit(CX18_F_S_CLAIMED, &s->s_flags)) { + CX18_DEBUG_WARN("Release stream %s not in use!\n", s->name); + return; + } + + cx18_flush_queues(s); + + /* + * CX18_ENC_STREAM_TYPE_MPG needs to release the + * CX18_ENC_STREAM_TYPE_VBI and/or CX18_ENC_STREAM_TYPE_IDX streams. + * + * For all other streams we're done. + */ + if (s->type != CX18_ENC_STREAM_TYPE_MPG) + return; + + /* Unclaim the associated MPEG Index stream */ + s_assoc = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; + if (test_and_clear_bit(CX18_F_S_INTERNAL_USE, &s_assoc->s_flags)) { + clear_bit(CX18_F_S_CLAIMED, &s_assoc->s_flags); + cx18_flush_queues(s_assoc); + } + + /* Unclaim the associated VBI stream */ + s_assoc = &cx->streams[CX18_ENC_STREAM_TYPE_VBI]; + if (test_and_clear_bit(CX18_F_S_INTERNAL_USE, &s_assoc->s_flags)) { + if (s_assoc->id == -1) { + /* + * The VBI stream is not still claimed by a file + * descriptor, so completely unclaim it. + */ + clear_bit(CX18_F_S_CLAIMED, &s_assoc->s_flags); + cx18_flush_queues(s_assoc); + } + } +} +EXPORT_SYMBOL(cx18_release_stream); + +static void cx18_dualwatch(struct cx18 *cx) +{ + struct v4l2_tuner vt; + u32 new_stereo_mode; + const u32 dual = 0x0200; + + new_stereo_mode = v4l2_ctrl_g_ctrl(cx->cxhdl.audio_mode); + memset(&vt, 0, sizeof(vt)); + cx18_call_all(cx, tuner, g_tuner, &vt); + if (vt.audmode == V4L2_TUNER_MODE_LANG1_LANG2 && + (vt.rxsubchans & V4L2_TUNER_SUB_LANG2)) + new_stereo_mode = dual; + + if (new_stereo_mode == cx->dualwatch_stereo_mode) + return; + + CX18_DEBUG_INFO("dualwatch: change stereo flag from 0x%x to 0x%x.\n", + cx->dualwatch_stereo_mode, new_stereo_mode); + if (v4l2_ctrl_s_ctrl(cx->cxhdl.audio_mode, new_stereo_mode)) + CX18_DEBUG_INFO("dualwatch: changing stereo flag failed\n"); +} + + +static struct cx18_mdl *cx18_get_mdl(struct cx18_stream *s, int non_block, + int *err) +{ + struct cx18 *cx = s->cx; + struct cx18_stream *s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI]; + struct cx18_mdl *mdl; + DEFINE_WAIT(wait); + + *err = 0; + while (1) { + if (s->type == CX18_ENC_STREAM_TYPE_MPG) { + /* Process pending program updates and VBI data */ + if (time_after(jiffies, cx->dualwatch_jiffies + msecs_to_jiffies(1000))) { + cx->dualwatch_jiffies = jiffies; + cx18_dualwatch(cx); + } + if (test_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags) && + !test_bit(CX18_F_S_APPL_IO, &s_vbi->s_flags)) { + while ((mdl = cx18_dequeue(s_vbi, + &s_vbi->q_full))) { + /* byteswap and process VBI data */ + cx18_process_vbi_data(cx, mdl, + s_vbi->type); + cx18_stream_put_mdl_fw(s_vbi, mdl); + } + } + mdl = &cx->vbi.sliced_mpeg_mdl; + if (mdl->readpos != mdl->bytesused) + return mdl; + } + + /* do we have new data? */ + mdl = cx18_dequeue(s, &s->q_full); + if (mdl) { + if (!test_and_clear_bit(CX18_F_M_NEED_SWAP, + &mdl->m_flags)) + return mdl; + if (s->type == CX18_ENC_STREAM_TYPE_MPG) + /* byteswap MPG data */ + cx18_mdl_swap(mdl); + else { + /* byteswap and process VBI data */ + cx18_process_vbi_data(cx, mdl, s->type); + } + return mdl; + } + + /* return if end of stream */ + if (!test_bit(CX18_F_S_STREAMING, &s->s_flags)) { + CX18_DEBUG_INFO("EOS %s\n", s->name); + return NULL; + } + + /* return if file was opened with O_NONBLOCK */ + if (non_block) { + *err = -EAGAIN; + return NULL; + } + + /* wait for more data to arrive */ + prepare_to_wait(&s->waitq, &wait, TASK_INTERRUPTIBLE); + /* New buffers might have become available before we were added + to the waitqueue */ + if (!atomic_read(&s->q_full.depth)) + schedule(); + finish_wait(&s->waitq, &wait); + if (signal_pending(current)) { + /* return if a signal was received */ + CX18_DEBUG_INFO("User stopped %s\n", s->name); + *err = -EINTR; + return NULL; + } + } +} + +static void cx18_setup_sliced_vbi_mdl(struct cx18 *cx) +{ + struct cx18_mdl *mdl = &cx->vbi.sliced_mpeg_mdl; + struct cx18_buffer *buf = &cx->vbi.sliced_mpeg_buf; + int idx = cx->vbi.inserted_frame % CX18_VBI_FRAMES; + + buf->buf = cx->vbi.sliced_mpeg_data[idx]; + buf->bytesused = cx->vbi.sliced_mpeg_size[idx]; + buf->readpos = 0; + + mdl->curr_buf = NULL; + mdl->bytesused = cx->vbi.sliced_mpeg_size[idx]; + mdl->readpos = 0; +} + +static size_t cx18_copy_buf_to_user(struct cx18_stream *s, + struct cx18_buffer *buf, char __user *ubuf, size_t ucount, bool *stop) +{ + struct cx18 *cx = s->cx; + size_t len = buf->bytesused - buf->readpos; + + *stop = false; + if (len > ucount) + len = ucount; + if (cx->vbi.insert_mpeg && s->type == CX18_ENC_STREAM_TYPE_MPG && + !cx18_raw_vbi(cx) && buf != &cx->vbi.sliced_mpeg_buf) { + /* + * Try to find a good splice point in the PS, just before + * an MPEG-2 Program Pack start code, and provide only + * up to that point to the user, so it's easy to insert VBI data + * the next time around. + * + * This will not work for an MPEG-2 TS and has only been + * verified by analysis to work for an MPEG-2 PS. Helen Buus + * pointed out this works for the CX23416 MPEG-2 DVD compatible + * stream, and research indicates both the MPEG 2 SVCD and DVD + * stream types use an MPEG-2 PS container. + */ + /* + * An MPEG-2 Program Stream (PS) is a series of + * MPEG-2 Program Packs terminated by an + * MPEG Program End Code after the last Program Pack. + * A Program Pack may hold a PS System Header packet and any + * number of Program Elementary Stream (PES) Packets + */ + const char *start = buf->buf + buf->readpos; + const char *p = start + 1; + const u8 *q; + u8 ch = cx->search_pack_header ? 0xba : 0xe0; + int stuffing, i; + + while (start + len > p) { + /* Scan for a 0 to find a potential MPEG-2 start code */ + q = memchr(p, 0, start + len - p); + if (q == NULL) + break; + p = q + 1; + /* + * Keep looking if not a + * MPEG-2 Pack header start code: 0x00 0x00 0x01 0xba + * or MPEG-2 video PES start code: 0x00 0x00 0x01 0xe0 + */ + if ((char *)q + 15 >= buf->buf + buf->bytesused || + q[1] != 0 || q[2] != 1 || q[3] != ch) + continue; + + /* If expecting the primary video PES */ + if (!cx->search_pack_header) { + /* Continue if it couldn't be a PES packet */ + if ((q[6] & 0xc0) != 0x80) + continue; + /* Check if a PTS or PTS & DTS follow */ + if (((q[7] & 0xc0) == 0x80 && /* PTS only */ + (q[9] & 0xf0) == 0x20) || /* PTS only */ + ((q[7] & 0xc0) == 0xc0 && /* PTS & DTS */ + (q[9] & 0xf0) == 0x30)) { /* DTS follows */ + /* Assume we found the video PES hdr */ + ch = 0xba; /* next want a Program Pack*/ + cx->search_pack_header = 1; + p = q + 9; /* Skip this video PES hdr */ + } + continue; + } + + /* We may have found a Program Pack start code */ + + /* Get the count of stuffing bytes & verify them */ + stuffing = q[13] & 7; + /* all stuffing bytes must be 0xff */ + for (i = 0; i < stuffing; i++) + if (q[14 + i] != 0xff) + break; + if (i == stuffing && /* right number of stuffing bytes*/ + (q[4] & 0xc4) == 0x44 && /* marker check */ + (q[12] & 3) == 3 && /* marker check */ + q[14 + stuffing] == 0 && /* PES Pack or Sys Hdr */ + q[15 + stuffing] == 0 && + q[16 + stuffing] == 1) { + /* We declare we actually found a Program Pack*/ + cx->search_pack_header = 0; /* expect vid PES */ + len = (char *)q - start; + cx18_setup_sliced_vbi_mdl(cx); + *stop = true; + break; + } + } + } + if (copy_to_user(ubuf, (u8 *)buf->buf + buf->readpos, len)) { + CX18_DEBUG_WARN("copy %zd bytes to user failed for %s\n", + len, s->name); + return -EFAULT; + } + buf->readpos += len; + if (s->type == CX18_ENC_STREAM_TYPE_MPG && + buf != &cx->vbi.sliced_mpeg_buf) + cx->mpg_data_received += len; + return len; +} + +static size_t cx18_copy_mdl_to_user(struct cx18_stream *s, + struct cx18_mdl *mdl, char __user *ubuf, size_t ucount) +{ + size_t tot_written = 0; + int rc; + bool stop = false; + + if (mdl->curr_buf == NULL) + mdl->curr_buf = list_first_entry(&mdl->buf_list, + struct cx18_buffer, list); + + if (list_entry_is_head(mdl->curr_buf, &mdl->buf_list, list)) { + /* + * For some reason we've exhausted the buffers, but the MDL + * object still said some data was unread. + * Fix that and bail out. + */ + mdl->readpos = mdl->bytesused; + return 0; + } + + list_for_each_entry_from(mdl->curr_buf, &mdl->buf_list, list) { + + if (mdl->curr_buf->readpos >= mdl->curr_buf->bytesused) + continue; + + rc = cx18_copy_buf_to_user(s, mdl->curr_buf, ubuf + tot_written, + ucount - tot_written, &stop); + if (rc < 0) + return rc; + mdl->readpos += rc; + tot_written += rc; + + if (stop || /* Forced stopping point for VBI insertion */ + tot_written >= ucount || /* Reader request satisfied */ + mdl->curr_buf->readpos < mdl->curr_buf->bytesused || + mdl->readpos >= mdl->bytesused) /* MDL buffers drained */ + break; + } + return tot_written; +} + +static ssize_t cx18_read(struct cx18_stream *s, char __user *ubuf, + size_t tot_count, int non_block) +{ + struct cx18 *cx = s->cx; + size_t tot_written = 0; + int single_frame = 0; + + if (atomic_read(&cx->ana_capturing) == 0 && s->id == -1) { + /* shouldn't happen */ + CX18_DEBUG_WARN("Stream %s not initialized before read\n", + s->name); + return -EIO; + } + + /* Each VBI buffer is one frame, the v4l2 API says that for VBI the + frames should arrive one-by-one, so make sure we never output more + than one VBI frame at a time */ + if (s->type == CX18_ENC_STREAM_TYPE_VBI && !cx18_raw_vbi(cx)) + single_frame = 1; + + for (;;) { + struct cx18_mdl *mdl; + int rc; + + mdl = cx18_get_mdl(s, non_block, &rc); + /* if there is no data available... */ + if (mdl == NULL) { + /* if we got data, then return that regardless */ + if (tot_written) + break; + /* EOS condition */ + if (rc == 0) { + clear_bit(CX18_F_S_STREAMOFF, &s->s_flags); + clear_bit(CX18_F_S_APPL_IO, &s->s_flags); + cx18_release_stream(s); + } + /* set errno */ + return rc; + } + + rc = cx18_copy_mdl_to_user(s, mdl, ubuf + tot_written, + tot_count - tot_written); + + if (mdl != &cx->vbi.sliced_mpeg_mdl) { + if (mdl->readpos == mdl->bytesused) + cx18_stream_put_mdl_fw(s, mdl); + else + cx18_push(s, mdl, &s->q_full); + } else if (mdl->readpos == mdl->bytesused) { + int idx = cx->vbi.inserted_frame % CX18_VBI_FRAMES; + + cx->vbi.sliced_mpeg_size[idx] = 0; + cx->vbi.inserted_frame++; + cx->vbi_data_inserted += mdl->bytesused; + } + if (rc < 0) + return rc; + tot_written += rc; + + if (tot_written == tot_count || single_frame) + break; + } + return tot_written; +} + +static ssize_t cx18_read_pos(struct cx18_stream *s, char __user *ubuf, + size_t count, loff_t *pos, int non_block) +{ + ssize_t rc = count ? cx18_read(s, ubuf, count, non_block) : 0; + struct cx18 *cx = s->cx; + + CX18_DEBUG_HI_FILE("read %zd from %s, got %zd\n", count, s->name, rc); + if (rc > 0) + *pos += rc; + return rc; +} + +int cx18_start_capture(struct cx18_open_id *id) +{ + struct cx18 *cx = id->cx; + struct cx18_stream *s = &cx->streams[id->type]; + struct cx18_stream *s_vbi; + struct cx18_stream *s_idx; + + if (s->type == CX18_ENC_STREAM_TYPE_RAD) { + /* you cannot read from these stream types. */ + return -EPERM; + } + + /* Try to claim this stream. */ + if (cx18_claim_stream(id, s->type)) + return -EBUSY; + + /* If capture is already in progress, then we also have to + do nothing extra. */ + if (test_bit(CX18_F_S_STREAMOFF, &s->s_flags) || + test_and_set_bit(CX18_F_S_STREAMING, &s->s_flags)) { + set_bit(CX18_F_S_APPL_IO, &s->s_flags); + return 0; + } + + /* Start associated VBI or IDX stream capture if required */ + s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI]; + s_idx = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; + if (s->type == CX18_ENC_STREAM_TYPE_MPG) { + /* + * The VBI and IDX streams should have been claimed + * automatically, if for internal use, when the MPG stream was + * claimed. We only need to start these streams capturing. + */ + if (test_bit(CX18_F_S_INTERNAL_USE, &s_idx->s_flags) && + !test_and_set_bit(CX18_F_S_STREAMING, &s_idx->s_flags)) { + if (cx18_start_v4l2_encode_stream(s_idx)) { + CX18_DEBUG_WARN("IDX capture start failed\n"); + clear_bit(CX18_F_S_STREAMING, &s_idx->s_flags); + goto start_failed; + } + CX18_DEBUG_INFO("IDX capture started\n"); + } + if (test_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags) && + !test_and_set_bit(CX18_F_S_STREAMING, &s_vbi->s_flags)) { + if (cx18_start_v4l2_encode_stream(s_vbi)) { + CX18_DEBUG_WARN("VBI capture start failed\n"); + clear_bit(CX18_F_S_STREAMING, &s_vbi->s_flags); + goto start_failed; + } + CX18_DEBUG_INFO("VBI insertion started\n"); + } + } + + /* Tell the card to start capturing */ + if (!cx18_start_v4l2_encode_stream(s)) { + /* We're done */ + set_bit(CX18_F_S_APPL_IO, &s->s_flags); + /* Resume a possibly paused encoder */ + if (test_and_clear_bit(CX18_F_I_ENC_PAUSED, &cx->i_flags)) + cx18_vapi(cx, CX18_CPU_CAPTURE_PAUSE, 1, s->handle); + return 0; + } + +start_failed: + CX18_DEBUG_WARN("Failed to start capturing for stream %s\n", s->name); + + /* + * The associated VBI and IDX streams for internal use are released + * automatically when the MPG stream is released. We only need to stop + * the associated stream. + */ + if (s->type == CX18_ENC_STREAM_TYPE_MPG) { + /* Stop the IDX stream which is always for internal use */ + if (test_bit(CX18_F_S_STREAMING, &s_idx->s_flags)) { + cx18_stop_v4l2_encode_stream(s_idx, 0); + clear_bit(CX18_F_S_STREAMING, &s_idx->s_flags); + } + /* Stop the VBI stream, if only running for internal use */ + if (test_bit(CX18_F_S_STREAMING, &s_vbi->s_flags) && + !test_bit(CX18_F_S_APPL_IO, &s_vbi->s_flags)) { + cx18_stop_v4l2_encode_stream(s_vbi, 0); + clear_bit(CX18_F_S_STREAMING, &s_vbi->s_flags); + } + } + clear_bit(CX18_F_S_STREAMING, &s->s_flags); + cx18_release_stream(s); /* Also releases associated streams */ + return -EIO; +} + +ssize_t cx18_v4l2_read(struct file *filp, char __user *buf, size_t count, + loff_t *pos) +{ + struct cx18_open_id *id = file2id(filp); + struct cx18 *cx = id->cx; + struct cx18_stream *s = &cx->streams[id->type]; + int rc; + + CX18_DEBUG_HI_FILE("read %zd bytes from %s\n", count, s->name); + + mutex_lock(&cx->serialize_lock); + rc = cx18_start_capture(id); + mutex_unlock(&cx->serialize_lock); + if (rc) + return rc; + + return cx18_read_pos(s, buf, count, pos, filp->f_flags & O_NONBLOCK); +} + +__poll_t cx18_v4l2_enc_poll(struct file *filp, poll_table *wait) +{ + __poll_t req_events = poll_requested_events(wait); + struct cx18_open_id *id = file2id(filp); + struct cx18 *cx = id->cx; + struct cx18_stream *s = &cx->streams[id->type]; + int eof = test_bit(CX18_F_S_STREAMOFF, &s->s_flags); + __poll_t res = 0; + + /* Start a capture if there is none */ + if (!eof && !test_bit(CX18_F_S_STREAMING, &s->s_flags) && + (req_events & (EPOLLIN | EPOLLRDNORM))) { + int rc; + + mutex_lock(&cx->serialize_lock); + rc = cx18_start_capture(id); + mutex_unlock(&cx->serialize_lock); + if (rc) { + CX18_DEBUG_INFO("Could not start capture for %s (%d)\n", + s->name, rc); + return EPOLLERR; + } + CX18_DEBUG_FILE("Encoder poll started capture\n"); + } + + /* add stream's waitq to the poll list */ + CX18_DEBUG_HI_FILE("Encoder poll\n"); + if (v4l2_event_pending(&id->fh)) + res |= EPOLLPRI; + else + poll_wait(filp, &s->waitq, wait); + + if (atomic_read(&s->q_full.depth)) + return res | EPOLLIN | EPOLLRDNORM; + if (eof) + return res | EPOLLHUP; + return res; +} + +void cx18_vb_timeout(struct timer_list *t) +{ + struct cx18_stream *s = timer_container_of(s, t, vb_timeout); + + /* + * Return all of the buffers in error state, so the vbi/vid inode + * can return from blocking. + */ + cx18_clear_queue(s, VB2_BUF_STATE_ERROR); +} + +void cx18_stop_capture(struct cx18_stream *s, int gop_end) +{ + struct cx18 *cx = s->cx; + struct cx18_stream *s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI]; + struct cx18_stream *s_idx = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; + + CX18_DEBUG_IOCTL("close() of %s\n", s->name); + + /* 'Unclaim' this stream */ + + /* Stop capturing */ + if (test_bit(CX18_F_S_STREAMING, &s->s_flags)) { + CX18_DEBUG_INFO("close stopping capture\n"); + if (s->type == CX18_ENC_STREAM_TYPE_MPG) { + /* Stop internal use associated VBI and IDX streams */ + if (test_bit(CX18_F_S_STREAMING, &s_vbi->s_flags) && + !test_bit(CX18_F_S_APPL_IO, &s_vbi->s_flags)) { + CX18_DEBUG_INFO("close stopping embedded VBI capture\n"); + cx18_stop_v4l2_encode_stream(s_vbi, 0); + } + if (test_bit(CX18_F_S_STREAMING, &s_idx->s_flags)) { + CX18_DEBUG_INFO("close stopping IDX capture\n"); + cx18_stop_v4l2_encode_stream(s_idx, 0); + } + } + if (s->type == CX18_ENC_STREAM_TYPE_VBI && + test_bit(CX18_F_S_INTERNAL_USE, &s->s_flags)) + /* Also used internally, don't stop capturing */ + s->id = -1; + else + cx18_stop_v4l2_encode_stream(s, gop_end); + } + if (!gop_end) { + clear_bit(CX18_F_S_APPL_IO, &s->s_flags); + clear_bit(CX18_F_S_STREAMOFF, &s->s_flags); + cx18_release_stream(s); + } +} + +int cx18_v4l2_close(struct file *filp) +{ + struct v4l2_fh *fh = file_to_v4l2_fh(filp); + struct cx18_open_id *id = fh2id(fh); + struct cx18 *cx = id->cx; + struct cx18_stream *s = &cx->streams[id->type]; + struct video_device *vdev = &s->video_dev; + + CX18_DEBUG_IOCTL("close() of %s\n", s->name); + + mutex_lock(&cx->serialize_lock); + /* Stop radio */ + if (id->type == CX18_ENC_STREAM_TYPE_RAD && + v4l2_fh_is_singular_file(filp)) { + /* Closing radio device, return to TV mode */ + cx18_mute(cx); + /* Mark that the radio is no longer in use */ + clear_bit(CX18_F_I_RADIO_USER, &cx->i_flags); + /* Switch tuner to TV */ + cx18_call_all(cx, video, s_std, cx->std); + /* Select correct audio input (i.e. TV tuner or Line in) */ + cx18_audio_set_io(cx); + if (atomic_read(&cx->ana_capturing) > 0) { + /* Undo video mute */ + cx18_vapi(cx, CX18_CPU_SET_VIDEO_MUTE, 2, s->handle, + (v4l2_ctrl_g_ctrl(cx->cxhdl.video_mute) | + (v4l2_ctrl_g_ctrl(cx->cxhdl.video_mute_yuv) << 8))); + } + /* Done! Unmute and continue. */ + cx18_unmute(cx); + } + + if (id->type == CX18_ENC_STREAM_TYPE_YUV && + file_to_v4l2_fh(filp) == vdev->queue->owner) { + vb2_queue_release(vdev->queue); + vdev->queue->owner = NULL; + } + v4l2_fh_del(fh, filp); + v4l2_fh_exit(fh); + + /* 'Unclaim' this stream */ + if (id->type != CX18_ENC_STREAM_TYPE_YUV && s->id == id->open_id) + cx18_stop_capture(s, 0); + kfree(id); + mutex_unlock(&cx->serialize_lock); + return 0; +} + +static int cx18_serialized_open(struct cx18_stream *s, struct file *filp) +{ + struct cx18 *cx = s->cx; + struct cx18_open_id *item; + + CX18_DEBUG_FILE("open %s\n", s->name); + + /* Allocate memory */ + item = kzalloc_obj(struct cx18_open_id); + if (NULL == item) { + CX18_DEBUG_WARN("nomem on v4l2 open\n"); + return -ENOMEM; + } + v4l2_fh_init(&item->fh, &s->video_dev); + + item->cx = cx; + item->type = s->type; + + item->open_id = cx->open_id++; + v4l2_fh_add(&item->fh, filp); + + if (item->type == CX18_ENC_STREAM_TYPE_RAD && + v4l2_fh_is_singular_file(filp)) { + if (!test_bit(CX18_F_I_RADIO_USER, &cx->i_flags)) { + if (atomic_read(&cx->ana_capturing) > 0) { + /* switching to radio while capture is + in progress is not polite */ + v4l2_fh_del(&item->fh, filp); + v4l2_fh_exit(&item->fh); + kfree(item); + return -EBUSY; + } + } + + /* Mark that the radio is being used. */ + set_bit(CX18_F_I_RADIO_USER, &cx->i_flags); + /* We have the radio */ + cx18_mute(cx); + /* Switch tuner to radio */ + cx18_call_all(cx, tuner, s_radio); + /* Select the correct audio input (i.e. radio tuner) */ + cx18_audio_set_io(cx); + /* Done! Unmute and continue. */ + cx18_unmute(cx); + } + return 0; +} + +int cx18_v4l2_open(struct file *filp) +{ + int res; + struct video_device *video_dev = video_devdata(filp); + struct cx18_stream *s = video_get_drvdata(video_dev); + struct cx18 *cx = s->cx; + + mutex_lock(&cx->serialize_lock); + if (cx18_init_on_first_open(cx)) { + CX18_ERR("Failed to initialize on %s\n", + video_device_node_name(video_dev)); + mutex_unlock(&cx->serialize_lock); + return -ENXIO; + } + res = cx18_serialized_open(s, filp); + mutex_unlock(&cx->serialize_lock); + return res; +} + +void cx18_mute(struct cx18 *cx) +{ + u32 h; + if (atomic_read(&cx->ana_capturing)) { + h = cx18_find_handle(cx); + if (h != CX18_INVALID_TASK_HANDLE) + cx18_vapi(cx, CX18_CPU_SET_AUDIO_MUTE, 2, h, 1); + else + CX18_ERR("Can't find valid task handle for mute\n"); + } + CX18_DEBUG_INFO("Mute\n"); +} + +void cx18_unmute(struct cx18 *cx) +{ + u32 h; + if (atomic_read(&cx->ana_capturing)) { + h = cx18_find_handle(cx); + if (h != CX18_INVALID_TASK_HANDLE) { + cx18_msleep_timeout(100, 0); + cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 2, h, 12); + cx18_vapi(cx, CX18_CPU_SET_AUDIO_MUTE, 2, h, 0); + } else + CX18_ERR("Can't find valid task handle for unmute\n"); + } + CX18_DEBUG_INFO("Unmute\n"); +} diff --git a/drivers/media/pci/cx18/cx18-fileops.h b/drivers/media/pci/cx18/cx18-fileops.h new file mode 100644 index 000000000..bc999ea85 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-fileops.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 file operation functions + * + * Derived from ivtv-fileops.h + * + * Copyright (C) 2007 Hans Verkuil + */ + +/* Testing/Debugging */ +int cx18_v4l2_open(struct file *filp); +ssize_t cx18_v4l2_read(struct file *filp, char __user *buf, size_t count, + loff_t *pos); +ssize_t cx18_v4l2_write(struct file *filp, const char __user *buf, size_t count, + loff_t *pos); +int cx18_v4l2_close(struct file *filp); +__poll_t cx18_v4l2_enc_poll(struct file *filp, poll_table *wait); +int cx18_start_capture(struct cx18_open_id *id); +void cx18_stop_capture(struct cx18_stream *s, int gop_end); +void cx18_mute(struct cx18 *cx); +void cx18_unmute(struct cx18 *cx); +int cx18_v4l2_mmap(struct file *file, struct vm_area_struct *vma); +void cx18_clear_queue(struct cx18_stream *s, enum vb2_buffer_state state); +void cx18_vb_timeout(struct timer_list *t); + +/* Shared with cx18-alsa module */ +int cx18_claim_stream(struct cx18_open_id *id, int type); +void cx18_release_stream(struct cx18_stream *s); diff --git a/drivers/media/pci/cx18/cx18-firmware.c b/drivers/media/pci/cx18/cx18-firmware.c new file mode 100644 index 000000000..94e17948f --- /dev/null +++ b/drivers/media/pci/cx18/cx18-firmware.c @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 firmware functions + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#include "cx18-driver.h" +#include "cx18-io.h" +#include "cx18-scb.h" +#include "cx18-irq.h" +#include "cx18-firmware.h" +#include "cx18-cards.h" +#include + +#define CX18_PROC_SOFT_RESET 0xc70010 +#define CX18_DDR_SOFT_RESET 0xc70014 +#define CX18_CLOCK_SELECT1 0xc71000 +#define CX18_CLOCK_SELECT2 0xc71004 +#define CX18_HALF_CLOCK_SELECT1 0xc71008 +#define CX18_HALF_CLOCK_SELECT2 0xc7100C +#define CX18_CLOCK_POLARITY1 0xc71010 +#define CX18_CLOCK_POLARITY2 0xc71014 +#define CX18_ADD_DELAY_ENABLE1 0xc71018 +#define CX18_ADD_DELAY_ENABLE2 0xc7101C +#define CX18_CLOCK_ENABLE1 0xc71020 +#define CX18_CLOCK_ENABLE2 0xc71024 + +#define CX18_REG_BUS_TIMEOUT_EN 0xc72024 + +#define CX18_FAST_CLOCK_PLL_INT 0xc78000 +#define CX18_FAST_CLOCK_PLL_FRAC 0xc78004 +#define CX18_FAST_CLOCK_PLL_POST 0xc78008 +#define CX18_FAST_CLOCK_PLL_PRESCALE 0xc7800C +#define CX18_FAST_CLOCK_PLL_ADJUST_BANDWIDTH 0xc78010 + +#define CX18_SLOW_CLOCK_PLL_INT 0xc78014 +#define CX18_SLOW_CLOCK_PLL_FRAC 0xc78018 +#define CX18_SLOW_CLOCK_PLL_POST 0xc7801C +#define CX18_MPEG_CLOCK_PLL_INT 0xc78040 +#define CX18_MPEG_CLOCK_PLL_FRAC 0xc78044 +#define CX18_MPEG_CLOCK_PLL_POST 0xc78048 +#define CX18_PLL_POWER_DOWN 0xc78088 +#define CX18_SW1_INT_STATUS 0xc73104 +#define CX18_SW1_INT_ENABLE_PCI 0xc7311C +#define CX18_SW2_INT_SET 0xc73140 +#define CX18_SW2_INT_STATUS 0xc73144 +#define CX18_ADEC_CONTROL 0xc78120 + +#define CX18_DDR_REQUEST_ENABLE 0xc80000 +#define CX18_DDR_CHIP_CONFIG 0xc80004 +#define CX18_DDR_REFRESH 0xc80008 +#define CX18_DDR_TIMING1 0xc8000C +#define CX18_DDR_TIMING2 0xc80010 +#define CX18_DDR_POWER_REG 0xc8001C + +#define CX18_DDR_TUNE_LANE 0xc80048 +#define CX18_DDR_INITIAL_EMRS 0xc80054 +#define CX18_DDR_MB_PER_ROW_7 0xc8009C +#define CX18_DDR_BASE_63_ADDR 0xc804FC + +#define CX18_WMB_CLIENT02 0xc90108 +#define CX18_WMB_CLIENT05 0xc90114 +#define CX18_WMB_CLIENT06 0xc90118 +#define CX18_WMB_CLIENT07 0xc9011C +#define CX18_WMB_CLIENT08 0xc90120 +#define CX18_WMB_CLIENT09 0xc90124 +#define CX18_WMB_CLIENT10 0xc90128 +#define CX18_WMB_CLIENT11 0xc9012C +#define CX18_WMB_CLIENT12 0xc90130 +#define CX18_WMB_CLIENT13 0xc90134 +#define CX18_WMB_CLIENT14 0xc90138 + +#define CX18_DSP0_INTERRUPT_MASK 0xd0004C + +#define APU_ROM_SYNC1 0x6D676553 /* "mgeS" */ +#define APU_ROM_SYNC2 0x72646548 /* "rdeH" */ + +struct cx18_apu_rom_seghdr { + u32 sync1; + u32 sync2; + u32 addr; + u32 size; +}; + +static int load_cpu_fw_direct(const char *fn, u8 __iomem *mem, struct cx18 *cx) +{ + const struct firmware *fw = NULL; + int i, j; + unsigned size; + u32 __iomem *dst = (u32 __iomem *)mem; + const u32 *src; + + if (request_firmware(&fw, fn, &cx->pci_dev->dev)) { + CX18_ERR("Unable to open firmware %s\n", fn); + CX18_ERR("Did you put the firmware in the hotplug firmware directory?\n"); + return -ENOMEM; + } + + src = (const u32 *)fw->data; + + for (i = 0; i < fw->size; i += 4096) { + cx18_setup_page(cx, i); + for (j = i; j < fw->size && j < i + 4096; j += 4) { + /* no need for endianness conversion on the ppc */ + cx18_raw_writel(cx, *src, dst); + if (cx18_raw_readl(cx, dst) != *src) { + CX18_ERR("Mismatch at offset %x\n", i); + release_firmware(fw); + cx18_setup_page(cx, 0); + return -EIO; + } + dst++; + src++; + } + } + if (!test_bit(CX18_F_I_LOADED_FW, &cx->i_flags)) + CX18_INFO("loaded %s firmware (%zu bytes)\n", fn, fw->size); + size = fw->size; + release_firmware(fw); + cx18_setup_page(cx, SCB_OFFSET); + return size; +} + +static int load_apu_fw_direct(const char *fn, u8 __iomem *dst, struct cx18 *cx, + u32 *entry_addr) +{ + const struct firmware *fw = NULL; + int i, j; + unsigned size; + const u32 *src; + struct cx18_apu_rom_seghdr seghdr; + const u8 *vers; + u32 offset = 0; + u32 apu_version = 0; + int sz; + + if (request_firmware(&fw, fn, &cx->pci_dev->dev)) { + CX18_ERR("unable to open firmware %s\n", fn); + CX18_ERR("did you put the firmware in the hotplug firmware directory?\n"); + cx18_setup_page(cx, 0); + return -ENOMEM; + } + + *entry_addr = 0; + src = (const u32 *)fw->data; + vers = fw->data + sizeof(seghdr); + sz = fw->size; + + apu_version = (vers[0] << 24) | (vers[4] << 16) | vers[32]; + while (offset + sizeof(seghdr) < fw->size) { + const __le32 *shptr = (__force __le32 *)src + offset / 4; + + seghdr.sync1 = le32_to_cpu(shptr[0]); + seghdr.sync2 = le32_to_cpu(shptr[1]); + seghdr.addr = le32_to_cpu(shptr[2]); + seghdr.size = le32_to_cpu(shptr[3]); + + offset += sizeof(seghdr); + if (seghdr.sync1 != APU_ROM_SYNC1 || + seghdr.sync2 != APU_ROM_SYNC2) { + offset += seghdr.size; + continue; + } + CX18_DEBUG_INFO("load segment %x-%x\n", seghdr.addr, + seghdr.addr + seghdr.size - 1); + if (*entry_addr == 0) + *entry_addr = seghdr.addr; + if (offset + seghdr.size > sz) + break; + for (i = 0; i < seghdr.size; i += 4096) { + cx18_setup_page(cx, seghdr.addr + i); + for (j = i; j < seghdr.size && j < i + 4096; j += 4) { + /* no need for endianness conversion on the ppc */ + cx18_raw_writel(cx, src[(offset + j) / 4], + dst + seghdr.addr + j); + if (cx18_raw_readl(cx, dst + seghdr.addr + j) + != src[(offset + j) / 4]) { + CX18_ERR("Mismatch at offset %x\n", + offset + j); + release_firmware(fw); + cx18_setup_page(cx, 0); + return -EIO; + } + } + } + offset += seghdr.size; + } + if (!test_bit(CX18_F_I_LOADED_FW, &cx->i_flags)) + CX18_INFO("loaded %s firmware V%08x (%zu bytes)\n", + fn, apu_version, fw->size); + size = fw->size; + release_firmware(fw); + cx18_setup_page(cx, 0); + return size; +} + +void cx18_halt_firmware(struct cx18 *cx) +{ + CX18_DEBUG_INFO("Preparing for firmware halt.\n"); + cx18_write_reg_expect(cx, 0x000F000F, CX18_PROC_SOFT_RESET, + 0x0000000F, 0x000F000F); + cx18_write_reg_expect(cx, 0x00020002, CX18_ADEC_CONTROL, + 0x00000002, 0x00020002); +} + +void cx18_init_power(struct cx18 *cx, int lowpwr) +{ + /* power-down Spare and AOM PLLs */ + /* power-up fast, slow and mpeg PLLs */ + cx18_write_reg(cx, 0x00000008, CX18_PLL_POWER_DOWN); + + /* ADEC out of sleep */ + cx18_write_reg_expect(cx, 0x00020000, CX18_ADEC_CONTROL, + 0x00000000, 0x00020002); + + /* + * The PLL parameters are based on the external crystal frequency that + * would ideally be: + * + * NTSC Color subcarrier freq * 8 = + * 4.5 MHz/286 * 455/2 * 8 = 28.63636363... MHz + * + * The accidents of history and rationale that explain from where this + * combination of magic numbers originate can be found in: + * + * [1] Abrahams, I. C., "Choice of Chrominance Subcarrier Frequency in + * the NTSC Standards", Proceedings of the I-R-E, January 1954, pp 79-80 + * + * [2] Abrahams, I. C., "The 'Frequency Interleaving' Principle in the + * NTSC Standards", Proceedings of the I-R-E, January 1954, pp 81-83 + * + * As Mike Bradley has rightly pointed out, it's not the exact crystal + * frequency that matters, only that all parts of the driver and + * firmware are using the same value (close to the ideal value). + * + * Since I have a strong suspicion that, if the firmware ever assumes a + * crystal value at all, it will assume 28.636360 MHz, the crystal + * freq used in calculations in this driver will be: + * + * xtal_freq = 28.636360 MHz + * + * an error of less than 0.13 ppm which is way, way better than any off + * the shelf crystal will have for accuracy anyway. + * + * Below I aim to run the PLLs' VCOs near 400 MHz to minimize errors. + * + * Many thanks to Jeff Campbell and Mike Bradley for their extensive + * investigation, experimentation, testing, and suggested solutions of + * audio/video sync problems with SVideo and CVBS captures. + */ + + /* the fast clock is at 200/245 MHz */ + /* 1 * xtal_freq * 0x0d.f7df9b8 / 2 = 200 MHz: 400 MHz pre post-divide*/ + /* 1 * xtal_freq * 0x11.1c71eb8 / 2 = 245 MHz: 490 MHz pre post-divide*/ + cx18_write_reg(cx, lowpwr ? 0xD : 0x11, CX18_FAST_CLOCK_PLL_INT); + cx18_write_reg(cx, lowpwr ? 0x1EFBF37 : 0x038E3D7, + CX18_FAST_CLOCK_PLL_FRAC); + + cx18_write_reg(cx, 2, CX18_FAST_CLOCK_PLL_POST); + cx18_write_reg(cx, 1, CX18_FAST_CLOCK_PLL_PRESCALE); + cx18_write_reg(cx, 4, CX18_FAST_CLOCK_PLL_ADJUST_BANDWIDTH); + + /* set slow clock to 125/120 MHz */ + /* xtal_freq * 0x0d.1861a20 / 3 = 125 MHz: 375 MHz before post-divide */ + /* xtal_freq * 0x0c.92493f8 / 3 = 120 MHz: 360 MHz before post-divide */ + cx18_write_reg(cx, lowpwr ? 0xD : 0xC, CX18_SLOW_CLOCK_PLL_INT); + cx18_write_reg(cx, lowpwr ? 0x30C344 : 0x124927F, + CX18_SLOW_CLOCK_PLL_FRAC); + cx18_write_reg(cx, 3, CX18_SLOW_CLOCK_PLL_POST); + + /* mpeg clock pll 54MHz */ + /* xtal_freq * 0xf.15f17f0 / 8 = 54 MHz: 432 MHz before post-divide */ + cx18_write_reg(cx, 0xF, CX18_MPEG_CLOCK_PLL_INT); + cx18_write_reg(cx, 0x2BE2FE, CX18_MPEG_CLOCK_PLL_FRAC); + cx18_write_reg(cx, 8, CX18_MPEG_CLOCK_PLL_POST); + + /* Defaults */ + /* APU = SC or SC/2 = 125/62.5 */ + /* EPU = SC = 125 */ + /* DDR = FC = 180 */ + /* ENC = SC = 125 */ + /* AI1 = SC = 125 */ + /* VIM2 = disabled */ + /* PCI = FC/2 = 90 */ + /* AI2 = disabled */ + /* DEMUX = disabled */ + /* AO = SC/2 = 62.5 */ + /* SER = 54MHz */ + /* VFC = disabled */ + /* USB = disabled */ + + if (lowpwr) { + cx18_write_reg_expect(cx, 0xFFFF0020, CX18_CLOCK_SELECT1, + 0x00000020, 0xFFFFFFFF); + cx18_write_reg_expect(cx, 0xFFFF0004, CX18_CLOCK_SELECT2, + 0x00000004, 0xFFFFFFFF); + } else { + /* This doesn't explicitly set every clock select */ + cx18_write_reg_expect(cx, 0x00060004, CX18_CLOCK_SELECT1, + 0x00000004, 0x00060006); + cx18_write_reg_expect(cx, 0x00060006, CX18_CLOCK_SELECT2, + 0x00000006, 0x00060006); + } + + cx18_write_reg_expect(cx, 0xFFFF0002, CX18_HALF_CLOCK_SELECT1, + 0x00000002, 0xFFFFFFFF); + cx18_write_reg_expect(cx, 0xFFFF0104, CX18_HALF_CLOCK_SELECT2, + 0x00000104, 0xFFFFFFFF); + cx18_write_reg_expect(cx, 0xFFFF9026, CX18_CLOCK_ENABLE1, + 0x00009026, 0xFFFFFFFF); + cx18_write_reg_expect(cx, 0xFFFF3105, CX18_CLOCK_ENABLE2, + 0x00003105, 0xFFFFFFFF); +} + +void cx18_init_memory(struct cx18 *cx) +{ + cx18_msleep_timeout(10, 0); + cx18_write_reg_expect(cx, 0x00010000, CX18_DDR_SOFT_RESET, + 0x00000000, 0x00010001); + cx18_msleep_timeout(10, 0); + + cx18_write_reg(cx, cx->card->ddr.chip_config, CX18_DDR_CHIP_CONFIG); + + cx18_msleep_timeout(10, 0); + + cx18_write_reg(cx, cx->card->ddr.refresh, CX18_DDR_REFRESH); + cx18_write_reg(cx, cx->card->ddr.timing1, CX18_DDR_TIMING1); + cx18_write_reg(cx, cx->card->ddr.timing2, CX18_DDR_TIMING2); + + cx18_msleep_timeout(10, 0); + + /* Initialize DQS pad time */ + cx18_write_reg(cx, cx->card->ddr.tune_lane, CX18_DDR_TUNE_LANE); + cx18_write_reg(cx, cx->card->ddr.initial_emrs, CX18_DDR_INITIAL_EMRS); + + cx18_msleep_timeout(10, 0); + + cx18_write_reg_expect(cx, 0x00020000, CX18_DDR_SOFT_RESET, + 0x00000000, 0x00020002); + cx18_msleep_timeout(10, 0); + + /* use power-down mode when idle */ + cx18_write_reg(cx, 0x00000010, CX18_DDR_POWER_REG); + + cx18_write_reg_expect(cx, 0x00010001, CX18_REG_BUS_TIMEOUT_EN, + 0x00000001, 0x00010001); + + cx18_write_reg(cx, 0x48, CX18_DDR_MB_PER_ROW_7); + cx18_write_reg(cx, 0xE0000, CX18_DDR_BASE_63_ADDR); + + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT02); /* AO */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT09); /* AI2 */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT05); /* VIM1 */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT06); /* AI1 */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT07); /* 3D comb */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT10); /* ME */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT12); /* ENC */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT13); /* PK */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT11); /* RC */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT14); /* AVO */ +} + +#define CX18_CPU_FIRMWARE "v4l-cx23418-cpu.fw" +#define CX18_APU_FIRMWARE "v4l-cx23418-apu.fw" + +int cx18_firmware_init(struct cx18 *cx) +{ + u32 fw_entry_addr; + int sz, retries; + u32 api_args[MAX_MB_ARGUMENTS]; + + /* Allow chip to control CLKRUN */ + cx18_write_reg(cx, 0x5, CX18_DSP0_INTERRUPT_MASK); + + /* Stop the firmware */ + cx18_write_reg_expect(cx, 0x000F000F, CX18_PROC_SOFT_RESET, + 0x0000000F, 0x000F000F); + + cx18_msleep_timeout(1, 0); + + /* If the CPU is still running */ + if ((cx18_read_reg(cx, CX18_PROC_SOFT_RESET) & 8) == 0) { + CX18_ERR("%s: couldn't stop CPU to load firmware\n", __func__); + return -EIO; + } + + cx18_sw1_irq_enable(cx, IRQ_CPU_TO_EPU | IRQ_APU_TO_EPU); + cx18_sw2_irq_enable(cx, IRQ_CPU_TO_EPU_ACK | IRQ_APU_TO_EPU_ACK); + + sz = load_cpu_fw_direct(CX18_CPU_FIRMWARE, cx->enc_mem, cx); + if (sz <= 0) + return sz; + + /* The SCB & IPC area *must* be correct before starting the firmwares */ + cx18_init_scb(cx); + + fw_entry_addr = 0; + sz = load_apu_fw_direct(CX18_APU_FIRMWARE, cx->enc_mem, cx, + &fw_entry_addr); + if (sz <= 0) + return sz; + + /* Start the CPU. The CPU will take care of the APU for us. */ + cx18_write_reg_expect(cx, 0x00080000, CX18_PROC_SOFT_RESET, + 0x00000000, 0x00080008); + + /* Wait up to 500 ms for the APU to come out of reset */ + for (retries = 0; + retries < 50 && (cx18_read_reg(cx, CX18_PROC_SOFT_RESET) & 1) == 1; + retries++) + cx18_msleep_timeout(10, 0); + + cx18_msleep_timeout(200, 0); + + if (retries == 50 && + (cx18_read_reg(cx, CX18_PROC_SOFT_RESET) & 1) == 1) { + CX18_ERR("Could not start the CPU\n"); + return -EIO; + } + + /* + * The CPU had once before set up to receive an interrupt for it's + * outgoing IRQ_CPU_TO_EPU_ACK to us. If it ever does this, we get an + * interrupt when it sends us an ack, but by the time we process it, + * that flag in the SW2 status register has been cleared by the CPU + * firmware. We'll prevent that not so useful condition from happening + * by clearing the CPU's interrupt enables for Ack IRQ's we want to + * process. + */ + cx18_sw2_irq_disable_cpu(cx, IRQ_CPU_TO_EPU_ACK | IRQ_APU_TO_EPU_ACK); + + /* Try a benign command to see if the CPU is alive and well */ + sz = cx18_vapi_result(cx, api_args, CX18_CPU_DEBUG_PEEK32, 1, 0); + if (sz < 0) + return sz; + + /* initialize GPIO */ + cx18_write_reg_expect(cx, 0x14001400, 0xc78110, 0x00001400, 0x14001400); + return 0; +} + +MODULE_FIRMWARE(CX18_CPU_FIRMWARE); +MODULE_FIRMWARE(CX18_APU_FIRMWARE); diff --git a/drivers/media/pci/cx18/cx18-firmware.h b/drivers/media/pci/cx18/cx18-firmware.h new file mode 100644 index 000000000..8e6367728 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-firmware.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 firmware functions + * + * Copyright (C) 2007 Hans Verkuil + */ + +int cx18_firmware_init(struct cx18 *cx); +void cx18_halt_firmware(struct cx18 *cx); +void cx18_init_memory(struct cx18 *cx); +void cx18_init_power(struct cx18 *cx, int lowpwr); diff --git a/drivers/media/pci/cx18/cx18-gpio.c b/drivers/media/pci/cx18/cx18-gpio.c new file mode 100644 index 000000000..4aea92639 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-gpio.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 gpio functions + * + * Derived from ivtv-gpio.c + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#include "cx18-driver.h" +#include "cx18-io.h" +#include "cx18-cards.h" +#include "cx18-gpio.h" +#include "xc2028.h" + +/********************* GPIO stuffs *********************/ + +/* GPIO registers */ +#define CX18_REG_GPIO_IN 0xc72010 +#define CX18_REG_GPIO_OUT1 0xc78100 +#define CX18_REG_GPIO_DIR1 0xc78108 +#define CX18_REG_GPIO_OUT2 0xc78104 +#define CX18_REG_GPIO_DIR2 0xc7810c + +/* + * HVR-1600 GPIO pins, courtesy of Hauppauge: + * + * gpio0: zilog ir process reset pin + * gpio1: zilog programming pin (you should never use this) + * gpio12: cx24227 reset pin + * gpio13: cs5345 reset pin +*/ + +/* + * File scope utility functions + */ +static void gpio_write(struct cx18 *cx) +{ + u32 dir_lo = cx->gpio_dir & 0xffff; + u32 val_lo = cx->gpio_val & 0xffff; + u32 dir_hi = cx->gpio_dir >> 16; + u32 val_hi = cx->gpio_val >> 16; + + cx18_write_reg_expect(cx, dir_lo << 16, + CX18_REG_GPIO_DIR1, ~dir_lo, dir_lo); + cx18_write_reg_expect(cx, (dir_lo << 16) | val_lo, + CX18_REG_GPIO_OUT1, val_lo, dir_lo); + cx18_write_reg_expect(cx, dir_hi << 16, + CX18_REG_GPIO_DIR2, ~dir_hi, dir_hi); + cx18_write_reg_expect(cx, (dir_hi << 16) | val_hi, + CX18_REG_GPIO_OUT2, val_hi, dir_hi); +} + +static void gpio_update(struct cx18 *cx, u32 mask, u32 data) +{ + if (mask == 0) + return; + + mutex_lock(&cx->gpio_lock); + cx->gpio_val = (cx->gpio_val & ~mask) | (data & mask); + gpio_write(cx); + mutex_unlock(&cx->gpio_lock); +} + +static void gpio_reset_seq(struct cx18 *cx, u32 active_lo, u32 active_hi, + unsigned int assert_msecs, + unsigned int recovery_msecs) +{ + u32 mask; + + mask = active_lo | active_hi; + if (mask == 0) + return; + + /* + * Assuming that active_hi and active_lo are a subsets of the bits in + * gpio_dir. Also assumes that active_lo and active_hi don't overlap + * in any bit position + */ + + /* Assert */ + gpio_update(cx, mask, ~active_lo); + schedule_timeout_uninterruptible(msecs_to_jiffies(assert_msecs)); + + /* Deassert */ + gpio_update(cx, mask, ~active_hi); + schedule_timeout_uninterruptible(msecs_to_jiffies(recovery_msecs)); +} + +/* + * GPIO Multiplexer - logical device + */ +static int gpiomux_log_status(struct v4l2_subdev *sd) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + + mutex_lock(&cx->gpio_lock); + CX18_INFO_DEV(sd, "GPIO: direction 0x%08x, value 0x%08x\n", + cx->gpio_dir, cx->gpio_val); + mutex_unlock(&cx->gpio_lock); + return 0; +} + +static int gpiomux_s_radio(struct v4l2_subdev *sd) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + + /* + * FIXME - work out the cx->active/audio_input mess - this is + * intended to handle the switch to radio mode and set the + * audio routing, but we need to update the state in cx + */ + gpio_update(cx, cx->card->gpio_audio_input.mask, + cx->card->gpio_audio_input.radio); + return 0; +} + +static int gpiomux_s_std(struct v4l2_subdev *sd, v4l2_std_id norm) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + u32 data; + + switch (cx->card->audio_inputs[cx->audio_input].muxer_input) { + case 1: + data = cx->card->gpio_audio_input.linein; + break; + case 0: + data = cx->card->gpio_audio_input.tuner; + break; + default: + /* + * FIXME - work out the cx->active/audio_input mess - this is + * intended to handle the switch from radio mode and set the + * audio routing, but we need to update the state in cx + */ + data = cx->card->gpio_audio_input.tuner; + break; + } + gpio_update(cx, cx->card->gpio_audio_input.mask, data); + return 0; +} + +static int gpiomux_s_audio_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + u32 data; + + switch (input) { + case 0: + data = cx->card->gpio_audio_input.tuner; + break; + case 1: + data = cx->card->gpio_audio_input.linein; + break; + case 2: + data = cx->card->gpio_audio_input.radio; + break; + default: + return -EINVAL; + } + gpio_update(cx, cx->card->gpio_audio_input.mask, data); + return 0; +} + +static const struct v4l2_subdev_core_ops gpiomux_core_ops = { + .log_status = gpiomux_log_status, +}; + +static const struct v4l2_subdev_tuner_ops gpiomux_tuner_ops = { + .s_radio = gpiomux_s_radio, +}; + +static const struct v4l2_subdev_audio_ops gpiomux_audio_ops = { + .s_routing = gpiomux_s_audio_routing, +}; + +static const struct v4l2_subdev_video_ops gpiomux_video_ops = { + .s_std = gpiomux_s_std, +}; + +static const struct v4l2_subdev_ops gpiomux_ops = { + .core = &gpiomux_core_ops, + .tuner = &gpiomux_tuner_ops, + .audio = &gpiomux_audio_ops, + .video = &gpiomux_video_ops, +}; + +/* + * GPIO Reset Controller - logical device + */ +static int resetctrl_log_status(struct v4l2_subdev *sd) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + + mutex_lock(&cx->gpio_lock); + CX18_INFO_DEV(sd, "GPIO: direction 0x%08x, value 0x%08x\n", + cx->gpio_dir, cx->gpio_val); + mutex_unlock(&cx->gpio_lock); + return 0; +} + +static int resetctrl_reset(struct v4l2_subdev *sd, u32 val) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + const struct cx18_gpio_i2c_slave_reset *p; + + p = &cx->card->gpio_i2c_slave_reset; + switch (val) { + case CX18_GPIO_RESET_I2C: + gpio_reset_seq(cx, p->active_lo_mask, p->active_hi_mask, + p->msecs_asserted, p->msecs_recovery); + break; + case CX18_GPIO_RESET_Z8F0811: + /* + * Assert timing for the Z8F0811 on HVR-1600 boards: + * 1. Assert RESET for min of 4 clock cycles at 18.432 MHz to + * initiate + * 2. Reset then takes 66 WDT cycles at 10 kHz + 16 xtal clock + * cycles (6,601,085 nanoseconds ~= 7 milliseconds) + * 3. DBG pin must be high before chip exits reset for normal + * operation. DBG is open drain and hopefully pulled high + * since we don't normally drive it (GPIO 1?) for the + * HVR-1600 + * 4. Z8F0811 won't exit reset until RESET is deasserted + * 5. Zilog comes out of reset, loads reset vector address and + * executes from there. Required recovery delay unknown. + */ + gpio_reset_seq(cx, p->ir_reset_mask, 0, + p->msecs_asserted, p->msecs_recovery); + break; + case CX18_GPIO_RESET_XC2028: + if (cx->card->tuners[0].tuner == TUNER_XC2028) + gpio_reset_seq(cx, (1 << cx->card->xceive_pin), 0, + 1, 1); + break; + } + return 0; +} + +static const struct v4l2_subdev_core_ops resetctrl_core_ops = { + .log_status = resetctrl_log_status, + .reset = resetctrl_reset, +}; + +static const struct v4l2_subdev_ops resetctrl_ops = { + .core = &resetctrl_core_ops, +}; + +/* + * External entry points + */ +void cx18_gpio_init(struct cx18 *cx) +{ + mutex_lock(&cx->gpio_lock); + cx->gpio_dir = cx->card->gpio_init.direction; + cx->gpio_val = cx->card->gpio_init.initial_value; + + if (cx->card->tuners[0].tuner == TUNER_XC2028) { + cx->gpio_dir |= 1 << cx->card->xceive_pin; + cx->gpio_val |= 1 << cx->card->xceive_pin; + } + + if (cx->gpio_dir == 0) { + mutex_unlock(&cx->gpio_lock); + return; + } + + CX18_DEBUG_INFO("GPIO initial dir: %08x/%08x out: %08x/%08x\n", + cx18_read_reg(cx, CX18_REG_GPIO_DIR1), + cx18_read_reg(cx, CX18_REG_GPIO_DIR2), + cx18_read_reg(cx, CX18_REG_GPIO_OUT1), + cx18_read_reg(cx, CX18_REG_GPIO_OUT2)); + + gpio_write(cx); + mutex_unlock(&cx->gpio_lock); +} + +int cx18_gpio_register(struct cx18 *cx, u32 hw) +{ + struct v4l2_subdev *sd; + const struct v4l2_subdev_ops *ops; + char *str; + + switch (hw) { + case CX18_HW_GPIO_MUX: + sd = &cx->sd_gpiomux; + ops = &gpiomux_ops; + str = "gpio-mux"; + break; + case CX18_HW_GPIO_RESET_CTRL: + sd = &cx->sd_resetctrl; + ops = &resetctrl_ops; + str = "gpio-reset-ctrl"; + break; + default: + return -EINVAL; + } + + v4l2_subdev_init(sd, ops); + v4l2_set_subdevdata(sd, cx); + snprintf(sd->name, sizeof(sd->name), "%s %s", cx->v4l2_dev.name, str); + sd->grp_id = hw; + return v4l2_device_register_subdev(&cx->v4l2_dev, sd); +} + +/* Xceive tuner reset function */ +int cx18_reset_tuner_gpio(void *dev, int component, int cmd, int value) +{ + struct i2c_algo_bit_data *algo = dev; + struct cx18_i2c_algo_callback_data *cb_data = algo->data; + struct cx18 *cx = cb_data->cx; + + if (cmd != XC2028_TUNER_RESET || + cx->card->tuners[0].tuner != TUNER_XC2028) + return 0; + + CX18_DEBUG_INFO("Resetting XCeive tuner\n"); + return v4l2_subdev_call(&cx->sd_resetctrl, + core, reset, CX18_GPIO_RESET_XC2028); +} diff --git a/drivers/media/pci/cx18/cx18-gpio.h b/drivers/media/pci/cx18/cx18-gpio.h new file mode 100644 index 000000000..3eefc4644 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-gpio.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 gpio functions + * + * Derived from ivtv-gpio.h + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +void cx18_gpio_init(struct cx18 *cx); +int cx18_gpio_register(struct cx18 *cx, u32 hw); + +enum cx18_gpio_reset_type { + CX18_GPIO_RESET_I2C = 0, + CX18_GPIO_RESET_Z8F0811 = 1, + CX18_GPIO_RESET_XC2028 = 2, +}; + +int cx18_reset_tuner_gpio(void *dev, int component, int cmd, int value); diff --git a/drivers/media/pci/cx18/cx18-i2c.c b/drivers/media/pci/cx18/cx18-i2c.c new file mode 100644 index 000000000..a1abb0631 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-i2c.c @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 I2C functions + * + * Derived from ivtv-i2c.c + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#include "cx18-driver.h" +#include "cx18-io.h" +#include "cx18-cards.h" +#include "cx18-gpio.h" +#include "cx18-i2c.h" +#include "cx18-irq.h" + +#define CX18_REG_I2C_1_WR 0xf15000 +#define CX18_REG_I2C_1_RD 0xf15008 +#define CX18_REG_I2C_2_WR 0xf25100 +#define CX18_REG_I2C_2_RD 0xf25108 + +#define SETSCL_BIT 0x0001 +#define SETSDL_BIT 0x0002 +#define GETSCL_BIT 0x0004 +#define GETSDL_BIT 0x0008 + +#define CX18_CS5345_I2C_ADDR 0x4c +#define CX18_Z8F0811_IR_TX_I2C_ADDR 0x70 +#define CX18_Z8F0811_IR_RX_I2C_ADDR 0x71 + +/* This array should match the CX18_HW_ defines */ +static const u8 hw_addrs[] = { + 0, /* CX18_HW_TUNER */ + 0, /* CX18_HW_TVEEPROM */ + CX18_CS5345_I2C_ADDR, /* CX18_HW_CS5345 */ + 0, /* CX18_HW_DVB */ + 0, /* CX18_HW_418_AV */ + 0, /* CX18_HW_GPIO_MUX */ + 0, /* CX18_HW_GPIO_RESET_CTRL */ + CX18_Z8F0811_IR_RX_I2C_ADDR, /* CX18_HW_Z8F0811_IR_HAUP */ +}; + +/* This array should match the CX18_HW_ defines */ +/* This might well become a card-specific array */ +static const u8 hw_bus[] = { + 1, /* CX18_HW_TUNER */ + 0, /* CX18_HW_TVEEPROM */ + 0, /* CX18_HW_CS5345 */ + 0, /* CX18_HW_DVB */ + 0, /* CX18_HW_418_AV */ + 0, /* CX18_HW_GPIO_MUX */ + 0, /* CX18_HW_GPIO_RESET_CTRL */ + 0, /* CX18_HW_Z8F0811_IR_HAUP */ +}; + +/* This array should match the CX18_HW_ defines */ +static const char * const hw_devicenames[] = { + "tuner", + "tveeprom", + "cs5345", + "cx23418_DTV", + "cx23418_AV", + "gpio_mux", + "gpio_reset_ctrl", + "ir_z8f0811_haup", +}; + +static int cx18_i2c_new_ir(struct cx18 *cx, struct i2c_adapter *adap, u32 hw, + const char *type, u8 addr) +{ + struct i2c_board_info info; + struct IR_i2c_init_data *init_data = &cx->ir_i2c_init_data; + unsigned short addr_list[2] = { addr, I2C_CLIENT_END }; + + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, type, I2C_NAME_SIZE); + + /* Our default information for ir-kbd-i2c.c to use */ + switch (hw) { + case CX18_HW_Z8F0811_IR_HAUP: + init_data->ir_codes = RC_MAP_HAUPPAUGE; + init_data->internal_get_key_func = IR_KBD_GET_KEY_HAUP_XVR; + init_data->type = RC_PROTO_BIT_RC5 | RC_PROTO_BIT_RC6_MCE | + RC_PROTO_BIT_RC6_6A_32; + init_data->name = cx->card_name; + info.platform_data = init_data; + break; + } + + return IS_ERR(i2c_new_scanned_device(adap, &info, addr_list, NULL)) ? + -1 : 0; +} + +int cx18_i2c_register(struct cx18 *cx, unsigned idx) +{ + struct v4l2_subdev *sd; + int bus = hw_bus[idx]; + struct i2c_adapter *adap = &cx->i2c_adap[bus]; + const char *type = hw_devicenames[idx]; + u32 hw = 1 << idx; + + if (hw == CX18_HW_TUNER) { + /* special tuner group handling */ + sd = v4l2_i2c_new_subdev(&cx->v4l2_dev, + adap, type, 0, cx->card_i2c->radio); + if (sd != NULL) + sd->grp_id = hw; + sd = v4l2_i2c_new_subdev(&cx->v4l2_dev, + adap, type, 0, cx->card_i2c->demod); + if (sd != NULL) + sd->grp_id = hw; + sd = v4l2_i2c_new_subdev(&cx->v4l2_dev, + adap, type, 0, cx->card_i2c->tv); + if (sd != NULL) + sd->grp_id = hw; + return sd != NULL ? 0 : -1; + } + + if (hw == CX18_HW_Z8F0811_IR_HAUP) + return cx18_i2c_new_ir(cx, adap, hw, type, hw_addrs[idx]); + + /* Is it not an I2C device or one we do not wish to register? */ + if (!hw_addrs[idx]) + return -1; + + /* It's an I2C device other than an analog tuner or IR chip */ + sd = v4l2_i2c_new_subdev(&cx->v4l2_dev, adap, type, hw_addrs[idx], + NULL); + if (sd != NULL) + sd->grp_id = hw; + return sd != NULL ? 0 : -1; +} + +/* Find the first member of the subdev group id in hw */ +struct v4l2_subdev *cx18_find_hw(struct cx18 *cx, u32 hw) +{ + struct v4l2_subdev *result = NULL; + struct v4l2_subdev *sd; + + spin_lock(&cx->v4l2_dev.lock); + v4l2_device_for_each_subdev(sd, &cx->v4l2_dev) { + if (sd->grp_id == hw) { + result = sd; + break; + } + } + spin_unlock(&cx->v4l2_dev.lock); + return result; +} + +static void cx18_setscl(void *data, int state) +{ + struct cx18 *cx = ((struct cx18_i2c_algo_callback_data *)data)->cx; + int bus_index = ((struct cx18_i2c_algo_callback_data *)data)->bus_index; + u32 addr = bus_index ? CX18_REG_I2C_2_WR : CX18_REG_I2C_1_WR; + u32 r = cx18_read_reg(cx, addr); + + if (state) + cx18_write_reg(cx, r | SETSCL_BIT, addr); + else + cx18_write_reg(cx, r & ~SETSCL_BIT, addr); +} + +static void cx18_setsda(void *data, int state) +{ + struct cx18 *cx = ((struct cx18_i2c_algo_callback_data *)data)->cx; + int bus_index = ((struct cx18_i2c_algo_callback_data *)data)->bus_index; + u32 addr = bus_index ? CX18_REG_I2C_2_WR : CX18_REG_I2C_1_WR; + u32 r = cx18_read_reg(cx, addr); + + if (state) + cx18_write_reg(cx, r | SETSDL_BIT, addr); + else + cx18_write_reg(cx, r & ~SETSDL_BIT, addr); +} + +static int cx18_getscl(void *data) +{ + struct cx18 *cx = ((struct cx18_i2c_algo_callback_data *)data)->cx; + int bus_index = ((struct cx18_i2c_algo_callback_data *)data)->bus_index; + u32 addr = bus_index ? CX18_REG_I2C_2_RD : CX18_REG_I2C_1_RD; + + return cx18_read_reg(cx, addr) & GETSCL_BIT; +} + +static int cx18_getsda(void *data) +{ + struct cx18 *cx = ((struct cx18_i2c_algo_callback_data *)data)->cx; + int bus_index = ((struct cx18_i2c_algo_callback_data *)data)->bus_index; + u32 addr = bus_index ? CX18_REG_I2C_2_RD : CX18_REG_I2C_1_RD; + + return cx18_read_reg(cx, addr) & GETSDL_BIT; +} + +/* template for i2c-bit-algo */ +static const struct i2c_adapter cx18_i2c_adap_template = { + .name = "cx18 i2c driver", + .algo = NULL, /* set by i2c-algo-bit */ + .algo_data = NULL, /* filled from template */ + .owner = THIS_MODULE, +}; + +#define CX18_SCL_PERIOD (10) /* usecs. 10 usec is period for a 100 KHz clock */ +#define CX18_ALGO_BIT_TIMEOUT (2) /* seconds */ + +static const struct i2c_algo_bit_data cx18_i2c_algo_template = { + .setsda = cx18_setsda, + .setscl = cx18_setscl, + .getsda = cx18_getsda, + .getscl = cx18_getscl, + .udelay = CX18_SCL_PERIOD/2, /* 1/2 clock period in usec*/ + .timeout = CX18_ALGO_BIT_TIMEOUT*HZ /* jiffies */ +}; + +/* init + register i2c adapter */ +int init_cx18_i2c(struct cx18 *cx) +{ + int i, err; + CX18_DEBUG_I2C("i2c init\n"); + + for (i = 0; i < 2; i++) { + /* Setup algorithm for adapter */ + cx->i2c_algo[i] = cx18_i2c_algo_template; + cx->i2c_algo_cb_data[i].cx = cx; + cx->i2c_algo_cb_data[i].bus_index = i; + cx->i2c_algo[i].data = &cx->i2c_algo_cb_data[i]; + + /* Setup adapter */ + cx->i2c_adap[i] = cx18_i2c_adap_template; + cx->i2c_adap[i].algo_data = &cx->i2c_algo[i]; + sprintf(cx->i2c_adap[i].name + strlen(cx->i2c_adap[i].name), + " #%d-%d", cx->instance, i); + i2c_set_adapdata(&cx->i2c_adap[i], &cx->v4l2_dev); + cx->i2c_adap[i].dev.parent = &cx->pci_dev->dev; + } + + if (cx18_read_reg(cx, CX18_REG_I2C_2_WR) != 0x0003c02f) { + /* Reset/Unreset I2C hardware block */ + /* Clock select 220MHz */ + cx18_write_reg_expect(cx, 0x10000000, 0xc71004, + 0x00000000, 0x10001000); + /* Clock Enable */ + cx18_write_reg_expect(cx, 0x10001000, 0xc71024, + 0x00001000, 0x10001000); + } + /* courtesy of Steven Toth */ + cx18_write_reg_expect(cx, 0x00c00000, 0xc7001c, 0x00000000, 0x00c000c0); + mdelay(10); + cx18_write_reg_expect(cx, 0x00c000c0, 0xc7001c, 0x000000c0, 0x00c000c0); + mdelay(10); + cx18_write_reg_expect(cx, 0x00c00000, 0xc7001c, 0x00000000, 0x00c000c0); + mdelay(10); + + /* Set to edge-triggered intrs. */ + cx18_write_reg(cx, 0x00c00000, 0xc730c8); + /* Clear any stale intrs */ + cx18_write_reg_expect(cx, HW2_I2C1_INT|HW2_I2C2_INT, HW2_INT_CLR_STATUS, + ~(HW2_I2C1_INT|HW2_I2C2_INT), HW2_I2C1_INT|HW2_I2C2_INT); + + /* Hw I2C1 Clock Freq ~100kHz */ + cx18_write_reg(cx, 0x00021c0f & ~4, CX18_REG_I2C_1_WR); + cx18_setscl(&cx->i2c_algo_cb_data[0], 1); + cx18_setsda(&cx->i2c_algo_cb_data[0], 1); + + /* Hw I2C2 Clock Freq ~100kHz */ + cx18_write_reg(cx, 0x00021c0f & ~4, CX18_REG_I2C_2_WR); + cx18_setscl(&cx->i2c_algo_cb_data[1], 1); + cx18_setsda(&cx->i2c_algo_cb_data[1], 1); + + cx18_call_hw(cx, CX18_HW_GPIO_RESET_CTRL, + core, reset, (u32) CX18_GPIO_RESET_I2C); + + err = i2c_bit_add_bus(&cx->i2c_adap[0]); + if (err) + goto err; + err = i2c_bit_add_bus(&cx->i2c_adap[1]); + if (err) + goto err_del_bus_0; + return 0; + + err_del_bus_0: + i2c_del_adapter(&cx->i2c_adap[0]); + err: + return err; +} + +void exit_cx18_i2c(struct cx18 *cx) +{ + int i; + CX18_DEBUG_I2C("i2c exit\n"); + cx18_write_reg(cx, cx18_read_reg(cx, CX18_REG_I2C_1_WR) | 4, + CX18_REG_I2C_1_WR); + cx18_write_reg(cx, cx18_read_reg(cx, CX18_REG_I2C_2_WR) | 4, + CX18_REG_I2C_2_WR); + + for (i = 0; i < 2; i++) { + i2c_del_adapter(&cx->i2c_adap[i]); + } +} + +/* + Hauppauge HVR1600 should have: + 32 cx24227 + 98 unknown + a0 eeprom + c2 tuner + e? zilog ir + */ diff --git a/drivers/media/pci/cx18/cx18-i2c.h b/drivers/media/pci/cx18/cx18-i2c.h new file mode 100644 index 000000000..8ae3125c7 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-i2c.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 I2C functions + * + * Derived from ivtv-i2c.h + * + * Copyright (C) 2007 Hans Verkuil + */ + +int cx18_i2c_register(struct cx18 *cx, unsigned idx); +struct v4l2_subdev *cx18_find_hw(struct cx18 *cx, u32 hw); + +/* init + register i2c adapter */ +int init_cx18_i2c(struct cx18 *cx); +void exit_cx18_i2c(struct cx18 *cx); diff --git a/drivers/media/pci/cx18/cx18-io.c b/drivers/media/pci/cx18/cx18-io.c new file mode 100644 index 000000000..1d3d006e6 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-io.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 driver PCI memory mapped IO access routines + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#include "cx18-driver.h" +#include "cx18-io.h" +#include "cx18-irq.h" + +void cx18_memset_io(struct cx18 *cx, void __iomem *addr, int val, size_t count) +{ + u8 __iomem *dst = addr; + u16 val2 = val | (val << 8); + u32 val4 = val2 | (val2 << 16); + + /* Align writes on the CX23418's addresses */ + if ((count > 0) && ((unsigned long)dst & 1)) { + cx18_writeb(cx, (u8) val, dst); + count--; + dst++; + } + if ((count > 1) && ((unsigned long)dst & 2)) { + cx18_writew(cx, val2, dst); + count -= 2; + dst += 2; + } + while (count > 3) { + cx18_writel(cx, val4, dst); + count -= 4; + dst += 4; + } + if (count > 1) { + cx18_writew(cx, val2, dst); + count -= 2; + dst += 2; + } + if (count > 0) + cx18_writeb(cx, (u8) val, dst); +} + +void cx18_sw1_irq_enable(struct cx18 *cx, u32 val) +{ + cx18_write_reg_expect(cx, val, SW1_INT_STATUS, ~val, val); + cx->sw1_irq_mask = cx18_read_reg(cx, SW1_INT_ENABLE_PCI) | val; + cx18_write_reg(cx, cx->sw1_irq_mask, SW1_INT_ENABLE_PCI); +} + +void cx18_sw1_irq_disable(struct cx18 *cx, u32 val) +{ + cx->sw1_irq_mask = cx18_read_reg(cx, SW1_INT_ENABLE_PCI) & ~val; + cx18_write_reg(cx, cx->sw1_irq_mask, SW1_INT_ENABLE_PCI); +} + +void cx18_sw2_irq_enable(struct cx18 *cx, u32 val) +{ + cx18_write_reg_expect(cx, val, SW2_INT_STATUS, ~val, val); + cx->sw2_irq_mask = cx18_read_reg(cx, SW2_INT_ENABLE_PCI) | val; + cx18_write_reg(cx, cx->sw2_irq_mask, SW2_INT_ENABLE_PCI); +} + +void cx18_sw2_irq_disable(struct cx18 *cx, u32 val) +{ + cx->sw2_irq_mask = cx18_read_reg(cx, SW2_INT_ENABLE_PCI) & ~val; + cx18_write_reg(cx, cx->sw2_irq_mask, SW2_INT_ENABLE_PCI); +} + +void cx18_sw2_irq_disable_cpu(struct cx18 *cx, u32 val) +{ + u32 r; + r = cx18_read_reg(cx, SW2_INT_ENABLE_CPU); + cx18_write_reg(cx, r & ~val, SW2_INT_ENABLE_CPU); +} + +void cx18_setup_page(struct cx18 *cx, u32 addr) +{ + u32 val; + val = cx18_read_reg(cx, 0xD000F8); + val = (val & ~0x1f00) | ((addr >> 17) & 0x1f00); + cx18_write_reg(cx, val, 0xD000F8); +} diff --git a/drivers/media/pci/cx18/cx18-io.h b/drivers/media/pci/cx18/cx18-io.h new file mode 100644 index 000000000..1f0bfad7d --- /dev/null +++ b/drivers/media/pci/cx18/cx18-io.h @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 driver PCI memory mapped IO access routines + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#ifndef CX18_IO_H +#define CX18_IO_H + +#include "cx18-driver.h" + +/* + * Readback and retry of MMIO access for reliability: + * The concept was suggested by Steve Toth . + * The implementation is the fault of Andy Walls . + * + * *write* functions are implied to retry the mmio unless suffixed with _noretry + * *read* functions never retry the mmio (it never helps to do so) + */ + +/* Non byteswapping memory mapped IO */ +static inline u32 cx18_raw_readl(struct cx18 *cx, const void __iomem *addr) +{ + return __raw_readl(addr); +} + +static inline +void cx18_raw_writel_noretry(struct cx18 *cx, u32 val, void __iomem *addr) +{ + __raw_writel(val, addr); +} + +static inline void cx18_raw_writel(struct cx18 *cx, u32 val, void __iomem *addr) +{ + int i; + for (i = 0; i < CX18_MAX_MMIO_WR_RETRIES; i++) { + cx18_raw_writel_noretry(cx, val, addr); + if (val == cx18_raw_readl(cx, addr)) + break; + } +} + +/* Normal memory mapped IO */ +static inline u32 cx18_readl(struct cx18 *cx, const void __iomem *addr) +{ + return readl(addr); +} + +static inline +void cx18_writel_noretry(struct cx18 *cx, u32 val, void __iomem *addr) +{ + writel(val, addr); +} + +static inline void cx18_writel(struct cx18 *cx, u32 val, void __iomem *addr) +{ + int i; + for (i = 0; i < CX18_MAX_MMIO_WR_RETRIES; i++) { + cx18_writel_noretry(cx, val, addr); + if (val == cx18_readl(cx, addr)) + break; + } +} + +static inline +void cx18_writel_expect(struct cx18 *cx, u32 val, void __iomem *addr, + u32 eval, u32 mask) +{ + int i; + u32 r; + eval &= mask; + for (i = 0; i < CX18_MAX_MMIO_WR_RETRIES; i++) { + cx18_writel_noretry(cx, val, addr); + r = cx18_readl(cx, addr); + if (r == 0xffffffff && eval != 0xffffffff) + continue; + if (eval == (r & mask)) + break; + } +} + +static inline u16 cx18_readw(struct cx18 *cx, const void __iomem *addr) +{ + return readw(addr); +} + +static inline +void cx18_writew_noretry(struct cx18 *cx, u16 val, void __iomem *addr) +{ + writew(val, addr); +} + +static inline void cx18_writew(struct cx18 *cx, u16 val, void __iomem *addr) +{ + int i; + for (i = 0; i < CX18_MAX_MMIO_WR_RETRIES; i++) { + cx18_writew_noretry(cx, val, addr); + if (val == cx18_readw(cx, addr)) + break; + } +} + +static inline u8 cx18_readb(struct cx18 *cx, const void __iomem *addr) +{ + return readb(addr); +} + +static inline +void cx18_writeb_noretry(struct cx18 *cx, u8 val, void __iomem *addr) +{ + writeb(val, addr); +} + +static inline void cx18_writeb(struct cx18 *cx, u8 val, void __iomem *addr) +{ + int i; + for (i = 0; i < CX18_MAX_MMIO_WR_RETRIES; i++) { + cx18_writeb_noretry(cx, val, addr); + if (val == cx18_readb(cx, addr)) + break; + } +} + +static inline +void cx18_memcpy_fromio(struct cx18 *cx, void *to, + const void __iomem *from, unsigned int len) +{ + memcpy_fromio(to, from, len); +} + +void cx18_memset_io(struct cx18 *cx, void __iomem *addr, int val, size_t count); + + +/* Access "register" region of CX23418 memory mapped I/O */ +static inline void cx18_write_reg_noretry(struct cx18 *cx, u32 val, u32 reg) +{ + cx18_writel_noretry(cx, val, cx->reg_mem + reg); +} + +static inline void cx18_write_reg(struct cx18 *cx, u32 val, u32 reg) +{ + cx18_writel(cx, val, cx->reg_mem + reg); +} + +static inline void cx18_write_reg_expect(struct cx18 *cx, u32 val, u32 reg, + u32 eval, u32 mask) +{ + cx18_writel_expect(cx, val, cx->reg_mem + reg, eval, mask); +} + +static inline u32 cx18_read_reg(struct cx18 *cx, u32 reg) +{ + return cx18_readl(cx, cx->reg_mem + reg); +} + + +/* Access "encoder memory" region of CX23418 memory mapped I/O */ +static inline void cx18_write_enc(struct cx18 *cx, u32 val, u32 addr) +{ + cx18_writel(cx, val, cx->enc_mem + addr); +} + +static inline u32 cx18_read_enc(struct cx18 *cx, u32 addr) +{ + return cx18_readl(cx, cx->enc_mem + addr); +} + +void cx18_sw1_irq_enable(struct cx18 *cx, u32 val); +void cx18_sw1_irq_disable(struct cx18 *cx, u32 val); +void cx18_sw2_irq_enable(struct cx18 *cx, u32 val); +void cx18_sw2_irq_disable(struct cx18 *cx, u32 val); +void cx18_sw2_irq_disable_cpu(struct cx18 *cx, u32 val); +void cx18_setup_page(struct cx18 *cx, u32 addr); + +#endif /* CX18_IO_H */ diff --git a/drivers/media/pci/cx18/cx18-ioctl.c b/drivers/media/pci/cx18/cx18-ioctl.c new file mode 100644 index 000000000..0d676a57e --- /dev/null +++ b/drivers/media/pci/cx18/cx18-ioctl.c @@ -0,0 +1,1057 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 ioctl system call + * + * Derived from ivtv-ioctl.c + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#include "cx18-driver.h" +#include "cx18-io.h" +#include "cx18-version.h" +#include "cx18-mailbox.h" +#include "cx18-i2c.h" +#include "cx18-queue.h" +#include "cx18-fileops.h" +#include "cx18-vbi.h" +#include "cx18-audio.h" +#include "cx18-video.h" +#include "cx18-streams.h" +#include "cx18-ioctl.h" +#include "cx18-gpio.h" +#include "cx18-controls.h" +#include "cx18-cards.h" +#include "cx18-av-core.h" +#include +#include + +static const struct v4l2_fmtdesc cx18_formats_yuv[] = { + { + .index = 0, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .pixelformat = V4L2_PIX_FMT_NV12_16L16, + }, + { + .index = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .pixelformat = V4L2_PIX_FMT_UYVY, + }, +}; + +static const struct v4l2_fmtdesc cx18_formats_mpeg[] = { + { + .index = 0, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = V4L2_FMT_FLAG_COMPRESSED, + .pixelformat = V4L2_PIX_FMT_MPEG, + }, +}; + +static int cx18_g_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct cx18_open_id *id = file2id(file); + struct cx18 *cx = id->cx; + struct cx18_stream *s = &cx->streams[id->type]; + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + + pixfmt->width = cx->cxhdl.width; + pixfmt->height = cx->cxhdl.height; + pixfmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + pixfmt->field = V4L2_FIELD_INTERLACED; + if (id->type == CX18_ENC_STREAM_TYPE_YUV) { + pixfmt->pixelformat = s->pixelformat; + pixfmt->sizeimage = s->vb_bytes_per_frame; + pixfmt->bytesperline = s->vb_bytes_per_line; + } else { + pixfmt->pixelformat = V4L2_PIX_FMT_MPEG; + pixfmt->sizeimage = 128 * 1024; + pixfmt->bytesperline = 0; + } + return 0; +} + +static int cx18_try_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct cx18_open_id *id = file2id(file); + struct cx18 *cx = id->cx; + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + int w = pixfmt->width; + int h = pixfmt->height; + + w = min(w, 720); + w = max(w, 720 / 16); + + h = min(h, cx->is_50hz ? 576 : 480); + h = max(h, (cx->is_50hz ? 576 : 480) / 8); + + if (id->type == CX18_ENC_STREAM_TYPE_YUV) { + if (pixfmt->pixelformat != V4L2_PIX_FMT_NV12_16L16 && + pixfmt->pixelformat != V4L2_PIX_FMT_UYVY) + pixfmt->pixelformat = V4L2_PIX_FMT_UYVY; + /* YUV height must be a multiple of 32 */ + h = round_up(h, 32); + /* + * HM12 YUV size is (Y=(h*720) + UV=(h*(720/2))) + * UYUV YUV size is (Y=(h*720) + UV=(h*(720))) + */ + if (pixfmt->pixelformat == V4L2_PIX_FMT_NV12_16L16) { + pixfmt->sizeimage = h * 720 * 3 / 2; + pixfmt->bytesperline = 720; /* First plane */ + } else { + pixfmt->sizeimage = h * 720 * 2; + pixfmt->bytesperline = 1440; /* Packed */ + } + } else { + pixfmt->pixelformat = V4L2_PIX_FMT_MPEG; + pixfmt->sizeimage = 128 * 1024; + pixfmt->bytesperline = 0; + } + + pixfmt->width = w; + pixfmt->height = h; + pixfmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + pixfmt->field = V4L2_FIELD_INTERLACED; + return 0; +} + +static int cx18_s_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct cx18_open_id *id = file2id(file); + struct cx18 *cx = id->cx; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct cx18_stream *s = &cx->streams[id->type]; + int ret; + int w, h; + + ret = cx18_try_fmt_vid_cap(file, fh, fmt); + if (ret) + return ret; + w = fmt->fmt.pix.width; + h = fmt->fmt.pix.height; + + if (cx->cxhdl.width == w && cx->cxhdl.height == h && + s->pixelformat == fmt->fmt.pix.pixelformat) + return 0; + + if (atomic_read(&cx->ana_capturing) > 0) + return -EBUSY; + + s->pixelformat = fmt->fmt.pix.pixelformat; + s->vb_bytes_per_frame = fmt->fmt.pix.sizeimage; + s->vb_bytes_per_line = fmt->fmt.pix.bytesperline; + + format.format.width = cx->cxhdl.width = w; + format.format.height = cx->cxhdl.height = h; + format.format.code = MEDIA_BUS_FMT_FIXED; + v4l2_subdev_call(cx->sd_av, pad, set_fmt, NULL, &format); + return cx18_g_fmt_vid_cap(file, fh, fmt); +} + +u16 cx18_service2vbi(int type) +{ + switch (type) { + case V4L2_SLICED_TELETEXT_B: + return CX18_SLICED_TYPE_TELETEXT_B; + case V4L2_SLICED_CAPTION_525: + return CX18_SLICED_TYPE_CAPTION_525; + case V4L2_SLICED_WSS_625: + return CX18_SLICED_TYPE_WSS_625; + case V4L2_SLICED_VPS: + return CX18_SLICED_TYPE_VPS; + default: + return 0; + } +} + +/* Check if VBI services are allowed on the (field, line) for the video std */ +static int valid_service_line(int field, int line, int is_pal) +{ + return (is_pal && line >= 6 && + ((field == 0 && line <= 23) || (field == 1 && line <= 22))) || + (!is_pal && line >= 10 && line < 22); +} + +/* + * For a (field, line, std) and inbound potential set of services for that line, + * return the first valid service of those passed in the incoming set for that + * line in priority order: + * CC, VPS, or WSS over TELETEXT for well known lines + * TELETEXT, before VPS, before CC, before WSS, for other lines + */ +static u16 select_service_from_set(int field, int line, u16 set, int is_pal) +{ + u16 valid_set = (is_pal ? V4L2_SLICED_VBI_625 : V4L2_SLICED_VBI_525); + int i; + + set = set & valid_set; + if (set == 0 || !valid_service_line(field, line, is_pal)) + return 0; + if (!is_pal) { + if (line == 21 && (set & V4L2_SLICED_CAPTION_525)) + return V4L2_SLICED_CAPTION_525; + } else { + if (line == 16 && field == 0 && (set & V4L2_SLICED_VPS)) + return V4L2_SLICED_VPS; + if (line == 23 && field == 0 && (set & V4L2_SLICED_WSS_625)) + return V4L2_SLICED_WSS_625; + if (line == 23) + return 0; + } + for (i = 0; i < 32; i++) { + if (BIT(i) & set) + return 1 << i; + } + return 0; +} + +/* + * Expand the service_set of *fmt into valid service_lines for the std, + * and clear the passed in fmt->service_set + */ +void cx18_expand_service_set(struct v4l2_sliced_vbi_format *fmt, int is_pal) +{ + u16 set = fmt->service_set; + int f, l; + + fmt->service_set = 0; + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) + fmt->service_lines[f][l] = select_service_from_set(f, l, set, is_pal); + } +} + +/* + * Sanitize the service_lines in *fmt per the video std, and return 1 + * if any service_line is left as valid after santization + */ +static int check_service_set(struct v4l2_sliced_vbi_format *fmt, int is_pal) +{ + int f, l; + u16 set = 0; + + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) { + fmt->service_lines[f][l] = select_service_from_set(f, l, fmt->service_lines[f][l], is_pal); + set |= fmt->service_lines[f][l]; + } + } + return set != 0; +} + +/* Compute the service_set from the assumed valid service_lines of *fmt */ +u16 cx18_get_service_set(struct v4l2_sliced_vbi_format *fmt) +{ + int f, l; + u16 set = 0; + + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) + set |= fmt->service_lines[f][l]; + } + return set; +} + +static int cx18_g_fmt_vbi_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct cx18 *cx = file2id(file)->cx; + struct v4l2_vbi_format *vbifmt = &fmt->fmt.vbi; + + vbifmt->sampling_rate = 27000000; + vbifmt->offset = 248; /* FIXME - slightly wrong for both 50 & 60 Hz */ + vbifmt->samples_per_line = VBI_ACTIVE_SAMPLES - 4; + vbifmt->sample_format = V4L2_PIX_FMT_GREY; + vbifmt->start[0] = cx->vbi.start[0]; + vbifmt->start[1] = cx->vbi.start[1]; + vbifmt->count[0] = vbifmt->count[1] = cx->vbi.count; + vbifmt->flags = 0; + vbifmt->reserved[0] = 0; + vbifmt->reserved[1] = 0; + return 0; +} + +static int cx18_g_fmt_sliced_vbi_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct cx18 *cx = file2id(file)->cx; + struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced; + + /* sane, V4L2 spec compliant, defaults */ + vbifmt->reserved[0] = 0; + vbifmt->reserved[1] = 0; + vbifmt->io_size = sizeof(struct v4l2_sliced_vbi_data) * 36; + memset(vbifmt->service_lines, 0, sizeof(vbifmt->service_lines)); + vbifmt->service_set = 0; + + /* + * Fetch the configured service_lines and total service_set from the + * digitizer/slicer. Note, cx18_av_vbi() wipes the passed in + * fmt->fmt.sliced under valid calling conditions + */ + if (v4l2_subdev_call(cx->sd_av, vbi, g_sliced_fmt, &fmt->fmt.sliced)) + return -EINVAL; + + vbifmt->service_set = cx18_get_service_set(vbifmt); + return 0; +} + +static int cx18_try_fmt_vbi_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + return cx18_g_fmt_vbi_cap(file, fh, fmt); +} + +static int cx18_try_fmt_sliced_vbi_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct cx18 *cx = file2id(file)->cx; + struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced; + + vbifmt->io_size = sizeof(struct v4l2_sliced_vbi_data) * 36; + vbifmt->reserved[0] = 0; + vbifmt->reserved[1] = 0; + + /* If given a service set, expand it validly & clear passed in set */ + if (vbifmt->service_set) + cx18_expand_service_set(vbifmt, cx->is_50hz); + /* Sanitize the service_lines, and compute the new set if any valid */ + if (check_service_set(vbifmt, cx->is_50hz)) + vbifmt->service_set = cx18_get_service_set(vbifmt); + return 0; +} + +static int cx18_s_fmt_vbi_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct cx18_open_id *id = file2id(file); + struct cx18 *cx = id->cx; + int ret; + + /* + * Changing the Encoder's Raw VBI parameters won't have any effect + * if any analog capture is ongoing + */ + if (!cx18_raw_vbi(cx) && atomic_read(&cx->ana_capturing) > 0) + return -EBUSY; + + /* + * Set the digitizer registers for raw active VBI. + * Note cx18_av_vbi_wipes out a lot of the passed in fmt under valid + * calling conditions + */ + ret = v4l2_subdev_call(cx->sd_av, vbi, s_raw_fmt, &fmt->fmt.vbi); + if (ret) + return ret; + + /* Store our new v4l2 (non-)sliced VBI state */ + cx->vbi.sliced_in->service_set = 0; + cx->vbi.in.type = V4L2_BUF_TYPE_VBI_CAPTURE; + + return cx18_g_fmt_vbi_cap(file, fh, fmt); +} + +static int cx18_s_fmt_sliced_vbi_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct cx18_open_id *id = file2id(file); + struct cx18 *cx = id->cx; + int ret; + struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced; + + cx18_try_fmt_sliced_vbi_cap(file, fh, fmt); + + /* + * Changing the Encoder's Raw VBI parameters won't have any effect + * if any analog capture is ongoing + */ + if (cx18_raw_vbi(cx) && atomic_read(&cx->ana_capturing) > 0) + return -EBUSY; + + /* + * Set the service_lines requested in the digitizer/slicer registers. + * Note, cx18_av_vbi() wipes some "impossible" service lines in the + * passed in fmt->fmt.sliced under valid calling conditions + */ + ret = v4l2_subdev_call(cx->sd_av, vbi, s_sliced_fmt, &fmt->fmt.sliced); + if (ret) + return ret; + /* Store our current v4l2 sliced VBI settings */ + cx->vbi.in.type = V4L2_BUF_TYPE_SLICED_VBI_CAPTURE; + memcpy(cx->vbi.sliced_in, vbifmt, sizeof(*cx->vbi.sliced_in)); + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int cx18_g_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + struct cx18 *cx = file2id(file)->cx; + + if (reg->reg & 0x3) + return -EINVAL; + if (reg->reg >= CX18_MEM_OFFSET + CX18_MEM_SIZE) + return -EINVAL; + reg->size = 4; + reg->val = cx18_read_enc(cx, reg->reg); + return 0; +} + +static int cx18_s_register(struct file *file, void *fh, + const struct v4l2_dbg_register *reg) +{ + struct cx18 *cx = file2id(file)->cx; + + if (reg->reg & 0x3) + return -EINVAL; + if (reg->reg >= CX18_MEM_OFFSET + CX18_MEM_SIZE) + return -EINVAL; + cx18_write_enc(cx, reg->val, reg->reg); + return 0; +} +#endif + +static int cx18_querycap(struct file *file, void *fh, + struct v4l2_capability *vcap) +{ + struct cx18_open_id *id = file2id(file); + struct cx18 *cx = id->cx; + + strscpy(vcap->driver, CX18_DRIVER_NAME, sizeof(vcap->driver)); + strscpy(vcap->card, cx->card_name, sizeof(vcap->card)); + vcap->capabilities = cx->v4l2_cap | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int cx18_enumaudio(struct file *file, void *fh, struct v4l2_audio *vin) +{ + struct cx18 *cx = file2id(file)->cx; + + return cx18_get_audio_input(cx, vin->index, vin); +} + +static int cx18_g_audio(struct file *file, void *fh, struct v4l2_audio *vin) +{ + struct cx18 *cx = file2id(file)->cx; + + vin->index = cx->audio_input; + return cx18_get_audio_input(cx, vin->index, vin); +} + +static int cx18_s_audio(struct file *file, void *fh, const struct v4l2_audio *vout) +{ + struct cx18 *cx = file2id(file)->cx; + + if (vout->index >= cx->nof_audio_inputs) + return -EINVAL; + cx->audio_input = vout->index; + cx18_audio_set_io(cx); + return 0; +} + +static int cx18_enum_input(struct file *file, void *fh, struct v4l2_input *vin) +{ + struct cx18 *cx = file2id(file)->cx; + + /* set it to defaults from our table */ + return cx18_get_input(cx, vin->index, vin); +} + +static int cx18_g_pixelaspect(struct file *file, void *fh, + int type, struct v4l2_fract *f) +{ + struct cx18 *cx = file2id(file)->cx; + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + f->numerator = cx->is_50hz ? 54 : 11; + f->denominator = cx->is_50hz ? 59 : 10; + return 0; +} + +static int cx18_g_selection(struct file *file, void *fh, + struct v4l2_selection *sel) +{ + struct cx18 *cx = file2id(file)->cx; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r.top = sel->r.left = 0; + sel->r.width = 720; + sel->r.height = cx->is_50hz ? 576 : 480; + break; + default: + return -EINVAL; + } + return 0; +} + +static int cx18_enum_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_fmtdesc *fmt) +{ + struct cx18_open_id *id = file2id(file); + + if (id->type == CX18_ENC_STREAM_TYPE_YUV) { + if (fmt->index >= ARRAY_SIZE(cx18_formats_yuv)) + return -EINVAL; + *fmt = cx18_formats_yuv[fmt->index]; + return 0; + } + if (fmt->index) + return -EINVAL; + *fmt = cx18_formats_mpeg[0]; + return 0; +} + +static int cx18_g_input(struct file *file, void *fh, unsigned int *i) +{ + struct cx18 *cx = file2id(file)->cx; + + *i = cx->active_input; + return 0; +} + +int cx18_do_s_input(struct cx18 *cx, unsigned int inp) +{ + v4l2_std_id std = V4L2_STD_ALL; + const struct cx18_card_video_input *card_input = + cx->card->video_inputs + inp; + + if (inp >= cx->nof_inputs) + return -EINVAL; + + if (inp == cx->active_input) { + CX18_DEBUG_INFO("Input unchanged\n"); + return 0; + } + + CX18_DEBUG_INFO("Changing input from %d to %d\n", + cx->active_input, inp); + + cx->active_input = inp; + /* Set the audio input to whatever is appropriate for the input type. */ + cx->audio_input = cx->card->video_inputs[inp].audio_index; + if (card_input->video_type == V4L2_INPUT_TYPE_TUNER) + std = cx->tuner_std; + cx->streams[CX18_ENC_STREAM_TYPE_MPG].video_dev.tvnorms = std; + cx->streams[CX18_ENC_STREAM_TYPE_YUV].video_dev.tvnorms = std; + cx->streams[CX18_ENC_STREAM_TYPE_VBI].video_dev.tvnorms = std; + + /* prevent others from messing with the streams until + we're finished changing inputs. */ + cx18_mute(cx); + cx18_video_set_io(cx); + cx18_audio_set_io(cx); + cx18_unmute(cx); + return 0; +} + +static int cx18_s_input(struct file *file, void *fh, unsigned int inp) +{ + return cx18_do_s_input(file2id(file)->cx, inp); +} + +static int cx18_g_frequency(struct file *file, void *fh, + struct v4l2_frequency *vf) +{ + struct cx18 *cx = file2id(file)->cx; + + if (vf->tuner != 0) + return -EINVAL; + + cx18_call_all(cx, tuner, g_frequency, vf); + return 0; +} + +int cx18_do_s_frequency(struct cx18 *cx, const struct v4l2_frequency *vf) +{ + if (vf->tuner != 0) + return -EINVAL; + + cx18_mute(cx); + CX18_DEBUG_INFO("v4l2 ioctl: set frequency %d\n", vf->frequency); + cx18_call_all(cx, tuner, s_frequency, vf); + cx18_unmute(cx); + return 0; +} + +static int cx18_s_frequency(struct file *file, void *fh, + const struct v4l2_frequency *vf) +{ + return cx18_do_s_frequency(file2id(file)->cx, vf); +} + +static int cx18_g_std(struct file *file, void *fh, v4l2_std_id *std) +{ + struct cx18 *cx = file2id(file)->cx; + + *std = cx->std; + return 0; +} + +int cx18_do_s_std(struct cx18 *cx, v4l2_std_id std) +{ + if ((std & V4L2_STD_ALL) == 0) + return -EINVAL; + + if (std == cx->std) + return 0; + + if (test_bit(CX18_F_I_RADIO_USER, &cx->i_flags) || + atomic_read(&cx->ana_capturing) > 0) { + /* Switching standard would turn off the radio or mess + with already running streams, prevent that by + returning EBUSY. */ + return -EBUSY; + } + + cx->std = std; + cx->is_60hz = (std & V4L2_STD_525_60) ? 1 : 0; + cx->is_50hz = !cx->is_60hz; + cx2341x_handler_set_50hz(&cx->cxhdl, cx->is_50hz); + cx->cxhdl.width = 720; + cx->cxhdl.height = cx->is_50hz ? 576 : 480; + /* + * HM12 YUV size is (Y=(h*720) + UV=(h*(720/2))) + * UYUV YUV size is (Y=(h*720) + UV=(h*(720))) + */ + if (cx->streams[CX18_ENC_STREAM_TYPE_YUV].pixelformat == V4L2_PIX_FMT_NV12_16L16) { + cx->streams[CX18_ENC_STREAM_TYPE_YUV].vb_bytes_per_frame = + cx->cxhdl.height * 720 * 3 / 2; + cx->streams[CX18_ENC_STREAM_TYPE_YUV].vb_bytes_per_line = 720; + } else { + cx->streams[CX18_ENC_STREAM_TYPE_YUV].vb_bytes_per_frame = + cx->cxhdl.height * 720 * 2; + cx->streams[CX18_ENC_STREAM_TYPE_YUV].vb_bytes_per_line = 1440; + } + cx->vbi.count = cx->is_50hz ? 18 : 12; + cx->vbi.start[0] = cx->is_50hz ? 6 : 10; + cx->vbi.start[1] = cx->is_50hz ? 318 : 273; + CX18_DEBUG_INFO("Switching standard to %llx.\n", + (unsigned long long) cx->std); + + /* Tuner */ + cx18_call_all(cx, video, s_std, cx->std); + return 0; +} + +static int cx18_s_std(struct file *file, void *fh, v4l2_std_id std) +{ + return cx18_do_s_std(file2id(file)->cx, std); +} + +static int cx18_s_tuner(struct file *file, void *fh, const struct v4l2_tuner *vt) +{ + struct cx18_open_id *id = file2id(file); + struct cx18 *cx = id->cx; + + if (vt->index != 0) + return -EINVAL; + + cx18_call_all(cx, tuner, s_tuner, vt); + return 0; +} + +static int cx18_g_tuner(struct file *file, void *fh, struct v4l2_tuner *vt) +{ + struct cx18 *cx = file2id(file)->cx; + + if (vt->index != 0) + return -EINVAL; + + cx18_call_all(cx, tuner, g_tuner, vt); + + if (vt->type == V4L2_TUNER_RADIO) + strscpy(vt->name, "cx18 Radio Tuner", sizeof(vt->name)); + else + strscpy(vt->name, "cx18 TV Tuner", sizeof(vt->name)); + return 0; +} + +static int cx18_g_sliced_vbi_cap(struct file *file, void *fh, + struct v4l2_sliced_vbi_cap *cap) +{ + struct cx18 *cx = file2id(file)->cx; + int set = cx->is_50hz ? V4L2_SLICED_VBI_625 : V4L2_SLICED_VBI_525; + int f, l; + + if (cap->type != V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) + return -EINVAL; + + cap->service_set = 0; + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) { + if (valid_service_line(f, l, cx->is_50hz)) { + /* + * We can find all v4l2 supported vbi services + * for the standard, on a valid line for the std + */ + cap->service_lines[f][l] = set; + cap->service_set |= set; + } else + cap->service_lines[f][l] = 0; + } + } + for (f = 0; f < 3; f++) + cap->reserved[f] = 0; + return 0; +} + +static int _cx18_process_idx_data(struct cx18_buffer *buf, + struct v4l2_enc_idx *idx) +{ + int consumed, remaining; + struct v4l2_enc_idx_entry *e_idx; + struct cx18_enc_idx_entry *e_buf; + + /* Frame type lookup: 1=I, 2=P, 4=B */ + static const int mapping[8] = { + -1, V4L2_ENC_IDX_FRAME_I, V4L2_ENC_IDX_FRAME_P, + -1, V4L2_ENC_IDX_FRAME_B, -1, -1, -1 + }; + + /* + * Assumption here is that a buf holds an integral number of + * struct cx18_enc_idx_entry objects and is properly aligned. + * This is enforced by the module options on IDX buffer sizes. + */ + remaining = buf->bytesused - buf->readpos; + consumed = 0; + e_idx = &idx->entry[idx->entries]; + e_buf = (struct cx18_enc_idx_entry *) &buf->buf[buf->readpos]; + + while (remaining >= sizeof(struct cx18_enc_idx_entry) && + idx->entries < V4L2_ENC_IDX_ENTRIES) { + + e_idx->offset = (((u64) le32_to_cpu(e_buf->offset_high)) << 32) + | le32_to_cpu(e_buf->offset_low); + + e_idx->pts = (((u64) (le32_to_cpu(e_buf->pts_high) & 1)) << 32) + | le32_to_cpu(e_buf->pts_low); + + e_idx->length = le32_to_cpu(e_buf->length); + + e_idx->flags = mapping[le32_to_cpu(e_buf->flags) & 0x7]; + + e_idx->reserved[0] = 0; + e_idx->reserved[1] = 0; + + idx->entries++; + e_idx = &idx->entry[idx->entries]; + e_buf++; + + remaining -= sizeof(struct cx18_enc_idx_entry); + consumed += sizeof(struct cx18_enc_idx_entry); + } + + /* Swallow any partial entries at the end, if there are any */ + if (remaining > 0 && remaining < sizeof(struct cx18_enc_idx_entry)) + consumed += remaining; + + buf->readpos += consumed; + return consumed; +} + +static int cx18_process_idx_data(struct cx18_stream *s, struct cx18_mdl *mdl, + struct v4l2_enc_idx *idx) +{ + if (s->type != CX18_ENC_STREAM_TYPE_IDX) + return -EINVAL; + + if (mdl->curr_buf == NULL) + mdl->curr_buf = list_first_entry(&mdl->buf_list, + struct cx18_buffer, list); + + if (list_entry_is_head(mdl->curr_buf, &mdl->buf_list, list)) { + /* + * For some reason we've exhausted the buffers, but the MDL + * object still said some data was unread. + * Fix that and bail out. + */ + mdl->readpos = mdl->bytesused; + return 0; + } + + list_for_each_entry_from(mdl->curr_buf, &mdl->buf_list, list) { + + /* Skip any empty buffers in the MDL */ + if (mdl->curr_buf->readpos >= mdl->curr_buf->bytesused) + continue; + + mdl->readpos += _cx18_process_idx_data(mdl->curr_buf, idx); + + /* exit when MDL drained or request satisfied */ + if (idx->entries >= V4L2_ENC_IDX_ENTRIES || + mdl->curr_buf->readpos < mdl->curr_buf->bytesused || + mdl->readpos >= mdl->bytesused) + break; + } + return 0; +} + +static int cx18_g_enc_index(struct file *file, void *fh, + struct v4l2_enc_idx *idx) +{ + struct cx18 *cx = file2id(file)->cx; + struct cx18_stream *s = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; + s32 tmp; + struct cx18_mdl *mdl; + + if (!cx18_stream_enabled(s)) /* Module options inhibited IDX stream */ + return -EINVAL; + + /* Compute the best case number of entries we can buffer */ + tmp = s->buffers - + s->bufs_per_mdl * CX18_ENC_STREAM_TYPE_IDX_FW_MDL_MIN; + if (tmp <= 0) + tmp = 1; + tmp = tmp * s->buf_size / sizeof(struct cx18_enc_idx_entry); + + /* Fill out the header of the return structure */ + idx->entries = 0; + idx->entries_cap = tmp; + memset(idx->reserved, 0, sizeof(idx->reserved)); + + /* Pull IDX MDLs and buffers from q_full and populate the entries */ + do { + mdl = cx18_dequeue(s, &s->q_full); + if (mdl == NULL) /* No more IDX data right now */ + break; + + /* Extract the Index entry data from the MDL and buffers */ + cx18_process_idx_data(s, mdl, idx); + if (mdl->readpos < mdl->bytesused) { + /* We finished with data remaining, push the MDL back */ + cx18_push(s, mdl, &s->q_full); + break; + } + + /* We drained this MDL, schedule it to go to the firmware */ + cx18_enqueue(s, mdl, &s->q_free); + + } while (idx->entries < V4L2_ENC_IDX_ENTRIES); + + /* Tell the work handler to send free IDX MDLs to the firmware */ + cx18_stream_load_fw_queue(s); + return 0; +} + +static int cx18_encoder_cmd(struct file *file, void *fh, + struct v4l2_encoder_cmd *enc) +{ + struct cx18_open_id *id = file2id(file); + struct cx18 *cx = id->cx; + u32 h; + + switch (enc->cmd) { + case V4L2_ENC_CMD_START: + CX18_DEBUG_IOCTL("V4L2_ENC_CMD_START\n"); + enc->flags = 0; + return cx18_start_capture(id); + + case V4L2_ENC_CMD_STOP: + CX18_DEBUG_IOCTL("V4L2_ENC_CMD_STOP\n"); + enc->flags &= V4L2_ENC_CMD_STOP_AT_GOP_END; + cx18_stop_capture(&cx->streams[id->type], + enc->flags & V4L2_ENC_CMD_STOP_AT_GOP_END); + break; + + case V4L2_ENC_CMD_PAUSE: + CX18_DEBUG_IOCTL("V4L2_ENC_CMD_PAUSE\n"); + enc->flags = 0; + if (!atomic_read(&cx->ana_capturing)) + return -EPERM; + if (test_and_set_bit(CX18_F_I_ENC_PAUSED, &cx->i_flags)) + return 0; + h = cx18_find_handle(cx); + if (h == CX18_INVALID_TASK_HANDLE) { + CX18_ERR("Can't find valid task handle for V4L2_ENC_CMD_PAUSE\n"); + return -EBADFD; + } + cx18_mute(cx); + cx18_vapi(cx, CX18_CPU_CAPTURE_PAUSE, 1, h); + break; + + case V4L2_ENC_CMD_RESUME: + CX18_DEBUG_IOCTL("V4L2_ENC_CMD_RESUME\n"); + enc->flags = 0; + if (!atomic_read(&cx->ana_capturing)) + return -EPERM; + if (!test_and_clear_bit(CX18_F_I_ENC_PAUSED, &cx->i_flags)) + return 0; + h = cx18_find_handle(cx); + if (h == CX18_INVALID_TASK_HANDLE) { + CX18_ERR("Can't find valid task handle for V4L2_ENC_CMD_RESUME\n"); + return -EBADFD; + } + cx18_vapi(cx, CX18_CPU_CAPTURE_RESUME, 1, h); + cx18_unmute(cx); + break; + + default: + CX18_DEBUG_IOCTL("Unknown cmd %d\n", enc->cmd); + return -EINVAL; + } + return 0; +} + +static int cx18_try_encoder_cmd(struct file *file, void *fh, + struct v4l2_encoder_cmd *enc) +{ + struct cx18 *cx = file2id(file)->cx; + + switch (enc->cmd) { + case V4L2_ENC_CMD_START: + CX18_DEBUG_IOCTL("V4L2_ENC_CMD_START\n"); + enc->flags = 0; + break; + + case V4L2_ENC_CMD_STOP: + CX18_DEBUG_IOCTL("V4L2_ENC_CMD_STOP\n"); + enc->flags &= V4L2_ENC_CMD_STOP_AT_GOP_END; + break; + + case V4L2_ENC_CMD_PAUSE: + CX18_DEBUG_IOCTL("V4L2_ENC_CMD_PAUSE\n"); + enc->flags = 0; + break; + + case V4L2_ENC_CMD_RESUME: + CX18_DEBUG_IOCTL("V4L2_ENC_CMD_RESUME\n"); + enc->flags = 0; + break; + + default: + CX18_DEBUG_IOCTL("Unknown cmd %d\n", enc->cmd); + return -EINVAL; + } + return 0; +} + +static int cx18_log_status(struct file *file, void *fh) +{ + struct cx18 *cx = file2id(file)->cx; + struct v4l2_input vidin; + struct v4l2_audio audin; + int i; + + CX18_INFO("Version: %s Card: %s\n", CX18_VERSION, cx->card_name); + if (cx->hw_flags & CX18_HW_TVEEPROM) { + struct tveeprom tv; + + cx18_read_eeprom(cx, &tv); + } + cx18_call_all(cx, core, log_status); + cx18_get_input(cx, cx->active_input, &vidin); + cx18_get_audio_input(cx, cx->audio_input, &audin); + CX18_INFO("Video Input: %s\n", vidin.name); + CX18_INFO("Audio Input: %s\n", audin.name); + mutex_lock(&cx->gpio_lock); + CX18_INFO("GPIO: direction 0x%08x, value 0x%08x\n", + cx->gpio_dir, cx->gpio_val); + mutex_unlock(&cx->gpio_lock); + CX18_INFO("Tuner: %s\n", + test_bit(CX18_F_I_RADIO_USER, &cx->i_flags) ? "Radio" : "TV"); + v4l2_ctrl_handler_log_status(&cx->cxhdl.hdl, cx->v4l2_dev.name); + CX18_INFO("Status flags: 0x%08lx\n", cx->i_flags); + for (i = 0; i < CX18_MAX_STREAMS; i++) { + struct cx18_stream *s = &cx->streams[i]; + + if (s->video_dev.v4l2_dev == NULL || s->buffers == 0) + continue; + CX18_INFO("Stream %s: status 0x%04lx, %d%% of %d KiB (%d buffers) in use\n", + s->name, s->s_flags, + atomic_read(&s->q_full.depth) * s->bufs_per_mdl * 100 + / s->buffers, + (s->buffers * s->buf_size) / 1024, s->buffers); + } + CX18_INFO("Read MPEG/VBI: %lld/%lld bytes\n", + (long long)cx->mpg_data_received, + (long long)cx->vbi_data_inserted); + return 0; +} + +static long cx18_default(struct file *file, void *fh, bool valid_prio, + unsigned int cmd, void *arg) +{ + struct cx18 *cx = file2id(file)->cx; + + switch (cmd) { + case VIDIOC_INT_RESET: { + u32 val = *(u32 *)arg; + + if ((val == 0) || (val & 0x01)) + cx18_call_hw(cx, CX18_HW_GPIO_RESET_CTRL, core, reset, + (u32) CX18_GPIO_RESET_Z8F0811); + break; + } + + default: + return -ENOTTY; + } + return 0; +} + +static const struct v4l2_ioctl_ops cx18_ioctl_ops = { + .vidioc_querycap = cx18_querycap, + .vidioc_s_audio = cx18_s_audio, + .vidioc_g_audio = cx18_g_audio, + .vidioc_enumaudio = cx18_enumaudio, + .vidioc_enum_input = cx18_enum_input, + .vidioc_g_pixelaspect = cx18_g_pixelaspect, + .vidioc_g_selection = cx18_g_selection, + .vidioc_g_input = cx18_g_input, + .vidioc_s_input = cx18_s_input, + .vidioc_g_frequency = cx18_g_frequency, + .vidioc_s_frequency = cx18_s_frequency, + .vidioc_s_tuner = cx18_s_tuner, + .vidioc_g_tuner = cx18_g_tuner, + .vidioc_g_enc_index = cx18_g_enc_index, + .vidioc_g_std = cx18_g_std, + .vidioc_s_std = cx18_s_std, + .vidioc_log_status = cx18_log_status, + .vidioc_enum_fmt_vid_cap = cx18_enum_fmt_vid_cap, + .vidioc_encoder_cmd = cx18_encoder_cmd, + .vidioc_try_encoder_cmd = cx18_try_encoder_cmd, + .vidioc_g_fmt_vid_cap = cx18_g_fmt_vid_cap, + .vidioc_g_fmt_vbi_cap = cx18_g_fmt_vbi_cap, + .vidioc_g_fmt_sliced_vbi_cap = cx18_g_fmt_sliced_vbi_cap, + .vidioc_s_fmt_vid_cap = cx18_s_fmt_vid_cap, + .vidioc_s_fmt_vbi_cap = cx18_s_fmt_vbi_cap, + .vidioc_s_fmt_sliced_vbi_cap = cx18_s_fmt_sliced_vbi_cap, + .vidioc_try_fmt_vid_cap = cx18_try_fmt_vid_cap, + .vidioc_try_fmt_vbi_cap = cx18_try_fmt_vbi_cap, + .vidioc_try_fmt_sliced_vbi_cap = cx18_try_fmt_sliced_vbi_cap, + .vidioc_g_sliced_vbi_cap = cx18_g_sliced_vbi_cap, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = cx18_g_register, + .vidioc_s_register = cx18_s_register, +#endif + .vidioc_default = cx18_default, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +void cx18_set_funcs(struct video_device *vdev) +{ + vdev->ioctl_ops = &cx18_ioctl_ops; +} diff --git a/drivers/media/pci/cx18/cx18-ioctl.h b/drivers/media/pci/cx18/cx18-ioctl.h new file mode 100644 index 000000000..42a8acd69 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-ioctl.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 ioctl system call + * + * Derived from ivtv-ioctl.h + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +u16 cx18_service2vbi(int type); +void cx18_expand_service_set(struct v4l2_sliced_vbi_format *fmt, int is_pal); +u16 cx18_get_service_set(struct v4l2_sliced_vbi_format *fmt); +void cx18_set_funcs(struct video_device *vdev); + +struct cx18; +int cx18_do_s_std(struct cx18 *cx, v4l2_std_id std); +int cx18_do_s_frequency(struct cx18 *cx, const struct v4l2_frequency *vf); +int cx18_do_s_input(struct cx18 *cx, unsigned int inp); diff --git a/drivers/media/pci/cx18/cx18-irq.c b/drivers/media/pci/cx18/cx18-irq.c new file mode 100644 index 000000000..0ef01e982 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-irq.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 interrupt handling + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#include "cx18-driver.h" +#include "cx18-io.h" +#include "cx18-irq.h" +#include "cx18-mailbox.h" +#include "cx18-scb.h" + +static void xpu_ack(struct cx18 *cx, u32 sw2) +{ + if (sw2 & IRQ_CPU_TO_EPU_ACK) + wake_up(&cx->mb_cpu_waitq); + if (sw2 & IRQ_APU_TO_EPU_ACK) + wake_up(&cx->mb_apu_waitq); +} + +static void epu_cmd(struct cx18 *cx, u32 sw1) +{ + if (sw1 & IRQ_CPU_TO_EPU) + cx18_api_epu_cmd_irq(cx, CPU); + if (sw1 & IRQ_APU_TO_EPU) + cx18_api_epu_cmd_irq(cx, APU); +} + +irqreturn_t cx18_irq_handler(int irq, void *dev_id) +{ + struct cx18 *cx = dev_id; + u32 sw1, sw2, hw2; + + sw1 = cx18_read_reg(cx, SW1_INT_STATUS) & cx->sw1_irq_mask; + sw2 = cx18_read_reg(cx, SW2_INT_STATUS) & cx->sw2_irq_mask; + hw2 = cx18_read_reg(cx, HW2_INT_CLR_STATUS) & cx->hw2_irq_mask; + + if (sw1) + cx18_write_reg_expect(cx, sw1, SW1_INT_STATUS, ~sw1, sw1); + if (sw2) + cx18_write_reg_expect(cx, sw2, SW2_INT_STATUS, ~sw2, sw2); + if (hw2) + cx18_write_reg_expect(cx, hw2, HW2_INT_CLR_STATUS, ~hw2, hw2); + + if (sw1 || sw2 || hw2) + CX18_DEBUG_HI_IRQ("received interrupts SW1: %x SW2: %x HW2: %x\n", + sw1, sw2, hw2); + + /* + * SW1 responses have to happen first. The sending XPU times out the + * incoming mailboxes on us rather rapidly. + */ + if (sw1) + epu_cmd(cx, sw1); + + /* To do: interrupt-based I2C handling + if (hw2 & (HW2_I2C1_INT|HW2_I2C2_INT)) { + } + */ + + if (sw2) + xpu_ack(cx, sw2); + + return (sw1 || sw2 || hw2) ? IRQ_HANDLED : IRQ_NONE; +} diff --git a/drivers/media/pci/cx18/cx18-irq.h b/drivers/media/pci/cx18/cx18-irq.h new file mode 100644 index 000000000..c1456d69d --- /dev/null +++ b/drivers/media/pci/cx18/cx18-irq.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 interrupt handling + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#define HW2_I2C1_INT (1 << 22) +#define HW2_I2C2_INT (1 << 23) +#define HW2_INT_CLR_STATUS 0xc730c4 +#define HW2_INT_MASK5_PCI 0xc730e4 +#define SW1_INT_SET 0xc73100 +#define SW1_INT_STATUS 0xc73104 +#define SW1_INT_ENABLE_PCI 0xc7311c +#define SW2_INT_SET 0xc73140 +#define SW2_INT_STATUS 0xc73144 +#define SW2_INT_ENABLE_CPU 0xc73158 +#define SW2_INT_ENABLE_PCI 0xc7315c + +irqreturn_t cx18_irq_handler(int irq, void *dev_id); diff --git a/drivers/media/pci/cx18/cx18-mailbox.c b/drivers/media/pci/cx18/cx18-mailbox.c new file mode 100644 index 000000000..8c70b638a --- /dev/null +++ b/drivers/media/pci/cx18/cx18-mailbox.c @@ -0,0 +1,846 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 mailbox functions + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#include + +#include "cx18-driver.h" +#include "cx18-io.h" +#include "cx18-scb.h" +#include "cx18-irq.h" +#include "cx18-mailbox.h" +#include "cx18-queue.h" +#include "cx18-streams.h" +#include "cx18-alsa-pcm.h" /* FIXME make configurable */ + +static const char *rpu_str[] = { "APU", "CPU", "EPU", "HPU" }; + +#define API_FAST (1 << 2) /* Short timeout */ +#define API_SLOW (1 << 3) /* Additional 300ms timeout */ + +struct cx18_api_info { + u32 cmd; + u8 flags; /* Flags, see above */ + u8 rpu; /* Processing unit */ + const char *name; /* The name of the command */ +}; + +#define API_ENTRY(rpu, x, f) { (x), (f), (rpu), #x } + +static const struct cx18_api_info api_info[] = { + /* MPEG encoder API */ + API_ENTRY(CPU, CX18_CPU_SET_CHANNEL_TYPE, 0), + API_ENTRY(CPU, CX18_EPU_DEBUG, 0), + API_ENTRY(CPU, CX18_CREATE_TASK, 0), + API_ENTRY(CPU, CX18_DESTROY_TASK, 0), + API_ENTRY(CPU, CX18_CPU_CAPTURE_START, API_SLOW), + API_ENTRY(CPU, CX18_CPU_CAPTURE_STOP, API_SLOW), + API_ENTRY(CPU, CX18_CPU_CAPTURE_PAUSE, 0), + API_ENTRY(CPU, CX18_CPU_CAPTURE_RESUME, 0), + API_ENTRY(CPU, CX18_CPU_SET_CHANNEL_TYPE, 0), + API_ENTRY(CPU, CX18_CPU_SET_STREAM_OUTPUT_TYPE, 0), + API_ENTRY(CPU, CX18_CPU_SET_VIDEO_IN, 0), + API_ENTRY(CPU, CX18_CPU_SET_VIDEO_RATE, 0), + API_ENTRY(CPU, CX18_CPU_SET_VIDEO_RESOLUTION, 0), + API_ENTRY(CPU, CX18_CPU_SET_FILTER_PARAM, 0), + API_ENTRY(CPU, CX18_CPU_SET_SPATIAL_FILTER_TYPE, 0), + API_ENTRY(CPU, CX18_CPU_SET_MEDIAN_CORING, 0), + API_ENTRY(CPU, CX18_CPU_SET_INDEXTABLE, 0), + API_ENTRY(CPU, CX18_CPU_SET_AUDIO_PARAMETERS, 0), + API_ENTRY(CPU, CX18_CPU_SET_VIDEO_MUTE, 0), + API_ENTRY(CPU, CX18_CPU_SET_AUDIO_MUTE, 0), + API_ENTRY(CPU, CX18_CPU_SET_MISC_PARAMETERS, 0), + API_ENTRY(CPU, CX18_CPU_SET_RAW_VBI_PARAM, API_SLOW), + API_ENTRY(CPU, CX18_CPU_SET_CAPTURE_LINE_NO, 0), + API_ENTRY(CPU, CX18_CPU_SET_COPYRIGHT, 0), + API_ENTRY(CPU, CX18_CPU_SET_AUDIO_PID, 0), + API_ENTRY(CPU, CX18_CPU_SET_VIDEO_PID, 0), + API_ENTRY(CPU, CX18_CPU_SET_VER_CROP_LINE, 0), + API_ENTRY(CPU, CX18_CPU_SET_GOP_STRUCTURE, 0), + API_ENTRY(CPU, CX18_CPU_SET_SCENE_CHANGE_DETECTION, 0), + API_ENTRY(CPU, CX18_CPU_SET_ASPECT_RATIO, 0), + API_ENTRY(CPU, CX18_CPU_SET_SKIP_INPUT_FRAME, 0), + API_ENTRY(CPU, CX18_CPU_SET_SLICED_VBI_PARAM, 0), + API_ENTRY(CPU, CX18_CPU_SET_USERDATA_PLACE_HOLDER, 0), + API_ENTRY(CPU, CX18_CPU_GET_ENC_PTS, 0), + API_ENTRY(CPU, CX18_CPU_SET_VFC_PARAM, 0), + API_ENTRY(CPU, CX18_CPU_DE_SET_MDL_ACK, 0), + API_ENTRY(CPU, CX18_CPU_DE_SET_MDL, API_FAST), + API_ENTRY(CPU, CX18_CPU_DE_RELEASE_MDL, API_SLOW), + API_ENTRY(APU, CX18_APU_START, 0), + API_ENTRY(APU, CX18_APU_STOP, 0), + API_ENTRY(APU, CX18_APU_RESETAI, 0), + API_ENTRY(CPU, CX18_CPU_DEBUG_PEEK32, 0), + API_ENTRY(0, 0, 0), +}; + +static const struct cx18_api_info *find_api_info(u32 cmd) +{ + int i; + + for (i = 0; api_info[i].cmd; i++) + if (api_info[i].cmd == cmd) + return &api_info[i]; + return NULL; +} + +/* Call with buf of n*11+1 bytes */ +static char *u32arr2hex(u32 data[], int n, char *buf) +{ + char *p; + int i; + + for (i = 0, p = buf; i < n; i++, p += 11) { + /* kernel snprintf() appends '\0' always */ + snprintf(p, 12, " %#010x", data[i]); + } + *p = '\0'; + return buf; +} + +static void dump_mb(struct cx18 *cx, struct cx18_mailbox *mb, char *name) +{ + char argstr[MAX_MB_ARGUMENTS*11+1]; + + if (!(cx18_debug & CX18_DBGFLG_API)) + return; + + CX18_DEBUG_API("%s: req %#010x ack %#010x cmd %#010x err %#010x args%s\n", + name, mb->request, mb->ack, mb->cmd, mb->error, + u32arr2hex(mb->args, MAX_MB_ARGUMENTS, argstr)); +} + + +/* + * Functions that run in a work_queue work handling context + */ + +static void cx18_mdl_send_to_dvb(struct cx18_stream *s, struct cx18_mdl *mdl) +{ + struct cx18_buffer *buf; + + if (s->dvb == NULL || !s->dvb->enabled || mdl->bytesused == 0) + return; + + /* We ignore mdl and buf readpos accounting here - it doesn't matter */ + + /* The likely case */ + if (list_is_singular(&mdl->buf_list)) { + buf = list_first_entry(&mdl->buf_list, struct cx18_buffer, + list); + if (buf->bytesused) + dvb_dmx_swfilter(&s->dvb->demux, + buf->buf, buf->bytesused); + return; + } + + list_for_each_entry(buf, &mdl->buf_list, list) { + if (buf->bytesused == 0) + break; + dvb_dmx_swfilter(&s->dvb->demux, buf->buf, buf->bytesused); + } +} + +static void cx18_mdl_send_to_vb2(struct cx18_stream *s, struct cx18_mdl *mdl) +{ + struct cx18_vb2_buffer *vb_buf; + struct cx18_buffer *buf; + u8 *p; + u32 offset = 0; + int dispatch = 0; + unsigned long bsize; + + if (mdl->bytesused == 0) + return; + + /* Acquire a vb2 buffer, clone to and release it */ + spin_lock(&s->vb_lock); + if (list_empty(&s->vb_capture)) + goto out; + + vb_buf = list_first_entry(&s->vb_capture, struct cx18_vb2_buffer, + list); + + p = vb2_plane_vaddr(&vb_buf->vb.vb2_buf, 0); + if (!p) + goto out; + + bsize = vb2_get_plane_payload(&vb_buf->vb.vb2_buf, 0); + offset = vb_buf->bytes_used; + list_for_each_entry(buf, &mdl->buf_list, list) { + if (buf->bytesused == 0) + break; + + if ((offset + buf->bytesused) <= bsize) { + memcpy(p + offset, buf->buf, buf->bytesused); + offset += buf->bytesused; + vb_buf->bytes_used += buf->bytesused; + } + } + + /* If we've filled the buffer as per the callers res then dispatch it */ + if (vb_buf->bytes_used >= s->vb_bytes_per_frame) { + dispatch = 1; + vb_buf->bytes_used = 0; + } + + if (dispatch) { + vb_buf->vb.vb2_buf.timestamp = ktime_get_ns(); + vb_buf->vb.sequence = s->sequence++; + list_del(&vb_buf->list); + vb2_buffer_done(&vb_buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + } + + mod_timer(&s->vb_timeout, msecs_to_jiffies(2000) + jiffies); + +out: + spin_unlock(&s->vb_lock); +} + +static void cx18_mdl_send_to_alsa(struct cx18 *cx, struct cx18_stream *s, + struct cx18_mdl *mdl) +{ + struct cx18_buffer *buf; + + if (mdl->bytesused == 0) + return; + + /* We ignore mdl and buf readpos accounting here - it doesn't matter */ + + /* The likely case */ + if (list_is_singular(&mdl->buf_list)) { + buf = list_first_entry(&mdl->buf_list, struct cx18_buffer, + list); + if (buf->bytesused) + cx->pcm_announce_callback(cx->alsa, buf->buf, + buf->bytesused); + return; + } + + list_for_each_entry(buf, &mdl->buf_list, list) { + if (buf->bytesused == 0) + break; + cx->pcm_announce_callback(cx->alsa, buf->buf, buf->bytesused); + } +} + +static void epu_dma_done(struct cx18 *cx, struct cx18_in_work_order *order) +{ + u32 handle, mdl_ack_count, id; + struct cx18_mailbox *mb; + struct cx18_mdl_ack *mdl_ack; + struct cx18_stream *s; + struct cx18_mdl *mdl; + int i; + + mb = &order->mb; + handle = mb->args[0]; + s = cx18_handle_to_stream(cx, handle); + + if (s == NULL) { + CX18_WARN("Got DMA done notification for unknown/inactive handle %d, %s mailbox seq no %d\n", + handle, + (order->flags & CX18_F_EWO_MB_STALE_UPON_RECEIPT) ? + "stale" : "good", mb->request); + return; + } + + mdl_ack_count = mb->args[2]; + mdl_ack = order->mdl_ack; + for (i = 0; i < mdl_ack_count; i++, mdl_ack++) { + id = mdl_ack->id; + /* + * Simple integrity check for processing a stale (and possibly + * inconsistent mailbox): make sure the MDL id is in the + * valid range for the stream. + * + * We go through the trouble of dealing with stale mailboxes + * because most of the time, the mailbox data is still valid and + * unchanged (and in practice the firmware ping-pongs the + * two mdl_ack buffers so mdl_acks are not stale). + * + * There are occasions when we get a half changed mailbox, + * which this check catches for a handle & id mismatch. If the + * handle and id do correspond, the worst case is that we + * completely lost the old MDL, but pick up the new MDL + * early (but the new mdl_ack is guaranteed to be good in this + * case as the firmware wouldn't point us to a new mdl_ack until + * it's filled in). + * + * cx18_queue_get_mdl() will detect the lost MDLs + * and send them back to q_free for fw rotation eventually. + */ + if ((order->flags & CX18_F_EWO_MB_STALE_UPON_RECEIPT) && + !(id >= s->mdl_base_idx && + id < (s->mdl_base_idx + s->buffers))) { + CX18_WARN("Fell behind! Ignoring stale mailbox with inconsistent data. Lost MDL for mailbox seq no %d\n", + mb->request); + break; + } + mdl = cx18_queue_get_mdl(s, id, mdl_ack->data_used); + + CX18_DEBUG_HI_DMA("DMA DONE for %s (MDL %d)\n", s->name, id); + if (mdl == NULL) { + CX18_WARN("Could not find MDL %d for stream %s\n", + id, s->name); + continue; + } + + CX18_DEBUG_HI_DMA("%s recv bytesused = %d\n", + s->name, mdl->bytesused); + + if (s->type == CX18_ENC_STREAM_TYPE_TS) { + cx18_mdl_send_to_dvb(s, mdl); + cx18_enqueue(s, mdl, &s->q_free); + } else if (s->type == CX18_ENC_STREAM_TYPE_PCM) { + /* Pass the data to cx18-alsa */ + if (cx->pcm_announce_callback != NULL) { + cx18_mdl_send_to_alsa(cx, s, mdl); + cx18_enqueue(s, mdl, &s->q_free); + } else { + cx18_enqueue(s, mdl, &s->q_full); + } + } else if (s->type == CX18_ENC_STREAM_TYPE_YUV) { + cx18_mdl_send_to_vb2(s, mdl); + cx18_enqueue(s, mdl, &s->q_free); + } else { + cx18_enqueue(s, mdl, &s->q_full); + if (s->type == CX18_ENC_STREAM_TYPE_IDX) + cx18_stream_rotate_idx_mdls(cx); + } + } + /* Put as many MDLs as possible back into fw use */ + cx18_stream_load_fw_queue(s); + + wake_up(&cx->dma_waitq); + if (s->id != -1) + wake_up(&s->waitq); +} + +static void epu_debug(struct cx18 *cx, struct cx18_in_work_order *order) +{ + char *p; + char *str = order->str; + + CX18_DEBUG_INFO("%x %s\n", order->mb.args[0], str); + p = strchr(str, '.'); + if (!test_bit(CX18_F_I_LOADED_FW, &cx->i_flags) && p && p > str) + CX18_INFO("FW version: %s\n", p - 1); +} + +static void epu_cmd(struct cx18 *cx, struct cx18_in_work_order *order) +{ + switch (order->rpu) { + case CPU: + { + switch (order->mb.cmd) { + case CX18_EPU_DMA_DONE: + epu_dma_done(cx, order); + break; + case CX18_EPU_DEBUG: + epu_debug(cx, order); + break; + default: + CX18_WARN("Unknown CPU to EPU mailbox command %#0x\n", + order->mb.cmd); + break; + } + break; + } + case APU: + CX18_WARN("Unknown APU to EPU mailbox command %#0x\n", + order->mb.cmd); + break; + default: + break; + } +} + +static +void free_in_work_order(struct cx18 *cx, struct cx18_in_work_order *order) +{ + atomic_set(&order->pending, 0); +} + +void cx18_in_work_handler(struct work_struct *work) +{ + struct cx18_in_work_order *order = + container_of(work, struct cx18_in_work_order, work); + struct cx18 *cx = order->cx; + epu_cmd(cx, order); + free_in_work_order(cx, order); +} + + +/* + * Functions that run in an interrupt handling context + */ + +static void mb_ack_irq(struct cx18 *cx, struct cx18_in_work_order *order) +{ + struct cx18_mailbox __iomem *ack_mb; + u32 ack_irq, req; + + switch (order->rpu) { + case APU: + ack_irq = IRQ_EPU_TO_APU_ACK; + ack_mb = &cx->scb->apu2epu_mb; + break; + case CPU: + ack_irq = IRQ_EPU_TO_CPU_ACK; + ack_mb = &cx->scb->cpu2epu_mb; + break; + default: + CX18_WARN("Unhandled RPU (%d) for command %x ack\n", + order->rpu, order->mb.cmd); + return; + } + + req = order->mb.request; + /* Don't ack if the RPU has gotten impatient and timed us out */ + if (req != cx18_readl(cx, &ack_mb->request) || + req == cx18_readl(cx, &ack_mb->ack)) { + CX18_DEBUG_WARN("Possibly falling behind: %s self-ack'ed our incoming %s to EPU mailbox (sequence no. %u) while processing\n", + rpu_str[order->rpu], rpu_str[order->rpu], req); + order->flags |= CX18_F_EWO_MB_STALE_WHILE_PROC; + return; + } + cx18_writel(cx, req, &ack_mb->ack); + cx18_write_reg_expect(cx, ack_irq, SW2_INT_SET, ack_irq, ack_irq); + return; +} + +static int epu_dma_done_irq(struct cx18 *cx, struct cx18_in_work_order *order) +{ + u32 handle, mdl_ack_offset, mdl_ack_count; + struct cx18_mailbox *mb; + int i; + + mb = &order->mb; + handle = mb->args[0]; + mdl_ack_offset = mb->args[1]; + mdl_ack_count = mb->args[2]; + + if (handle == CX18_INVALID_TASK_HANDLE || + mdl_ack_count == 0 || mdl_ack_count > CX18_MAX_MDL_ACKS) { + if ((order->flags & CX18_F_EWO_MB_STALE) == 0) + mb_ack_irq(cx, order); + return -1; + } + + for (i = 0; i < sizeof(struct cx18_mdl_ack) * mdl_ack_count; i += sizeof(u32)) + ((u32 *)order->mdl_ack)[i / sizeof(u32)] = + cx18_readl(cx, cx->enc_mem + mdl_ack_offset + i); + + if ((order->flags & CX18_F_EWO_MB_STALE) == 0) + mb_ack_irq(cx, order); + return 1; +} + +static +int epu_debug_irq(struct cx18 *cx, struct cx18_in_work_order *order) +{ + u32 str_offset; + char *str = order->str; + + str[0] = '\0'; + str_offset = order->mb.args[1]; + if (str_offset) { + cx18_setup_page(cx, str_offset); + cx18_memcpy_fromio(cx, str, cx->enc_mem + str_offset, 252); + str[252] = '\0'; + cx18_setup_page(cx, SCB_OFFSET); + } + + if ((order->flags & CX18_F_EWO_MB_STALE) == 0) + mb_ack_irq(cx, order); + + return str_offset ? 1 : 0; +} + +static inline +int epu_cmd_irq(struct cx18 *cx, struct cx18_in_work_order *order) +{ + int ret = -1; + + switch (order->rpu) { + case CPU: + { + switch (order->mb.cmd) { + case CX18_EPU_DMA_DONE: + ret = epu_dma_done_irq(cx, order); + break; + case CX18_EPU_DEBUG: + ret = epu_debug_irq(cx, order); + break; + default: + CX18_WARN("Unknown CPU to EPU mailbox command %#0x\n", + order->mb.cmd); + break; + } + break; + } + case APU: + CX18_WARN("Unknown APU to EPU mailbox command %#0x\n", + order->mb.cmd); + break; + default: + break; + } + return ret; +} + +static inline +struct cx18_in_work_order *alloc_in_work_order_irq(struct cx18 *cx) +{ + int i; + struct cx18_in_work_order *order = NULL; + + for (i = 0; i < CX18_MAX_IN_WORK_ORDERS; i++) { + /* + * We only need "pending" atomic to inspect its contents, + * and need not do a check and set because: + * 1. Any work handler thread only clears "pending" and only + * on one, particular work order at a time, per handler thread. + * 2. "pending" is only set here, and we're serialized because + * we're called in an IRQ handler context. + */ + if (atomic_read(&cx->in_work_order[i].pending) == 0) { + order = &cx->in_work_order[i]; + atomic_set(&order->pending, 1); + break; + } + } + return order; +} + +void cx18_api_epu_cmd_irq(struct cx18 *cx, int rpu) +{ + struct cx18_mailbox __iomem *mb; + struct cx18_mailbox *order_mb; + struct cx18_in_work_order *order; + int submit; + int i; + + switch (rpu) { + case CPU: + mb = &cx->scb->cpu2epu_mb; + break; + case APU: + mb = &cx->scb->apu2epu_mb; + break; + default: + return; + } + + order = alloc_in_work_order_irq(cx); + if (order == NULL) { + CX18_WARN("Unable to find blank work order form to schedule incoming mailbox command processing\n"); + return; + } + + order->flags = 0; + order->rpu = rpu; + order_mb = &order->mb; + + /* mb->cmd and mb->args[0] through mb->args[2] */ + for (i = 0; i < 4; i++) + (&order_mb->cmd)[i] = cx18_readl(cx, &mb->cmd + i); + + /* mb->request and mb->ack. N.B. we want to read mb->ack last */ + for (i = 0; i < 2; i++) + (&order_mb->request)[i] = cx18_readl(cx, &mb->request + i); + + if (order_mb->request == order_mb->ack) { + CX18_DEBUG_WARN("Possibly falling behind: %s self-ack'ed our incoming %s to EPU mailbox (sequence no. %u)\n", + rpu_str[rpu], rpu_str[rpu], order_mb->request); + if (cx18_debug & CX18_DBGFLG_WARN) + dump_mb(cx, order_mb, "incoming"); + order->flags = CX18_F_EWO_MB_STALE_UPON_RECEIPT; + } + + /* + * Individual EPU command processing is responsible for ack-ing + * a non-stale mailbox as soon as possible + */ + submit = epu_cmd_irq(cx, order); + if (submit > 0) { + queue_work(cx->in_work_queue, &order->work); + } +} + + +/* + * Functions called from a non-interrupt, non work_queue context + */ + +static int cx18_api_call(struct cx18 *cx, u32 cmd, int args, u32 data[]) +{ + const struct cx18_api_info *info = find_api_info(cmd); + u32 irq, req, ack, err; + struct cx18_mailbox __iomem *mb; + wait_queue_head_t *waitq; + struct mutex *mb_lock; + unsigned long int t0, timeout, ret; + int i; + char argstr[MAX_MB_ARGUMENTS*11+1]; + DEFINE_WAIT(w); + + if (info == NULL) { + CX18_WARN("unknown cmd %x\n", cmd); + return -EINVAL; + } + + if (cx18_debug & CX18_DBGFLG_API) { /* only call u32arr2hex if needed */ + if (cmd == CX18_CPU_DE_SET_MDL) { + if (cx18_debug & CX18_DBGFLG_HIGHVOL) + CX18_DEBUG_HI_API("%s\tcmd %#010x args%s\n", + info->name, cmd, + u32arr2hex(data, args, argstr)); + } else + CX18_DEBUG_API("%s\tcmd %#010x args%s\n", + info->name, cmd, + u32arr2hex(data, args, argstr)); + } + + switch (info->rpu) { + case APU: + waitq = &cx->mb_apu_waitq; + mb_lock = &cx->epu2apu_mb_lock; + irq = IRQ_EPU_TO_APU; + mb = &cx->scb->epu2apu_mb; + break; + case CPU: + waitq = &cx->mb_cpu_waitq; + mb_lock = &cx->epu2cpu_mb_lock; + irq = IRQ_EPU_TO_CPU; + mb = &cx->scb->epu2cpu_mb; + break; + default: + CX18_WARN("Unknown RPU (%d) for API call\n", info->rpu); + return -EINVAL; + } + + mutex_lock(mb_lock); + /* + * Wait for an in-use mailbox to complete + * + * If the XPU is responding with Ack's, the mailbox shouldn't be in + * a busy state, since we serialize access to it on our end. + * + * If the wait for ack after sending a previous command was interrupted + * by a signal, we may get here and find a busy mailbox. After waiting, + * mark it "not busy" from our end, if the XPU hasn't ack'ed it still. + */ + req = cx18_readl(cx, &mb->request); + timeout = msecs_to_jiffies(10); + ret = wait_event_timeout(*waitq, + (ack = cx18_readl(cx, &mb->ack)) == req, + timeout); + if (req != ack) { + /* waited long enough, make the mbox "not busy" from our end */ + cx18_writel(cx, req, &mb->ack); + CX18_ERR("mbox was found stuck busy when setting up for %s; clearing busy and trying to proceed\n", + info->name); + } else if (ret != timeout) + CX18_DEBUG_API("waited %u msecs for busy mbox to be acked\n", + jiffies_to_msecs(timeout-ret)); + + /* Build the outgoing mailbox */ + req = ((req & 0xfffffffe) == 0xfffffffe) ? 1 : req + 1; + + cx18_writel(cx, cmd, &mb->cmd); + for (i = 0; i < args; i++) + cx18_writel(cx, data[i], &mb->args[i]); + cx18_writel(cx, 0, &mb->error); + cx18_writel(cx, req, &mb->request); + cx18_writel(cx, req - 1, &mb->ack); /* ensure ack & req are distinct */ + + /* + * Notify the XPU and wait for it to send an Ack back + */ + timeout = msecs_to_jiffies((info->flags & API_FAST) ? 10 : 20); + + CX18_DEBUG_HI_IRQ("sending interrupt SW1: %x to send %s\n", + irq, info->name); + + /* So we don't miss the wakeup, prepare to wait before notifying fw */ + prepare_to_wait(waitq, &w, TASK_UNINTERRUPTIBLE); + cx18_write_reg_expect(cx, irq, SW1_INT_SET, irq, irq); + + t0 = jiffies; + ack = cx18_readl(cx, &mb->ack); + if (ack != req) { + schedule_timeout(timeout); + ret = jiffies - t0; + ack = cx18_readl(cx, &mb->ack); + } else { + ret = jiffies - t0; + } + + finish_wait(waitq, &w); + + if (req != ack) { + mutex_unlock(mb_lock); + if (ret >= timeout) { + /* Timed out */ + CX18_DEBUG_WARN("sending %s timed out waiting %d msecs for RPU acknowledgment\n", + info->name, jiffies_to_msecs(ret)); + } else { + CX18_DEBUG_WARN("woken up before mailbox ack was ready after submitting %s to RPU. only waited %d msecs on req %u but awakened with unmatched ack %u\n", + info->name, + jiffies_to_msecs(ret), + req, ack); + } + return -EINVAL; + } + + if (ret >= timeout) + CX18_DEBUG_WARN("failed to be awakened upon RPU acknowledgment sending %s; timed out waiting %d msecs\n", + info->name, jiffies_to_msecs(ret)); + else + CX18_DEBUG_HI_API("waited %u msecs for %s to be acked\n", + jiffies_to_msecs(ret), info->name); + + /* Collect data returned by the XPU */ + for (i = 0; i < MAX_MB_ARGUMENTS; i++) + data[i] = cx18_readl(cx, &mb->args[i]); + err = cx18_readl(cx, &mb->error); + mutex_unlock(mb_lock); + + /* + * Wait for XPU to perform extra actions for the caller in some cases. + * e.g. CX18_CPU_DE_RELEASE_MDL will cause the CPU to send all MDLs + * back in a burst shortly thereafter + */ + if (info->flags & API_SLOW) + cx18_msleep_timeout(300, 0); + + if (err) + CX18_DEBUG_API("mailbox error %08x for command %s\n", err, + info->name); + return err ? -EIO : 0; +} + +int cx18_api(struct cx18 *cx, u32 cmd, int args, u32 data[]) +{ + return cx18_api_call(cx, cmd, args, data); +} + +static int cx18_set_filter_param(struct cx18_stream *s) +{ + struct cx18 *cx = s->cx; + u32 mode; + int ret; + + mode = (cx->filter_mode & 1) ? 2 : (cx->spatial_strength ? 1 : 0); + ret = cx18_vapi(cx, CX18_CPU_SET_FILTER_PARAM, 4, + s->handle, 1, mode, cx->spatial_strength); + mode = (cx->filter_mode & 2) ? 2 : (cx->temporal_strength ? 1 : 0); + ret = ret ? ret : cx18_vapi(cx, CX18_CPU_SET_FILTER_PARAM, 4, + s->handle, 0, mode, cx->temporal_strength); + ret = ret ? ret : cx18_vapi(cx, CX18_CPU_SET_FILTER_PARAM, 4, + s->handle, 2, cx->filter_mode >> 2, 0); + return ret; +} + +int cx18_api_func(void *priv, u32 cmd, int in, int out, + u32 data[CX2341X_MBOX_MAX_DATA]) +{ + struct cx18_stream *s = priv; + struct cx18 *cx = s->cx; + + switch (cmd) { + case CX2341X_ENC_SET_OUTPUT_PORT: + return 0; + case CX2341X_ENC_SET_FRAME_RATE: + return cx18_vapi(cx, CX18_CPU_SET_VIDEO_IN, 6, + s->handle, 0, 0, 0, 0, data[0]); + case CX2341X_ENC_SET_FRAME_SIZE: + return cx18_vapi(cx, CX18_CPU_SET_VIDEO_RESOLUTION, 3, + s->handle, data[1], data[0]); + case CX2341X_ENC_SET_STREAM_TYPE: + return cx18_vapi(cx, CX18_CPU_SET_STREAM_OUTPUT_TYPE, 2, + s->handle, data[0]); + case CX2341X_ENC_SET_ASPECT_RATIO: + return cx18_vapi(cx, CX18_CPU_SET_ASPECT_RATIO, 2, + s->handle, data[0]); + + case CX2341X_ENC_SET_GOP_PROPERTIES: + return cx18_vapi(cx, CX18_CPU_SET_GOP_STRUCTURE, 3, + s->handle, data[0], data[1]); + case CX2341X_ENC_SET_GOP_CLOSURE: + return 0; + case CX2341X_ENC_SET_AUDIO_PROPERTIES: + return cx18_vapi(cx, CX18_CPU_SET_AUDIO_PARAMETERS, 2, + s->handle, data[0]); + case CX2341X_ENC_MUTE_AUDIO: + return cx18_vapi(cx, CX18_CPU_SET_AUDIO_MUTE, 2, + s->handle, data[0]); + case CX2341X_ENC_SET_BIT_RATE: + return cx18_vapi(cx, CX18_CPU_SET_VIDEO_RATE, 5, + s->handle, data[0], data[1], data[2], data[3]); + case CX2341X_ENC_MUTE_VIDEO: + return cx18_vapi(cx, CX18_CPU_SET_VIDEO_MUTE, 2, + s->handle, data[0]); + case CX2341X_ENC_SET_FRAME_DROP_RATE: + return cx18_vapi(cx, CX18_CPU_SET_SKIP_INPUT_FRAME, 2, + s->handle, data[0]); + case CX2341X_ENC_MISC: + return cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 4, + s->handle, data[0], data[1], data[2]); + case CX2341X_ENC_SET_DNR_FILTER_MODE: + cx->filter_mode = (data[0] & 3) | (data[1] << 2); + return cx18_set_filter_param(s); + case CX2341X_ENC_SET_DNR_FILTER_PROPS: + cx->spatial_strength = data[0]; + cx->temporal_strength = data[1]; + return cx18_set_filter_param(s); + case CX2341X_ENC_SET_SPATIAL_FILTER_TYPE: + return cx18_vapi(cx, CX18_CPU_SET_SPATIAL_FILTER_TYPE, 3, + s->handle, data[0], data[1]); + case CX2341X_ENC_SET_CORING_LEVELS: + return cx18_vapi(cx, CX18_CPU_SET_MEDIAN_CORING, 5, + s->handle, data[0], data[1], data[2], data[3]); + } + CX18_WARN("Unknown cmd %x\n", cmd); + return 0; +} + +int cx18_vapi_result(struct cx18 *cx, u32 data[MAX_MB_ARGUMENTS], + u32 cmd, int args, ...) +{ + va_list ap; + int i; + + va_start(ap, args); + for (i = 0; i < args; i++) + data[i] = va_arg(ap, u32); + va_end(ap); + return cx18_api(cx, cmd, args, data); +} + +int cx18_vapi(struct cx18 *cx, u32 cmd, int args, ...) +{ + u32 data[MAX_MB_ARGUMENTS]; + va_list ap; + int i; + + if (cx == NULL) { + pr_err("cx == NULL (cmd=%x)\n", cmd); + return 0; + } + if (args > MAX_MB_ARGUMENTS) { + CX18_ERR("args too big (cmd=%x)\n", cmd); + args = MAX_MB_ARGUMENTS; + } + va_start(ap, args); + for (i = 0; i < args; i++) + data[i] = va_arg(ap, u32); + va_end(ap); + return cx18_api(cx, cmd, args, data); +} diff --git a/drivers/media/pci/cx18/cx18-mailbox.h b/drivers/media/pci/cx18/cx18-mailbox.h new file mode 100644 index 000000000..1752b8e1c --- /dev/null +++ b/drivers/media/pci/cx18/cx18-mailbox.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 mailbox functions + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#ifndef _CX18_MAILBOX_H_ +#define _CX18_MAILBOX_H_ + +/* mailbox max args */ +#define MAX_MB_ARGUMENTS 6 +/* compatibility, should be same as the define in cx2341x.h */ +#define CX2341X_MBOX_MAX_DATA 16 + +#define MB_RESERVED_HANDLE_0 0 +#define MB_RESERVED_HANDLE_1 0xFFFFFFFF + +#define APU 0 +#define CPU 1 +#define EPU 2 +#define HPU 3 + +struct cx18; + +/* + * This structure is used by CPU to provide completed MDL & buffers information. + * Its structure is dictated by the layout of the SCB, required by the + * firmware, but its definition needs to be here, instead of in cx18-scb.h, + * for mailbox work order scheduling + */ +struct cx18_mdl_ack { + u32 id; /* ID of a completed MDL */ + u32 data_used; /* Total data filled in the MDL with 'id' */ +}; + +/* The cx18_mailbox struct is the mailbox structure which is used for passing + messages between processors */ +struct cx18_mailbox { + /* The sender sets a handle in 'request' after he fills the command. The + 'request' should be different than 'ack'. The sender, also, generates + an interrupt on XPU2YPU_irq where XPU is the sender and YPU is the + receiver. */ + u32 request; + /* The receiver detects a new command when 'req' is different than 'ack'. + He sets 'ack' to the same value as 'req' to clear the command. He, also, + generates an interrupt on YPU2XPU_irq where XPU is the sender and YPU + is the receiver. */ + u32 ack; + u32 reserved[6]; + /* 'cmd' identifies the command. The list of these commands are in + cx23418.h */ + u32 cmd; + /* Each command can have up to 6 arguments */ + u32 args[MAX_MB_ARGUMENTS]; + /* The return code can be one of the codes in the file cx23418.h. If the + command is completed successfully, the error will be ERR_SYS_SUCCESS. + If it is pending, the code is ERR_SYS_PENDING. If it failed, the error + code would indicate the task from which the error originated and will + be one of the errors in cx23418.h. In that case, the following + applies ((error & 0xff) != 0). + If the command is pending, the return will be passed in a MB from the + receiver to the sender. 'req' will be returned in args[0] */ + u32 error; +}; + +struct cx18_stream; + +int cx18_api(struct cx18 *cx, u32 cmd, int args, u32 data[]); +int cx18_vapi_result(struct cx18 *cx, u32 data[MAX_MB_ARGUMENTS], u32 cmd, + int args, ...); +int cx18_vapi(struct cx18 *cx, u32 cmd, int args, ...); +int cx18_api_func(void *priv, u32 cmd, int in, int out, + u32 data[CX2341X_MBOX_MAX_DATA]); + +void cx18_api_epu_cmd_irq(struct cx18 *cx, int rpu); + +void cx18_in_work_handler(struct work_struct *work); + +#endif diff --git a/drivers/media/pci/cx18/cx18-queue.c b/drivers/media/pci/cx18/cx18-queue.c new file mode 100644 index 000000000..04d6828f0 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-queue.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 buffer queues + * + * Derived from ivtv-queue.c + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#include "cx18-driver.h" +#include "cx18-queue.h" +#include "cx18-streams.h" +#include "cx18-scb.h" +#include "cx18-io.h" + +void cx18_buf_swap(struct cx18_buffer *buf) +{ + int i; + + for (i = 0; i < buf->bytesused; i += 4) + swab32s((u32 *)(buf->buf + i)); +} + +void _cx18_mdl_swap(struct cx18_mdl *mdl) +{ + struct cx18_buffer *buf; + + list_for_each_entry(buf, &mdl->buf_list, list) { + if (buf->bytesused == 0) + break; + cx18_buf_swap(buf); + } +} + +void cx18_queue_init(struct cx18_queue *q) +{ + INIT_LIST_HEAD(&q->list); + atomic_set(&q->depth, 0); + q->bytesused = 0; +} + +struct cx18_queue *_cx18_enqueue(struct cx18_stream *s, struct cx18_mdl *mdl, + struct cx18_queue *q, int to_front) +{ + /* clear the mdl if it is not to be enqueued to the full queue */ + if (q != &s->q_full) { + mdl->bytesused = 0; + mdl->readpos = 0; + mdl->m_flags = 0; + mdl->skipped = 0; + mdl->curr_buf = NULL; + } + + /* q_busy is restricted to a max buffer count imposed by firmware */ + if (q == &s->q_busy && + atomic_read(&q->depth) >= CX18_MAX_FW_MDLS_PER_STREAM) + q = &s->q_free; + + spin_lock(&q->lock); + + if (to_front) + list_add(&mdl->list, &q->list); /* LIFO */ + else + list_add_tail(&mdl->list, &q->list); /* FIFO */ + q->bytesused += mdl->bytesused - mdl->readpos; + atomic_inc(&q->depth); + + spin_unlock(&q->lock); + return q; +} + +struct cx18_mdl *cx18_dequeue(struct cx18_stream *s, struct cx18_queue *q) +{ + struct cx18_mdl *mdl = NULL; + + spin_lock(&q->lock); + if (!list_empty(&q->list)) { + mdl = list_first_entry(&q->list, struct cx18_mdl, list); + list_del_init(&mdl->list); + q->bytesused -= mdl->bytesused - mdl->readpos; + mdl->skipped = 0; + atomic_dec(&q->depth); + } + spin_unlock(&q->lock); + return mdl; +} + +static void _cx18_mdl_update_bufs_for_cpu(struct cx18_stream *s, + struct cx18_mdl *mdl) +{ + struct cx18_buffer *buf; + u32 buf_size = s->buf_size; + u32 bytesused = mdl->bytesused; + + list_for_each_entry(buf, &mdl->buf_list, list) { + buf->readpos = 0; + if (bytesused >= buf_size) { + buf->bytesused = buf_size; + bytesused -= buf_size; + } else { + buf->bytesused = bytesused; + bytesused = 0; + } + cx18_buf_sync_for_cpu(s, buf); + } +} + +static inline void cx18_mdl_update_bufs_for_cpu(struct cx18_stream *s, + struct cx18_mdl *mdl) +{ + struct cx18_buffer *buf; + + if (list_is_singular(&mdl->buf_list)) { + buf = list_first_entry(&mdl->buf_list, struct cx18_buffer, + list); + buf->bytesused = mdl->bytesused; + buf->readpos = 0; + cx18_buf_sync_for_cpu(s, buf); + } else { + _cx18_mdl_update_bufs_for_cpu(s, mdl); + } +} + +struct cx18_mdl *cx18_queue_get_mdl(struct cx18_stream *s, u32 id, + u32 bytesused) +{ + struct cx18 *cx = s->cx; + struct cx18_mdl *mdl; + struct cx18_mdl *tmp; + struct cx18_mdl *ret = NULL; + LIST_HEAD(sweep_up); + + /* + * We don't have to acquire multiple q locks here, because we are + * serialized by the single threaded work handler. + * MDLs from the firmware will thus remain in order as + * they are moved from q_busy to q_full or to the dvb ring buffer. + */ + spin_lock(&s->q_busy.lock); + list_for_each_entry_safe(mdl, tmp, &s->q_busy.list, list) { + /* + * We should find what the firmware told us is done, + * right at the front of the queue. If we don't, we likely have + * missed an mdl done message from the firmware. + * Once we skip an mdl repeatedly, relative to the size of + * q_busy, we have high confidence we've missed it. + */ + if (mdl->id != id) { + mdl->skipped++; + if (mdl->skipped >= atomic_read(&s->q_busy.depth)-1) { + /* mdl must have fallen out of rotation */ + CX18_WARN("Skipped %s, MDL %d, %d times - it must have dropped out of rotation\n", + s->name, mdl->id, + mdl->skipped); + /* Sweep it up to put it back into rotation */ + list_move_tail(&mdl->list, &sweep_up); + atomic_dec(&s->q_busy.depth); + } + continue; + } + /* + * We pull the desired mdl off of the queue here. Something + * will have to put it back on a queue later. + */ + list_del_init(&mdl->list); + atomic_dec(&s->q_busy.depth); + ret = mdl; + break; + } + spin_unlock(&s->q_busy.lock); + + /* + * We found the mdl for which we were looking. Get it ready for + * the caller to put on q_full or in the dvb ring buffer. + */ + if (ret != NULL) { + ret->bytesused = bytesused; + ret->skipped = 0; + /* 0'ed readpos, m_flags & curr_buf when mdl went on q_busy */ + cx18_mdl_update_bufs_for_cpu(s, ret); + if (s->type != CX18_ENC_STREAM_TYPE_TS) + set_bit(CX18_F_M_NEED_SWAP, &ret->m_flags); + } + + /* Put any mdls the firmware is ignoring back into normal rotation */ + list_for_each_entry_safe(mdl, tmp, &sweep_up, list) { + list_del_init(&mdl->list); + cx18_enqueue(s, mdl, &s->q_free); + } + return ret; +} + +/* Move all mdls of a queue, while flushing the mdl */ +static void cx18_queue_flush(struct cx18_stream *s, + struct cx18_queue *q_src, struct cx18_queue *q_dst) +{ + struct cx18_mdl *mdl; + + /* It only makes sense to flush to q_free or q_idle */ + if (q_src == q_dst || q_dst == &s->q_full || q_dst == &s->q_busy) + return; + + spin_lock(&q_src->lock); + spin_lock(&q_dst->lock); + while (!list_empty(&q_src->list)) { + mdl = list_first_entry(&q_src->list, struct cx18_mdl, list); + list_move_tail(&mdl->list, &q_dst->list); + mdl->bytesused = 0; + mdl->readpos = 0; + mdl->m_flags = 0; + mdl->skipped = 0; + mdl->curr_buf = NULL; + atomic_inc(&q_dst->depth); + } + cx18_queue_init(q_src); + spin_unlock(&q_src->lock); + spin_unlock(&q_dst->lock); +} + +void cx18_flush_queues(struct cx18_stream *s) +{ + cx18_queue_flush(s, &s->q_busy, &s->q_free); + cx18_queue_flush(s, &s->q_full, &s->q_free); +} + +/* + * Note, s->buf_pool is not protected by a lock, + * the stream better not have *anything* going on when calling this + */ +void cx18_unload_queues(struct cx18_stream *s) +{ + struct cx18_queue *q_idle = &s->q_idle; + struct cx18_mdl *mdl; + struct cx18_buffer *buf; + + /* Move all MDLS to q_idle */ + cx18_queue_flush(s, &s->q_busy, q_idle); + cx18_queue_flush(s, &s->q_full, q_idle); + cx18_queue_flush(s, &s->q_free, q_idle); + + /* Reset MDL id's and move all buffers back to the stream's buf_pool */ + spin_lock(&q_idle->lock); + list_for_each_entry(mdl, &q_idle->list, list) { + while (!list_empty(&mdl->buf_list)) { + buf = list_first_entry(&mdl->buf_list, + struct cx18_buffer, list); + list_move_tail(&buf->list, &s->buf_pool); + buf->bytesused = 0; + buf->readpos = 0; + } + mdl->id = s->mdl_base_idx; /* reset id to a "safe" value */ + /* all other mdl fields were cleared by cx18_queue_flush() */ + } + spin_unlock(&q_idle->lock); +} + +/* + * Note, s->buf_pool is not protected by a lock, + * the stream better not have *anything* going on when calling this + */ +void cx18_load_queues(struct cx18_stream *s) +{ + struct cx18 *cx = s->cx; + struct cx18_mdl *mdl; + struct cx18_buffer *buf; + int mdl_id; + int i; + u32 partial_buf_size; + + /* + * Attach buffers to MDLs, give the MDLs ids, and add MDLs to q_free + * Excess MDLs are left on q_idle + * Excess buffers are left in buf_pool and/or on an MDL in q_idle + */ + mdl_id = s->mdl_base_idx; + for (mdl = cx18_dequeue(s, &s->q_idle), i = s->bufs_per_mdl; + mdl != NULL && i == s->bufs_per_mdl; + mdl = cx18_dequeue(s, &s->q_idle)) { + + mdl->id = mdl_id; + + for (i = 0; i < s->bufs_per_mdl; i++) { + if (list_empty(&s->buf_pool)) + break; + + buf = list_first_entry(&s->buf_pool, struct cx18_buffer, + list); + list_move_tail(&buf->list, &mdl->buf_list); + + /* update the firmware's MDL array with this buffer */ + cx18_writel(cx, buf->dma_handle, + &cx->scb->cpu_mdl[mdl_id + i].paddr); + cx18_writel(cx, s->buf_size, + &cx->scb->cpu_mdl[mdl_id + i].length); + } + + if (i == s->bufs_per_mdl) { + /* + * The encoder doesn't honor s->mdl_size. So in the + * case of a non-integral number of buffers to meet + * mdl_size, we lie about the size of the last buffer + * in the MDL to get the encoder to really only send + * us mdl_size bytes per MDL transfer. + */ + partial_buf_size = s->mdl_size % s->buf_size; + if (partial_buf_size) { + cx18_writel(cx, partial_buf_size, + &cx->scb->cpu_mdl[mdl_id + i - 1].length); + } + cx18_enqueue(s, mdl, &s->q_free); + } else { + /* Not enough buffers for this MDL; we won't use it */ + cx18_push(s, mdl, &s->q_idle); + } + mdl_id += i; + } +} + +void _cx18_mdl_sync_for_device(struct cx18_stream *s, struct cx18_mdl *mdl) +{ + int dma = s->dma; + u32 buf_size = s->buf_size; + struct pci_dev *pci_dev = s->cx->pci_dev; + struct cx18_buffer *buf; + + list_for_each_entry(buf, &mdl->buf_list, list) + dma_sync_single_for_device(&pci_dev->dev, buf->dma_handle, + buf_size, dma); +} + +int cx18_stream_alloc(struct cx18_stream *s) +{ + struct cx18 *cx = s->cx; + int i; + + if (s->buffers == 0) + return 0; + + CX18_DEBUG_INFO("Allocate %s stream: %d x %d buffers (%d.%02d kB total)\n", + s->name, s->buffers, s->buf_size, + s->buffers * s->buf_size / 1024, + (s->buffers * s->buf_size * 100 / 1024) % 100); + + if (((char __iomem *)&cx->scb->cpu_mdl[cx->free_mdl_idx + s->buffers] - + (char __iomem *)cx->scb) > SCB_RESERVED_SIZE) { + unsigned bufsz = (((char __iomem *)cx->scb) + SCB_RESERVED_SIZE - + ((char __iomem *)cx->scb->cpu_mdl)); + + CX18_ERR("Too many buffers, cannot fit in SCB area\n"); + CX18_ERR("Max buffers = %zu\n", + bufsz / sizeof(struct cx18_mdl_ent)); + return -ENOMEM; + } + + s->mdl_base_idx = cx->free_mdl_idx; + + /* allocate stream buffers and MDLs */ + for (i = 0; i < s->buffers; i++) { + struct cx18_mdl *mdl; + struct cx18_buffer *buf; + + /* 1 MDL per buffer to handle the worst & also default case */ + mdl = kzalloc_obj(struct cx18_mdl, GFP_KERNEL | __GFP_NOWARN); + if (mdl == NULL) + break; + + buf = kzalloc_obj(struct cx18_buffer, GFP_KERNEL | __GFP_NOWARN); + if (buf == NULL) { + kfree(mdl); + break; + } + + buf->buf = kmalloc(s->buf_size, GFP_KERNEL|__GFP_NOWARN); + if (buf->buf == NULL) { + kfree(mdl); + kfree(buf); + break; + } + + buf->dma_handle = dma_map_single(&s->cx->pci_dev->dev, + buf->buf, s->buf_size, + s->dma); + if (dma_mapping_error(&s->cx->pci_dev->dev, buf->dma_handle)) { + kfree(buf->buf); + kfree(mdl); + kfree(buf); + break; + } + + INIT_LIST_HEAD(&mdl->list); + INIT_LIST_HEAD(&mdl->buf_list); + mdl->id = s->mdl_base_idx; /* a somewhat safe value */ + cx18_enqueue(s, mdl, &s->q_idle); + + INIT_LIST_HEAD(&buf->list); + cx18_buf_sync_for_cpu(s, buf); + list_add_tail(&buf->list, &s->buf_pool); + } + if (i == s->buffers) { + cx->free_mdl_idx += s->buffers; + return 0; + } + CX18_ERR("Couldn't allocate buffers for %s stream\n", s->name); + cx18_stream_free(s); + return -ENOMEM; +} + +void cx18_stream_free(struct cx18_stream *s) +{ + struct cx18_mdl *mdl; + struct cx18_buffer *buf; + struct cx18 *cx = s->cx; + + CX18_DEBUG_INFO("Deallocating buffers for %s stream\n", s->name); + + /* move all buffers to buf_pool and all MDLs to q_idle */ + cx18_unload_queues(s); + + /* empty q_idle */ + while ((mdl = cx18_dequeue(s, &s->q_idle))) + kfree(mdl); + + /* empty buf_pool */ + while (!list_empty(&s->buf_pool)) { + buf = list_first_entry(&s->buf_pool, struct cx18_buffer, list); + list_del_init(&buf->list); + + dma_unmap_single(&s->cx->pci_dev->dev, buf->dma_handle, + s->buf_size, s->dma); + kfree(buf->buf); + kfree(buf); + } +} diff --git a/drivers/media/pci/cx18/cx18-queue.h b/drivers/media/pci/cx18/cx18-queue.h new file mode 100644 index 000000000..972f234ff --- /dev/null +++ b/drivers/media/pci/cx18/cx18-queue.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 buffer queues + * + * Derived from ivtv-queue.h + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#define CX18_DMA_UNMAPPED ((u32) -1) + +/* cx18_buffer utility functions */ + +static inline void cx18_buf_sync_for_cpu(struct cx18_stream *s, + struct cx18_buffer *buf) +{ + dma_sync_single_for_cpu(&s->cx->pci_dev->dev, buf->dma_handle, + s->buf_size, s->dma); +} + +static inline void cx18_buf_sync_for_device(struct cx18_stream *s, + struct cx18_buffer *buf) +{ + dma_sync_single_for_device(&s->cx->pci_dev->dev, buf->dma_handle, + s->buf_size, s->dma); +} + +void _cx18_mdl_sync_for_device(struct cx18_stream *s, struct cx18_mdl *mdl); + +static inline void cx18_mdl_sync_for_device(struct cx18_stream *s, + struct cx18_mdl *mdl) +{ + if (list_is_singular(&mdl->buf_list)) + cx18_buf_sync_for_device(s, list_first_entry(&mdl->buf_list, + struct cx18_buffer, + list)); + else + _cx18_mdl_sync_for_device(s, mdl); +} + +void cx18_buf_swap(struct cx18_buffer *buf); +void _cx18_mdl_swap(struct cx18_mdl *mdl); + +static inline void cx18_mdl_swap(struct cx18_mdl *mdl) +{ + if (list_is_singular(&mdl->buf_list)) + cx18_buf_swap(list_first_entry(&mdl->buf_list, + struct cx18_buffer, list)); + else + _cx18_mdl_swap(mdl); +} + +/* cx18_queue utility functions */ +struct cx18_queue *_cx18_enqueue(struct cx18_stream *s, struct cx18_mdl *mdl, + struct cx18_queue *q, int to_front); + +static inline +struct cx18_queue *cx18_enqueue(struct cx18_stream *s, struct cx18_mdl *mdl, + struct cx18_queue *q) +{ + return _cx18_enqueue(s, mdl, q, 0); /* FIFO */ +} + +static inline +struct cx18_queue *cx18_push(struct cx18_stream *s, struct cx18_mdl *mdl, + struct cx18_queue *q) +{ + return _cx18_enqueue(s, mdl, q, 1); /* LIFO */ +} + +void cx18_queue_init(struct cx18_queue *q); +struct cx18_mdl *cx18_dequeue(struct cx18_stream *s, struct cx18_queue *q); +struct cx18_mdl *cx18_queue_get_mdl(struct cx18_stream *s, u32 id, + u32 bytesused); +void cx18_flush_queues(struct cx18_stream *s); + +/* queue MDL reconfiguration helpers */ +void cx18_unload_queues(struct cx18_stream *s); +void cx18_load_queues(struct cx18_stream *s); + +/* cx18_stream utility functions */ +int cx18_stream_alloc(struct cx18_stream *s); +void cx18_stream_free(struct cx18_stream *s); diff --git a/drivers/media/pci/cx18/cx18-scb.c b/drivers/media/pci/cx18/cx18-scb.c new file mode 100644 index 000000000..670dd9b0a --- /dev/null +++ b/drivers/media/pci/cx18/cx18-scb.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 System Control Block initialization + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#include "cx18-driver.h" +#include "cx18-io.h" +#include "cx18-scb.h" + +void cx18_init_scb(struct cx18 *cx) +{ + cx18_setup_page(cx, SCB_OFFSET); + cx18_memset_io(cx, cx->scb, 0, 0x10000); + + cx18_writel(cx, IRQ_APU_TO_CPU, &cx->scb->apu2cpu_irq); + cx18_writel(cx, IRQ_CPU_TO_APU_ACK, &cx->scb->cpu2apu_irq_ack); + cx18_writel(cx, IRQ_HPU_TO_CPU, &cx->scb->hpu2cpu_irq); + cx18_writel(cx, IRQ_CPU_TO_HPU_ACK, &cx->scb->cpu2hpu_irq_ack); + cx18_writel(cx, IRQ_PPU_TO_CPU, &cx->scb->ppu2cpu_irq); + cx18_writel(cx, IRQ_CPU_TO_PPU_ACK, &cx->scb->cpu2ppu_irq_ack); + cx18_writel(cx, IRQ_EPU_TO_CPU, &cx->scb->epu2cpu_irq); + cx18_writel(cx, IRQ_CPU_TO_EPU_ACK, &cx->scb->cpu2epu_irq_ack); + + cx18_writel(cx, IRQ_CPU_TO_APU, &cx->scb->cpu2apu_irq); + cx18_writel(cx, IRQ_APU_TO_CPU_ACK, &cx->scb->apu2cpu_irq_ack); + cx18_writel(cx, IRQ_HPU_TO_APU, &cx->scb->hpu2apu_irq); + cx18_writel(cx, IRQ_APU_TO_HPU_ACK, &cx->scb->apu2hpu_irq_ack); + cx18_writel(cx, IRQ_PPU_TO_APU, &cx->scb->ppu2apu_irq); + cx18_writel(cx, IRQ_APU_TO_PPU_ACK, &cx->scb->apu2ppu_irq_ack); + cx18_writel(cx, IRQ_EPU_TO_APU, &cx->scb->epu2apu_irq); + cx18_writel(cx, IRQ_APU_TO_EPU_ACK, &cx->scb->apu2epu_irq_ack); + + cx18_writel(cx, IRQ_CPU_TO_HPU, &cx->scb->cpu2hpu_irq); + cx18_writel(cx, IRQ_HPU_TO_CPU_ACK, &cx->scb->hpu2cpu_irq_ack); + cx18_writel(cx, IRQ_APU_TO_HPU, &cx->scb->apu2hpu_irq); + cx18_writel(cx, IRQ_HPU_TO_APU_ACK, &cx->scb->hpu2apu_irq_ack); + cx18_writel(cx, IRQ_PPU_TO_HPU, &cx->scb->ppu2hpu_irq); + cx18_writel(cx, IRQ_HPU_TO_PPU_ACK, &cx->scb->hpu2ppu_irq_ack); + cx18_writel(cx, IRQ_EPU_TO_HPU, &cx->scb->epu2hpu_irq); + cx18_writel(cx, IRQ_HPU_TO_EPU_ACK, &cx->scb->hpu2epu_irq_ack); + + cx18_writel(cx, IRQ_CPU_TO_PPU, &cx->scb->cpu2ppu_irq); + cx18_writel(cx, IRQ_PPU_TO_CPU_ACK, &cx->scb->ppu2cpu_irq_ack); + cx18_writel(cx, IRQ_APU_TO_PPU, &cx->scb->apu2ppu_irq); + cx18_writel(cx, IRQ_PPU_TO_APU_ACK, &cx->scb->ppu2apu_irq_ack); + cx18_writel(cx, IRQ_HPU_TO_PPU, &cx->scb->hpu2ppu_irq); + cx18_writel(cx, IRQ_PPU_TO_HPU_ACK, &cx->scb->ppu2hpu_irq_ack); + cx18_writel(cx, IRQ_EPU_TO_PPU, &cx->scb->epu2ppu_irq); + cx18_writel(cx, IRQ_PPU_TO_EPU_ACK, &cx->scb->ppu2epu_irq_ack); + + cx18_writel(cx, IRQ_CPU_TO_EPU, &cx->scb->cpu2epu_irq); + cx18_writel(cx, IRQ_EPU_TO_CPU_ACK, &cx->scb->epu2cpu_irq_ack); + cx18_writel(cx, IRQ_APU_TO_EPU, &cx->scb->apu2epu_irq); + cx18_writel(cx, IRQ_EPU_TO_APU_ACK, &cx->scb->epu2apu_irq_ack); + cx18_writel(cx, IRQ_HPU_TO_EPU, &cx->scb->hpu2epu_irq); + cx18_writel(cx, IRQ_EPU_TO_HPU_ACK, &cx->scb->epu2hpu_irq_ack); + cx18_writel(cx, IRQ_PPU_TO_EPU, &cx->scb->ppu2epu_irq); + cx18_writel(cx, IRQ_EPU_TO_PPU_ACK, &cx->scb->epu2ppu_irq_ack); + + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, apu2cpu_mb), + &cx->scb->apu2cpu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, hpu2cpu_mb), + &cx->scb->hpu2cpu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, ppu2cpu_mb), + &cx->scb->ppu2cpu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, epu2cpu_mb), + &cx->scb->epu2cpu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, cpu2apu_mb), + &cx->scb->cpu2apu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, hpu2apu_mb), + &cx->scb->hpu2apu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, ppu2apu_mb), + &cx->scb->ppu2apu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, epu2apu_mb), + &cx->scb->epu2apu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, cpu2hpu_mb), + &cx->scb->cpu2hpu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, apu2hpu_mb), + &cx->scb->apu2hpu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, ppu2hpu_mb), + &cx->scb->ppu2hpu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, epu2hpu_mb), + &cx->scb->epu2hpu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, cpu2ppu_mb), + &cx->scb->cpu2ppu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, apu2ppu_mb), + &cx->scb->apu2ppu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, hpu2ppu_mb), + &cx->scb->hpu2ppu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, epu2ppu_mb), + &cx->scb->epu2ppu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, cpu2epu_mb), + &cx->scb->cpu2epu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, apu2epu_mb), + &cx->scb->apu2epu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, hpu2epu_mb), + &cx->scb->hpu2epu_mb_offset); + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, ppu2epu_mb), + &cx->scb->ppu2epu_mb_offset); + + cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, cpu_state), + &cx->scb->ipc_offset); + + cx18_writel(cx, 1, &cx->scb->epu_state); +} diff --git a/drivers/media/pci/cx18/cx18-scb.h b/drivers/media/pci/cx18/cx18-scb.h new file mode 100644 index 000000000..900f72919 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-scb.h @@ -0,0 +1,266 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 System Control Block initialization + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#ifndef CX18_SCB_H +#define CX18_SCB_H + +#include "cx18-mailbox.h" + +/* NOTE: All ACK interrupts are in the SW2 register. All non-ACK interrupts + are in the SW1 register. */ + +#define IRQ_APU_TO_CPU 0x00000001 +#define IRQ_CPU_TO_APU_ACK 0x00000001 +#define IRQ_HPU_TO_CPU 0x00000002 +#define IRQ_CPU_TO_HPU_ACK 0x00000002 +#define IRQ_PPU_TO_CPU 0x00000004 +#define IRQ_CPU_TO_PPU_ACK 0x00000004 +#define IRQ_EPU_TO_CPU 0x00000008 +#define IRQ_CPU_TO_EPU_ACK 0x00000008 + +#define IRQ_CPU_TO_APU 0x00000010 +#define IRQ_APU_TO_CPU_ACK 0x00000010 +#define IRQ_HPU_TO_APU 0x00000020 +#define IRQ_APU_TO_HPU_ACK 0x00000020 +#define IRQ_PPU_TO_APU 0x00000040 +#define IRQ_APU_TO_PPU_ACK 0x00000040 +#define IRQ_EPU_TO_APU 0x00000080 +#define IRQ_APU_TO_EPU_ACK 0x00000080 + +#define IRQ_CPU_TO_HPU 0x00000100 +#define IRQ_HPU_TO_CPU_ACK 0x00000100 +#define IRQ_APU_TO_HPU 0x00000200 +#define IRQ_HPU_TO_APU_ACK 0x00000200 +#define IRQ_PPU_TO_HPU 0x00000400 +#define IRQ_HPU_TO_PPU_ACK 0x00000400 +#define IRQ_EPU_TO_HPU 0x00000800 +#define IRQ_HPU_TO_EPU_ACK 0x00000800 + +#define IRQ_CPU_TO_PPU 0x00001000 +#define IRQ_PPU_TO_CPU_ACK 0x00001000 +#define IRQ_APU_TO_PPU 0x00002000 +#define IRQ_PPU_TO_APU_ACK 0x00002000 +#define IRQ_HPU_TO_PPU 0x00004000 +#define IRQ_PPU_TO_HPU_ACK 0x00004000 +#define IRQ_EPU_TO_PPU 0x00008000 +#define IRQ_PPU_TO_EPU_ACK 0x00008000 + +#define IRQ_CPU_TO_EPU 0x00010000 +#define IRQ_EPU_TO_CPU_ACK 0x00010000 +#define IRQ_APU_TO_EPU 0x00020000 +#define IRQ_EPU_TO_APU_ACK 0x00020000 +#define IRQ_HPU_TO_EPU 0x00040000 +#define IRQ_EPU_TO_HPU_ACK 0x00040000 +#define IRQ_PPU_TO_EPU 0x00080000 +#define IRQ_EPU_TO_PPU_ACK 0x00080000 + +#define SCB_OFFSET 0xDC0000 + +/* If Firmware uses fixed memory map, it shall not allocate the area + between SCB_OFFSET and SCB_OFFSET+SCB_RESERVED_SIZE-1 inclusive */ +#define SCB_RESERVED_SIZE 0x10000 + + +/* This structure is used by EPU to provide memory descriptors in its memory */ +struct cx18_mdl_ent { + u32 paddr; /* Physical address of a buffer segment */ + u32 length; /* Length of the buffer segment */ +}; + +struct cx18_scb { + /* These fields form the System Control Block which is used at boot time + for localizing the IPC data as well as the code positions for all + processors. The offsets are from the start of this struct. */ + + /* Offset where to find the Inter-Processor Communication data */ + u32 ipc_offset; + u32 reserved01[7]; + /* Offset where to find the start of the CPU code */ + u32 cpu_code_offset; + u32 reserved02[3]; + /* Offset where to find the start of the APU code */ + u32 apu_code_offset; + u32 reserved03[3]; + /* Offset where to find the start of the HPU code */ + u32 hpu_code_offset; + u32 reserved04[3]; + /* Offset where to find the start of the PPU code */ + u32 ppu_code_offset; + u32 reserved05[3]; + + /* These fields form Inter-Processor Communication data which is used + by all processors to locate the information needed for communicating + with other processors */ + + /* Fields for CPU: */ + + /* bit 0: 1/0 processor ready/not ready. Set other bits to 0. */ + u32 cpu_state; + u32 reserved1[7]; + /* Offset to the mailbox used for sending commands from APU to CPU */ + u32 apu2cpu_mb_offset; + /* Value to write to register SW1 register set (0xC7003100) after the + command is ready */ + u32 apu2cpu_irq; + /* Value to write to register SW2 register set (0xC7003140) after the + command is cleared */ + u32 cpu2apu_irq_ack; + u32 reserved2[13]; + + u32 hpu2cpu_mb_offset; + u32 hpu2cpu_irq; + u32 cpu2hpu_irq_ack; + u32 reserved3[13]; + + u32 ppu2cpu_mb_offset; + u32 ppu2cpu_irq; + u32 cpu2ppu_irq_ack; + u32 reserved4[13]; + + u32 epu2cpu_mb_offset; + u32 epu2cpu_irq; + u32 cpu2epu_irq_ack; + u32 reserved5[13]; + u32 reserved6[8]; + + /* Fields for APU: */ + + u32 apu_state; + u32 reserved11[7]; + u32 cpu2apu_mb_offset; + u32 cpu2apu_irq; + u32 apu2cpu_irq_ack; + u32 reserved12[13]; + + u32 hpu2apu_mb_offset; + u32 hpu2apu_irq; + u32 apu2hpu_irq_ack; + u32 reserved13[13]; + + u32 ppu2apu_mb_offset; + u32 ppu2apu_irq; + u32 apu2ppu_irq_ack; + u32 reserved14[13]; + + u32 epu2apu_mb_offset; + u32 epu2apu_irq; + u32 apu2epu_irq_ack; + u32 reserved15[13]; + u32 reserved16[8]; + + /* Fields for HPU: */ + + u32 hpu_state; + u32 reserved21[7]; + u32 cpu2hpu_mb_offset; + u32 cpu2hpu_irq; + u32 hpu2cpu_irq_ack; + u32 reserved22[13]; + + u32 apu2hpu_mb_offset; + u32 apu2hpu_irq; + u32 hpu2apu_irq_ack; + u32 reserved23[13]; + + u32 ppu2hpu_mb_offset; + u32 ppu2hpu_irq; + u32 hpu2ppu_irq_ack; + u32 reserved24[13]; + + u32 epu2hpu_mb_offset; + u32 epu2hpu_irq; + u32 hpu2epu_irq_ack; + u32 reserved25[13]; + u32 reserved26[8]; + + /* Fields for PPU: */ + + u32 ppu_state; + u32 reserved31[7]; + u32 cpu2ppu_mb_offset; + u32 cpu2ppu_irq; + u32 ppu2cpu_irq_ack; + u32 reserved32[13]; + + u32 apu2ppu_mb_offset; + u32 apu2ppu_irq; + u32 ppu2apu_irq_ack; + u32 reserved33[13]; + + u32 hpu2ppu_mb_offset; + u32 hpu2ppu_irq; + u32 ppu2hpu_irq_ack; + u32 reserved34[13]; + + u32 epu2ppu_mb_offset; + u32 epu2ppu_irq; + u32 ppu2epu_irq_ack; + u32 reserved35[13]; + u32 reserved36[8]; + + /* Fields for EPU: */ + + u32 epu_state; + u32 reserved41[7]; + u32 cpu2epu_mb_offset; + u32 cpu2epu_irq; + u32 epu2cpu_irq_ack; + u32 reserved42[13]; + + u32 apu2epu_mb_offset; + u32 apu2epu_irq; + u32 epu2apu_irq_ack; + u32 reserved43[13]; + + u32 hpu2epu_mb_offset; + u32 hpu2epu_irq; + u32 epu2hpu_irq_ack; + u32 reserved44[13]; + + u32 ppu2epu_mb_offset; + u32 ppu2epu_irq; + u32 epu2ppu_irq_ack; + u32 reserved45[13]; + u32 reserved46[8]; + + u32 semaphores[8]; /* Semaphores */ + + u32 reserved50[32]; /* Reserved for future use */ + + struct cx18_mailbox apu2cpu_mb; + struct cx18_mailbox hpu2cpu_mb; + struct cx18_mailbox ppu2cpu_mb; + struct cx18_mailbox epu2cpu_mb; + + struct cx18_mailbox cpu2apu_mb; + struct cx18_mailbox hpu2apu_mb; + struct cx18_mailbox ppu2apu_mb; + struct cx18_mailbox epu2apu_mb; + + struct cx18_mailbox cpu2hpu_mb; + struct cx18_mailbox apu2hpu_mb; + struct cx18_mailbox ppu2hpu_mb; + struct cx18_mailbox epu2hpu_mb; + + struct cx18_mailbox cpu2ppu_mb; + struct cx18_mailbox apu2ppu_mb; + struct cx18_mailbox hpu2ppu_mb; + struct cx18_mailbox epu2ppu_mb; + + struct cx18_mailbox cpu2epu_mb; + struct cx18_mailbox apu2epu_mb; + struct cx18_mailbox hpu2epu_mb; + struct cx18_mailbox ppu2epu_mb; + + struct cx18_mdl_ack cpu_mdl_ack[CX18_MAX_STREAMS][CX18_MAX_MDL_ACKS]; + struct cx18_mdl_ent cpu_mdl[]; +}; + +void cx18_init_scb(struct cx18 *cx); + +#endif diff --git a/drivers/media/pci/cx18/cx18-streams.c b/drivers/media/pci/cx18/cx18-streams.c new file mode 100644 index 000000000..5da1a5d69 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-streams.c @@ -0,0 +1,1051 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 init/start/stop/exit stream functions + * + * Derived from ivtv-streams.c + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +#include "cx18-driver.h" +#include "cx18-io.h" +#include "cx18-fileops.h" +#include "cx18-mailbox.h" +#include "cx18-i2c.h" +#include "cx18-queue.h" +#include "cx18-ioctl.h" +#include "cx18-streams.h" +#include "cx18-cards.h" +#include "cx18-scb.h" +#include "cx18-dvb.h" + +#define CX18_DSP0_INTERRUPT_MASK 0xd0004C + +static const struct v4l2_file_operations cx18_v4l2_enc_fops = { + .owner = THIS_MODULE, + .read = cx18_v4l2_read, + .open = cx18_v4l2_open, + .unlocked_ioctl = video_ioctl2, + .release = cx18_v4l2_close, + .poll = cx18_v4l2_enc_poll, +}; + +static const struct v4l2_file_operations cx18_v4l2_enc_yuv_fops = { + .owner = THIS_MODULE, + .open = cx18_v4l2_open, + .unlocked_ioctl = video_ioctl2, + .release = cx18_v4l2_close, + .poll = vb2_fop_poll, + .read = vb2_fop_read, + .mmap = vb2_fop_mmap, +}; + +/* offset from 0 to register ts v4l2 minors on */ +#define CX18_V4L2_ENC_TS_OFFSET 16 +/* offset from 0 to register pcm v4l2 minors on */ +#define CX18_V4L2_ENC_PCM_OFFSET 24 +/* offset from 0 to register yuv v4l2 minors on */ +#define CX18_V4L2_ENC_YUV_OFFSET 32 + +static struct { + const char *name; + int vfl_type; + int num_offset; + int dma; + u32 caps; +} cx18_stream_info[] = { + { /* CX18_ENC_STREAM_TYPE_MPG */ + "encoder MPEG", + VFL_TYPE_VIDEO, 0, + DMA_FROM_DEVICE, + V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_AUDIO | V4L2_CAP_TUNER + }, + { /* CX18_ENC_STREAM_TYPE_TS */ + "TS", + VFL_TYPE_VIDEO, -1, + DMA_FROM_DEVICE, + }, + { /* CX18_ENC_STREAM_TYPE_YUV */ + "encoder YUV", + VFL_TYPE_VIDEO, CX18_V4L2_ENC_YUV_OFFSET, + DMA_FROM_DEVICE, + V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING | V4L2_CAP_AUDIO | V4L2_CAP_TUNER + }, + { /* CX18_ENC_STREAM_TYPE_VBI */ + "encoder VBI", + VFL_TYPE_VBI, 0, + DMA_FROM_DEVICE, + V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_CAPTURE | + V4L2_CAP_READWRITE | V4L2_CAP_AUDIO | V4L2_CAP_TUNER + }, + { /* CX18_ENC_STREAM_TYPE_PCM */ + "encoder PCM audio", + VFL_TYPE_VIDEO, CX18_V4L2_ENC_PCM_OFFSET, + DMA_FROM_DEVICE, + V4L2_CAP_TUNER | V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, + }, + { /* CX18_ENC_STREAM_TYPE_IDX */ + "encoder IDX", + VFL_TYPE_VIDEO, -1, + DMA_FROM_DEVICE, + }, + { /* CX18_ENC_STREAM_TYPE_RAD */ + "encoder radio", + VFL_TYPE_RADIO, 0, + DMA_NONE, + V4L2_CAP_RADIO | V4L2_CAP_TUNER + }, +}; + +static int cx18_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + unsigned int q_num_bufs = vb2_get_num_buffers(vq); + struct cx18_stream *s = vb2_get_drv_priv(vq); + struct cx18 *cx = s->cx; + unsigned int szimage; + + /* + * HM12 YUV size is (Y=(h*720) + UV=(h*(720/2))) + * UYUV YUV size is (Y=(h*720) + UV=(h*(720))) + */ + if (s->pixelformat == V4L2_PIX_FMT_NV12_16L16) + szimage = cx->cxhdl.height * 720 * 3 / 2; + else + szimage = cx->cxhdl.height * 720 * 2; + + /* + * Let's request at least three buffers: two for the + * DMA engine and one for userspace. + */ + if (q_num_bufs + *nbuffers < 3) + *nbuffers = 3 - q_num_bufs; + + if (*nplanes) { + if (*nplanes != 1 || sizes[0] < szimage) + return -EINVAL; + return 0; + } + + sizes[0] = szimage; + *nplanes = 1; + return 0; +} + +static void cx18_buf_queue(struct vb2_buffer *vb) +{ + struct cx18_stream *s = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx18_vb2_buffer *buf = + container_of(vbuf, struct cx18_vb2_buffer, vb); + unsigned long flags; + + spin_lock_irqsave(&s->vb_lock, flags); + list_add_tail(&buf->list, &s->vb_capture); + spin_unlock_irqrestore(&s->vb_lock, flags); +} + +static int cx18_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx18_stream *s = vb2_get_drv_priv(vb->vb2_queue); + struct cx18 *cx = s->cx; + unsigned int size; + + /* + * HM12 YUV size is (Y=(h*720) + UV=(h*(720/2))) + * UYUV YUV size is (Y=(h*720) + UV=(h*(720))) + */ + if (s->pixelformat == V4L2_PIX_FMT_NV12_16L16) + size = cx->cxhdl.height * 720 * 3 / 2; + else + size = cx->cxhdl.height * 720 * 2; + + if (vb2_plane_size(vb, 0) < size) + return -EINVAL; + vb2_set_plane_payload(vb, 0, size); + vbuf->field = V4L2_FIELD_INTERLACED; + return 0; +} + +void cx18_clear_queue(struct cx18_stream *s, enum vb2_buffer_state state) +{ + while (!list_empty(&s->vb_capture)) { + struct cx18_vb2_buffer *buf; + + buf = list_first_entry(&s->vb_capture, + struct cx18_vb2_buffer, list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, state); + } +} + +static int cx18_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct v4l2_fh *owner = vq->owner; + struct cx18_stream *s = vb2_get_drv_priv(vq); + unsigned long flags; + int rc; + + if (WARN_ON(!owner)) { + rc = -EIO; + goto clear_queue; + } + + s->sequence = 0; + rc = cx18_start_capture(fh2id(owner)); + if (!rc) { + /* Establish a buffer timeout */ + mod_timer(&s->vb_timeout, msecs_to_jiffies(2000) + jiffies); + return 0; + } + +clear_queue: + spin_lock_irqsave(&s->vb_lock, flags); + cx18_clear_queue(s, VB2_BUF_STATE_QUEUED); + spin_unlock_irqrestore(&s->vb_lock, flags); + return rc; +} + +static void cx18_stop_streaming(struct vb2_queue *vq) +{ + struct cx18_stream *s = vb2_get_drv_priv(vq); + unsigned long flags; + + cx18_stop_capture(s, 0); + timer_delete_sync(&s->vb_timeout); + spin_lock_irqsave(&s->vb_lock, flags); + cx18_clear_queue(s, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&s->vb_lock, flags); +} + +static const struct vb2_ops cx18_vb2_qops = { + .queue_setup = cx18_queue_setup, + .buf_queue = cx18_buf_queue, + .buf_prepare = cx18_buf_prepare, + .start_streaming = cx18_start_streaming, + .stop_streaming = cx18_stop_streaming, +}; + +static int cx18_stream_init(struct cx18 *cx, int type) +{ + struct cx18_stream *s = &cx->streams[type]; + int err = 0; + + memset(s, 0, sizeof(*s)); + + /* initialize cx18_stream fields */ + s->dvb = NULL; + s->cx = cx; + s->type = type; + s->name = cx18_stream_info[type].name; + s->handle = CX18_INVALID_TASK_HANDLE; + + s->dma = cx18_stream_info[type].dma; + s->v4l2_dev_caps = cx18_stream_info[type].caps; + s->buffers = cx->stream_buffers[type]; + s->buf_size = cx->stream_buf_size[type]; + INIT_LIST_HEAD(&s->buf_pool); + s->bufs_per_mdl = 1; + s->mdl_size = s->buf_size * s->bufs_per_mdl; + + init_waitqueue_head(&s->waitq); + s->id = -1; + spin_lock_init(&s->q_free.lock); + cx18_queue_init(&s->q_free); + spin_lock_init(&s->q_busy.lock); + cx18_queue_init(&s->q_busy); + spin_lock_init(&s->q_full.lock); + cx18_queue_init(&s->q_full); + spin_lock_init(&s->q_idle.lock); + cx18_queue_init(&s->q_idle); + + INIT_WORK(&s->out_work_order, cx18_out_work_handler); + + INIT_LIST_HEAD(&s->vb_capture); + timer_setup(&s->vb_timeout, cx18_vb_timeout, 0); + spin_lock_init(&s->vb_lock); + + if (type == CX18_ENC_STREAM_TYPE_YUV) { + s->vb_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* Assume the previous pixel default */ + s->pixelformat = V4L2_PIX_FMT_NV12_16L16; + s->vb_bytes_per_frame = cx->cxhdl.height * 720 * 3 / 2; + s->vb_bytes_per_line = 720; + + s->vidq.io_modes = VB2_READ | VB2_MMAP | VB2_DMABUF; + s->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + s->vidq.drv_priv = s; + s->vidq.buf_struct_size = sizeof(struct cx18_vb2_buffer); + s->vidq.ops = &cx18_vb2_qops; + s->vidq.mem_ops = &vb2_vmalloc_memops; + s->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + s->vidq.min_queued_buffers = 2; + s->vidq.gfp_flags = GFP_DMA32; + s->vidq.dev = &cx->pci_dev->dev; + s->vidq.lock = &cx->serialize_lock; + + err = vb2_queue_init(&s->vidq); + if (err) + v4l2_err(&cx->v4l2_dev, "cannot init vb2 queue\n"); + s->video_dev.queue = &s->vidq; + } + return err; +} + +static int cx18_prep_dev(struct cx18 *cx, int type) +{ + struct cx18_stream *s = &cx->streams[type]; + u32 cap = cx->v4l2_cap; + int num_offset = cx18_stream_info[type].num_offset; + int num = cx->instance + cx18_first_minor + num_offset; + int err; + + /* + * These five fields are always initialized. + * For analog capture related streams, if video_dev.v4l2_dev == NULL then the + * stream is not in use. + * For the TS stream, if dvb == NULL then the stream is not in use. + * In those cases no other fields but these four can be used. + */ + s->video_dev.v4l2_dev = NULL; + s->dvb = NULL; + s->cx = cx; + s->type = type; + s->name = cx18_stream_info[type].name; + + /* Check whether the radio is supported */ + if (type == CX18_ENC_STREAM_TYPE_RAD && !(cap & V4L2_CAP_RADIO)) + return 0; + + /* Check whether VBI is supported */ + if (type == CX18_ENC_STREAM_TYPE_VBI && + !(cap & (V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_CAPTURE))) + return 0; + + /* User explicitly selected 0 buffers for these streams, so don't + create them. */ + if (cx18_stream_info[type].dma != DMA_NONE && + cx->stream_buffers[type] == 0) { + CX18_INFO("Disabled %s device\n", cx18_stream_info[type].name); + return 0; + } + + err = cx18_stream_init(cx, type); + if (err) + return err; + + /* Allocate the cx18_dvb struct only for the TS on cards with DTV */ + if (type == CX18_ENC_STREAM_TYPE_TS) { + if (cx->card->hw_all & CX18_HW_DVB) { + s->dvb = kzalloc_obj(struct cx18_dvb); + if (s->dvb == NULL) { + CX18_ERR("Couldn't allocate cx18_dvb structure for %s\n", + s->name); + return -ENOMEM; + } + } else { + /* Don't need buffers for the TS, if there is no DVB */ + s->buffers = 0; + } + } + + if (num_offset == -1) + return 0; + + /* initialize the v4l2 video device structure */ + snprintf(s->video_dev.name, sizeof(s->video_dev.name), "%s %s", + cx->v4l2_dev.name, s->name); + + s->video_dev.num = num; + s->video_dev.v4l2_dev = &cx->v4l2_dev; + if (type == CX18_ENC_STREAM_TYPE_YUV) + s->video_dev.fops = &cx18_v4l2_enc_yuv_fops; + else + s->video_dev.fops = &cx18_v4l2_enc_fops; + s->video_dev.release = video_device_release_empty; + if (cx->card->video_inputs->video_type == CX18_CARD_INPUT_VID_TUNER) + s->video_dev.tvnorms = cx->tuner_std; + else + s->video_dev.tvnorms = V4L2_STD_ALL; + s->video_dev.lock = &cx->serialize_lock; + cx18_set_funcs(&s->video_dev); + return 0; +} + +/* Initialize v4l2 variables and register v4l2 devices */ +int cx18_streams_setup(struct cx18 *cx) +{ + int type, ret; + + /* Setup V4L2 Devices */ + for (type = 0; type < CX18_MAX_STREAMS; type++) { + /* Prepare device */ + ret = cx18_prep_dev(cx, type); + if (ret < 0) + break; + + /* Allocate Stream */ + ret = cx18_stream_alloc(&cx->streams[type]); + if (ret < 0) + break; + } + if (type == CX18_MAX_STREAMS) + return 0; + + /* One or more streams could not be initialized. Clean 'em all up. */ + cx18_streams_cleanup(cx, 0); + return ret; +} + +static int cx18_reg_dev(struct cx18 *cx, int type) +{ + struct cx18_stream *s = &cx->streams[type]; + int vfl_type = cx18_stream_info[type].vfl_type; + const char *name; + int num, ret; + + if (type == CX18_ENC_STREAM_TYPE_TS && s->dvb != NULL) { + ret = cx18_dvb_register(s); + if (ret < 0) { + CX18_ERR("DVB failed to register\n"); + return ret; + } + } + + if (s->video_dev.v4l2_dev == NULL) + return 0; + + num = s->video_dev.num; + s->video_dev.device_caps = s->v4l2_dev_caps; /* device capabilities */ + /* card number + user defined offset + device offset */ + if (type != CX18_ENC_STREAM_TYPE_MPG) { + struct cx18_stream *s_mpg = &cx->streams[CX18_ENC_STREAM_TYPE_MPG]; + + if (s_mpg->video_dev.v4l2_dev) + num = s_mpg->video_dev.num + + cx18_stream_info[type].num_offset; + } + video_set_drvdata(&s->video_dev, s); + + /* Register device. First try the desired minor, then any free one. */ + ret = video_register_device_no_warn(&s->video_dev, vfl_type, num); + if (ret < 0) { + CX18_ERR("Couldn't register v4l2 device for %s (device node number %d)\n", + s->name, num); + s->video_dev.v4l2_dev = NULL; + return ret; + } + + name = video_device_node_name(&s->video_dev); + + switch (vfl_type) { + case VFL_TYPE_VIDEO: + CX18_INFO("Registered device %s for %s (%d x %d.%02d kB)\n", + name, s->name, cx->stream_buffers[type], + cx->stream_buf_size[type] / 1024, + (cx->stream_buf_size[type] * 100 / 1024) % 100); + break; + + case VFL_TYPE_RADIO: + CX18_INFO("Registered device %s for %s\n", name, s->name); + break; + + case VFL_TYPE_VBI: + if (cx->stream_buffers[type]) + CX18_INFO("Registered device %s for %s (%d x %d bytes)\n", + name, s->name, cx->stream_buffers[type], + cx->stream_buf_size[type]); + else + CX18_INFO("Registered device %s for %s\n", + name, s->name); + break; + } + + return 0; +} + +/* Register v4l2 devices */ +int cx18_streams_register(struct cx18 *cx) +{ + int type; + int err; + int ret = 0; + + /* Register V4L2 devices */ + for (type = 0; type < CX18_MAX_STREAMS; type++) { + err = cx18_reg_dev(cx, type); + if (err && ret == 0) + ret = err; + } + + if (ret == 0) + return 0; + + /* One or more streams could not be initialized. Clean 'em all up. */ + cx18_streams_cleanup(cx, 1); + return ret; +} + +/* Unregister v4l2 devices */ +void cx18_streams_cleanup(struct cx18 *cx, int unregister) +{ + struct video_device *vdev; + int type; + + /* Teardown all streams */ + for (type = 0; type < CX18_MAX_STREAMS; type++) { + + /* The TS has a cx18_dvb structure, not a video_device */ + if (type == CX18_ENC_STREAM_TYPE_TS) { + if (cx->streams[type].dvb != NULL) { + if (unregister) + cx18_dvb_unregister(&cx->streams[type]); + kfree(cx->streams[type].dvb); + cx->streams[type].dvb = NULL; + cx18_stream_free(&cx->streams[type]); + } + continue; + } + + /* No struct video_device, but can have buffers allocated */ + if (type == CX18_ENC_STREAM_TYPE_IDX) { + /* If the module params didn't inhibit IDX ... */ + if (cx->stream_buffers[type] != 0) { + cx->stream_buffers[type] = 0; + /* + * Before calling cx18_stream_free(), + * check if the IDX stream was actually set up. + * Needed, since the cx18_probe() error path + * exits through here as well as normal clean up + */ + if (cx->streams[type].buffers != 0) + cx18_stream_free(&cx->streams[type]); + } + continue; + } + + /* If struct video_device exists, can have buffers allocated */ + vdev = &cx->streams[type].video_dev; + + if (vdev->v4l2_dev == NULL) + continue; + + cx18_stream_free(&cx->streams[type]); + + if (type == CX18_ENC_STREAM_TYPE_YUV) + vb2_video_unregister_device(vdev); + else + video_unregister_device(vdev); + } +} + +static void cx18_vbi_setup(struct cx18_stream *s) +{ + struct cx18 *cx = s->cx; + int raw = cx18_raw_vbi(cx); + u32 data[CX2341X_MBOX_MAX_DATA]; + int lines; + + if (cx->is_60hz) { + cx->vbi.count = 12; + cx->vbi.start[0] = 10; + cx->vbi.start[1] = 273; + } else { /* PAL/SECAM */ + cx->vbi.count = 18; + cx->vbi.start[0] = 6; + cx->vbi.start[1] = 318; + } + + /* setup VBI registers */ + if (raw) + v4l2_subdev_call(cx->sd_av, vbi, s_raw_fmt, &cx->vbi.in.fmt.vbi); + else + v4l2_subdev_call(cx->sd_av, vbi, s_sliced_fmt, &cx->vbi.in.fmt.sliced); + + /* + * Send the CX18_CPU_SET_RAW_VBI_PARAM API command to setup Encoder Raw + * VBI when the first analog capture channel starts, as once it starts + * (e.g. MPEG), we can't effect any change in the Encoder Raw VBI setup + * (i.e. for the VBI capture channels). We also send it for each + * analog capture channel anyway just to make sure we get the proper + * behavior + */ + if (raw) { + lines = cx->vbi.count * 2; + } else { + /* + * For 525/60 systems, according to the VIP 2 & BT.656 std: + * The EAV RP code's Field bit toggles on line 4, a few lines + * after the Vertcal Blank bit has already toggled. + * Tell the encoder to capture 21-4+1=18 lines per field, + * since we want lines 10 through 21. + * + * For 625/50 systems, according to the VIP 2 & BT.656 std: + * The EAV RP code's Field bit toggles on line 1, a few lines + * after the Vertcal Blank bit has already toggled. + * (We've actually set the digitizer so that the Field bit + * toggles on line 2.) Tell the encoder to capture 23-2+1=22 + * lines per field, since we want lines 6 through 23. + */ + lines = cx->is_60hz ? (21 - 4 + 1) * 2 : (23 - 2 + 1) * 2; + } + + data[0] = s->handle; + /* Lines per field */ + data[1] = (lines / 2) | ((lines / 2) << 16); + /* bytes per line */ + data[2] = (raw ? VBI_ACTIVE_SAMPLES + : (cx->is_60hz ? VBI_HBLANK_SAMPLES_60HZ + : VBI_HBLANK_SAMPLES_50HZ)); + /* Every X number of frames a VBI interrupt arrives + (frames as in 25 or 30 fps) */ + data[3] = 1; + /* + * Set the SAV/EAV RP codes to look for as start/stop points + * when in VIP-1.1 mode + */ + if (raw) { + /* + * Start codes for beginning of "active" line in vertical blank + * 0x20 ( VerticalBlank ) + * 0x60 ( EvenField VerticalBlank ) + */ + data[4] = 0x20602060; + /* + * End codes for end of "active" raw lines and regular lines + * 0x30 ( VerticalBlank HorizontalBlank) + * 0x70 ( EvenField VerticalBlank HorizontalBlank) + * 0x90 (Task HorizontalBlank) + * 0xd0 (Task EvenField HorizontalBlank) + */ + data[5] = 0x307090d0; + } else { + /* + * End codes for active video, we want data in the hblank region + * 0xb0 (Task 0 VerticalBlank HorizontalBlank) + * 0xf0 (Task EvenField VerticalBlank HorizontalBlank) + * + * Since the V bit is only allowed to toggle in the EAV RP code, + * just before the first active region line, these two + * are problematic: + * 0x90 (Task HorizontalBlank) + * 0xd0 (Task EvenField HorizontalBlank) + * + * We have set the digitzer such that we don't have to worry + * about these problem codes. + */ + data[4] = 0xB0F0B0F0; + /* + * Start codes for beginning of active line in vertical blank + * 0xa0 (Task VerticalBlank ) + * 0xe0 (Task EvenField VerticalBlank ) + */ + data[5] = 0xA0E0A0E0; + } + + CX18_DEBUG_INFO("Setup VBI h: %d lines %x bpl %d fr %d %x %x\n", + data[0], data[1], data[2], data[3], data[4], data[5]); + + cx18_api(cx, CX18_CPU_SET_RAW_VBI_PARAM, 6, data); +} + +void cx18_stream_rotate_idx_mdls(struct cx18 *cx) +{ + struct cx18_stream *s = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; + struct cx18_mdl *mdl; + + if (!cx18_stream_enabled(s)) + return; + + /* Return if the firmware is not running low on MDLs */ + if ((atomic_read(&s->q_free.depth) + atomic_read(&s->q_busy.depth)) >= + CX18_ENC_STREAM_TYPE_IDX_FW_MDL_MIN) + return; + + /* Return if there are no MDLs to rotate back to the firmware */ + if (atomic_read(&s->q_full.depth) < 2) + return; + + /* + * Take the oldest IDX MDL still holding data, and discard its index + * entries by scheduling the MDL to go back to the firmware + */ + mdl = cx18_dequeue(s, &s->q_full); + if (mdl != NULL) + cx18_enqueue(s, mdl, &s->q_free); +} + +static +struct cx18_queue *_cx18_stream_put_mdl_fw(struct cx18_stream *s, + struct cx18_mdl *mdl) +{ + struct cx18 *cx = s->cx; + struct cx18_queue *q; + + /* Don't give it to the firmware, if we're not running a capture */ + if (s->handle == CX18_INVALID_TASK_HANDLE || + test_bit(CX18_F_S_STOPPING, &s->s_flags) || + !test_bit(CX18_F_S_STREAMING, &s->s_flags)) + return cx18_enqueue(s, mdl, &s->q_free); + + q = cx18_enqueue(s, mdl, &s->q_busy); + if (q != &s->q_busy) + return q; /* The firmware has the max MDLs it can handle */ + + cx18_mdl_sync_for_device(s, mdl); + cx18_vapi(cx, CX18_CPU_DE_SET_MDL, 5, s->handle, + (void __iomem *) &cx->scb->cpu_mdl[mdl->id] - cx->enc_mem, + s->bufs_per_mdl, mdl->id, s->mdl_size); + return q; +} + +static +void _cx18_stream_load_fw_queue(struct cx18_stream *s) +{ + struct cx18_queue *q; + struct cx18_mdl *mdl; + + if (atomic_read(&s->q_free.depth) == 0 || + atomic_read(&s->q_busy.depth) >= CX18_MAX_FW_MDLS_PER_STREAM) + return; + + /* Move from q_free to q_busy notifying the firmware, until the limit */ + do { + mdl = cx18_dequeue(s, &s->q_free); + if (mdl == NULL) + break; + q = _cx18_stream_put_mdl_fw(s, mdl); + } while (atomic_read(&s->q_busy.depth) < CX18_MAX_FW_MDLS_PER_STREAM + && q == &s->q_busy); +} + +void cx18_out_work_handler(struct work_struct *work) +{ + struct cx18_stream *s = + container_of(work, struct cx18_stream, out_work_order); + + _cx18_stream_load_fw_queue(s); +} + +static void cx18_stream_configure_mdls(struct cx18_stream *s) +{ + cx18_unload_queues(s); + + switch (s->type) { + case CX18_ENC_STREAM_TYPE_YUV: + /* + * Height should be a multiple of 32 lines. + * Set the MDL size to the exact size needed for one frame. + * Use enough buffers per MDL to cover the MDL size + */ + if (s->pixelformat == V4L2_PIX_FMT_NV12_16L16) + s->mdl_size = 720 * s->cx->cxhdl.height * 3 / 2; + else + s->mdl_size = 720 * s->cx->cxhdl.height * 2; + s->bufs_per_mdl = s->mdl_size / s->buf_size; + if (s->mdl_size % s->buf_size) + s->bufs_per_mdl++; + break; + case CX18_ENC_STREAM_TYPE_VBI: + s->bufs_per_mdl = 1; + if (cx18_raw_vbi(s->cx)) { + s->mdl_size = (s->cx->is_60hz ? 12 : 18) + * 2 * VBI_ACTIVE_SAMPLES; + } else { + /* + * See comment in cx18_vbi_setup() below about the + * extra lines we capture in sliced VBI mode due to + * the lines on which EAV RP codes toggle. + */ + s->mdl_size = s->cx->is_60hz + ? (21 - 4 + 1) * 2 * VBI_HBLANK_SAMPLES_60HZ + : (23 - 2 + 1) * 2 * VBI_HBLANK_SAMPLES_50HZ; + } + break; + default: + s->bufs_per_mdl = 1; + s->mdl_size = s->buf_size * s->bufs_per_mdl; + break; + } + + cx18_load_queues(s); +} + +int cx18_start_v4l2_encode_stream(struct cx18_stream *s) +{ + u32 data[MAX_MB_ARGUMENTS]; + struct cx18 *cx = s->cx; + int captype = 0; + struct cx18_stream *s_idx; + + if (!cx18_stream_enabled(s)) + return -EINVAL; + + CX18_DEBUG_INFO("Start encoder stream %s\n", s->name); + + switch (s->type) { + case CX18_ENC_STREAM_TYPE_MPG: + captype = CAPTURE_CHANNEL_TYPE_MPEG; + cx->mpg_data_received = cx->vbi_data_inserted = 0; + cx->dualwatch_jiffies = jiffies; + cx->dualwatch_stereo_mode = v4l2_ctrl_g_ctrl(cx->cxhdl.audio_mode); + cx->search_pack_header = 0; + break; + + case CX18_ENC_STREAM_TYPE_IDX: + captype = CAPTURE_CHANNEL_TYPE_INDEX; + break; + case CX18_ENC_STREAM_TYPE_TS: + captype = CAPTURE_CHANNEL_TYPE_TS; + break; + case CX18_ENC_STREAM_TYPE_YUV: + captype = CAPTURE_CHANNEL_TYPE_YUV; + break; + case CX18_ENC_STREAM_TYPE_PCM: + captype = CAPTURE_CHANNEL_TYPE_PCM; + break; + case CX18_ENC_STREAM_TYPE_VBI: +#ifdef CX18_ENCODER_PARSES_SLICED + captype = cx18_raw_vbi(cx) ? + CAPTURE_CHANNEL_TYPE_VBI : CAPTURE_CHANNEL_TYPE_SLICED_VBI; +#else + /* + * Currently we set things up so that Sliced VBI from the + * digitizer is handled as Raw VBI by the encoder + */ + captype = CAPTURE_CHANNEL_TYPE_VBI; +#endif + cx->vbi.frame = 0; + cx->vbi.inserted_frame = 0; + memset(cx->vbi.sliced_mpeg_size, + 0, sizeof(cx->vbi.sliced_mpeg_size)); + break; + default: + return -EINVAL; + } + + /* Clear Streamoff flags in case left from last capture */ + clear_bit(CX18_F_S_STREAMOFF, &s->s_flags); + + cx18_vapi_result(cx, data, CX18_CREATE_TASK, 1, CPU_CMD_MASK_CAPTURE); + s->handle = data[0]; + cx18_vapi(cx, CX18_CPU_SET_CHANNEL_TYPE, 2, s->handle, captype); + + /* + * For everything but CAPTURE_CHANNEL_TYPE_TS, play it safe and + * set up all the parameters, as it is not obvious which parameters the + * firmware shares across capture channel types and which it does not. + * + * Some of the cx18_vapi() calls below apply to only certain capture + * channel types. We're hoping there's no harm in calling most of them + * anyway, as long as the values are all consistent. Setting some + * shared parameters will have no effect once an analog capture channel + * has started streaming. + */ + if (captype != CAPTURE_CHANNEL_TYPE_TS) { + cx18_vapi(cx, CX18_CPU_SET_VER_CROP_LINE, 2, s->handle, 0); + cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 3, s->handle, 3, 1); + cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 3, s->handle, 8, 0); + cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 3, s->handle, 4, 1); + + /* + * Audio related reset according to + * Documentation/driver-api/media/drivers/cx2341x-devel.rst + */ + if (atomic_read(&cx->ana_capturing) == 0) + cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 2, + s->handle, 12); + + /* + * Number of lines for Field 1 & Field 2 according to + * Documentation/driver-api/media/drivers/cx2341x-devel.rst + * Field 1 is 312 for 625 line systems in BT.656 + * Field 2 is 313 for 625 line systems in BT.656 + */ + cx18_vapi(cx, CX18_CPU_SET_CAPTURE_LINE_NO, 3, + s->handle, 312, 313); + + if (cx->v4l2_cap & V4L2_CAP_VBI_CAPTURE) + cx18_vbi_setup(s); + + /* + * Select to receive I, P, and B frame index entries, if the + * index stream is enabled. Otherwise disable index entry + * generation. + */ + s_idx = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; + cx18_vapi_result(cx, data, CX18_CPU_SET_INDEXTABLE, 2, + s->handle, cx18_stream_enabled(s_idx) ? 7 : 0); + + /* Call out to the common CX2341x API setup for user controls */ + cx->cxhdl.priv = s; + cx2341x_handler_setup(&cx->cxhdl); + + /* + * When starting a capture and we're set for radio, + * ensure the video is muted, despite the user control. + */ + if (!cx->cxhdl.video_mute && + test_bit(CX18_F_I_RADIO_USER, &cx->i_flags)) + cx18_vapi(cx, CX18_CPU_SET_VIDEO_MUTE, 2, s->handle, + (v4l2_ctrl_g_ctrl(cx->cxhdl.video_mute_yuv) << 8) | 1); + + /* Enable the Video Format Converter for UYVY 4:2:2 support, + * rather than the default HM12 Macroblovk 4:2:0 support. + */ + if (captype == CAPTURE_CHANNEL_TYPE_YUV) { + if (s->pixelformat == V4L2_PIX_FMT_UYVY) + cx18_vapi(cx, CX18_CPU_SET_VFC_PARAM, 2, + s->handle, 1); + else + /* If in doubt, default to HM12 */ + cx18_vapi(cx, CX18_CPU_SET_VFC_PARAM, 2, + s->handle, 0); + } + } + + if (atomic_read(&cx->tot_capturing) == 0) { + cx2341x_handler_set_busy(&cx->cxhdl, 1); + clear_bit(CX18_F_I_EOS, &cx->i_flags); + cx18_write_reg(cx, 7, CX18_DSP0_INTERRUPT_MASK); + } + + cx18_vapi(cx, CX18_CPU_DE_SET_MDL_ACK, 3, s->handle, + (void __iomem *)&cx->scb->cpu_mdl_ack[s->type][0] - cx->enc_mem, + (void __iomem *)&cx->scb->cpu_mdl_ack[s->type][1] - cx->enc_mem); + + /* Init all the cpu_mdls for this stream */ + cx18_stream_configure_mdls(s); + _cx18_stream_load_fw_queue(s); + + /* begin_capture */ + if (cx18_vapi(cx, CX18_CPU_CAPTURE_START, 1, s->handle)) { + CX18_DEBUG_WARN("Error starting capture!\n"); + /* Ensure we're really not capturing before releasing MDLs */ + set_bit(CX18_F_S_STOPPING, &s->s_flags); + if (s->type == CX18_ENC_STREAM_TYPE_MPG) + cx18_vapi(cx, CX18_CPU_CAPTURE_STOP, 2, s->handle, 1); + else + cx18_vapi(cx, CX18_CPU_CAPTURE_STOP, 1, s->handle); + clear_bit(CX18_F_S_STREAMING, &s->s_flags); + /* FIXME - CX18_F_S_STREAMOFF as well? */ + cx18_vapi(cx, CX18_CPU_DE_RELEASE_MDL, 1, s->handle); + cx18_vapi(cx, CX18_DESTROY_TASK, 1, s->handle); + s->handle = CX18_INVALID_TASK_HANDLE; + clear_bit(CX18_F_S_STOPPING, &s->s_flags); + if (atomic_read(&cx->tot_capturing) == 0) { + set_bit(CX18_F_I_EOS, &cx->i_flags); + cx18_write_reg(cx, 5, CX18_DSP0_INTERRUPT_MASK); + } + return -EINVAL; + } + + /* you're live! sit back and await interrupts :) */ + if (captype != CAPTURE_CHANNEL_TYPE_TS) + atomic_inc(&cx->ana_capturing); + atomic_inc(&cx->tot_capturing); + return 0; +} +EXPORT_SYMBOL(cx18_start_v4l2_encode_stream); + +void cx18_stop_all_captures(struct cx18 *cx) +{ + int i; + + for (i = CX18_MAX_STREAMS - 1; i >= 0; i--) { + struct cx18_stream *s = &cx->streams[i]; + + if (!cx18_stream_enabled(s)) + continue; + if (test_bit(CX18_F_S_STREAMING, &s->s_flags)) + cx18_stop_v4l2_encode_stream(s, 0); + } +} + +int cx18_stop_v4l2_encode_stream(struct cx18_stream *s, int gop_end) +{ + struct cx18 *cx = s->cx; + + if (!cx18_stream_enabled(s)) + return -EINVAL; + + /* This function assumes that you are allowed to stop the capture + and that we are actually capturing */ + + CX18_DEBUG_INFO("Stop Capture\n"); + + if (atomic_read(&cx->tot_capturing) == 0) + return 0; + + set_bit(CX18_F_S_STOPPING, &s->s_flags); + if (s->type == CX18_ENC_STREAM_TYPE_MPG) + cx18_vapi(cx, CX18_CPU_CAPTURE_STOP, 2, s->handle, !gop_end); + else + cx18_vapi(cx, CX18_CPU_CAPTURE_STOP, 1, s->handle); + + if (s->type == CX18_ENC_STREAM_TYPE_MPG && gop_end) { + CX18_INFO("ignoring gop_end: not (yet?) supported by the firmware\n"); + } + + if (s->type != CX18_ENC_STREAM_TYPE_TS) + atomic_dec(&cx->ana_capturing); + atomic_dec(&cx->tot_capturing); + + /* Clear capture and no-read bits */ + clear_bit(CX18_F_S_STREAMING, &s->s_flags); + + /* Tell the CX23418 it can't use our buffers anymore */ + cx18_vapi(cx, CX18_CPU_DE_RELEASE_MDL, 1, s->handle); + + cx18_vapi(cx, CX18_DESTROY_TASK, 1, s->handle); + s->handle = CX18_INVALID_TASK_HANDLE; + clear_bit(CX18_F_S_STOPPING, &s->s_flags); + + if (atomic_read(&cx->tot_capturing) > 0) + return 0; + + cx2341x_handler_set_busy(&cx->cxhdl, 0); + cx18_write_reg(cx, 5, CX18_DSP0_INTERRUPT_MASK); + wake_up(&s->waitq); + + return 0; +} +EXPORT_SYMBOL(cx18_stop_v4l2_encode_stream); + +u32 cx18_find_handle(struct cx18 *cx) +{ + int i; + + /* find first available handle to be used for global settings */ + for (i = 0; i < CX18_MAX_STREAMS; i++) { + struct cx18_stream *s = &cx->streams[i]; + + if (s->video_dev.v4l2_dev && (s->handle != CX18_INVALID_TASK_HANDLE)) + return s->handle; + } + return CX18_INVALID_TASK_HANDLE; +} + +struct cx18_stream *cx18_handle_to_stream(struct cx18 *cx, u32 handle) +{ + int i; + struct cx18_stream *s; + + if (handle == CX18_INVALID_TASK_HANDLE) + return NULL; + + for (i = 0; i < CX18_MAX_STREAMS; i++) { + s = &cx->streams[i]; + if (s->handle != handle) + continue; + if (cx18_stream_enabled(s)) + return s; + } + return NULL; +} diff --git a/drivers/media/pci/cx18/cx18-streams.h b/drivers/media/pci/cx18/cx18-streams.h new file mode 100644 index 000000000..e01bed6b4 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-streams.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 init/start/stop/exit stream functions + * + * Derived from ivtv-streams.h + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + */ + +u32 cx18_find_handle(struct cx18 *cx); +struct cx18_stream *cx18_handle_to_stream(struct cx18 *cx, u32 handle); +int cx18_streams_setup(struct cx18 *cx); +int cx18_streams_register(struct cx18 *cx); +void cx18_streams_cleanup(struct cx18 *cx, int unregister); + +#define CX18_ENC_STREAM_TYPE_IDX_FW_MDL_MIN (3) +void cx18_stream_rotate_idx_mdls(struct cx18 *cx); + +static inline bool cx18_stream_enabled(struct cx18_stream *s) +{ + return s->video_dev.v4l2_dev || + (s->dvb && s->dvb->enabled) || + (s->type == CX18_ENC_STREAM_TYPE_IDX && + s->cx->stream_buffers[CX18_ENC_STREAM_TYPE_IDX] != 0); +} + +/* Related to submission of mdls to firmware */ +static inline void cx18_stream_load_fw_queue(struct cx18_stream *s) +{ + schedule_work(&s->out_work_order); +} + +static inline void cx18_stream_put_mdl_fw(struct cx18_stream *s, + struct cx18_mdl *mdl) +{ + /* Put mdl on q_free; the out work handler will move mdl(s) to q_busy */ + cx18_enqueue(s, mdl, &s->q_free); + cx18_stream_load_fw_queue(s); +} + +void cx18_out_work_handler(struct work_struct *work); + +/* Capture related */ +int cx18_start_v4l2_encode_stream(struct cx18_stream *s); +int cx18_stop_v4l2_encode_stream(struct cx18_stream *s, int gop_end); + +void cx18_stop_all_captures(struct cx18 *cx); diff --git a/drivers/media/pci/cx18/cx18-vbi.c b/drivers/media/pci/cx18/cx18-vbi.c new file mode 100644 index 000000000..3a8430f32 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-vbi.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 Vertical Blank Interval support functions + * + * Derived from ivtv-vbi.c + * + * Copyright (C) 2007 Hans Verkuil + */ + +#include "cx18-driver.h" +#include "cx18-vbi.h" +#include "cx18-ioctl.h" +#include "cx18-queue.h" + +/* + * Raster Reference/Protection (RP) bytes, used in Start/End Active + * Video codes emitted from the digitzer in VIP 1.x mode, that flag the start + * of VBI sample or VBI ancillary data regions in the digital ratser line. + * + * Task FieldEven VerticalBlank HorizontalBlank 0 0 0 0 + */ +static const u8 raw_vbi_sav_rp[2] = { 0x20, 0x60 }; /* __V_, _FV_ */ +static const u8 sliced_vbi_eav_rp[2] = { 0xb0, 0xf0 }; /* T_VH, TFVH */ + +static void copy_vbi_data(struct cx18 *cx, int lines, u32 pts_stamp) +{ + int line = 0; + int i; + u32 linemask[2] = { 0, 0 }; + unsigned short size; + static const u8 mpeg_hdr_data[] = { + /* MPEG-2 Program Pack */ + 0x00, 0x00, 0x01, 0xba, /* Prog Pack start code */ + 0x44, 0x00, 0x0c, 0x66, 0x24, 0x01, /* SCR, SCR Ext, markers */ + 0x01, 0xd1, 0xd3, /* Mux Rate, markers */ + 0xfa, 0xff, 0xff, /* Res, Suff cnt, Stuff */ + /* MPEG-2 Private Stream 1 PES Packet */ + 0x00, 0x00, 0x01, 0xbd, /* Priv Stream 1 start */ + 0x00, 0x1a, /* length */ + 0x84, 0x80, 0x07, /* flags, hdr data len */ + 0x21, 0x00, 0x5d, 0x63, 0xa7, /* PTS, markers */ + 0xff, 0xff /* stuffing */ + }; + const int sd = sizeof(mpeg_hdr_data); /* start of vbi data */ + int idx = cx->vbi.frame % CX18_VBI_FRAMES; + u8 *dst = &cx->vbi.sliced_mpeg_data[idx][0]; + + for (i = 0; i < lines; i++) { + struct v4l2_sliced_vbi_data *sdata = cx->vbi.sliced_data + i; + int f, l; + + if (sdata->id == 0) + continue; + + l = sdata->line - 6; + f = sdata->field; + if (f) + l += 18; + if (l < 32) + linemask[0] |= (1 << l); + else + linemask[1] |= (1 << (l - 32)); + dst[sd + 12 + line * 43] = cx18_service2vbi(sdata->id); + memcpy(dst + sd + 12 + line * 43 + 1, sdata->data, 42); + line++; + } + memcpy(dst, mpeg_hdr_data, sizeof(mpeg_hdr_data)); + if (line == 36) { + /* All lines are used, so there is no space for the linemask + (the max size of the VBI data is 36 * 43 + 4 bytes). + So in this case we use the magic number 'ITV0'. */ + memcpy(dst + sd, "ITV0", 4); + memmove(dst + sd + 4, dst + sd + 12, line * 43); + size = 4 + ((43 * line + 3) & ~3); + } else { + memcpy(dst + sd, "itv0", 4); + cpu_to_le32s(&linemask[0]); + cpu_to_le32s(&linemask[1]); + memcpy(dst + sd + 4, &linemask[0], 8); + size = 12 + ((43 * line + 3) & ~3); + } + dst[4+16] = (size + 10) >> 8; + dst[5+16] = (size + 10) & 0xff; + dst[9+16] = 0x21 | ((pts_stamp >> 29) & 0x6); + dst[10+16] = (pts_stamp >> 22) & 0xff; + dst[11+16] = 1 | ((pts_stamp >> 14) & 0xff); + dst[12+16] = (pts_stamp >> 7) & 0xff; + dst[13+16] = 1 | ((pts_stamp & 0x7f) << 1); + cx->vbi.sliced_mpeg_size[idx] = sd + size; +} + +/* Compress raw VBI format, removes leading SAV codes and surplus space + after the frame. Returns new compressed size. */ +/* FIXME - this function ignores the input size. */ +static u32 compress_raw_buf(struct cx18 *cx, u8 *buf, u32 size, u32 hdr_size) +{ + u32 line_size = VBI_ACTIVE_SAMPLES; + u32 lines = cx->vbi.count * 2; + u8 *q = buf; + u8 *p; + int i; + + /* Skip the header */ + buf += hdr_size; + + for (i = 0; i < lines; i++) { + p = buf + i * line_size; + + /* Look for SAV code */ + if (p[0] != 0xff || p[1] || p[2] || + (p[3] != raw_vbi_sav_rp[0] && + p[3] != raw_vbi_sav_rp[1])) + break; + if (i == lines - 1) { + /* last line is hdr_size bytes short - extrapolate it */ + memcpy(q, p + 4, line_size - 4 - hdr_size); + q += line_size - 4 - hdr_size; + p += line_size - hdr_size - 1; + memset(q, (int) *p, hdr_size); + } else { + memcpy(q, p + 4, line_size - 4); + q += line_size - 4; + } + } + return lines * (line_size - 4); +} + +static u32 compress_sliced_buf(struct cx18 *cx, u8 *buf, u32 size, + const u32 hdr_size) +{ + struct v4l2_decode_vbi_line vbi; + int i; + u32 line = 0; + u32 line_size = cx->is_60hz ? VBI_HBLANK_SAMPLES_60HZ + : VBI_HBLANK_SAMPLES_50HZ; + + /* find the first valid line */ + for (i = hdr_size, buf += hdr_size; i + 3 < size; i++, buf++) { + if (buf[0] == 0xff && !buf[1] && !buf[2] && + (buf[3] == sliced_vbi_eav_rp[0] || + buf[3] == sliced_vbi_eav_rp[1])) + break; + } + + /* + * The last line is short by hdr_size bytes, but for the remaining + * checks against size, we pretend that it is not, by counting the + * header bytes we knowingly skipped + */ + size -= (i - hdr_size); + if (size < line_size) + return line; + + for (i = 0; i < size / line_size; i++) { + u8 *p = buf + i * line_size; + + /* Look for EAV code */ + if (p[0] != 0xff || p[1] || p[2] || + (p[3] != sliced_vbi_eav_rp[0] && + p[3] != sliced_vbi_eav_rp[1])) + continue; + vbi.p = p + 4; + v4l2_subdev_call(cx->sd_av, vbi, decode_vbi_line, &vbi); + if (vbi.type) { + cx->vbi.sliced_data[line].id = vbi.type; + cx->vbi.sliced_data[line].field = vbi.is_second_field; + cx->vbi.sliced_data[line].line = vbi.line; + memcpy(cx->vbi.sliced_data[line].data, vbi.p, 42); + line++; + } + } + return line; +} + +static void _cx18_process_vbi_data(struct cx18 *cx, struct cx18_buffer *buf) +{ + /* + * The CX23418 provides a 12 byte header in its raw VBI buffers to us: + * 0x3fffffff [4 bytes of something] [4 byte presentation time stamp] + */ + struct vbi_data_hdr { + __be32 magic; + __be32 unknown; + __be32 pts; + } *hdr = (struct vbi_data_hdr *) buf->buf; + + u8 *p = (u8 *) buf->buf; + u32 size = buf->bytesused; + u32 pts; + int lines; + + /* + * The CX23418 sends us data that is 32 bit little-endian swapped, + * but we want the raw VBI bytes in the order they were in the raster + * line. This has a side effect of making the header big endian + */ + cx18_buf_swap(buf); + + /* Raw VBI data */ + if (cx18_raw_vbi(cx)) { + + size = buf->bytesused = + compress_raw_buf(cx, p, size, sizeof(struct vbi_data_hdr)); + + /* + * Hack needed for compatibility with old VBI software. + * Write the frame # at the last 4 bytes of the frame + */ + p += size - 4; + memcpy(p, &cx->vbi.frame, 4); + cx->vbi.frame++; + return; + } + + /* Sliced VBI data with data insertion */ + + pts = (be32_to_cpu(hdr->magic) == 0x3fffffff) ? be32_to_cpu(hdr->pts) + : 0; + + lines = compress_sliced_buf(cx, p, size, sizeof(struct vbi_data_hdr)); + + /* always return at least one empty line */ + if (lines == 0) { + cx->vbi.sliced_data[0].id = 0; + cx->vbi.sliced_data[0].line = 0; + cx->vbi.sliced_data[0].field = 0; + lines = 1; + } + buf->bytesused = size = lines * sizeof(cx->vbi.sliced_data[0]); + memcpy(p, &cx->vbi.sliced_data[0], size); + + if (cx->vbi.insert_mpeg) + copy_vbi_data(cx, lines, pts); + cx->vbi.frame++; +} + +void cx18_process_vbi_data(struct cx18 *cx, struct cx18_mdl *mdl, + int streamtype) +{ + struct cx18_buffer *buf; + u32 orig_used; + + if (streamtype != CX18_ENC_STREAM_TYPE_VBI) + return; + + /* + * Big assumption here: + * Every buffer hooked to the MDL's buf_list is a complete VBI frame + * that ends at the end of the buffer. + * + * To assume anything else would make the code in this file + * more complex, or require extra memcpy()'s to make the + * buffers satisfy the above assumption. It's just simpler to set + * up the encoder buffer transfers to make the assumption true. + */ + list_for_each_entry(buf, &mdl->buf_list, list) { + orig_used = buf->bytesused; + if (orig_used == 0) + break; + _cx18_process_vbi_data(cx, buf); + mdl->bytesused -= (orig_used - buf->bytesused); + } +} diff --git a/drivers/media/pci/cx18/cx18-vbi.h b/drivers/media/pci/cx18/cx18-vbi.h new file mode 100644 index 000000000..41f502669 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-vbi.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 Vertical Blank Interval support functions + * + * Derived from ivtv-vbi.h + * + * Copyright (C) 2007 Hans Verkuil + */ + +void cx18_process_vbi_data(struct cx18 *cx, struct cx18_mdl *mdl, + int streamtype); +int cx18_used_line(struct cx18 *cx, int line, int field); diff --git a/drivers/media/pci/cx18/cx18-version.h b/drivers/media/pci/cx18/cx18-version.h new file mode 100644 index 000000000..e8636ac5d --- /dev/null +++ b/drivers/media/pci/cx18/cx18-version.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 driver version information + * + * Copyright (C) 2007 Hans Verkuil + */ + +#ifndef CX18_VERSION_H +#define CX18_VERSION_H + +#define CX18_DRIVER_NAME "cx18" +#define CX18_VERSION "1.5.1" + +#endif diff --git a/drivers/media/pci/cx18/cx18-video.c b/drivers/media/pci/cx18/cx18-video.c new file mode 100644 index 000000000..86cd44053 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-video.c @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx18 video interface functions + * + * Copyright (C) 2007 Hans Verkuil + */ + +#include "cx18-driver.h" +#include "cx18-video.h" +#include "cx18-cards.h" + +void cx18_video_set_io(struct cx18 *cx) +{ + int inp = cx->active_input; + + v4l2_subdev_call(cx->sd_av, video, s_routing, + cx->card->video_inputs[inp].video_input, 0, 0); +} diff --git a/drivers/media/pci/cx18/cx18-video.h b/drivers/media/pci/cx18/cx18-video.h new file mode 100644 index 000000000..ef212f1c5 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-video.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 video interface functions + * + * Copyright (C) 2007 Hans Verkuil + */ + +void cx18_video_set_io(struct cx18 *cx); diff --git a/drivers/media/pci/cx18/cx23418.h b/drivers/media/pci/cx18/cx23418.h new file mode 100644 index 000000000..22486f39b --- /dev/null +++ b/drivers/media/pci/cx18/cx23418.h @@ -0,0 +1,478 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx18 header containing common defines. + * + * Copyright (C) 2007 Hans Verkuil + */ + +#ifndef CX23418_H +#define CX23418_H + +#include + +#define MGR_CMD_MASK 0x40000000 +/* The MSB of the command code indicates that this is the completion of a + command */ +#define MGR_CMD_MASK_ACK (MGR_CMD_MASK | 0x80000000) + +/* Description: This command creates a new instance of a certain task + IN[0] - Task ID. This is one of the XPU_CMD_MASK_YYY where XPU is + the processor on which the task YYY will be created + OUT[0] - Task handle. This handle is passed along with commands to + dispatch to the right instance of the task + ReturnCode - One of the ERR_SYS_... */ +#define CX18_CREATE_TASK (MGR_CMD_MASK | 0x0001) + +/* Description: This command destroys an instance of a task + IN[0] - Task handle. Hanlde of the task to destroy + ReturnCode - One of the ERR_SYS_... */ +#define CX18_DESTROY_TASK (MGR_CMD_MASK | 0x0002) + +/* All commands for CPU have the following mask set */ +#define CPU_CMD_MASK 0x20000000 +#define CPU_CMD_MASK_DEBUG (CPU_CMD_MASK | 0x00000000) +#define CPU_CMD_MASK_ACK (CPU_CMD_MASK | 0x80000000) +#define CPU_CMD_MASK_CAPTURE (CPU_CMD_MASK | 0x00020000) +#define CPU_CMD_MASK_TS (CPU_CMD_MASK | 0x00040000) + +#define EPU_CMD_MASK 0x02000000 +#define EPU_CMD_MASK_DEBUG (EPU_CMD_MASK | 0x000000) +#define EPU_CMD_MASK_DE (EPU_CMD_MASK | 0x040000) + +#define APU_CMD_MASK 0x10000000 +#define APU_CMD_MASK_ACK (APU_CMD_MASK | 0x80000000) + +#define CX18_APU_ENCODING_METHOD_MPEG (0 << 28) +#define CX18_APU_ENCODING_METHOD_AC3 (1 << 28) + +/* Description: Command APU to start audio + IN[0] - audio parameters (same as CX18_CPU_SET_AUDIO_PARAMETERS?) + IN[1] - caller buffer address, or 0 + ReturnCode - ??? */ +#define CX18_APU_START (APU_CMD_MASK | 0x01) + +/* Description: Command APU to stop audio + IN[0] - encoding method to stop + ReturnCode - ??? */ +#define CX18_APU_STOP (APU_CMD_MASK | 0x02) + +/* Description: Command APU to reset the AI + ReturnCode - ??? */ +#define CX18_APU_RESETAI (APU_CMD_MASK | 0x05) + +/* Description: This command indicates that a Memory Descriptor List has been + filled with the requested channel type + IN[0] - Task handle. Handle of the task + IN[1] - Offset of the MDL_ACK from the beginning of the local DDR. + IN[2] - Number of CNXT_MDL_ACK structures in the array pointed to by IN[1] + ReturnCode - One of the ERR_DE_... */ +#define CX18_EPU_DMA_DONE (EPU_CMD_MASK_DE | 0x0001) + +/* Something interesting happened + IN[0] - A value to log + IN[1] - An offset of a string in the MiniMe memory; + 0/zero/NULL means "I have nothing to say" */ +#define CX18_EPU_DEBUG (EPU_CMD_MASK_DEBUG | 0x0003) + +/* Reads memory/registers (32-bit) + IN[0] - Address + OUT[1] - Value */ +#define CX18_CPU_DEBUG_PEEK32 (CPU_CMD_MASK_DEBUG | 0x0003) + +/* Description: This command starts streaming with the set channel type + IN[0] - Task handle. Handle of the task to start + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_CAPTURE_START (CPU_CMD_MASK_CAPTURE | 0x0002) + +/* Description: This command stops streaming with the set channel type + IN[0] - Task handle. Handle of the task to stop + IN[1] - 0 = stop at end of GOP, 1 = stop at end of frame (MPEG only) + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_CAPTURE_STOP (CPU_CMD_MASK_CAPTURE | 0x0003) + +/* Description: This command pauses streaming with the set channel type + IN[0] - Task handle. Handle of the task to pause + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_CAPTURE_PAUSE (CPU_CMD_MASK_CAPTURE | 0x0007) + +/* Description: This command resumes streaming with the set channel type + IN[0] - Task handle. Handle of the task to resume + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_CAPTURE_RESUME (CPU_CMD_MASK_CAPTURE | 0x0008) + +#define CAPTURE_CHANNEL_TYPE_NONE 0 +#define CAPTURE_CHANNEL_TYPE_MPEG 1 +#define CAPTURE_CHANNEL_TYPE_INDEX 2 +#define CAPTURE_CHANNEL_TYPE_YUV 3 +#define CAPTURE_CHANNEL_TYPE_PCM 4 +#define CAPTURE_CHANNEL_TYPE_VBI 5 +#define CAPTURE_CHANNEL_TYPE_SLICED_VBI 6 +#define CAPTURE_CHANNEL_TYPE_TS 7 +#define CAPTURE_CHANNEL_TYPE_MAX 15 + +/* Description: This command sets the channel type. This can only be done + when stopped. + IN[0] - Task handle. Handle of the task to start + IN[1] - Channel Type. See Below. + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_CHANNEL_TYPE (CPU_CMD_MASK_CAPTURE + 1) + +/* Description: Set stream output type + IN[0] - task handle. Handle of the task to start + IN[1] - type + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_STREAM_OUTPUT_TYPE (CPU_CMD_MASK_CAPTURE | 0x0012) + +/* Description: Set video input resolution and frame rate + IN[0] - task handle + IN[1] - reserved + IN[2] - reserved + IN[3] - reserved + IN[4] - reserved + IN[5] - frame rate, 0 - 29.97f/s, 1 - 25f/s + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_VIDEO_IN (CPU_CMD_MASK_CAPTURE | 0x0004) + +/* Description: Set video frame rate + IN[0] - task handle. Handle of the task to start + IN[1] - video bit rate mode + IN[2] - video average rate + IN[3] - video peak rate + IN[4] - system mux rate + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_VIDEO_RATE (CPU_CMD_MASK_CAPTURE | 0x0005) + +/* Description: Set video output resolution + IN[0] - task handle + IN[1] - horizontal size + IN[2] - vertical size + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_VIDEO_RESOLUTION (CPU_CMD_MASK_CAPTURE | 0x0006) + +/* Description: This command set filter parameters + IN[0] - Task handle. Handle of the task + IN[1] - type, 0 - temporal, 1 - spatial, 2 - median + IN[2] - mode, temporal/spatial: 0 - disable, 1 - static, 2 - dynamic + median: 0 = disable, 1 = horizontal, 2 = vertical, + 3 = horizontal/vertical, 4 = diagonal + IN[3] - strength, temporal 0 - 31, spatial 0 - 15 + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_FILTER_PARAM (CPU_CMD_MASK_CAPTURE | 0x0009) + +/* Description: This command set spatial filter type + IN[0] - Task handle. + IN[1] - luma type: 0 = disable, 1 = 1D horizontal only, 2 = 1D vertical only, + 3 = 2D H/V separable, 4 = 2D symmetric non-separable + IN[2] - chroma type: 0 - disable, 1 = 1D horizontal + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_SPATIAL_FILTER_TYPE (CPU_CMD_MASK_CAPTURE | 0x000C) + +/* Description: This command set coring levels for median filter + IN[0] - Task handle. + IN[1] - luma_high + IN[2] - luma_low + IN[3] - chroma_high + IN[4] - chroma_low + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_MEDIAN_CORING (CPU_CMD_MASK_CAPTURE | 0x000E) + +/* Description: This command set the picture type mask for index file + IN[0] - Task handle (ignored by firmware) + IN[1] - 0 = disable index file output + 1 = output I picture + 2 = P picture + 4 = B picture + other = illegal */ +#define CX18_CPU_SET_INDEXTABLE (CPU_CMD_MASK_CAPTURE | 0x0010) + +/* Description: Set audio parameters + IN[0] - task handle. Handle of the task to start + IN[1] - audio parameter + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_AUDIO_PARAMETERS (CPU_CMD_MASK_CAPTURE | 0x0011) + +/* Description: Set video mute + IN[0] - task handle. Handle of the task to start + IN[1] - bit31-24: muteYvalue + bit23-16: muteUvalue + bit15-8: muteVvalue + bit0: 1:mute, 0: unmute + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_VIDEO_MUTE (CPU_CMD_MASK_CAPTURE | 0x0013) + +/* Description: Set audio mute + IN[0] - task handle. Handle of the task to start + IN[1] - mute/unmute + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_AUDIO_MUTE (CPU_CMD_MASK_CAPTURE | 0x0014) + +/* Description: Set stream output type + IN[0] - task handle. Handle of the task to start + IN[1] - subType + SET_INITIAL_SCR 1 + SET_QUALITY_MODE 2 + SET_VIM_PROTECT_MODE 3 + SET_PTS_CORRECTION 4 + SET_USB_FLUSH_MODE 5 + SET_MERAQPAR_ENABLE 6 + SET_NAV_PACK_INSERTION 7 + SET_SCENE_CHANGE_ENABLE 8 + IN[2] - parameter 1 + IN[3] - parameter 2 + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_MISC_PARAMETERS (CPU_CMD_MASK_CAPTURE | 0x0015) + +/* Description: Set raw VBI parameters + IN[0] - Task handle + IN[1] - No. of input lines per field: + bit[15:0]: field 1, + bit[31:16]: field 2 + IN[2] - No. of input bytes per line + IN[3] - No. of output frames per transfer + IN[4] - start code + IN[5] - stop code + ReturnCode */ +#define CX18_CPU_SET_RAW_VBI_PARAM (CPU_CMD_MASK_CAPTURE | 0x0016) + +/* Description: Set capture line No. + IN[0] - task handle. Handle of the task to start + IN[1] - height1 + IN[2] - height2 + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_CAPTURE_LINE_NO (CPU_CMD_MASK_CAPTURE | 0x0017) + +/* Description: Set copyright + IN[0] - task handle. Handle of the task to start + IN[1] - copyright + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_COPYRIGHT (CPU_CMD_MASK_CAPTURE | 0x0018) + +/* Description: Set audio PID + IN[0] - task handle. Handle of the task to start + IN[1] - PID + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_AUDIO_PID (CPU_CMD_MASK_CAPTURE | 0x0019) + +/* Description: Set video PID + IN[0] - task handle. Handle of the task to start + IN[1] - PID + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_VIDEO_PID (CPU_CMD_MASK_CAPTURE | 0x001A) + +/* Description: Set Vertical Crop Line + IN[0] - task handle. Handle of the task to start + IN[1] - Line + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_VER_CROP_LINE (CPU_CMD_MASK_CAPTURE | 0x001B) + +/* Description: Set COP structure + IN[0] - task handle. Handle of the task to start + IN[1] - M + IN[2] - N + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_GOP_STRUCTURE (CPU_CMD_MASK_CAPTURE | 0x001C) + +/* Description: Set Scene Change Detection + IN[0] - task handle. Handle of the task to start + IN[1] - scene change + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_SCENE_CHANGE_DETECTION (CPU_CMD_MASK_CAPTURE | 0x001D) + +/* Description: Set Aspect Ratio + IN[0] - task handle. Handle of the task to start + IN[1] - AspectRatio + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_ASPECT_RATIO (CPU_CMD_MASK_CAPTURE | 0x001E) + +/* Description: Set Skip Input Frame + IN[0] - task handle. Handle of the task to start + IN[1] - skip input frames + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_SKIP_INPUT_FRAME (CPU_CMD_MASK_CAPTURE | 0x001F) + +/* Description: Set sliced VBI parameters - + Note This API will only apply to MPEG and Sliced VBI Channels + IN[0] - Task handle + IN[1] - output type, 0 - CC, 1 - Moji, 2 - Teletext + IN[2] - start / stop line + bit[15:0] start line number + bit[31:16] stop line number + IN[3] - number of output frames per interrupt + IN[4] - VBI insertion mode + bit 0: output user data, 1 - enable + bit 1: output private stream, 1 - enable + bit 2: mux option, 0 - in GOP, 1 - in picture + bit[7:0] private stream ID + IN[5] - insertion period while mux option is in picture + ReturnCode - VBI data offset */ +#define CX18_CPU_SET_SLICED_VBI_PARAM (CPU_CMD_MASK_CAPTURE | 0x0020) + +/* Description: Set the user data place holder + IN[0] - type of data (0 for user) + IN[1] - Stuffing period + IN[2] - ID data size in word (less than 10) + IN[3] - Pointer to ID buffer */ +#define CX18_CPU_SET_USERDATA_PLACE_HOLDER (CPU_CMD_MASK_CAPTURE | 0x0021) + + +/* Description: + In[0] Task Handle + return parameter: + Out[0] Reserved + Out[1] Video PTS bit[32:2] of last output video frame. + Out[2] Video PTS bit[ 1:0] of last output video frame. + Out[3] Hardware Video PTS counter bit[31:0], + these bits get incremented on every 90kHz clock tick. + Out[4] Hardware Video PTS counter bit32, + these bits get incremented on every 90kHz clock tick. + ReturnCode */ +#define CX18_CPU_GET_ENC_PTS (CPU_CMD_MASK_CAPTURE | 0x0022) + +/* Description: Set VFC parameters + IN[0] - task handle + IN[1] - VFC enable flag, 1 - enable, 0 - disable +*/ +#define CX18_CPU_SET_VFC_PARAM (CPU_CMD_MASK_CAPTURE | 0x0023) + +/* Below is the list of commands related to the data exchange */ +#define CPU_CMD_MASK_DE (CPU_CMD_MASK | 0x040000) + +/* Description: This command provides the physical base address of the local + DDR as viewed by EPU + IN[0] - Physical offset where EPU has the local DDR mapped + ReturnCode - One of the ERR_DE_... */ +#define CPU_CMD_DE_SetBase (CPU_CMD_MASK_DE | 0x0001) + +/* Description: This command provides the offsets in the device memory where + the 2 cx18_mdl_ack blocks reside + IN[0] - Task handle. Handle of the task to start + IN[1] - Offset of the first cx18_mdl_ack from the beginning of the + local DDR. + IN[2] - Offset of the second cx18_mdl_ack from the beginning of the + local DDR. + ReturnCode - One of the ERR_DE_... */ +#define CX18_CPU_DE_SET_MDL_ACK (CPU_CMD_MASK_DE | 0x0002) + +/* Description: This command provides the offset to a Memory Descriptor List + IN[0] - Task handle. Handle of the task to start + IN[1] - Offset of the MDL from the beginning of the local DDR. + IN[2] - Number of cx18_mdl_ent structures in the array pointed to by IN[1] + IN[3] - Buffer ID + IN[4] - Total buffer length + ReturnCode - One of the ERR_DE_... */ +#define CX18_CPU_DE_SET_MDL (CPU_CMD_MASK_DE | 0x0005) + +/* Description: This command requests return of all current Memory + Descriptor Lists to the driver + IN[0] - Task handle. Handle of the task to start + ReturnCode - One of the ERR_DE_... */ +#define CX18_CPU_DE_RELEASE_MDL (CPU_CMD_MASK_DE | 0x0006) + +/* Description: This command signals the cpu that the dat buffer has been + consumed and ready for re-use. + IN[0] - Task handle. Handle of the task + IN[1] - Offset of the data block from the beginning of the local DDR. + IN[2] - Number of bytes in the data block + ReturnCode - One of the ERR_DE_... */ +/* #define CX18_CPU_DE_RELEASE_BUFFER (CPU_CMD_MASK_DE | 0x0007) */ + +/* No Error / Success */ +#define CNXT_OK 0x000000 + +/* Received unknown command */ +#define CXERR_UNK_CMD 0x000001 + +/* First parameter in the command is invalid */ +#define CXERR_INVALID_PARAM1 0x000002 + +/* Second parameter in the command is invalid */ +#define CXERR_INVALID_PARAM2 0x000003 + +/* Device interface is not open/found */ +#define CXERR_DEV_NOT_FOUND 0x000004 + +/* Requested function is not implemented/available */ +#define CXERR_NOTSUPPORTED 0x000005 + +/* Invalid pointer is provided */ +#define CXERR_BADPTR 0x000006 + +/* Unable to allocate memory */ +#define CXERR_NOMEM 0x000007 + +/* Object/Link not found */ +#define CXERR_LINK 0x000008 + +/* Device busy, command cannot be executed */ +#define CXERR_BUSY 0x000009 + +/* File/device/handle is not open. */ +#define CXERR_NOT_OPEN 0x00000A + +/* Value is out of range */ +#define CXERR_OUTOFRANGE 0x00000B + +/* Buffer overflow */ +#define CXERR_OVERFLOW 0x00000C + +/* Version mismatch */ +#define CXERR_BADVER 0x00000D + +/* Operation timed out */ +#define CXERR_TIMEOUT 0x00000E + +/* Operation aborted */ +#define CXERR_ABORT 0x00000F + +/* Specified I2C device not found for read/write */ +#define CXERR_I2CDEV_NOTFOUND 0x000010 + +/* Error in I2C data xfer (but I2C device is present) */ +#define CXERR_I2CDEV_XFERERR 0x000011 + +/* Channel changing component not ready */ +#define CXERR_CHANNELNOTREADY 0x000012 + +/* PPU (Presensation/Decoder) mail box is corrupted */ +#define CXERR_PPU_MB_CORRUPT 0x000013 + +/* CPU (Capture/Encoder) mail box is corrupted */ +#define CXERR_CPU_MB_CORRUPT 0x000014 + +/* APU (Audio) mail box is corrupted */ +#define CXERR_APU_MB_CORRUPT 0x000015 + +/* Unable to open file for reading */ +#define CXERR_FILE_OPEN_READ 0x000016 + +/* Unable to open file for writing */ +#define CXERR_FILE_OPEN_WRITE 0x000017 + +/* Unable to find the I2C section specified */ +#define CXERR_I2C_BADSECTION 0x000018 + +/* Error in I2C data xfer (but I2C device is present) */ +#define CXERR_I2CDEV_DATALOW 0x000019 + +/* Error in I2C data xfer (but I2C device is present) */ +#define CXERR_I2CDEV_CLOCKLOW 0x00001A + +/* No Interrupt received from HW (for I2C access) */ +#define CXERR_NO_HW_I2C_INTR 0x00001B + +/* RPU is not ready to accept commands! */ +#define CXERR_RPU_NOT_READY 0x00001C + +/* RPU is not ready to accept commands! */ +#define CXERR_RPU_NO_ACK 0x00001D + +/* The are no buffers ready. Try again soon! */ +#define CXERR_NODATA_AGAIN 0x00001E + +/* The stream is stopping. Function not allowed now! */ +#define CXERR_STOPPING_STATUS 0x00001F + +/* Trying to access hardware when the power is turned OFF */ +#define CXERR_DEVPOWER_OFF 0x000020 + +#endif /* CX23418_H */ diff --git a/drivers/media/pci/cx23885/Kconfig b/drivers/media/pci/cx23885/Kconfig new file mode 100644 index 000000000..926da8819 --- /dev/null +++ b/drivers/media/pci/cx23885/Kconfig @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_CX23885 + tristate "Conexant cx23885 (2388x successor) support" + depends on DVB_CORE && VIDEO_DEV && PCI && I2C && INPUT && SND + select SND_PCM + select I2C_ALGOBIT + select VIDEO_TUNER + select VIDEO_TVEEPROM + depends on RC_CORE + select VIDEOBUF2_DVB + select VIDEOBUF2_DMA_SG + select VIDEO_CX25840 + select VIDEO_CX2341X + select VIDEO_CS3308 + select DVB_DIB7000P if MEDIA_SUBDRV_AUTOSELECT + select DVB_DRXK if MEDIA_SUBDRV_AUTOSELECT + select DVB_S5H1409 if MEDIA_SUBDRV_AUTOSELECT + select DVB_S5H1411 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT + select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10048 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV090x if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV6110 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CX24116 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CX24117 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0900 if MEDIA_SUBDRV_AUTOSELECT + select DVB_DS3000 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TS2020 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0367 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10071 if MEDIA_SUBDRV_AUTOSELECT + select DVB_A8293 if MEDIA_SUBDRV_AUTOSELECT + select DVB_MB86A20S if MEDIA_SUBDRV_AUTOSELECT + select DVB_SI2165 if MEDIA_SUBDRV_AUTOSELECT + select DVB_SI2168 if MEDIA_SUBDRV_AUTOSELECT + select DVB_M88DS3103 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MT2063 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MT2131 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_XC2028 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA8290 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_XC5000 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_SI2157 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_M88RS6000T if MEDIA_SUBDRV_AUTOSELECT + select DVB_TUNER_DIB0070 if MEDIA_SUBDRV_AUTOSELECT + help + This is a video4linux driver for Conexant 23885 based + TV cards. + + To compile this driver as a module, choose M here: the + module will be called cx23885 + +config MEDIA_ALTERA_CI + tristate "Altera FPGA based CI module" + depends on VIDEO_CX23885 && DVB_CORE + select ALTERA_STAPL + help + An Altera FPGA CI module for NetUP Dual DVB-T/C RF CI card. + + To compile this driver as a module, choose M here: the + module will be called altera-ci diff --git a/drivers/media/pci/cx23885/Makefile b/drivers/media/pci/cx23885/Makefile new file mode 100644 index 000000000..a785169ec --- /dev/null +++ b/drivers/media/pci/cx23885/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +cx23885-objs := cx23885-cards.o cx23885-video.o cx23885-vbi.o \ + cx23885-core.o cx23885-i2c.o cx23885-dvb.o cx23885-417.o \ + cx23885-ioctl.o cx23885-ir.o cx23885-av.o cx23885-input.o \ + cx23888-ir.o netup-init.o cimax2.o netup-eeprom.o \ + cx23885-f300.o cx23885-alsa.o + +obj-$(CONFIG_VIDEO_CX23885) += cx23885.o +obj-$(CONFIG_MEDIA_ALTERA_CI) += altera-ci.o + +ccflags-y += -I $(srctree)/drivers/media/tuners +ccflags-y += -I $(srctree)/drivers/media/dvb-frontends + +ccflags-y += $(extra-cflags-y) $(extra-cflags-m) diff --git a/drivers/media/pci/cx23885/altera-ci.c b/drivers/media/pci/cx23885/altera-ci.c new file mode 100644 index 000000000..20a3b6c51 --- /dev/null +++ b/drivers/media/pci/cx23885/altera-ci.c @@ -0,0 +1,838 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * altera-ci.c + * + * CI driver in conjunction with NetUp Dual DVB-T/C RF CI card + * + * Copyright (C) 2010,2011 NetUP Inc. + * Copyright (C) 2010,2011 Igor M. Liplianin + */ + +/* + * currently cx23885 GPIO's used. + * GPIO-0 ~INT in + * GPIO-1 TMS out + * GPIO-2 ~reset chips out + * GPIO-3 to GPIO-10 data/addr for CA in/out + * GPIO-11 ~CS out + * GPIO-12 AD_RG out + * GPIO-13 ~WR out + * GPIO-14 ~RD out + * GPIO-15 ~RDY in + * GPIO-16 TCK out + * GPIO-17 TDO in + * GPIO-18 TDI out + */ +/* + * Bit definitions for MC417_RWD and MC417_OEN registers + * bits 31-16 + * +-----------+ + * | Reserved | + * +-----------+ + * bit 15 bit 14 bit 13 bit 12 bit 11 bit 10 bit 9 bit 8 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | TDI | TDO | TCK | RDY# | #RD | #WR | AD_RG | #CS | + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | DATA7| DATA6| DATA5| DATA4| DATA3| DATA2| DATA1| DATA0| + * +-------+-------+-------+-------+-------+-------+-------+-------+ + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include "altera-ci.h" +#include + +/* FPGA regs */ +#define NETUP_CI_INT_CTRL 0x00 +#define NETUP_CI_BUSCTRL2 0x01 +#define NETUP_CI_ADDR0 0x04 +#define NETUP_CI_ADDR1 0x05 +#define NETUP_CI_DATA 0x06 +#define NETUP_CI_BUSCTRL 0x07 +#define NETUP_CI_PID_ADDR0 0x08 +#define NETUP_CI_PID_ADDR1 0x09 +#define NETUP_CI_PID_DATA 0x0a +#define NETUP_CI_TSA_DIV 0x0c +#define NETUP_CI_TSB_DIV 0x0d +#define NETUP_CI_REVISION 0x0f + +/* const for ci op */ +#define NETUP_CI_FLG_CTL 1 +#define NETUP_CI_FLG_RD 1 +#define NETUP_CI_FLG_AD 1 + +static unsigned int ci_dbg; +module_param(ci_dbg, int, 0644); +MODULE_PARM_DESC(ci_dbg, "Enable CI debugging"); + +static unsigned int pid_dbg; +module_param(pid_dbg, int, 0644); +MODULE_PARM_DESC(pid_dbg, "Enable PID filtering debugging"); + +MODULE_DESCRIPTION("altera FPGA CI module"); +MODULE_AUTHOR("Igor M. Liplianin "); +MODULE_LICENSE("GPL"); + +#define ci_dbg_print(fmt, args...) \ + do { \ + if (ci_dbg) \ + printk(KERN_DEBUG pr_fmt("%s: " fmt), \ + __func__, ##args); \ + } while (0) + +#define pid_dbg_print(fmt, args...) \ + do { \ + if (pid_dbg) \ + printk(KERN_DEBUG pr_fmt("%s: " fmt), \ + __func__, ##args); \ + } while (0) + +struct altera_ci_state; +struct netup_hw_pid_filter; + +struct fpga_internal { + void *dev; + struct mutex fpga_mutex;/* two CI's on the same fpga */ + struct netup_hw_pid_filter *pid_filt[2]; + struct altera_ci_state *state[2]; + struct work_struct work; + int (*fpga_rw) (void *dev, int flag, int data, int rw); + int cis_used; + int filts_used; + int strt_wrk; +}; + +/* stores all private variables for communication with CI */ +struct altera_ci_state { + struct fpga_internal *internal; + struct dvb_ca_en50221 ca; + int status; + int nr; +}; + +/* stores all private variables for hardware pid filtering */ +struct netup_hw_pid_filter { + struct fpga_internal *internal; + struct dvb_demux *demux; + /* save old functions */ + int (*start_feed)(struct dvb_demux_feed *feed); + int (*stop_feed)(struct dvb_demux_feed *feed); + + int status; + int nr; +}; + +/* internal params node */ +struct fpga_inode { + /* pointer for internal params, one for each pair of CI's */ + struct fpga_internal *internal; + struct fpga_inode *next_inode; +}; + +/* first internal params */ +static struct fpga_inode *fpga_first_inode; + +/* find chip by dev */ +static struct fpga_inode *find_inode(void *dev) +{ + struct fpga_inode *temp_chip = fpga_first_inode; + + if (temp_chip == NULL) + return temp_chip; + + /* + Search for the last fpga CI chip or + find it by dev */ + while ((temp_chip != NULL) && + (temp_chip->internal->dev != dev)) + temp_chip = temp_chip->next_inode; + + return temp_chip; +} +/* check demux */ +static struct fpga_internal *check_filter(struct fpga_internal *temp_int, + void *demux_dev, int filt_nr) +{ + if (temp_int == NULL) + return NULL; + + if ((temp_int->pid_filt[filt_nr]) == NULL) + return NULL; + + if (temp_int->pid_filt[filt_nr]->demux == demux_dev) + return temp_int; + + return NULL; +} + +/* find chip by demux */ +static struct fpga_inode *find_dinode(void *demux_dev) +{ + struct fpga_inode *temp_chip = fpga_first_inode; + struct fpga_internal *temp_int; + + /* + * Search of the last fpga CI chip or + * find it by demux + */ + while (temp_chip != NULL) { + if (temp_chip->internal != NULL) { + temp_int = temp_chip->internal; + if (check_filter(temp_int, demux_dev, 0)) + break; + if (check_filter(temp_int, demux_dev, 1)) + break; + } + + temp_chip = temp_chip->next_inode; + } + + return temp_chip; +} + +/* deallocating chip */ +static void remove_inode(struct fpga_internal *internal) +{ + struct fpga_inode *prev_node = fpga_first_inode; + struct fpga_inode *del_node = find_inode(internal->dev); + + if (del_node != NULL) { + if (del_node == fpga_first_inode) { + fpga_first_inode = del_node->next_inode; + } else { + while (prev_node->next_inode != del_node) + prev_node = prev_node->next_inode; + + if (del_node->next_inode == NULL) + prev_node->next_inode = NULL; + else + prev_node->next_inode = + prev_node->next_inode->next_inode; + } + + kfree(del_node); + } +} + +/* allocating new chip */ +static struct fpga_inode *append_internal(struct fpga_internal *internal) +{ + struct fpga_inode *new_node = fpga_first_inode; + + if (new_node == NULL) { + new_node = kmalloc_obj(struct fpga_inode); + fpga_first_inode = new_node; + } else { + while (new_node->next_inode != NULL) + new_node = new_node->next_inode; + + new_node->next_inode = + kmalloc_obj(struct fpga_inode); + if (new_node->next_inode != NULL) + new_node = new_node->next_inode; + else + new_node = NULL; + } + + if (new_node != NULL) { + new_node->internal = internal; + new_node->next_inode = NULL; + } + + return new_node; +} + +static int netup_fpga_op_rw(struct fpga_internal *inter, int addr, + u8 val, u8 read) +{ + inter->fpga_rw(inter->dev, NETUP_CI_FLG_AD, addr, 0); + return inter->fpga_rw(inter->dev, 0, val, read); +} + +/* flag - mem/io, read - read/write */ +static int altera_ci_op_cam(struct dvb_ca_en50221 *en50221, int slot, + u8 flag, u8 read, int addr, u8 val) +{ + + struct altera_ci_state *state = en50221->data; + struct fpga_internal *inter = state->internal; + + u8 store; + int mem = 0; + + if (0 != slot) + return -EINVAL; + + mutex_lock(&inter->fpga_mutex); + + netup_fpga_op_rw(inter, NETUP_CI_ADDR0, ((addr << 1) & 0xfe), 0); + netup_fpga_op_rw(inter, NETUP_CI_ADDR1, ((addr >> 7) & 0x7f), 0); + store = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, 0, NETUP_CI_FLG_RD); + + store &= 0x0f; + store |= ((state->nr << 7) | (flag << 6)); + + netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, store, 0); + mem = netup_fpga_op_rw(inter, NETUP_CI_DATA, val, read); + + mutex_unlock(&inter->fpga_mutex); + + ci_dbg_print("%s: %s: addr=[0x%02x], %s=%x\n", __func__, + (read) ? "read" : "write", addr, + (flag == NETUP_CI_FLG_CTL) ? "ctl" : "mem", + (read) ? mem : val); + + return mem; +} + +static int altera_ci_read_attribute_mem(struct dvb_ca_en50221 *en50221, + int slot, int addr) +{ + return altera_ci_op_cam(en50221, slot, 0, NETUP_CI_FLG_RD, addr, 0); +} + +static int altera_ci_write_attribute_mem(struct dvb_ca_en50221 *en50221, + int slot, int addr, u8 data) +{ + return altera_ci_op_cam(en50221, slot, 0, 0, addr, data); +} + +static int altera_ci_read_cam_ctl(struct dvb_ca_en50221 *en50221, + int slot, u8 addr) +{ + return altera_ci_op_cam(en50221, slot, NETUP_CI_FLG_CTL, + NETUP_CI_FLG_RD, addr, 0); +} + +static int altera_ci_write_cam_ctl(struct dvb_ca_en50221 *en50221, int slot, + u8 addr, u8 data) +{ + return altera_ci_op_cam(en50221, slot, NETUP_CI_FLG_CTL, 0, addr, data); +} + +static int altera_ci_slot_reset(struct dvb_ca_en50221 *en50221, int slot) +{ + struct altera_ci_state *state = en50221->data; + struct fpga_internal *inter = state->internal; + /* reasonable timeout for CI reset is 10 seconds */ + unsigned long t_out = jiffies + msecs_to_jiffies(9999); + int ret; + + ci_dbg_print("%s\n", __func__); + + if (0 != slot) + return -EINVAL; + + mutex_lock(&inter->fpga_mutex); + + ret = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, 0, NETUP_CI_FLG_RD); + netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, + (ret & 0xcf) | (1 << (5 - state->nr)), 0); + + mutex_unlock(&inter->fpga_mutex); + + for (;;) { + msleep(50); + + mutex_lock(&inter->fpga_mutex); + + ret = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, + 0, NETUP_CI_FLG_RD); + mutex_unlock(&inter->fpga_mutex); + + if ((ret & (1 << (5 - state->nr))) == 0) + break; + if (time_after(jiffies, t_out)) + break; + } + + + ci_dbg_print("%s: %d msecs\n", __func__, + jiffies_to_msecs(jiffies + msecs_to_jiffies(9999) - t_out)); + + return 0; +} + +static int altera_ci_slot_shutdown(struct dvb_ca_en50221 *en50221, int slot) +{ + /* not implemented */ + return 0; +} + +static int altera_ci_slot_ts_ctl(struct dvb_ca_en50221 *en50221, int slot) +{ + struct altera_ci_state *state = en50221->data; + struct fpga_internal *inter = state->internal; + int ret; + + ci_dbg_print("%s\n", __func__); + + if (0 != slot) + return -EINVAL; + + mutex_lock(&inter->fpga_mutex); + + ret = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, 0, NETUP_CI_FLG_RD); + netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, + (ret & 0x0f) | (1 << (3 - state->nr)), 0); + + mutex_unlock(&inter->fpga_mutex); + + return 0; +} + +/* work handler */ +static void netup_read_ci_status(struct work_struct *work) +{ + struct fpga_internal *inter = + container_of(work, struct fpga_internal, work); + int ret; + + ci_dbg_print("%s\n", __func__); + + mutex_lock(&inter->fpga_mutex); + /* ack' irq */ + ret = netup_fpga_op_rw(inter, NETUP_CI_INT_CTRL, 0, NETUP_CI_FLG_RD); + ret = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, 0, NETUP_CI_FLG_RD); + + mutex_unlock(&inter->fpga_mutex); + + if (inter->state[1] != NULL) { + inter->state[1]->status = + ((ret & 1) == 0 ? + DVB_CA_EN50221_POLL_CAM_PRESENT | + DVB_CA_EN50221_POLL_CAM_READY : 0); + ci_dbg_print("%s: setting CI[1] status = 0x%x\n", + __func__, inter->state[1]->status); + } + + if (inter->state[0] != NULL) { + inter->state[0]->status = + ((ret & 2) == 0 ? + DVB_CA_EN50221_POLL_CAM_PRESENT | + DVB_CA_EN50221_POLL_CAM_READY : 0); + ci_dbg_print("%s: setting CI[0] status = 0x%x\n", + __func__, inter->state[0]->status); + } +} + +/* CI irq handler */ +int altera_ci_irq(void *dev) +{ + struct fpga_inode *temp_int = NULL; + struct fpga_internal *inter = NULL; + + ci_dbg_print("%s\n", __func__); + + if (dev != NULL) { + temp_int = find_inode(dev); + if (temp_int != NULL) { + inter = temp_int->internal; + schedule_work(&inter->work); + } + } + + return 1; +} +EXPORT_SYMBOL(altera_ci_irq); + +static int altera_poll_ci_slot_status(struct dvb_ca_en50221 *en50221, + int slot, int open) +{ + struct altera_ci_state *state = en50221->data; + + if (0 != slot) + return -EINVAL; + + return state->status; +} + +static void altera_hw_filt_release(void *main_dev, int filt_nr) +{ + struct fpga_inode *temp_int = find_inode(main_dev); + struct netup_hw_pid_filter *pid_filt = NULL; + + ci_dbg_print("%s\n", __func__); + + if (temp_int != NULL) { + pid_filt = temp_int->internal->pid_filt[filt_nr - 1]; + /* stored old feed controls */ + pid_filt->demux->start_feed = pid_filt->start_feed; + pid_filt->demux->stop_feed = pid_filt->stop_feed; + + if (((--(temp_int->internal->filts_used)) <= 0) && + ((temp_int->internal->cis_used) <= 0)) { + + ci_dbg_print("%s: Actually removing\n", __func__); + + remove_inode(temp_int->internal); + kfree(pid_filt->internal); + } + + kfree(pid_filt); + + } + +} + +void altera_ci_release(void *dev, int ci_nr) +{ + struct fpga_inode *temp_int = find_inode(dev); + struct altera_ci_state *state = NULL; + + ci_dbg_print("%s\n", __func__); + + if (temp_int != NULL) { + state = temp_int->internal->state[ci_nr - 1]; + altera_hw_filt_release(dev, ci_nr); + + + if (((temp_int->internal->filts_used) <= 0) && + ((--(temp_int->internal->cis_used)) <= 0)) { + + ci_dbg_print("%s: Actually removing\n", __func__); + + remove_inode(temp_int->internal); + kfree(state->internal); + } + + if (state != NULL) { + if (state->ca.data != NULL) + dvb_ca_en50221_release(&state->ca); + + kfree(state); + } + } + +} +EXPORT_SYMBOL(altera_ci_release); + +static void altera_pid_control(struct netup_hw_pid_filter *pid_filt, + u16 pid, int onoff) +{ + struct fpga_internal *inter = pid_filt->internal; + u8 store = 0; + + /* pid 0-0x1f always enabled, don't touch them */ + if ((pid == 0x2000) || (pid < 0x20)) + return; + + mutex_lock(&inter->fpga_mutex); + + netup_fpga_op_rw(inter, NETUP_CI_PID_ADDR0, (pid >> 3) & 0xff, 0); + netup_fpga_op_rw(inter, NETUP_CI_PID_ADDR1, + ((pid >> 11) & 0x03) | (pid_filt->nr << 2), 0); + + store = netup_fpga_op_rw(inter, NETUP_CI_PID_DATA, 0, NETUP_CI_FLG_RD); + + if (onoff)/* 0 - on, 1 - off */ + store |= (1 << (pid & 7)); + else + store &= ~(1 << (pid & 7)); + + netup_fpga_op_rw(inter, NETUP_CI_PID_DATA, store, 0); + + mutex_unlock(&inter->fpga_mutex); + + pid_dbg_print("%s: (%d) set pid: %5d 0x%04x '%s'\n", __func__, + pid_filt->nr, pid, pid, onoff ? "off" : "on"); +} + +static void altera_toggle_fullts_streaming(struct netup_hw_pid_filter *pid_filt, + int filt_nr, int onoff) +{ + struct fpga_internal *inter = pid_filt->internal; + u8 store = 0; + int i; + + pid_dbg_print("%s: pid_filt->nr[%d] now %s\n", __func__, pid_filt->nr, + onoff ? "off" : "on"); + + if (onoff)/* 0 - on, 1 - off */ + store = 0xff;/* ignore pid */ + else + store = 0;/* enable pid */ + + mutex_lock(&inter->fpga_mutex); + + for (i = 0; i < 1024; i++) { + netup_fpga_op_rw(inter, NETUP_CI_PID_ADDR0, i & 0xff, 0); + + netup_fpga_op_rw(inter, NETUP_CI_PID_ADDR1, + ((i >> 8) & 0x03) | (pid_filt->nr << 2), 0); + /* pid 0-0x1f always enabled */ + netup_fpga_op_rw(inter, NETUP_CI_PID_DATA, + (i > 3 ? store : 0), 0); + } + + mutex_unlock(&inter->fpga_mutex); +} + +static int altera_pid_feed_control(void *demux_dev, int filt_nr, + struct dvb_demux_feed *feed, int onoff) +{ + struct fpga_inode *temp_int = find_dinode(demux_dev); + struct fpga_internal *inter = temp_int->internal; + struct netup_hw_pid_filter *pid_filt = inter->pid_filt[filt_nr - 1]; + + altera_pid_control(pid_filt, feed->pid, onoff ? 0 : 1); + /* call old feed proc's */ + if (onoff) + pid_filt->start_feed(feed); + else + pid_filt->stop_feed(feed); + + if (feed->pid == 0x2000) + altera_toggle_fullts_streaming(pid_filt, filt_nr, + onoff ? 0 : 1); + + return 0; +} + +static int altera_ci_start_feed(struct dvb_demux_feed *feed, int num) +{ + altera_pid_feed_control(feed->demux, num, feed, 1); + + return 0; +} + +static int altera_ci_stop_feed(struct dvb_demux_feed *feed, int num) +{ + altera_pid_feed_control(feed->demux, num, feed, 0); + + return 0; +} + +static int altera_ci_start_feed_1(struct dvb_demux_feed *feed) +{ + return altera_ci_start_feed(feed, 1); +} + +static int altera_ci_stop_feed_1(struct dvb_demux_feed *feed) +{ + return altera_ci_stop_feed(feed, 1); +} + +static int altera_ci_start_feed_2(struct dvb_demux_feed *feed) +{ + return altera_ci_start_feed(feed, 2); +} + +static int altera_ci_stop_feed_2(struct dvb_demux_feed *feed) +{ + return altera_ci_stop_feed(feed, 2); +} + +static int altera_hw_filt_init(struct altera_ci_config *config, int hw_filt_nr) +{ + struct netup_hw_pid_filter *pid_filt = NULL; + struct fpga_inode *temp_int = find_inode(config->dev); + struct fpga_internal *inter = NULL; + int ret = 0; + + pid_filt = kzalloc_obj(struct netup_hw_pid_filter); + + ci_dbg_print("%s\n", __func__); + + if (!pid_filt) { + ret = -ENOMEM; + goto err; + } + + if (temp_int != NULL) { + inter = temp_int->internal; + (inter->filts_used)++; + ci_dbg_print("%s: Find Internal Structure!\n", __func__); + } else { + inter = kzalloc_obj(struct fpga_internal); + if (!inter) { + ret = -ENOMEM; + goto err; + } + + temp_int = append_internal(inter); + if (!temp_int) { + ret = -ENOMEM; + goto err; + } + inter->filts_used = 1; + inter->dev = config->dev; + inter->fpga_rw = config->fpga_rw; + mutex_init(&inter->fpga_mutex); + inter->strt_wrk = 1; + ci_dbg_print("%s: Create New Internal Structure!\n", __func__); + } + + ci_dbg_print("%s: setting hw pid filter = %p for ci = %d\n", __func__, + pid_filt, hw_filt_nr - 1); + inter->pid_filt[hw_filt_nr - 1] = pid_filt; + pid_filt->demux = config->demux; + pid_filt->internal = inter; + pid_filt->nr = hw_filt_nr - 1; + /* store old feed controls */ + pid_filt->start_feed = config->demux->start_feed; + pid_filt->stop_feed = config->demux->stop_feed; + /* replace with new feed controls */ + if (hw_filt_nr == 1) { + pid_filt->demux->start_feed = altera_ci_start_feed_1; + pid_filt->demux->stop_feed = altera_ci_stop_feed_1; + } else if (hw_filt_nr == 2) { + pid_filt->demux->start_feed = altera_ci_start_feed_2; + pid_filt->demux->stop_feed = altera_ci_stop_feed_2; + } + + altera_toggle_fullts_streaming(pid_filt, 0, 1); + + return 0; +err: + ci_dbg_print("%s: Can't init hardware filter: Error %d\n", + __func__, ret); + + kfree(pid_filt); + kfree(inter); + + return ret; +} + +int altera_ci_init(struct altera_ci_config *config, int ci_nr) +{ + struct altera_ci_state *state; + struct fpga_inode *temp_int = find_inode(config->dev); + struct fpga_internal *inter = NULL; + int ret = 0; + u8 store = 0; + + state = kzalloc_obj(struct altera_ci_state); + + ci_dbg_print("%s\n", __func__); + + if (!state) { + ret = -ENOMEM; + goto err; + } + + if (temp_int != NULL) { + inter = temp_int->internal; + (inter->cis_used)++; + inter->fpga_rw = config->fpga_rw; + ci_dbg_print("%s: Find Internal Structure!\n", __func__); + } else { + inter = kzalloc_obj(struct fpga_internal); + if (!inter) { + ret = -ENOMEM; + goto err; + } + + temp_int = append_internal(inter); + if (!temp_int) { + ret = -ENOMEM; + goto err; + } + inter->cis_used = 1; + inter->dev = config->dev; + inter->fpga_rw = config->fpga_rw; + mutex_init(&inter->fpga_mutex); + inter->strt_wrk = 1; + ci_dbg_print("%s: Create New Internal Structure!\n", __func__); + } + + ci_dbg_print("%s: setting state = %p for ci = %d\n", __func__, + state, ci_nr - 1); + state->internal = inter; + state->nr = ci_nr - 1; + + state->ca.owner = THIS_MODULE; + state->ca.read_attribute_mem = altera_ci_read_attribute_mem; + state->ca.write_attribute_mem = altera_ci_write_attribute_mem; + state->ca.read_cam_control = altera_ci_read_cam_ctl; + state->ca.write_cam_control = altera_ci_write_cam_ctl; + state->ca.slot_reset = altera_ci_slot_reset; + state->ca.slot_shutdown = altera_ci_slot_shutdown; + state->ca.slot_ts_enable = altera_ci_slot_ts_ctl; + state->ca.poll_slot_status = altera_poll_ci_slot_status; + state->ca.data = state; + + ret = dvb_ca_en50221_init(config->adapter, + &state->ca, + /* flags */ 0, + /* n_slots */ 1); + if (0 != ret) + goto err; + + inter->state[ci_nr - 1] = state; + + altera_hw_filt_init(config, ci_nr); + + if (inter->strt_wrk) { + INIT_WORK(&inter->work, netup_read_ci_status); + inter->strt_wrk = 0; + } + + ci_dbg_print("%s: CI initialized!\n", __func__); + + mutex_lock(&inter->fpga_mutex); + + /* Enable div */ + netup_fpga_op_rw(inter, NETUP_CI_TSA_DIV, 0x0, 0); + netup_fpga_op_rw(inter, NETUP_CI_TSB_DIV, 0x0, 0); + + /* enable TS out */ + store = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL2, 0, NETUP_CI_FLG_RD); + store |= (3 << 4); + netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL2, store, 0); + + ret = netup_fpga_op_rw(inter, NETUP_CI_REVISION, 0, NETUP_CI_FLG_RD); + /* enable irq */ + netup_fpga_op_rw(inter, NETUP_CI_INT_CTRL, 0x44, 0); + + mutex_unlock(&inter->fpga_mutex); + + ci_dbg_print("%s: NetUP CI Revision = 0x%x\n", __func__, ret); + + schedule_work(&inter->work); + + return 0; +err: + ci_dbg_print("%s: Cannot initialize CI: Error %d.\n", __func__, ret); + + kfree(state); + kfree(inter); + + return ret; +} +EXPORT_SYMBOL(altera_ci_init); + +int altera_ci_tuner_reset(void *dev, int ci_nr) +{ + struct fpga_inode *temp_int = find_inode(dev); + struct fpga_internal *inter = NULL; + u8 store; + + ci_dbg_print("%s\n", __func__); + + if (temp_int == NULL) + return -1; + + if (temp_int->internal == NULL) + return -1; + + inter = temp_int->internal; + + mutex_lock(&inter->fpga_mutex); + + store = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL2, 0, NETUP_CI_FLG_RD); + store &= ~(4 << (2 - ci_nr)); + netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL2, store, 0); + msleep(100); + store |= (4 << (2 - ci_nr)); + netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL2, store, 0); + + mutex_unlock(&inter->fpga_mutex); + + return 0; +} +EXPORT_SYMBOL(altera_ci_tuner_reset); diff --git a/drivers/media/pci/cx23885/altera-ci.h b/drivers/media/pci/cx23885/altera-ci.h new file mode 100644 index 000000000..3646936da --- /dev/null +++ b/drivers/media/pci/cx23885/altera-ci.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * altera-ci.c + * + * CI driver in conjunction with NetUp Dual DVB-T/C RF CI card + * + * Copyright (C) 2010 NetUP Inc. + * Copyright (C) 2010 Igor M. Liplianin + */ +#ifndef __ALTERA_CI_H +#define __ALTERA_CI_H + +#define ALT_DATA 0x000000ff +#define ALT_TDI 0x00008000 +#define ALT_TDO 0x00004000 +#define ALT_TCK 0x00002000 +#define ALT_RDY 0x00001000 +#define ALT_RD 0x00000800 +#define ALT_WR 0x00000400 +#define ALT_AD_RG 0x00000200 +#define ALT_CS 0x00000100 + +struct altera_ci_config { + void *dev;/* main dev, for example cx23885_dev */ + void *adapter;/* for CI to connect to */ + struct dvb_demux *demux;/* for hardware PID filter to connect to */ + int (*fpga_rw) (void *dev, int ad_rg, int val, int rw); +}; + +#if IS_REACHABLE(CONFIG_MEDIA_ALTERA_CI) + +extern int altera_ci_init(struct altera_ci_config *config, int ci_nr); +extern void altera_ci_release(void *dev, int ci_nr); +extern int altera_ci_irq(void *dev); +extern int altera_ci_tuner_reset(void *dev, int ci_nr); + +#else + +static inline int altera_ci_init(struct altera_ci_config *config, int ci_nr) +{ + pr_warn("%s: driver disabled by Kconfig\n", __func__); + return 0; +} + +static inline void altera_ci_release(void *dev, int ci_nr) +{ + pr_warn("%s: driver disabled by Kconfig\n", __func__); +} + +static inline int altera_ci_irq(void *dev) +{ + pr_warn("%s: driver disabled by Kconfig\n", __func__); + return 0; +} + +static inline int altera_ci_tuner_reset(void *dev, int ci_nr) +{ + pr_warn("%s: driver disabled by Kconfig\n", __func__); + return 0; +} + +#endif +#if 0 +static inline int altera_hw_filt_init(struct altera_ci_config *config, + int hw_filt_nr) +{ + pr_warn("%s: driver disabled by Kconfig\n", __func__); + return 0; +} + +static inline void altera_hw_filt_release(void *dev, int filt_nr) +{ + pr_warn("%s: driver disabled by Kconfig\n", __func__); +} + +static inline int altera_pid_feed_control(void *dev, int filt_nr, + struct dvb_demux_feed *dvbdmxfeed, int onoff) +{ + pr_warn("%s: driver disabled by Kconfig\n", __func__); + return 0; +} + +#endif /* CONFIG_MEDIA_ALTERA_CI */ + +#endif /* __ALTERA_CI_H */ diff --git a/drivers/media/pci/cx23885/cimax2.c b/drivers/media/pci/cx23885/cimax2.c new file mode 100644 index 000000000..723509fa1 --- /dev/null +++ b/drivers/media/pci/cx23885/cimax2.c @@ -0,0 +1,534 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cimax2.c + * + * CIMax2(R) SP2 driver in conjunction with NetUp Dual DVB-S2 CI card + * + * Copyright (C) 2009 NetUP Inc. + * Copyright (C) 2009 Igor M. Liplianin + * Copyright (C) 2009 Abylay Ospan + */ + +#include "cx23885.h" +#include "cimax2.h" +#include + +/* Max transfer size done by I2C transfer functions */ +#define MAX_XFER_SIZE 64 + +/**** Bit definitions for MC417_RWD and MC417_OEN registers *** + bits 31-16 ++-----------+ +| Reserved | ++-----------+ + bit 15 bit 14 bit 13 bit 12 bit 11 bit 10 bit 9 bit 8 ++-------+-------+-------+-------+-------+-------+-------+-------+ +| WR# | RD# | | ACK# | ADHI | ADLO | CS1# | CS0# | ++-------+-------+-------+-------+-------+-------+-------+-------+ + bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0 ++-------+-------+-------+-------+-------+-------+-------+-------+ +| DATA7| DATA6| DATA5| DATA4| DATA3| DATA2| DATA1| DATA0| ++-------+-------+-------+-------+-------+-------+-------+-------+ +***/ +/* MC417 */ +#define NETUP_DATA 0x000000ff +#define NETUP_WR 0x00008000 +#define NETUP_RD 0x00004000 +#define NETUP_ACK 0x00001000 +#define NETUP_ADHI 0x00000800 +#define NETUP_ADLO 0x00000400 +#define NETUP_CS1 0x00000200 +#define NETUP_CS0 0x00000100 +#define NETUP_EN_ALL 0x00001000 +#define NETUP_CTRL_OFF (NETUP_CS1 | NETUP_CS0 | NETUP_WR | NETUP_RD) +#define NETUP_CI_CTL 0x04 +#define NETUP_CI_RD 1 + +#define NETUP_IRQ_DETAM 0x1 +#define NETUP_IRQ_IRQAM 0x4 + +static unsigned int ci_dbg; +module_param(ci_dbg, int, 0644); +MODULE_PARM_DESC(ci_dbg, "Enable CI debugging"); + +static unsigned int ci_irq_enable; +module_param(ci_irq_enable, int, 0644); +MODULE_PARM_DESC(ci_irq_enable, "Enable IRQ from CAM"); + +#define ci_dbg_print(fmt, args...) \ + do { \ + if (ci_dbg) \ + printk(KERN_DEBUG pr_fmt("%s: " fmt), \ + __func__, ##args); \ + } while (0) + +#define ci_irq_flags() (ci_irq_enable ? NETUP_IRQ_IRQAM : 0) + +/* stores all private variables for communication with CI */ +struct netup_ci_state { + struct dvb_ca_en50221 ca; + struct mutex ca_mutex; + struct i2c_adapter *i2c_adap; + u8 ci_i2c_addr; + int status; + struct work_struct work; + void *priv; + u8 current_irq_mode; + int current_ci_flag; + unsigned long next_status_checked_time; +}; + + +static int netup_read_i2c(struct i2c_adapter *i2c_adap, u8 addr, u8 reg, + u8 *buf, int len) +{ + int ret; + struct i2c_msg msg[] = { + { + .addr = addr, + .flags = 0, + .buf = ®, + .len = 1 + }, { + .addr = addr, + .flags = I2C_M_RD, + .buf = buf, + .len = len + } + }; + + ret = i2c_transfer(i2c_adap, msg, 2); + + if (ret != 2) { + ci_dbg_print("%s: i2c read error, Reg = 0x%02x, Status = %d\n", + __func__, reg, ret); + + return -1; + } + + ci_dbg_print("%s: i2c read Addr=0x%04x, Reg = 0x%02x, data = %02x\n", + __func__, addr, reg, buf[0]); + + return 0; +} + +static int netup_write_i2c(struct i2c_adapter *i2c_adap, u8 addr, u8 reg, + u8 *buf, int len) +{ + int ret; + u8 buffer[MAX_XFER_SIZE]; + + struct i2c_msg msg = { + .addr = addr, + .flags = 0, + .buf = &buffer[0], + .len = len + 1 + }; + + if (1 + len > sizeof(buffer)) { + pr_warn("%s: i2c wr reg=%04x: len=%d is too big!\n", + KBUILD_MODNAME, reg, len); + return -EINVAL; + } + + buffer[0] = reg; + memcpy(&buffer[1], buf, len); + + ret = i2c_transfer(i2c_adap, &msg, 1); + + if (ret != 1) { + ci_dbg_print("%s: i2c write error, Reg=[0x%02x], Status=%d\n", + __func__, reg, ret); + return -1; + } + + return 0; +} + +static int netup_ci_get_mem(struct cx23885_dev *dev) +{ + int mem; + unsigned long timeout = jiffies + msecs_to_jiffies(1); + + for (;;) { + mem = cx_read(MC417_RWD); + if ((mem & NETUP_ACK) == 0) + break; + if (time_after(jiffies, timeout)) + break; + udelay(1); + } + + cx_set(MC417_RWD, NETUP_CTRL_OFF); + + return mem & 0xff; +} + +static int netup_ci_op_cam(struct dvb_ca_en50221 *en50221, int slot, + u8 flag, u8 read, int addr, u8 data) +{ + struct netup_ci_state *state = en50221->data; + struct cx23885_tsport *port = state->priv; + struct cx23885_dev *dev = port->dev; + + u8 store; + int mem; + int ret; + + if (0 != slot) + return -EINVAL; + + if (state->current_ci_flag != flag) { + ret = netup_read_i2c(state->i2c_adap, state->ci_i2c_addr, + 0, &store, 1); + if (ret != 0) + return ret; + + store &= ~0x0c; + store |= flag; + + ret = netup_write_i2c(state->i2c_adap, state->ci_i2c_addr, + 0, &store, 1); + if (ret != 0) + return ret; + } + state->current_ci_flag = flag; + + mutex_lock(&dev->gpio_lock); + + /* write addr */ + cx_write(MC417_OEN, NETUP_EN_ALL); + cx_write(MC417_RWD, NETUP_CTRL_OFF | + NETUP_ADLO | (0xff & addr)); + cx_clear(MC417_RWD, NETUP_ADLO); + cx_write(MC417_RWD, NETUP_CTRL_OFF | + NETUP_ADHI | (0xff & (addr >> 8))); + cx_clear(MC417_RWD, NETUP_ADHI); + + if (read) { /* data in */ + cx_write(MC417_OEN, NETUP_EN_ALL | NETUP_DATA); + } else /* data out */ + cx_write(MC417_RWD, NETUP_CTRL_OFF | data); + + /* choose chip */ + cx_clear(MC417_RWD, + (state->ci_i2c_addr == 0x40) ? NETUP_CS0 : NETUP_CS1); + /* read/write */ + cx_clear(MC417_RWD, (read) ? NETUP_RD : NETUP_WR); + mem = netup_ci_get_mem(dev); + + mutex_unlock(&dev->gpio_lock); + + if (!read) + if (mem < 0) + return -EREMOTEIO; + + ci_dbg_print("%s: %s: chipaddr=[0x%x] addr=[0x%02x], %s=%x\n", __func__, + (read) ? "read" : "write", state->ci_i2c_addr, addr, + (flag == NETUP_CI_CTL) ? "ctl" : "mem", + (read) ? mem : data); + + if (read) + return mem; + + return 0; +} + +int netup_ci_read_attribute_mem(struct dvb_ca_en50221 *en50221, + int slot, int addr) +{ + return netup_ci_op_cam(en50221, slot, 0, NETUP_CI_RD, addr, 0); +} + +int netup_ci_write_attribute_mem(struct dvb_ca_en50221 *en50221, + int slot, int addr, u8 data) +{ + return netup_ci_op_cam(en50221, slot, 0, 0, addr, data); +} + +int netup_ci_read_cam_ctl(struct dvb_ca_en50221 *en50221, int slot, + u8 addr) +{ + return netup_ci_op_cam(en50221, slot, NETUP_CI_CTL, + NETUP_CI_RD, addr, 0); +} + +int netup_ci_write_cam_ctl(struct dvb_ca_en50221 *en50221, int slot, + u8 addr, u8 data) +{ + return netup_ci_op_cam(en50221, slot, NETUP_CI_CTL, 0, addr, data); +} + +int netup_ci_slot_reset(struct dvb_ca_en50221 *en50221, int slot) +{ + struct netup_ci_state *state = en50221->data; + u8 buf = 0x80; + int ret; + + if (0 != slot) + return -EINVAL; + + udelay(500); + ret = netup_write_i2c(state->i2c_adap, state->ci_i2c_addr, + 0, &buf, 1); + + if (ret != 0) + return ret; + + udelay(500); + + buf = 0x00; + ret = netup_write_i2c(state->i2c_adap, state->ci_i2c_addr, + 0, &buf, 1); + + msleep(1000); + dvb_ca_en50221_camready_irq(&state->ca, 0); + + return 0; + +} + +int netup_ci_slot_shutdown(struct dvb_ca_en50221 *en50221, int slot) +{ + /* not implemented */ + return 0; +} + +static int netup_ci_set_irq(struct dvb_ca_en50221 *en50221, u8 irq_mode) +{ + struct netup_ci_state *state = en50221->data; + int ret; + + if (irq_mode == state->current_irq_mode) + return 0; + + ci_dbg_print("%s: chipaddr=[0x%x] setting ci IRQ to [0x%x] \n", + __func__, state->ci_i2c_addr, irq_mode); + ret = netup_write_i2c(state->i2c_adap, state->ci_i2c_addr, + 0x1b, &irq_mode, 1); + + if (ret != 0) + return ret; + + state->current_irq_mode = irq_mode; + + return 0; +} + +int netup_ci_slot_ts_ctl(struct dvb_ca_en50221 *en50221, int slot) +{ + struct netup_ci_state *state = en50221->data; + u8 buf; + + if (0 != slot) + return -EINVAL; + + netup_read_i2c(state->i2c_adap, state->ci_i2c_addr, + 0, &buf, 1); + buf |= 0x60; + + return netup_write_i2c(state->i2c_adap, state->ci_i2c_addr, + 0, &buf, 1); +} + +/* work handler */ +static void netup_read_ci_status(struct work_struct *work) +{ + struct netup_ci_state *state = + container_of(work, struct netup_ci_state, work); + u8 buf[33]; + int ret; + + /* CAM module IRQ processing. fast operation */ + dvb_ca_en50221_frda_irq(&state->ca, 0); + + /* CAM module INSERT/REMOVE processing. slow operation because of i2c + * transfers */ + if (time_after(jiffies, state->next_status_checked_time) + || !state->status) { + ret = netup_read_i2c(state->i2c_adap, state->ci_i2c_addr, + 0, &buf[0], 33); + + state->next_status_checked_time = jiffies + + msecs_to_jiffies(1000); + + if (ret != 0) + return; + + ci_dbg_print("%s: Slot Status Addr=[0x%04x], Reg=[0x%02x], data=%02x, TS config = %02x\n", + __func__, state->ci_i2c_addr, 0, buf[0], buf[0]); + + + if (buf[0] & 1) + state->status = DVB_CA_EN50221_POLL_CAM_PRESENT | + DVB_CA_EN50221_POLL_CAM_READY; + else + state->status = 0; + } +} + +/* CI irq handler */ +int netup_ci_slot_status(struct cx23885_dev *dev, u32 pci_status) +{ + struct cx23885_tsport *port = NULL; + struct netup_ci_state *state = NULL; + + ci_dbg_print("%s:\n", __func__); + + if (0 == (pci_status & (PCI_MSK_GPIO0 | PCI_MSK_GPIO1))) + return 0; + + if (pci_status & PCI_MSK_GPIO0) { + port = &dev->ts1; + state = port->port_priv; + schedule_work(&state->work); + ci_dbg_print("%s: Wakeup CI0\n", __func__); + } + + if (pci_status & PCI_MSK_GPIO1) { + port = &dev->ts2; + state = port->port_priv; + schedule_work(&state->work); + ci_dbg_print("%s: Wakeup CI1\n", __func__); + } + + return 1; +} + +int netup_poll_ci_slot_status(struct dvb_ca_en50221 *en50221, + int slot, int open) +{ + struct netup_ci_state *state = en50221->data; + + if (0 != slot) + return -EINVAL; + + netup_ci_set_irq(en50221, open ? (NETUP_IRQ_DETAM | ci_irq_flags()) + : NETUP_IRQ_DETAM); + + return state->status; +} + +int netup_ci_init(struct cx23885_tsport *port) +{ + struct netup_ci_state *state; + u8 cimax_init[34] = { + 0x00, /* module A control*/ + 0x00, /* auto select mask high A */ + 0x00, /* auto select mask low A */ + 0x00, /* auto select pattern high A */ + 0x00, /* auto select pattern low A */ + 0x44, /* memory access time A */ + 0x00, /* invert input A */ + 0x00, /* RFU */ + 0x00, /* RFU */ + 0x00, /* module B control*/ + 0x00, /* auto select mask high B */ + 0x00, /* auto select mask low B */ + 0x00, /* auto select pattern high B */ + 0x00, /* auto select pattern low B */ + 0x44, /* memory access time B */ + 0x00, /* invert input B */ + 0x00, /* RFU */ + 0x00, /* RFU */ + 0x00, /* auto select mask high Ext */ + 0x00, /* auto select mask low Ext */ + 0x00, /* auto select pattern high Ext */ + 0x00, /* auto select pattern low Ext */ + 0x00, /* RFU */ + 0x02, /* destination - module A */ + 0x01, /* power on (use it like store place) */ + 0x00, /* RFU */ + 0x00, /* int status read only */ + ci_irq_flags() | NETUP_IRQ_DETAM, /* DETAM, IRQAM unmasked */ + 0x05, /* EXTINT=active-high, INT=push-pull */ + 0x00, /* USCG1 */ + 0x04, /* ack active low */ + 0x00, /* LOCK = 0 */ + 0x33, /* serial mode, rising in, rising out, MSB first*/ + 0x31, /* synchronization */ + }; + int ret; + + ci_dbg_print("%s\n", __func__); + state = kzalloc_obj(struct netup_ci_state); + if (!state) { + ci_dbg_print("%s: Unable create CI structure!\n", __func__); + ret = -ENOMEM; + goto err; + } + + port->port_priv = state; + + switch (port->nr) { + case 1: + state->ci_i2c_addr = 0x40; + break; + case 2: + state->ci_i2c_addr = 0x41; + break; + } + + state->i2c_adap = &port->dev->i2c_bus[0].i2c_adap; + state->ca.owner = THIS_MODULE; + state->ca.read_attribute_mem = netup_ci_read_attribute_mem; + state->ca.write_attribute_mem = netup_ci_write_attribute_mem; + state->ca.read_cam_control = netup_ci_read_cam_ctl; + state->ca.write_cam_control = netup_ci_write_cam_ctl; + state->ca.slot_reset = netup_ci_slot_reset; + state->ca.slot_shutdown = netup_ci_slot_shutdown; + state->ca.slot_ts_enable = netup_ci_slot_ts_ctl; + state->ca.poll_slot_status = netup_poll_ci_slot_status; + state->ca.data = state; + state->priv = port; + state->current_irq_mode = ci_irq_flags() | NETUP_IRQ_DETAM; + + ret = netup_write_i2c(state->i2c_adap, state->ci_i2c_addr, + 0, &cimax_init[0], 34); + /* lock registers */ + ret |= netup_write_i2c(state->i2c_adap, state->ci_i2c_addr, + 0x1f, &cimax_init[0x18], 1); + /* power on slots */ + ret |= netup_write_i2c(state->i2c_adap, state->ci_i2c_addr, + 0x18, &cimax_init[0x18], 1); + + if (0 != ret) + goto err; + + ret = dvb_ca_en50221_init(&port->frontends.adapter, + &state->ca, + /* flags */ 0, + /* n_slots */ 1); + if (0 != ret) + goto err; + + INIT_WORK(&state->work, netup_read_ci_status); + schedule_work(&state->work); + + ci_dbg_print("%s: CI initialized!\n", __func__); + + return 0; +err: + ci_dbg_print("%s: Cannot initialize CI: Error %d.\n", __func__, ret); + kfree(state); + return ret; +} + +void netup_ci_exit(struct cx23885_tsport *port) +{ + struct netup_ci_state *state; + + if (NULL == port) + return; + + state = (struct netup_ci_state *)port->port_priv; + if (NULL == state) + return; + + if (NULL == state->ca.data) + return; + + cancel_work_sync(&state->work); + dvb_ca_en50221_release(&state->ca); + kfree(state); +} diff --git a/drivers/media/pci/cx23885/cimax2.h b/drivers/media/pci/cx23885/cimax2.h new file mode 100644 index 000000000..79f9cca1b --- /dev/null +++ b/drivers/media/pci/cx23885/cimax2.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cimax2.h + * + * CIMax(R) SP2 driver in conjunction with NetUp Dual DVB-S2 CI card + * + * Copyright (C) 2009 NetUP Inc. + * Copyright (C) 2009 Igor M. Liplianin + * Copyright (C) 2009 Abylay Ospan + */ + +#ifndef CIMAX2_H +#define CIMAX2_H +#include + +extern int netup_ci_read_attribute_mem(struct dvb_ca_en50221 *en50221, + int slot, int addr); +extern int netup_ci_write_attribute_mem(struct dvb_ca_en50221 *en50221, + int slot, int addr, u8 data); +extern int netup_ci_read_cam_ctl(struct dvb_ca_en50221 *en50221, + int slot, u8 addr); +extern int netup_ci_write_cam_ctl(struct dvb_ca_en50221 *en50221, + int slot, u8 addr, u8 data); +extern int netup_ci_slot_reset(struct dvb_ca_en50221 *en50221, int slot); +extern int netup_ci_slot_shutdown(struct dvb_ca_en50221 *en50221, int slot); +extern int netup_ci_slot_ts_ctl(struct dvb_ca_en50221 *en50221, int slot); +extern int netup_ci_slot_status(struct cx23885_dev *dev, u32 pci_status); +extern int netup_poll_ci_slot_status(struct dvb_ca_en50221 *en50221, + int slot, int open); +extern int netup_ci_init(struct cx23885_tsport *port); +extern void netup_ci_exit(struct cx23885_tsport *port); + +#endif diff --git a/drivers/media/pci/cx23885/cx23885-417.c b/drivers/media/pci/cx23885/cx23885-417.c new file mode 100644 index 000000000..219937a15 --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-417.c @@ -0,0 +1,1564 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Support for a cx23417 mpeg encoder via cx23885 host port. + * + * (c) 2004 Jelle Foks + * (c) 2004 Gerd Knorr + * (c) 2008 Steven Toth + * - CX23885/7/8 support + * + * Includes parts from the ivtv driver + */ + +#include "cx23885.h" +#include "cx23885-ioctl.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CX23885_FIRM_IMAGE_SIZE 376836 +#define CX23885_FIRM_IMAGE_NAME "v4l-cx23885-enc.fw" + +static unsigned int mpegbufs = 32; +module_param(mpegbufs, int, 0644); +MODULE_PARM_DESC(mpegbufs, "number of mpeg buffers, range 2-32"); +static unsigned int mpeglines = 32; +module_param(mpeglines, int, 0644); +MODULE_PARM_DESC(mpeglines, "number of lines in an MPEG buffer, range 2-32"); +static unsigned int mpeglinesize = 512; +module_param(mpeglinesize, int, 0644); +MODULE_PARM_DESC(mpeglinesize, + "number of bytes in each line of an MPEG buffer, range 512-1024"); + +static unsigned int v4l_debug; +module_param(v4l_debug, int, 0644); +MODULE_PARM_DESC(v4l_debug, "enable V4L debug messages"); + +#define dprintk(level, fmt, arg...)\ + do { if (v4l_debug >= level) \ + printk(KERN_DEBUG pr_fmt("%s: 417:" fmt), \ + __func__, ##arg); \ + } while (0) + +static struct cx23885_tvnorm cx23885_tvnorms[] = { + { + .name = "NTSC-M", + .id = V4L2_STD_NTSC_M, + }, { + .name = "NTSC-JP", + .id = V4L2_STD_NTSC_M_JP, + }, { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + }, { + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + }, { + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + }, { + .name = "PAL-N", + .id = V4L2_STD_PAL_N, + }, { + .name = "PAL-Nc", + .id = V4L2_STD_PAL_Nc, + }, { + .name = "PAL-60", + .id = V4L2_STD_PAL_60, + }, { + .name = "SECAM-L", + .id = V4L2_STD_SECAM_L, + }, { + .name = "SECAM-DK", + .id = V4L2_STD_SECAM_DK, + } +}; + +/* ------------------------------------------------------------------ */ +enum cx23885_capture_type { + CX23885_MPEG_CAPTURE, + CX23885_RAW_CAPTURE, + CX23885_RAW_PASSTHRU_CAPTURE +}; +enum cx23885_capture_bits { + CX23885_RAW_BITS_NONE = 0x00, + CX23885_RAW_BITS_YUV_CAPTURE = 0x01, + CX23885_RAW_BITS_PCM_CAPTURE = 0x02, + CX23885_RAW_BITS_VBI_CAPTURE = 0x04, + CX23885_RAW_BITS_PASSTHRU_CAPTURE = 0x08, + CX23885_RAW_BITS_TO_HOST_CAPTURE = 0x10 +}; +enum cx23885_capture_end { + CX23885_END_AT_GOP, /* stop at the end of gop, generate irq */ + CX23885_END_NOW, /* stop immediately, no irq */ +}; +enum cx23885_framerate { + CX23885_FRAMERATE_NTSC_30, /* NTSC: 30fps */ + CX23885_FRAMERATE_PAL_25 /* PAL: 25fps */ +}; +enum cx23885_stream_port { + CX23885_OUTPUT_PORT_MEMORY, + CX23885_OUTPUT_PORT_STREAMING, + CX23885_OUTPUT_PORT_SERIAL +}; +enum cx23885_data_xfer_status { + CX23885_MORE_BUFFERS_FOLLOW, + CX23885_LAST_BUFFER, +}; +enum cx23885_picture_mask { + CX23885_PICTURE_MASK_NONE, + CX23885_PICTURE_MASK_I_FRAMES, + CX23885_PICTURE_MASK_I_P_FRAMES = 0x3, + CX23885_PICTURE_MASK_ALL_FRAMES = 0x7, +}; +enum cx23885_vbi_mode_bits { + CX23885_VBI_BITS_SLICED, + CX23885_VBI_BITS_RAW, +}; +enum cx23885_vbi_insertion_bits { + CX23885_VBI_BITS_INSERT_IN_XTENSION_USR_DATA, + CX23885_VBI_BITS_INSERT_IN_PRIVATE_PACKETS = 0x1 << 1, + CX23885_VBI_BITS_SEPARATE_STREAM = 0x2 << 1, + CX23885_VBI_BITS_SEPARATE_STREAM_USR_DATA = 0x4 << 1, + CX23885_VBI_BITS_SEPARATE_STREAM_PRV_DATA = 0x5 << 1, +}; +enum cx23885_dma_unit { + CX23885_DMA_BYTES, + CX23885_DMA_FRAMES, +}; +enum cx23885_dma_transfer_status_bits { + CX23885_DMA_TRANSFER_BITS_DONE = 0x01, + CX23885_DMA_TRANSFER_BITS_ERROR = 0x04, + CX23885_DMA_TRANSFER_BITS_LL_ERROR = 0x10, +}; +enum cx23885_pause { + CX23885_PAUSE_ENCODING, + CX23885_RESUME_ENCODING, +}; +enum cx23885_copyright { + CX23885_COPYRIGHT_OFF, + CX23885_COPYRIGHT_ON, +}; +enum cx23885_notification_type { + CX23885_NOTIFICATION_REFRESH, +}; +enum cx23885_notification_status { + CX23885_NOTIFICATION_OFF, + CX23885_NOTIFICATION_ON, +}; +enum cx23885_notification_mailbox { + CX23885_NOTIFICATION_NO_MAILBOX = -1, +}; +enum cx23885_field1_lines { + CX23885_FIELD1_SAA7114 = 0x00EF, /* 239 */ + CX23885_FIELD1_SAA7115 = 0x00F0, /* 240 */ + CX23885_FIELD1_MICRONAS = 0x0105, /* 261 */ +}; +enum cx23885_field2_lines { + CX23885_FIELD2_SAA7114 = 0x00EF, /* 239 */ + CX23885_FIELD2_SAA7115 = 0x00F0, /* 240 */ + CX23885_FIELD2_MICRONAS = 0x0106, /* 262 */ +}; +enum cx23885_custom_data_type { + CX23885_CUSTOM_EXTENSION_USR_DATA, + CX23885_CUSTOM_PRIVATE_PACKET, +}; +enum cx23885_mute { + CX23885_UNMUTE, + CX23885_MUTE, +}; +enum cx23885_mute_video_mask { + CX23885_MUTE_VIDEO_V_MASK = 0x0000FF00, + CX23885_MUTE_VIDEO_U_MASK = 0x00FF0000, + CX23885_MUTE_VIDEO_Y_MASK = 0xFF000000, +}; +enum cx23885_mute_video_shift { + CX23885_MUTE_VIDEO_V_SHIFT = 8, + CX23885_MUTE_VIDEO_U_SHIFT = 16, + CX23885_MUTE_VIDEO_Y_SHIFT = 24, +}; + +/* defines below are from ivtv-driver.h */ +#define IVTV_CMD_HW_BLOCKS_RST 0xFFFFFFFF + +/* Firmware API commands */ +#define IVTV_API_STD_TIMEOUT 500 + +/* Registers */ +/* IVTV_REG_OFFSET */ +#define IVTV_REG_ENC_SDRAM_REFRESH (0x07F8) +#define IVTV_REG_ENC_SDRAM_PRECHARGE (0x07FC) +#define IVTV_REG_SPU (0x9050) +#define IVTV_REG_HW_BLOCKS (0x9054) +#define IVTV_REG_VPU (0x9058) +#define IVTV_REG_APU (0xA064) + +/**** Bit definitions for MC417_RWD and MC417_OEN registers *** + bits 31-16 ++-----------+ +| Reserved | ++-----------+ + bit 15 bit 14 bit 13 bit 12 bit 11 bit 10 bit 9 bit 8 ++-------+-------+-------+-------+-------+-------+-------+-------+ +| MIWR# | MIRD# | MICS# |MIRDY# |MIADDR3|MIADDR2|MIADDR1|MIADDR0| ++-------+-------+-------+-------+-------+-------+-------+-------+ + bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0 ++-------+-------+-------+-------+-------+-------+-------+-------+ +|MIDATA7|MIDATA6|MIDATA5|MIDATA4|MIDATA3|MIDATA2|MIDATA1|MIDATA0| ++-------+-------+-------+-------+-------+-------+-------+-------+ +***/ +#define MC417_MIWR 0x8000 +#define MC417_MIRD 0x4000 +#define MC417_MICS 0x2000 +#define MC417_MIRDY 0x1000 +#define MC417_MIADDR 0x0F00 +#define MC417_MIDATA 0x00FF + +/* MIADDR* nibble definitions */ +#define MCI_MEMORY_DATA_BYTE0 0x000 +#define MCI_MEMORY_DATA_BYTE1 0x100 +#define MCI_MEMORY_DATA_BYTE2 0x200 +#define MCI_MEMORY_DATA_BYTE3 0x300 +#define MCI_MEMORY_ADDRESS_BYTE2 0x400 +#define MCI_MEMORY_ADDRESS_BYTE1 0x500 +#define MCI_MEMORY_ADDRESS_BYTE0 0x600 +#define MCI_REGISTER_DATA_BYTE0 0x800 +#define MCI_REGISTER_DATA_BYTE1 0x900 +#define MCI_REGISTER_DATA_BYTE2 0xA00 +#define MCI_REGISTER_DATA_BYTE3 0xB00 +#define MCI_REGISTER_ADDRESS_BYTE0 0xC00 +#define MCI_REGISTER_ADDRESS_BYTE1 0xD00 +#define MCI_REGISTER_MODE 0xE00 + +/* Read and write modes */ +#define MCI_MODE_REGISTER_READ 0 +#define MCI_MODE_REGISTER_WRITE 1 +#define MCI_MODE_MEMORY_READ 0 +#define MCI_MODE_MEMORY_WRITE 0x40 + +/*** Bit definitions for MC417_CTL register **** + bits 31-6 bits 5-4 bit 3 bits 2-1 Bit 0 ++--------+-------------+--------+--------------+------------+ +|Reserved|MC417_SPD_CTL|Reserved|MC417_GPIO_SEL|UART_GPIO_EN| ++--------+-------------+--------+--------------+------------+ +***/ +#define MC417_SPD_CTL(x) (((x) << 4) & 0x00000030) +#define MC417_GPIO_SEL(x) (((x) << 1) & 0x00000006) +#define MC417_UART_GPIO_EN 0x00000001 + +/* Values for speed control */ +#define MC417_SPD_CTL_SLOW 0x1 +#define MC417_SPD_CTL_MEDIUM 0x0 +#define MC417_SPD_CTL_FAST 0x3 /* b'1x, but we use b'11 */ + +/* Values for GPIO select */ +#define MC417_GPIO_SEL_GPIO3 0x3 +#define MC417_GPIO_SEL_GPIO2 0x2 +#define MC417_GPIO_SEL_GPIO1 0x1 +#define MC417_GPIO_SEL_GPIO0 0x0 + +void cx23885_mc417_init(struct cx23885_dev *dev) +{ + u32 regval; + + dprintk(2, "%s()\n", __func__); + + /* Configure MC417_CTL register to defaults. */ + regval = MC417_SPD_CTL(MC417_SPD_CTL_FAST) | + MC417_GPIO_SEL(MC417_GPIO_SEL_GPIO3) | + MC417_UART_GPIO_EN; + cx_write(MC417_CTL, regval); + + /* Configure MC417_OEN to defaults. */ + regval = MC417_MIRDY; + cx_write(MC417_OEN, regval); + + /* Configure MC417_RWD to defaults. */ + regval = MC417_MIWR | MC417_MIRD | MC417_MICS; + cx_write(MC417_RWD, regval); +} + +static int mc417_wait_ready(struct cx23885_dev *dev) +{ + u32 mi_ready; + unsigned long timeout = jiffies + msecs_to_jiffies(1); + + for (;;) { + mi_ready = cx_read(MC417_RWD) & MC417_MIRDY; + if (mi_ready != 0) + return 0; + if (time_after(jiffies, timeout)) + return -1; + udelay(1); + } +} + +int mc417_register_write(struct cx23885_dev *dev, u16 address, u32 value) +{ + u32 regval; + + /* Enable MC417 GPIO outputs except for MC417_MIRDY, + * which is an input. + */ + cx_write(MC417_OEN, MC417_MIRDY); + + /* Write data byte 0 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_DATA_BYTE0 | + (value & 0x000000FF); + cx_write(MC417_RWD, regval); + + /* Transition CS/WR to effect write transaction across bus. */ + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write data byte 1 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_DATA_BYTE1 | + ((value >> 8) & 0x000000FF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write data byte 2 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_DATA_BYTE2 | + ((value >> 16) & 0x000000FF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write data byte 3 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_DATA_BYTE3 | + ((value >> 24) & 0x000000FF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write address byte 0 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_ADDRESS_BYTE0 | + (address & 0xFF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write address byte 1 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_ADDRESS_BYTE1 | + ((address >> 8) & 0xFF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Indicate that this is a write. */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_MODE | + MCI_MODE_REGISTER_WRITE; + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Wait for the trans to complete (MC417_MIRDY asserted). */ + return mc417_wait_ready(dev); +} + +int mc417_register_read(struct cx23885_dev *dev, u16 address, u32 *value) +{ + int retval; + u32 regval; + u32 tempval; + u32 dataval; + + /* Enable MC417 GPIO outputs except for MC417_MIRDY, + * which is an input. + */ + cx_write(MC417_OEN, MC417_MIRDY); + + /* Write address byte 0 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_ADDRESS_BYTE0 | + ((address & 0x00FF)); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write address byte 1 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_ADDRESS_BYTE1 | + ((address >> 8) & 0xFF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Indicate that this is a register read. */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_MODE | + MCI_MODE_REGISTER_READ; + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Wait for the trans to complete (MC417_MIRDY asserted). */ + retval = mc417_wait_ready(dev); + + /* switch the DAT0-7 GPIO[10:3] to input mode */ + cx_write(MC417_OEN, MC417_MIRDY | MC417_MIDATA); + + /* Read data byte 0 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_DATA_BYTE0; + cx_write(MC417_RWD, regval); + + /* Transition RD to effect read transaction across bus. + * Transition 0x5000 -> 0x9000 correct (RD/RDY -> WR/RDY)? + * Should it be 0x9000 -> 0xF000 (also why is RDY being set, its + * input only...) + */ + regval = MC417_MIWR | MC417_MIRDY | MCI_REGISTER_DATA_BYTE0; + cx_write(MC417_RWD, regval); + + /* Collect byte */ + tempval = cx_read(MC417_RWD); + dataval = tempval & 0x000000FF; + + /* Bring CS and RD high. */ + regval = MC417_MIWR | MC417_MIRD | MC417_MICS | MC417_MIRDY; + cx_write(MC417_RWD, regval); + + /* Read data byte 1 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_DATA_BYTE1; + cx_write(MC417_RWD, regval); + regval = MC417_MIWR | MC417_MIRDY | MCI_REGISTER_DATA_BYTE1; + cx_write(MC417_RWD, regval); + tempval = cx_read(MC417_RWD); + dataval |= ((tempval & 0x000000FF) << 8); + regval = MC417_MIWR | MC417_MIRD | MC417_MICS | MC417_MIRDY; + cx_write(MC417_RWD, regval); + + /* Read data byte 2 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_DATA_BYTE2; + cx_write(MC417_RWD, regval); + regval = MC417_MIWR | MC417_MIRDY | MCI_REGISTER_DATA_BYTE2; + cx_write(MC417_RWD, regval); + tempval = cx_read(MC417_RWD); + dataval |= ((tempval & 0x000000FF) << 16); + regval = MC417_MIWR | MC417_MIRD | MC417_MICS | MC417_MIRDY; + cx_write(MC417_RWD, regval); + + /* Read data byte 3 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_DATA_BYTE3; + cx_write(MC417_RWD, regval); + regval = MC417_MIWR | MC417_MIRDY | MCI_REGISTER_DATA_BYTE3; + cx_write(MC417_RWD, regval); + tempval = cx_read(MC417_RWD); + dataval |= ((tempval & 0x000000FF) << 24); + regval = MC417_MIWR | MC417_MIRD | MC417_MICS | MC417_MIRDY; + cx_write(MC417_RWD, regval); + + *value = dataval; + + return retval; +} + +int mc417_memory_write(struct cx23885_dev *dev, u32 address, u32 value) +{ + u32 regval; + + /* Enable MC417 GPIO outputs except for MC417_MIRDY, + * which is an input. + */ + cx_write(MC417_OEN, MC417_MIRDY); + + /* Write data byte 0 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_DATA_BYTE0 | + (value & 0x000000FF); + cx_write(MC417_RWD, regval); + + /* Transition CS/WR to effect write transaction across bus. */ + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write data byte 1 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_DATA_BYTE1 | + ((value >> 8) & 0x000000FF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write data byte 2 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_DATA_BYTE2 | + ((value >> 16) & 0x000000FF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write data byte 3 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_DATA_BYTE3 | + ((value >> 24) & 0x000000FF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write address byte 2 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_ADDRESS_BYTE2 | + MCI_MODE_MEMORY_WRITE | ((address >> 16) & 0x3F); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write address byte 1 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_ADDRESS_BYTE1 | + ((address >> 8) & 0xFF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write address byte 0 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_ADDRESS_BYTE0 | + (address & 0xFF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Wait for the trans to complete (MC417_MIRDY asserted). */ + return mc417_wait_ready(dev); +} + +int mc417_memory_read(struct cx23885_dev *dev, u32 address, u32 *value) +{ + int retval; + u32 regval; + u32 tempval; + u32 dataval; + + /* Enable MC417 GPIO outputs except for MC417_MIRDY, + * which is an input. + */ + cx_write(MC417_OEN, MC417_MIRDY); + + /* Write address byte 2 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_ADDRESS_BYTE2 | + MCI_MODE_MEMORY_READ | ((address >> 16) & 0x3F); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write address byte 1 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_ADDRESS_BYTE1 | + ((address >> 8) & 0xFF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write address byte 0 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_ADDRESS_BYTE0 | + (address & 0xFF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Wait for the trans to complete (MC417_MIRDY asserted). */ + retval = mc417_wait_ready(dev); + + /* switch the DAT0-7 GPIO[10:3] to input mode */ + cx_write(MC417_OEN, MC417_MIRDY | MC417_MIDATA); + + /* Read data byte 3 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_DATA_BYTE3; + cx_write(MC417_RWD, regval); + + /* Transition RD to effect read transaction across bus. */ + regval = MC417_MIWR | MC417_MIRDY | MCI_MEMORY_DATA_BYTE3; + cx_write(MC417_RWD, regval); + + /* Collect byte */ + tempval = cx_read(MC417_RWD); + dataval = ((tempval & 0x000000FF) << 24); + + /* Bring CS and RD high. */ + regval = MC417_MIWR | MC417_MIRD | MC417_MICS | MC417_MIRDY; + cx_write(MC417_RWD, regval); + + /* Read data byte 2 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_DATA_BYTE2; + cx_write(MC417_RWD, regval); + regval = MC417_MIWR | MC417_MIRDY | MCI_MEMORY_DATA_BYTE2; + cx_write(MC417_RWD, regval); + tempval = cx_read(MC417_RWD); + dataval |= ((tempval & 0x000000FF) << 16); + regval = MC417_MIWR | MC417_MIRD | MC417_MICS | MC417_MIRDY; + cx_write(MC417_RWD, regval); + + /* Read data byte 1 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_DATA_BYTE1; + cx_write(MC417_RWD, regval); + regval = MC417_MIWR | MC417_MIRDY | MCI_MEMORY_DATA_BYTE1; + cx_write(MC417_RWD, regval); + tempval = cx_read(MC417_RWD); + dataval |= ((tempval & 0x000000FF) << 8); + regval = MC417_MIWR | MC417_MIRD | MC417_MICS | MC417_MIRDY; + cx_write(MC417_RWD, regval); + + /* Read data byte 0 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_DATA_BYTE0; + cx_write(MC417_RWD, regval); + regval = MC417_MIWR | MC417_MIRDY | MCI_MEMORY_DATA_BYTE0; + cx_write(MC417_RWD, regval); + tempval = cx_read(MC417_RWD); + dataval |= (tempval & 0x000000FF); + regval = MC417_MIWR | MC417_MIRD | MC417_MICS | MC417_MIRDY; + cx_write(MC417_RWD, regval); + + *value = dataval; + + return retval; +} + +void mc417_gpio_set(struct cx23885_dev *dev, u32 mask) +{ + u32 val; + + /* Set the gpio value */ + mc417_register_read(dev, 0x900C, &val); + val |= (mask & 0x000ffff); + mc417_register_write(dev, 0x900C, val); +} + +void mc417_gpio_clear(struct cx23885_dev *dev, u32 mask) +{ + u32 val; + + /* Clear the gpio value */ + mc417_register_read(dev, 0x900C, &val); + val &= ~(mask & 0x0000ffff); + mc417_register_write(dev, 0x900C, val); +} + +void mc417_gpio_enable(struct cx23885_dev *dev, u32 mask, int asoutput) +{ + u32 val; + + /* Enable GPIO direction bits */ + mc417_register_read(dev, 0x9020, &val); + if (asoutput) + val |= (mask & 0x0000ffff); + else + val &= ~(mask & 0x0000ffff); + + mc417_register_write(dev, 0x9020, val); +} +/* ------------------------------------------------------------------ */ + +/* MPEG encoder API */ +static char *cmd_to_str(int cmd) +{ + switch (cmd) { + case CX2341X_ENC_PING_FW: + return "PING_FW"; + case CX2341X_ENC_START_CAPTURE: + return "START_CAPTURE"; + case CX2341X_ENC_STOP_CAPTURE: + return "STOP_CAPTURE"; + case CX2341X_ENC_SET_AUDIO_ID: + return "SET_AUDIO_ID"; + case CX2341X_ENC_SET_VIDEO_ID: + return "SET_VIDEO_ID"; + case CX2341X_ENC_SET_PCR_ID: + return "SET_PCR_ID"; + case CX2341X_ENC_SET_FRAME_RATE: + return "SET_FRAME_RATE"; + case CX2341X_ENC_SET_FRAME_SIZE: + return "SET_FRAME_SIZE"; + case CX2341X_ENC_SET_BIT_RATE: + return "SET_BIT_RATE"; + case CX2341X_ENC_SET_GOP_PROPERTIES: + return "SET_GOP_PROPERTIES"; + case CX2341X_ENC_SET_ASPECT_RATIO: + return "SET_ASPECT_RATIO"; + case CX2341X_ENC_SET_DNR_FILTER_MODE: + return "SET_DNR_FILTER_MODE"; + case CX2341X_ENC_SET_DNR_FILTER_PROPS: + return "SET_DNR_FILTER_PROPS"; + case CX2341X_ENC_SET_CORING_LEVELS: + return "SET_CORING_LEVELS"; + case CX2341X_ENC_SET_SPATIAL_FILTER_TYPE: + return "SET_SPATIAL_FILTER_TYPE"; + case CX2341X_ENC_SET_VBI_LINE: + return "SET_VBI_LINE"; + case CX2341X_ENC_SET_STREAM_TYPE: + return "SET_STREAM_TYPE"; + case CX2341X_ENC_SET_OUTPUT_PORT: + return "SET_OUTPUT_PORT"; + case CX2341X_ENC_SET_AUDIO_PROPERTIES: + return "SET_AUDIO_PROPERTIES"; + case CX2341X_ENC_HALT_FW: + return "HALT_FW"; + case CX2341X_ENC_GET_VERSION: + return "GET_VERSION"; + case CX2341X_ENC_SET_GOP_CLOSURE: + return "SET_GOP_CLOSURE"; + case CX2341X_ENC_GET_SEQ_END: + return "GET_SEQ_END"; + case CX2341X_ENC_SET_PGM_INDEX_INFO: + return "SET_PGM_INDEX_INFO"; + case CX2341X_ENC_SET_VBI_CONFIG: + return "SET_VBI_CONFIG"; + case CX2341X_ENC_SET_DMA_BLOCK_SIZE: + return "SET_DMA_BLOCK_SIZE"; + case CX2341X_ENC_GET_PREV_DMA_INFO_MB_10: + return "GET_PREV_DMA_INFO_MB_10"; + case CX2341X_ENC_GET_PREV_DMA_INFO_MB_9: + return "GET_PREV_DMA_INFO_MB_9"; + case CX2341X_ENC_SCHED_DMA_TO_HOST: + return "SCHED_DMA_TO_HOST"; + case CX2341X_ENC_INITIALIZE_INPUT: + return "INITIALIZE_INPUT"; + case CX2341X_ENC_SET_FRAME_DROP_RATE: + return "SET_FRAME_DROP_RATE"; + case CX2341X_ENC_PAUSE_ENCODER: + return "PAUSE_ENCODER"; + case CX2341X_ENC_REFRESH_INPUT: + return "REFRESH_INPUT"; + case CX2341X_ENC_SET_COPYRIGHT: + return "SET_COPYRIGHT"; + case CX2341X_ENC_SET_EVENT_NOTIFICATION: + return "SET_EVENT_NOTIFICATION"; + case CX2341X_ENC_SET_NUM_VSYNC_LINES: + return "SET_NUM_VSYNC_LINES"; + case CX2341X_ENC_SET_PLACEHOLDER: + return "SET_PLACEHOLDER"; + case CX2341X_ENC_MUTE_VIDEO: + return "MUTE_VIDEO"; + case CX2341X_ENC_MUTE_AUDIO: + return "MUTE_AUDIO"; + case CX2341X_ENC_MISC: + return "MISC"; + default: + return "UNKNOWN"; + } +} + +static int cx23885_mbox_func(void *priv, + u32 command, + int in, + int out, + u32 data[CX2341X_MBOX_MAX_DATA]) +{ + struct cx23885_dev *dev = priv; + unsigned long timeout; + u32 value, flag, retval = 0; + int i; + + dprintk(3, "%s: command(0x%X) = %s\n", __func__, command, + cmd_to_str(command)); + + /* this may not be 100% safe if we can't read any memory location + without side effects */ + mc417_memory_read(dev, dev->cx23417_mailbox - 4, &value); + if (value != 0x12345678) { + pr_err("Firmware and/or mailbox pointer not initialized or corrupted, signature = 0x%x, cmd = %s\n", + value, cmd_to_str(command)); + return -1; + } + + /* This read looks at 32 bits, but flag is only 8 bits. + * Seems we also bail if CMD or TIMEOUT bytes are set??? + */ + mc417_memory_read(dev, dev->cx23417_mailbox, &flag); + if (flag) { + pr_err("ERROR: Mailbox appears to be in use (%x), cmd = %s\n", + flag, cmd_to_str(command)); + return -1; + } + + flag |= 1; /* tell 'em we're working on it */ + mc417_memory_write(dev, dev->cx23417_mailbox, flag); + + /* write command + args + fill remaining with zeros */ + /* command code */ + mc417_memory_write(dev, dev->cx23417_mailbox + 1, command); + mc417_memory_write(dev, dev->cx23417_mailbox + 3, + IVTV_API_STD_TIMEOUT); /* timeout */ + for (i = 0; i < in; i++) { + mc417_memory_write(dev, dev->cx23417_mailbox + 4 + i, data[i]); + dprintk(3, "API Input %d = %d\n", i, data[i]); + } + for (; i < CX2341X_MBOX_MAX_DATA; i++) + mc417_memory_write(dev, dev->cx23417_mailbox + 4 + i, 0); + + flag |= 3; /* tell 'em we're done writing */ + mc417_memory_write(dev, dev->cx23417_mailbox, flag); + + /* wait for firmware to handle the API command */ + timeout = jiffies + msecs_to_jiffies(10); + for (;;) { + mc417_memory_read(dev, dev->cx23417_mailbox, &flag); + if (0 != (flag & 4)) + break; + if (time_after(jiffies, timeout)) { + pr_err("ERROR: API Mailbox timeout\n"); + return -1; + } + udelay(10); + } + + /* read output values */ + for (i = 0; i < out; i++) { + mc417_memory_read(dev, dev->cx23417_mailbox + 4 + i, data + i); + dprintk(3, "API Output %d = %d\n", i, data[i]); + } + + mc417_memory_read(dev, dev->cx23417_mailbox + 2, &retval); + dprintk(3, "API result = %d\n", retval); + + flag = 0; + mc417_memory_write(dev, dev->cx23417_mailbox, flag); + + return retval; +} + +/* We don't need to call the API often, so using just one + * mailbox will probably suffice + */ +static int cx23885_api_cmd(struct cx23885_dev *dev, + u32 command, + u32 inputcnt, + u32 outputcnt, + ...) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + va_list vargs; + int i, err; + + dprintk(3, "%s() cmds = 0x%08x\n", __func__, command); + + va_start(vargs, outputcnt); + for (i = 0; i < inputcnt; i++) + data[i] = va_arg(vargs, int); + + err = cx23885_mbox_func(dev, command, inputcnt, outputcnt, data); + for (i = 0; i < outputcnt; i++) { + int *vptr = va_arg(vargs, int *); + *vptr = data[i]; + } + va_end(vargs); + + return err; +} + +static int cx23885_api_func(void *priv, u32 cmd, int in, int out, u32 data[CX2341X_MBOX_MAX_DATA]) +{ + return cx23885_mbox_func(priv, cmd, in, out, data); +} + +static int cx23885_find_mailbox(struct cx23885_dev *dev) +{ + u32 signature[4] = { + 0x12345678, 0x34567812, 0x56781234, 0x78123456 + }; + int signaturecnt = 0; + u32 value; + int i; + + dprintk(2, "%s()\n", __func__); + + for (i = 0; i < CX23885_FIRM_IMAGE_SIZE; i++) { + mc417_memory_read(dev, i, &value); + if (value == signature[signaturecnt]) + signaturecnt++; + else + signaturecnt = 0; + if (4 == signaturecnt) { + dprintk(1, "Mailbox signature found at 0x%x\n", i+1); + return i+1; + } + } + pr_err("Mailbox signature values not found!\n"); + return -1; +} + +static int cx23885_load_firmware(struct cx23885_dev *dev) +{ + static const unsigned char magic[8] = { + 0xa7, 0x0d, 0x00, 0x00, 0x66, 0xbb, 0x55, 0xaa + }; + const struct firmware *firmware; + int i, retval = 0; + u32 value = 0; + u32 gpio_output = 0; + u32 gpio_value; + u32 checksum = 0; + u32 *dataptr; + + dprintk(2, "%s()\n", __func__); + + /* Save GPIO settings before reset of APU */ + retval |= mc417_memory_read(dev, 0x9020, &gpio_output); + retval |= mc417_memory_read(dev, 0x900C, &gpio_value); + + retval = mc417_register_write(dev, + IVTV_REG_VPU, 0xFFFFFFED); + retval |= mc417_register_write(dev, + IVTV_REG_HW_BLOCKS, IVTV_CMD_HW_BLOCKS_RST); + retval |= mc417_register_write(dev, + IVTV_REG_ENC_SDRAM_REFRESH, 0x80000800); + retval |= mc417_register_write(dev, + IVTV_REG_ENC_SDRAM_PRECHARGE, 0x1A); + retval |= mc417_register_write(dev, + IVTV_REG_APU, 0); + + if (retval != 0) { + pr_err("%s: Error with mc417_register_write\n", + __func__); + return -1; + } + + retval = request_firmware(&firmware, CX23885_FIRM_IMAGE_NAME, + &dev->pci->dev); + + if (retval != 0) { + pr_err("ERROR: Hotplug firmware request failed (%s).\n", + CX23885_FIRM_IMAGE_NAME); + pr_err("Please fix your hotplug setup, the board will not work without firmware loaded!\n"); + return -1; + } + + if (firmware->size != CX23885_FIRM_IMAGE_SIZE) { + pr_err("ERROR: Firmware size mismatch (have %zu, expected %d)\n", + firmware->size, CX23885_FIRM_IMAGE_SIZE); + release_firmware(firmware); + return -1; + } + + if (0 != memcmp(firmware->data, magic, 8)) { + pr_err("ERROR: Firmware magic mismatch, wrong file?\n"); + release_firmware(firmware); + return -1; + } + + /* transfer to the chip */ + dprintk(2, "Loading firmware ...\n"); + dataptr = (u32 *)firmware->data; + for (i = 0; i < (firmware->size >> 2); i++) { + value = *dataptr; + checksum += ~value; + if (mc417_memory_write(dev, i, value) != 0) { + pr_err("ERROR: Loading firmware failed!\n"); + release_firmware(firmware); + return -1; + } + dataptr++; + } + + /* read back to verify with the checksum */ + dprintk(1, "Verifying firmware ...\n"); + for (i--; i >= 0; i--) { + if (mc417_memory_read(dev, i, &value) != 0) { + pr_err("ERROR: Reading firmware failed!\n"); + release_firmware(firmware); + return -1; + } + checksum -= ~value; + } + if (checksum) { + pr_err("ERROR: Firmware load failed (checksum mismatch).\n"); + release_firmware(firmware); + return -1; + } + release_firmware(firmware); + dprintk(1, "Firmware upload successful.\n"); + + retval |= mc417_register_write(dev, IVTV_REG_HW_BLOCKS, + IVTV_CMD_HW_BLOCKS_RST); + + /* F/W power up disturbs the GPIOs, restore state */ + retval |= mc417_register_write(dev, 0x9020, gpio_output); + retval |= mc417_register_write(dev, 0x900C, gpio_value); + + retval |= mc417_register_read(dev, IVTV_REG_VPU, &value); + retval |= mc417_register_write(dev, IVTV_REG_VPU, value & 0xFFFFFFE8); + + /* Hardcoded GPIO's here */ + retval |= mc417_register_write(dev, 0x9020, 0x4000); + retval |= mc417_register_write(dev, 0x900C, 0x4000); + + mc417_register_read(dev, 0x9020, &gpio_output); + mc417_register_read(dev, 0x900C, &gpio_value); + + if (retval < 0) + pr_err("%s: Error with mc417_register_write\n", + __func__); + return 0; +} + +void cx23885_417_check_encoder(struct cx23885_dev *dev) +{ + u32 status, seq; + + status = seq = 0; + cx23885_api_cmd(dev, CX2341X_ENC_GET_SEQ_END, 0, 2, &status, &seq); + dprintk(1, "%s() status = %d, seq = %d\n", __func__, status, seq); +} + +static void cx23885_codec_settings(struct cx23885_dev *dev) +{ + dprintk(1, "%s()\n", __func__); + + /* Dynamically change the height based on video standard */ + if (dev->encodernorm.id & V4L2_STD_525_60) + dev->ts1.height = 480; + else + dev->ts1.height = 576; + + /* assign frame size */ + cx23885_api_cmd(dev, CX2341X_ENC_SET_FRAME_SIZE, 2, 0, + dev->ts1.height, dev->ts1.width); + + dev->cxhdl.width = dev->ts1.width; + dev->cxhdl.height = dev->ts1.height; + dev->cxhdl.is_50hz = + (dev->encodernorm.id & V4L2_STD_625_50) != 0; + + cx2341x_handler_setup(&dev->cxhdl); + + cx23885_api_cmd(dev, CX2341X_ENC_MISC, 2, 0, 3, 1); + cx23885_api_cmd(dev, CX2341X_ENC_MISC, 2, 0, 4, 1); +} + +static int cx23885_initialize_codec(struct cx23885_dev *dev, int startencoder) +{ + int version; + int retval; + u32 i, data[7]; + + dprintk(1, "%s()\n", __func__); + + retval = cx23885_api_cmd(dev, CX2341X_ENC_PING_FW, 0, 0); /* ping */ + if (retval < 0) { + dprintk(2, "%s() PING OK\n", __func__); + retval = cx23885_load_firmware(dev); + if (retval < 0) { + pr_err("%s() f/w load failed\n", __func__); + return retval; + } + retval = cx23885_find_mailbox(dev); + if (retval < 0) { + pr_err("%s() mailbox < 0, error\n", + __func__); + return -1; + } + dev->cx23417_mailbox = retval; + retval = cx23885_api_cmd(dev, CX2341X_ENC_PING_FW, 0, 0); + if (retval < 0) { + pr_err("ERROR: cx23417 firmware ping failed!\n"); + return -1; + } + retval = cx23885_api_cmd(dev, CX2341X_ENC_GET_VERSION, 0, 1, + &version); + if (retval < 0) { + pr_err("ERROR: cx23417 firmware get encoder :version failed!\n"); + return -1; + } + dprintk(1, "cx23417 firmware version is 0x%08x\n", version); + msleep(200); + } + + cx23885_codec_settings(dev); + msleep(60); + + cx23885_api_cmd(dev, CX2341X_ENC_SET_NUM_VSYNC_LINES, 2, 0, + CX23885_FIELD1_SAA7115, CX23885_FIELD2_SAA7115); + cx23885_api_cmd(dev, CX2341X_ENC_SET_PLACEHOLDER, 12, 0, + CX23885_CUSTOM_EXTENSION_USR_DATA, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0); + + /* Setup to capture VBI */ + data[0] = 0x0001BD00; + data[1] = 1; /* frames per interrupt */ + data[2] = 4; /* total bufs */ + data[3] = 0x91559155; /* start codes */ + data[4] = 0x206080C0; /* stop codes */ + data[5] = 6; /* lines */ + data[6] = 64; /* BPL */ + + cx23885_api_cmd(dev, CX2341X_ENC_SET_VBI_CONFIG, 7, 0, data[0], data[1], + data[2], data[3], data[4], data[5], data[6]); + + for (i = 2; i <= 24; i++) { + int valid; + + valid = ((i >= 19) && (i <= 21)); + cx23885_api_cmd(dev, CX2341X_ENC_SET_VBI_LINE, 5, 0, i, + valid, 0 , 0, 0); + cx23885_api_cmd(dev, CX2341X_ENC_SET_VBI_LINE, 5, 0, + i | 0x80000000, valid, 0, 0, 0); + } + + cx23885_api_cmd(dev, CX2341X_ENC_MUTE_AUDIO, 1, 0, CX23885_UNMUTE); + msleep(60); + + /* initialize the video input */ + cx23885_api_cmd(dev, CX2341X_ENC_INITIALIZE_INPUT, 0, 0); + msleep(60); + + /* Enable VIP style pixel invalidation so we work with scaled mode */ + mc417_memory_write(dev, 2120, 0x00000080); + + /* start capturing to the host interface */ + if (startencoder) { + cx23885_api_cmd(dev, CX2341X_ENC_START_CAPTURE, 2, 0, + CX23885_MPEG_CAPTURE, CX23885_RAW_BITS_NONE); + msleep(10); + } + + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct cx23885_dev *dev = q->drv_priv; + + dev->ts1.ts_packet_size = mpeglinesize; + dev->ts1.ts_packet_count = mpeglines; + *num_planes = 1; + sizes[0] = mpeglinesize * mpeglines; + *num_buffers = mpegbufs; + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx23885_dev *dev = vb->vb2_queue->drv_priv; + struct cx23885_buffer *buf = + container_of(vbuf, struct cx23885_buffer, vb); + + return cx23885_buf_prepare(buf, &dev->ts1); +} + +static void buffer_finish(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx23885_dev *dev = vb->vb2_queue->drv_priv; + struct cx23885_buffer *buf = container_of(vbuf, + struct cx23885_buffer, vb); + + cx23885_free_buffer(dev, buf); +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx23885_dev *dev = vb->vb2_queue->drv_priv; + struct cx23885_buffer *buf = container_of(vbuf, + struct cx23885_buffer, vb); + + cx23885_buf_queue(&dev->ts1, buf); +} + +static int cx23885_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct cx23885_dev *dev = q->drv_priv; + struct cx23885_dmaqueue *dmaq = &dev->ts1.mpegq; + unsigned long flags; + int ret; + + ret = cx23885_initialize_codec(dev, 1); + if (ret == 0) { + struct cx23885_buffer *buf = list_entry(dmaq->active.next, + struct cx23885_buffer, queue); + + cx23885_start_dma(&dev->ts1, dmaq, buf); + return 0; + } + spin_lock_irqsave(&dev->slock, flags); + while (!list_empty(&dmaq->active)) { + struct cx23885_buffer *buf = list_entry(dmaq->active.next, + struct cx23885_buffer, queue); + + list_del(&buf->queue); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); + } + spin_unlock_irqrestore(&dev->slock, flags); + return ret; +} + +static void cx23885_stop_streaming(struct vb2_queue *q) +{ + struct cx23885_dev *dev = q->drv_priv; + + /* stop mpeg capture */ + cx23885_api_cmd(dev, CX2341X_ENC_STOP_CAPTURE, 3, 0, + CX23885_END_NOW, CX23885_MPEG_CAPTURE, + CX23885_RAW_BITS_NONE); + + msleep(500); + cx23885_417_check_encoder(dev); + cx23885_cancel_buffers(&dev->ts1); +} + +static const struct vb2_ops cx23885_qops = { + .queue_setup = queue_setup, + .buf_prepare = buffer_prepare, + .buf_finish = buffer_finish, + .buf_queue = buffer_queue, + .start_streaming = cx23885_start_streaming, + .stop_streaming = cx23885_stop_streaming, +}; + +/* ------------------------------------------------------------------ */ + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct cx23885_dev *dev = video_drvdata(file); + + *id = dev->tvnorm; + return 0; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id id) +{ + struct cx23885_dev *dev = video_drvdata(file); + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(cx23885_tvnorms); i++) + if (id & cx23885_tvnorms[i].id) + break; + if (i == ARRAY_SIZE(cx23885_tvnorms)) + return -EINVAL; + + ret = cx23885_set_tvnorm(dev, id); + if (!ret) + dev->encodernorm = cx23885_tvnorms[i]; + return ret; +} + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct cx23885_dev *dev = video_drvdata(file); + dprintk(1, "%s()\n", __func__); + return cx23885_enum_input(dev, i); +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + return cx23885_get_input(file, priv, i); +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + return cx23885_set_input(file, priv, i); +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx23885_dev *dev = video_drvdata(file); + + if (dev->tuner_type == TUNER_ABSENT) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + strscpy(t->name, "Television", sizeof(t->name)); + call_all(dev, tuner, g_tuner, t); + + dprintk(1, "VIDIOC_G_TUNER: tuner type %d\n", t->type); + + return 0; +} + +static int vidioc_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *t) +{ + struct cx23885_dev *dev = video_drvdata(file); + + if (dev->tuner_type == TUNER_ABSENT) + return -EINVAL; + + /* Update the A/V core */ + call_all(dev, tuner, s_tuner, t); + + return 0; +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct cx23885_dev *dev = video_drvdata(file); + + if (dev->tuner_type == TUNER_ABSENT) + return -EINVAL; + f->type = V4L2_TUNER_ANALOG_TV; + f->frequency = dev->freq; + + call_all(dev, tuner, g_frequency, f); + + return 0; +} + +static int vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + return cx23885_set_frequency(file, priv, f); +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct cx23885_dev *dev = video_drvdata(file); + struct cx23885_tsport *tsport = &dev->ts1; + + strscpy(cap->driver, dev->name, sizeof(cap->driver)); + strscpy(cap->card, cx23885_boards[tsport->dev->board].name, + sizeof(cap->card)); + sprintf(cap->bus_info, "PCIe:%s", pci_name(dev->pci)); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING | V4L2_CAP_VBI_CAPTURE | + V4L2_CAP_AUDIO | V4L2_CAP_DEVICE_CAPS; + if (dev->tuner_type != TUNER_ABSENT) + cap->capabilities |= V4L2_CAP_TUNER; + + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index != 0) + return -EINVAL; + + f->pixelformat = V4L2_PIX_FMT_MPEG; + + return 0; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx23885_dev *dev = video_drvdata(file); + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = + dev->ts1.ts_packet_size * dev->ts1.ts_packet_count; + f->fmt.pix.colorspace = 0; + f->fmt.pix.width = dev->ts1.width; + f->fmt.pix.height = dev->ts1.height; + f->fmt.pix.field = V4L2_FIELD_INTERLACED; + dprintk(1, "VIDIOC_G_FMT: w: %d, h: %d\n", + dev->ts1.width, dev->ts1.height); + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx23885_dev *dev = video_drvdata(file); + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = + dev->ts1.ts_packet_size * dev->ts1.ts_packet_count; + f->fmt.pix.colorspace = 0; + f->fmt.pix.field = V4L2_FIELD_INTERLACED; + dprintk(1, "VIDIOC_TRY_FMT: w: %d, h: %d\n", + dev->ts1.width, dev->ts1.height); + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx23885_dev *dev = video_drvdata(file); + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = + dev->ts1.ts_packet_size * dev->ts1.ts_packet_count; + f->fmt.pix.colorspace = 0; + f->fmt.pix.field = V4L2_FIELD_INTERLACED; + dprintk(1, "VIDIOC_S_FMT: w: %d, h: %d, f: %d\n", + f->fmt.pix.width, f->fmt.pix.height, f->fmt.pix.field); + return 0; +} + +static int vidioc_log_status(struct file *file, void *priv) +{ + struct cx23885_dev *dev = video_drvdata(file); + char name[32 + 2]; + + snprintf(name, sizeof(name), "%s/2", dev->name); + call_all(dev, core, log_status); + v4l2_ctrl_handler_log_status(&dev->cxhdl.hdl, name); + return 0; +} + +static const struct v4l2_file_operations mpeg_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +static const struct v4l2_ioctl_ops mpeg_ioctl_ops = { + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_log_status = vidioc_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_chip_info = cx23885_g_chip_info, + .vidioc_g_register = cx23885_g_register, + .vidioc_s_register = cx23885_s_register, +#endif +}; + +static struct video_device cx23885_mpeg_template = { + .name = "cx23885", + .fops = &mpeg_fops, + .ioctl_ops = &mpeg_ioctl_ops, + .tvnorms = CX23885_NORMS, +}; + +void cx23885_417_unregister(struct cx23885_dev *dev) +{ + dprintk(1, "%s()\n", __func__); + + if (dev->v4l_device) { + if (video_is_registered(dev->v4l_device)) + video_unregister_device(dev->v4l_device); + else + video_device_release(dev->v4l_device); + v4l2_ctrl_handler_free(&dev->cxhdl.hdl); + dev->v4l_device = NULL; + } +} + +static struct video_device *cx23885_video_dev_alloc( + struct cx23885_tsport *tsport, + struct pci_dev *pci, + struct video_device *template, + char *type) +{ + struct video_device *vfd; + struct cx23885_dev *dev = tsport->dev; + + dprintk(1, "%s()\n", __func__); + + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + *vfd = *template; + snprintf(vfd->name, sizeof(vfd->name), "%s (%s)", + cx23885_boards[tsport->dev->board].name, type); + vfd->v4l2_dev = &dev->v4l2_dev; + vfd->release = video_device_release; + return vfd; +} + +int cx23885_417_register(struct cx23885_dev *dev) +{ + /* FIXME: Port1 hardcoded here */ + int err = -ENODEV; + struct cx23885_tsport *tsport = &dev->ts1; + struct vb2_queue *q; + + dprintk(1, "%s()\n", __func__); + + if (cx23885_boards[dev->board].portb != CX23885_MPEG_ENCODER) + return err; + + /* Set default TV standard */ + dev->encodernorm = cx23885_tvnorms[0]; + + if (dev->encodernorm.id & V4L2_STD_525_60) + tsport->height = 480; + else + tsport->height = 576; + + tsport->width = 720; + dev->cxhdl.port = CX2341X_PORT_SERIAL; + err = cx2341x_handler_init(&dev->cxhdl, 50); + if (err) + return err; + dev->cxhdl.priv = dev; + dev->cxhdl.func = cx23885_api_func; + cx2341x_handler_set_50hz(&dev->cxhdl, tsport->height == 576); + v4l2_ctrl_add_handler(&dev->ctrl_handler, &dev->cxhdl.hdl, NULL, false); + + /* Allocate and initialize V4L video device */ + dev->v4l_device = cx23885_video_dev_alloc(tsport, + dev->pci, &cx23885_mpeg_template, "mpeg"); + q = &dev->vb2_mpegq; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ; + q->gfp_flags = GFP_DMA32; + q->min_queued_buffers = 2; + q->drv_priv = dev; + q->buf_struct_size = sizeof(struct cx23885_buffer); + q->ops = &cx23885_qops; + q->mem_ops = &vb2_dma_sg_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &dev->lock; + q->dev = &dev->pci->dev; + + err = vb2_queue_init(q); + if (err < 0) + return err; + video_set_drvdata(dev->v4l_device, dev); + dev->v4l_device->lock = &dev->lock; + dev->v4l_device->queue = q; + dev->v4l_device->device_caps = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; + if (dev->tuner_type != TUNER_ABSENT) + dev->v4l_device->device_caps |= V4L2_CAP_TUNER; + err = video_register_device(dev->v4l_device, + VFL_TYPE_VIDEO, -1); + if (err < 0) { + pr_info("%s: can't register mpeg device\n", dev->name); + return err; + } + + pr_info("%s: registered device %s [mpeg]\n", + dev->name, video_device_node_name(dev->v4l_device)); + + /* ST: Configure the encoder parameters, but don't begin + * encoding, this resolves an issue where the first time the + * encoder is started video can be choppy. + */ + cx23885_initialize_codec(dev, 0); + + return 0; +} + +MODULE_FIRMWARE(CX23885_FIRM_IMAGE_NAME); diff --git a/drivers/media/pci/cx23885/cx23885-alsa.c b/drivers/media/pci/cx23885/cx23885-alsa.c new file mode 100644 index 000000000..29f6df0e6 --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-alsa.c @@ -0,0 +1,596 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Support for CX23885 analog audio capture + * + * (c) 2008 Mijhail Moreyra + * Adapted from cx88-alsa.c + * (c) 2009 Steven Toth + */ + +#include "cx23885.h" +#include "cx23885-reg.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#define AUDIO_SRAM_CHANNEL SRAM_CH07 + +#define dprintk(level, fmt, arg...) do { \ + if (audio_debug + 1 > level) \ + printk(KERN_DEBUG pr_fmt("%s: alsa: " fmt), \ + chip->dev->name, ##arg); \ +} while(0) + +/**************************************************************************** + Module global static vars + ****************************************************************************/ + +static unsigned int disable_analog_audio; +module_param(disable_analog_audio, int, 0644); +MODULE_PARM_DESC(disable_analog_audio, "disable analog audio ALSA driver"); + +static unsigned int audio_debug; +module_param(audio_debug, int, 0644); +MODULE_PARM_DESC(audio_debug, "enable debug messages [analog audio]"); + +/**************************************************************************** + Board specific functions + ****************************************************************************/ + +/* Constants taken from cx88-reg.h */ +#define AUD_INT_DN_RISCI1 (1 << 0) +#define AUD_INT_UP_RISCI1 (1 << 1) +#define AUD_INT_RDS_DN_RISCI1 (1 << 2) +#define AUD_INT_DN_RISCI2 (1 << 4) /* yes, 3 is skipped */ +#define AUD_INT_UP_RISCI2 (1 << 5) +#define AUD_INT_RDS_DN_RISCI2 (1 << 6) +#define AUD_INT_DN_SYNC (1 << 12) +#define AUD_INT_UP_SYNC (1 << 13) +#define AUD_INT_RDS_DN_SYNC (1 << 14) +#define AUD_INT_OPC_ERR (1 << 16) +#define AUD_INT_BER_IRQ (1 << 20) +#define AUD_INT_MCHG_IRQ (1 << 21) +#define GP_COUNT_CONTROL_RESET 0x3 + +static int cx23885_alsa_dma_init(struct cx23885_audio_dev *chip, + unsigned long nr_pages) +{ + struct cx23885_audio_buffer *buf = chip->buf; + struct page *pg; + int i; + + buf->vaddr = vmalloc_32(nr_pages << PAGE_SHIFT); + if (NULL == buf->vaddr) { + dprintk(1, "vmalloc_32(%lu pages) failed\n", nr_pages); + return -ENOMEM; + } + + dprintk(1, "vmalloc is at addr %p, size=%lu\n", + buf->vaddr, nr_pages << PAGE_SHIFT); + + memset(buf->vaddr, 0, nr_pages << PAGE_SHIFT); + buf->nr_pages = nr_pages; + + buf->sglist = vzalloc(array_size(sizeof(*buf->sglist), buf->nr_pages)); + if (NULL == buf->sglist) + goto vzalloc_err; + + sg_init_table(buf->sglist, buf->nr_pages); + for (i = 0; i < buf->nr_pages; i++) { + pg = vmalloc_to_page(buf->vaddr + i * PAGE_SIZE); + if (NULL == pg) + goto vmalloc_to_page_err; + sg_set_page(&buf->sglist[i], pg, PAGE_SIZE, 0); + } + return 0; + +vmalloc_to_page_err: + vfree(buf->sglist); + buf->sglist = NULL; +vzalloc_err: + vfree(buf->vaddr); + buf->vaddr = NULL; + return -ENOMEM; +} + +static int cx23885_alsa_dma_map(struct cx23885_audio_dev *dev) +{ + struct cx23885_audio_buffer *buf = dev->buf; + + buf->sglen = dma_map_sg(&dev->pci->dev, buf->sglist, + buf->nr_pages, DMA_FROM_DEVICE); + + if (0 == buf->sglen) { + pr_warn("%s: cx23885_alsa_map_sg failed\n", __func__); + return -ENOMEM; + } + return 0; +} + +static int cx23885_alsa_dma_unmap(struct cx23885_audio_dev *dev) +{ + struct cx23885_audio_buffer *buf = dev->buf; + + if (!buf->sglen) + return 0; + + dma_unmap_sg(&dev->pci->dev, buf->sglist, buf->nr_pages, DMA_FROM_DEVICE); + buf->sglen = 0; + return 0; +} + +static int cx23885_alsa_dma_free(struct cx23885_audio_buffer *buf) +{ + vfree(buf->sglist); + buf->sglist = NULL; + vfree(buf->vaddr); + buf->vaddr = NULL; + return 0; +} + +/* + * BOARD Specific: Sets audio DMA + */ + +static int cx23885_start_audio_dma(struct cx23885_audio_dev *chip) +{ + struct cx23885_audio_buffer *buf = chip->buf; + struct cx23885_dev *dev = chip->dev; + struct sram_channel *audio_ch = + &dev->sram_channels[AUDIO_SRAM_CHANNEL]; + + dprintk(1, "%s()\n", __func__); + + /* Make sure RISC/FIFO are off before changing FIFO/RISC settings */ + cx_clear(AUD_INT_DMA_CTL, 0x11); + + /* setup fifo + format - out channel */ + cx23885_sram_channel_setup(chip->dev, audio_ch, buf->bpl, + buf->risc.dma); + + /* sets bpl size */ + cx_write(AUD_INT_A_LNGTH, buf->bpl); + + /* This is required to get good audio (1 seems to be ok) */ + cx_write(AUD_INT_A_MODE, 1); + + /* reset counter */ + cx_write(AUD_INT_A_GPCNT_CTL, GP_COUNT_CONTROL_RESET); + atomic_set(&chip->count, 0); + + dprintk(1, "Start audio DMA, %d B/line, %d lines/FIFO, %d periods, %d byte buffer\n", + buf->bpl, cx_read(audio_ch->cmds_start+12)>>1, + chip->num_periods, buf->bpl * chip->num_periods); + + /* Enables corresponding bits at AUD_INT_STAT */ + cx_write(AUDIO_INT_INT_MSK, AUD_INT_OPC_ERR | AUD_INT_DN_SYNC | + AUD_INT_DN_RISCI1); + + /* Clean any pending interrupt bits already set */ + cx_write(AUDIO_INT_INT_STAT, ~0); + + /* enable audio irqs */ + cx_set(PCI_INT_MSK, chip->dev->pci_irqmask | PCI_MSK_AUD_INT); + + /* start dma */ + cx_set(DEV_CNTRL2, (1<<5)); /* Enables Risc Processor */ + cx_set(AUD_INT_DMA_CTL, 0x11); /* audio downstream FIFO and + RISC enable */ + if (audio_debug) + cx23885_sram_channel_dump(chip->dev, audio_ch); + + return 0; +} + +/* + * BOARD Specific: Resets audio DMA + */ +static int cx23885_stop_audio_dma(struct cx23885_audio_dev *chip) +{ + struct cx23885_dev *dev = chip->dev; + dprintk(1, "Stopping audio DMA\n"); + + /* stop dma */ + cx_clear(AUD_INT_DMA_CTL, 0x11); + + /* disable irqs */ + cx_clear(PCI_INT_MSK, PCI_MSK_AUD_INT); + cx_clear(AUDIO_INT_INT_MSK, AUD_INT_OPC_ERR | AUD_INT_DN_SYNC | + AUD_INT_DN_RISCI1); + + if (audio_debug) + cx23885_sram_channel_dump(chip->dev, + &dev->sram_channels[AUDIO_SRAM_CHANNEL]); + + return 0; +} + +/* + * BOARD Specific: Handles audio IRQ + */ +int cx23885_audio_irq(struct cx23885_dev *dev, u32 status, u32 mask) +{ + struct cx23885_audio_dev *chip = dev->audio_dev; + + if (0 == (status & mask)) + return 0; + + cx_write(AUDIO_INT_INT_STAT, status); + + /* risc op code error */ + if (status & AUD_INT_OPC_ERR) { + pr_warn("%s/1: Audio risc op code error\n", + dev->name); + cx_clear(AUD_INT_DMA_CTL, 0x11); + cx23885_sram_channel_dump(dev, + &dev->sram_channels[AUDIO_SRAM_CHANNEL]); + } + if (status & AUD_INT_DN_SYNC) { + dprintk(1, "Downstream sync error\n"); + cx_write(AUD_INT_A_GPCNT_CTL, GP_COUNT_CONTROL_RESET); + return 1; + } + /* risc1 downstream */ + if (status & AUD_INT_DN_RISCI1) { + atomic_set(&chip->count, cx_read(AUD_INT_A_GPCNT)); + snd_pcm_period_elapsed(chip->substream); + } + /* FIXME: Any other status should deserve a special handling? */ + + return 1; +} + +static int dsp_buffer_free(struct cx23885_audio_dev *chip) +{ + struct cx23885_riscmem *risc; + + BUG_ON(!chip->dma_size); + + dprintk(2, "Freeing buffer\n"); + cx23885_alsa_dma_unmap(chip); + cx23885_alsa_dma_free(chip->buf); + risc = &chip->buf->risc; + dma_free_coherent(&chip->pci->dev, risc->size, risc->cpu, risc->dma); + kfree(chip->buf); + + chip->buf = NULL; + chip->dma_size = 0; + + return 0; +} + +/**************************************************************************** + ALSA PCM Interface + ****************************************************************************/ + +/* + * Digital hardware definition + */ +#define DEFAULT_FIFO_SIZE 4096 + +static const struct snd_pcm_hardware snd_cx23885_digital_hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + /* Analog audio output will be full of clicks and pops if there + are not exactly four lines in the SRAM FIFO buffer. */ + .period_bytes_min = DEFAULT_FIFO_SIZE/4, + .period_bytes_max = DEFAULT_FIFO_SIZE/4, + .periods_min = 1, + .periods_max = 1024, + .buffer_bytes_max = (1024*1024), +}; + +/* + * audio pcm capture open callback + */ +static int snd_cx23885_pcm_open(struct snd_pcm_substream *substream) +{ + struct cx23885_audio_dev *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + if (!chip) { + pr_err("BUG: cx23885 can't find device struct. Can't proceed with open\n"); + return -ENODEV; + } + + err = snd_pcm_hw_constraint_pow2(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + goto _error; + + chip->substream = substream; + + runtime->hw = snd_cx23885_digital_hw; + + if (chip->dev->sram_channels[AUDIO_SRAM_CHANNEL].fifo_size != + DEFAULT_FIFO_SIZE) { + unsigned int bpl = chip->dev-> + sram_channels[AUDIO_SRAM_CHANNEL].fifo_size / 4; + bpl &= ~7; /* must be multiple of 8 */ + runtime->hw.period_bytes_min = bpl; + runtime->hw.period_bytes_max = bpl; + } + + return 0; +_error: + dprintk(1, "Error opening PCM!\n"); + return err; +} + +/* + * audio close callback + */ +static int snd_cx23885_close(struct snd_pcm_substream *substream) +{ + return 0; +} + + +/* + * hw_params callback + */ +static int snd_cx23885_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct cx23885_audio_dev *chip = snd_pcm_substream_chip(substream); + struct cx23885_audio_buffer *buf; + int ret; + + if (substream->runtime->dma_area) { + dsp_buffer_free(chip); + substream->runtime->dma_area = NULL; + } + + chip->period_size = params_period_bytes(hw_params); + chip->num_periods = params_periods(hw_params); + chip->dma_size = chip->period_size * params_periods(hw_params); + + BUG_ON(!chip->dma_size); + BUG_ON(chip->num_periods & (chip->num_periods-1)); + + buf = kzalloc_obj(*buf); + if (NULL == buf) + return -ENOMEM; + + buf->bpl = chip->period_size; + chip->buf = buf; + + ret = cx23885_alsa_dma_init(chip, + (PAGE_ALIGN(chip->dma_size) >> PAGE_SHIFT)); + if (ret < 0) + goto error; + + ret = cx23885_alsa_dma_map(chip); + if (ret < 0) + goto error; + + ret = cx23885_risc_databuffer(chip->pci, &buf->risc, buf->sglist, + chip->period_size, chip->num_periods, 1); + if (ret < 0) { + cx23885_alsa_dma_unmap(chip); + goto error; + } + + /* Loop back to start of program */ + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP|RISC_IRQ1|RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + buf->risc.jmp[2] = cpu_to_le32(0); /* bits 63-32 */ + + substream->runtime->dma_area = chip->buf->vaddr; + substream->runtime->dma_bytes = chip->dma_size; + substream->runtime->dma_addr = 0; + + return 0; + +error: + kfree(buf); + chip->buf = NULL; + return ret; +} + +/* + * hw free callback + */ +static int snd_cx23885_hw_free(struct snd_pcm_substream *substream) +{ + + struct cx23885_audio_dev *chip = snd_pcm_substream_chip(substream); + + if (substream->runtime->dma_area) { + dsp_buffer_free(chip); + substream->runtime->dma_area = NULL; + } + + return 0; +} + +/* + * prepare callback + */ +static int snd_cx23885_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* + * trigger callback + */ +static int snd_cx23885_card_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct cx23885_audio_dev *chip = snd_pcm_substream_chip(substream); + int err; + + /* Local interrupts are already disabled by ALSA */ + spin_lock(&chip->lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + err = cx23885_start_audio_dma(chip); + break; + case SNDRV_PCM_TRIGGER_STOP: + err = cx23885_stop_audio_dma(chip); + break; + default: + err = -EINVAL; + break; + } + + spin_unlock(&chip->lock); + + return err; +} + +/* + * pointer callback + */ +static snd_pcm_uframes_t snd_cx23885_pointer( + struct snd_pcm_substream *substream) +{ + struct cx23885_audio_dev *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + u16 count; + + count = atomic_read(&chip->count); + + return runtime->period_size * (count & (runtime->periods-1)); +} + +/* + * page callback (needed for mmap) + */ +static struct page *snd_cx23885_page(struct snd_pcm_substream *substream, + unsigned long offset) +{ + void *pageptr = substream->runtime->dma_area + offset; + return vmalloc_to_page(pageptr); +} + +/* + * operators + */ +static const struct snd_pcm_ops snd_cx23885_pcm_ops = { + .open = snd_cx23885_pcm_open, + .close = snd_cx23885_close, + .hw_params = snd_cx23885_hw_params, + .hw_free = snd_cx23885_hw_free, + .prepare = snd_cx23885_prepare, + .trigger = snd_cx23885_card_trigger, + .pointer = snd_cx23885_pointer, + .page = snd_cx23885_page, +}; + +/* + * create a PCM device + */ +static int snd_cx23885_pcm(struct cx23885_audio_dev *chip, int device, + char *name) +{ + int err; + struct snd_pcm *pcm; + + err = snd_pcm_new(chip->card, name, device, 0, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = chip; + strscpy(pcm->name, name, sizeof(pcm->name)); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cx23885_pcm_ops); + + return 0; +} + +/**************************************************************************** + Basic Flow for Sound Devices + ****************************************************************************/ + +/* + * Alsa Constructor - Component probe + */ + +struct cx23885_audio_dev *cx23885_audio_register(struct cx23885_dev *dev) +{ + struct snd_card *card; + struct cx23885_audio_dev *chip; + int err; + + if (disable_analog_audio) + return NULL; + + if (dev->sram_channels[AUDIO_SRAM_CHANNEL].cmds_start == 0) { + pr_warn("%s(): Missing SRAM channel configuration for analog TV Audio\n", + __func__); + return NULL; + } + + err = snd_card_new(&dev->pci->dev, + SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, sizeof(struct cx23885_audio_dev), &card); + if (err < 0) + goto error_msg; + + chip = (struct cx23885_audio_dev *) card->private_data; + chip->dev = dev; + chip->pci = dev->pci; + chip->card = card; + spin_lock_init(&chip->lock); + + err = snd_cx23885_pcm(chip, 0, "CX23885 Digital"); + if (err < 0) + goto error; + + strscpy(card->driver, "CX23885", sizeof(card->driver)); + sprintf(card->shortname, "Conexant CX23885"); + sprintf(card->longname, "%s at %s", card->shortname, dev->name); + + err = snd_card_register(card); + if (err < 0) + goto error; + + dprintk(0, "registered ALSA audio device\n"); + + return chip; + +error: + snd_card_free(card); +error_msg: + pr_err("%s(): Failed to register analog audio adapter\n", + __func__); + + return NULL; +} + +/* + * ALSA destructor + */ +void cx23885_audio_unregister(struct cx23885_dev *dev) +{ + struct cx23885_audio_dev *chip = dev->audio_dev; + + snd_card_free(chip->card); +} diff --git a/drivers/media/pci/cx23885/cx23885-av.c b/drivers/media/pci/cx23885/cx23885-av.c new file mode 100644 index 000000000..f8ac29056 --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-av.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX23885/7/8 PCIe bridge + * + * AV device support routines - non-input, non-vl42_subdev routines + * + * Copyright (C) 2010 Andy Walls + */ + +#include "cx23885.h" +#include "cx23885-av.h" +#include "cx23885-video.h" + +void cx23885_av_work_handler(struct work_struct *work) +{ + struct cx23885_dev *dev = + container_of(work, struct cx23885_dev, cx25840_work); + bool handled = false; + + v4l2_subdev_call(dev->sd_cx25840, core, interrupt_service_routine, + PCI_MSK_AV_CORE, &handled); + + /* Getting here with the interrupt not handled + then probbaly flatiron does have pending interrupts. + */ + if (!handled) { + /* clear left and right adc channel interrupt request flag */ + cx23885_flatiron_write(dev, 0x1f, + cx23885_flatiron_read(dev, 0x1f) | 0x80); + cx23885_flatiron_write(dev, 0x23, + cx23885_flatiron_read(dev, 0x23) | 0x80); + } + + cx23885_irq_enable(dev, PCI_MSK_AV_CORE); +} diff --git a/drivers/media/pci/cx23885/cx23885-av.h b/drivers/media/pci/cx23885/cx23885-av.h new file mode 100644 index 000000000..11a0941ae --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-av.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the Conexant CX23885/7/8 PCIe bridge + * + * AV device support routines - non-input, non-vl42_subdev routines + * + * Copyright (C) 2010 Andy Walls + */ + +#ifndef _CX23885_AV_H_ +#define _CX23885_AV_H_ +void cx23885_av_work_handler(struct work_struct *work); +#endif diff --git a/drivers/media/pci/cx23885/cx23885-cards.c b/drivers/media/pci/cx23885/cx23885-cards.c new file mode 100644 index 000000000..95a84709d --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-cards.c @@ -0,0 +1,2526 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX23885 PCIe bridge + * + * Copyright (c) 2006 Steven Toth + */ + +#include "cx23885.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "xc2028.h" +#include "netup-eeprom.h" +#include "netup-init.h" +#include "altera-ci.h" +#include "xc4000.h" +#include "xc5000.h" +#include "cx23888-ir.h" + +static unsigned int netup_card_rev = 4; +module_param(netup_card_rev, int, 0644); +MODULE_PARM_DESC(netup_card_rev, + "NetUP Dual DVB-T/C CI card revision"); +static unsigned int enable_885_ir; +module_param(enable_885_ir, int, 0644); +MODULE_PARM_DESC(enable_885_ir, + "Enable integrated IR controller for supported\n" + "\t\t CX2388[57] boards that are wired for it:\n" + "\t\t\tHVR-1250 (reported safe)\n" + "\t\t\tTerraTec Cinergy T PCIe Dual (not well tested, appears to be safe)\n" + "\t\t\tTeVii S470 (reported unsafe)\n" + "\t\t This can cause an interrupt storm with some cards.\n" + "\t\t Default: 0 [Disabled]"); + +/* ------------------------------------------------------------------ */ +/* board config info */ + +struct cx23885_board cx23885_boards[] = { + [CX23885_BOARD_UNKNOWN] = { + .name = "UNKNOWN/GENERIC", + /* Ensure safe default for unknown boards */ + .clk_freq = 0, + .input = {{ + .type = CX23885_VMUX_COMPOSITE1, + .vmux = 0, + }, { + .type = CX23885_VMUX_COMPOSITE2, + .vmux = 1, + }, { + .type = CX23885_VMUX_COMPOSITE3, + .vmux = 2, + }, { + .type = CX23885_VMUX_COMPOSITE4, + .vmux = 3, + } }, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1800lp] = { + .name = "Hauppauge WinTV-HVR1800lp", + .portc = CX23885_MPEG_DVB, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xff00, + }, { + .type = CX23885_VMUX_DEBUG, + .vmux = 0, + .gpio0 = 0xff01, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xff02, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xff02, + } }, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1800] = { + .name = "Hauppauge WinTV-HVR1800", + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_ENCODER, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_PHILIPS_TDA8290, + .tuner_addr = 0x42, /* 0x84 >> 1 */ + .tuner_bus = 1, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH2 | + CX25840_VIN2_CH1, + .amux = CX25840_AUDIO8, + .gpio0 = 0, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN6_CH1, + .amux = CX25840_AUDIO7, + .gpio0 = 0, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN8_CH1 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO7, + .gpio0 = 0, + } }, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1250] = { + .name = "Hauppauge WinTV-HVR1250", + .porta = CX23885_ANALOG_VIDEO, + .portc = CX23885_MPEG_DVB, +#ifdef MT2131_NO_ANALOG_SUPPORT_YET + .tuner_type = TUNER_PHILIPS_TDA8290, + .tuner_addr = 0x42, /* 0x84 >> 1 */ + .tuner_bus = 1, +#endif + .force_bff = 1, + .input = {{ +#ifdef MT2131_NO_ANALOG_SUPPORT_YET + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH2 | + CX25840_VIN2_CH1, + .amux = CX25840_AUDIO8, + .gpio0 = 0xff00, + }, { +#endif + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN6_CH1, + .amux = CX25840_AUDIO7, + .gpio0 = 0xff02, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN8_CH1 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO7, + .gpio0 = 0xff02, + } }, + }, + [CX23885_BOARD_DVICO_FUSIONHDTV_5_EXP] = { + .name = "DViCO FusionHDTV5 Express", + .portb = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1500Q] = { + .name = "Hauppauge WinTV-HVR1500Q", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1500] = { + .name = "Hauppauge WinTV-HVR1500", + .porta = CX23885_ANALOG_VIDEO, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, /* 0xc2 >> 1 */ + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH2 | + CX25840_VIN2_CH1, + .gpio0 = 0, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN6_CH1, + .gpio0 = 0, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN8_CH1 | + CX25840_SVIDEO_ON, + .gpio0 = 0, + } }, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1200] = { + .name = "Hauppauge WinTV-HVR1200", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1700] = { + .name = "Hauppauge WinTV-HVR1700", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1400] = { + .name = "Hauppauge WinTV-HVR1400", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_DVICO_FUSIONHDTV_7_DUAL_EXP] = { + .name = "DViCO FusionHDTV7 Dual Express", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP] = { + .name = "DViCO FusionHDTV DVB-T Dual Express", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H] = { + .name = "Leadtek Winfast PxDVR3200 H", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_LEADTEK_WINFAST_PXPVR2200] = { + .name = "Leadtek Winfast PxPVR2200", + .porta = CX23885_ANALOG_VIDEO, + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .tuner_bus = 1, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN2_CH1 | + CX25840_VIN5_CH2, + .amux = CX25840_AUDIO8, + .gpio0 = 0x704040, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_COMPOSITE1, + .amux = CX25840_AUDIO7, + .gpio0 = 0x704040, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_SVIDEO_LUMA3 | + CX25840_SVIDEO_CHROMA4, + .amux = CX25840_AUDIO7, + .gpio0 = 0x704040, + }, { + .type = CX23885_VMUX_COMPONENT, + .vmux = CX25840_VIN7_CH1 | + CX25840_VIN6_CH2 | + CX25840_VIN8_CH3 | + CX25840_COMPONENT_ON, + .amux = CX25840_AUDIO7, + .gpio0 = 0x704040, + } }, + }, + [CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000] = { + .name = "Leadtek Winfast PxDVR3200 H XC4000", + .porta = CX23885_ANALOG_VIDEO, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_XC4000, + .tuner_addr = 0x61, + .radio_type = UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN2_CH1 | + CX25840_VIN5_CH2 | + CX25840_NONE0_CH3, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_COMPOSITE1, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_SVIDEO_LUMA3 | + CX25840_SVIDEO_CHROMA4, + }, { + .type = CX23885_VMUX_COMPONENT, + .vmux = CX25840_VIN7_CH1 | + CX25840_VIN6_CH2 | + CX25840_VIN8_CH3 | + CX25840_COMPONENT_ON, + } }, + }, + [CX23885_BOARD_COMPRO_VIDEOMATE_E650F] = { + .name = "Compro VideoMate E650F", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_TBS_6920] = { + .name = "TurboSight TBS 6920", + .portb = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_TBS_6980] = { + .name = "TurboSight TBS 6980", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_TBS_6981] = { + .name = "TurboSight TBS 6981", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_TEVII_S470] = { + .name = "TeVii S470", + .portb = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_DVBWORLD_2005] = { + .name = "DVBWorld DVB-S2 2005", + .portb = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_NETUP_DUAL_DVBS2_CI] = { + .ci_type = 1, + .name = "NetUP Dual DVB-S2 CI", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1270] = { + .name = "Hauppauge WinTV-HVR1270", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1275] = { + .name = "Hauppauge WinTV-HVR1275", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1255] = { + .name = "Hauppauge WinTV-HVR1255", + .porta = CX23885_ANALOG_VIDEO, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_ABSENT, + .tuner_addr = 0x42, /* 0x84 >> 1 */ + .force_bff = 1, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH2 | + CX25840_VIN2_CH1 | + CX25840_DIF_ON, + .amux = CX25840_AUDIO8, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN6_CH1, + .amux = CX25840_AUDIO7, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN8_CH1 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO7, + } }, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1255_22111] = { + .name = "Hauppauge WinTV-HVR1255", + .porta = CX23885_ANALOG_VIDEO, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_ABSENT, + .tuner_addr = 0x42, /* 0x84 >> 1 */ + .force_bff = 1, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH2 | + CX25840_VIN2_CH1 | + CX25840_DIF_ON, + .amux = CX25840_AUDIO8, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN8_CH1 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO7, + } }, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1210] = { + .name = "Hauppauge WinTV-HVR1210", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_MYGICA_X8506] = { + .name = "Mygica X8506 DMB-TH", + .tuner_type = TUNER_XC5000, + .tuner_addr = 0x61, + .tuner_bus = 1, + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_DVB, + .input = { + { + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_COMPOSITE2, + }, + { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_COMPOSITE8, + }, + { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_SVIDEO_LUMA3 | + CX25840_SVIDEO_CHROMA4, + }, + { + .type = CX23885_VMUX_COMPONENT, + .vmux = CX25840_COMPONENT_ON | + CX25840_VIN1_CH1 | + CX25840_VIN6_CH2 | + CX25840_VIN7_CH3, + }, + }, + }, + [CX23885_BOARD_MAGICPRO_PROHDTVE2] = { + .name = "Magic-Pro ProHDTV Extreme 2", + .tuner_type = TUNER_XC5000, + .tuner_addr = 0x61, + .tuner_bus = 1, + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_DVB, + .input = { + { + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_COMPOSITE2, + }, + { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_COMPOSITE8, + }, + { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_SVIDEO_LUMA3 | + CX25840_SVIDEO_CHROMA4, + }, + { + .type = CX23885_VMUX_COMPONENT, + .vmux = CX25840_COMPONENT_ON | + CX25840_VIN1_CH1 | + CX25840_VIN6_CH2 | + CX25840_VIN7_CH3, + }, + }, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1850] = { + .name = "Hauppauge WinTV-HVR1850", + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_ENCODER, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_ABSENT, + .tuner_addr = 0x42, /* 0x84 >> 1 */ + .force_bff = 1, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH2 | + CX25840_VIN2_CH1 | + CX25840_DIF_ON, + .amux = CX25840_AUDIO8, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN6_CH1, + .amux = CX25840_AUDIO7, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN8_CH1 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO7, + } }, + }, + [CX23885_BOARD_COMPRO_VIDEOMATE_E800] = { + .name = "Compro VideoMate E800", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1290] = { + .name = "Hauppauge WinTV-HVR1290", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_MYGICA_X8558PRO] = { + .name = "Mygica X8558 PRO DMB-TH", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_LEADTEK_WINFAST_PXTV1200] = { + .name = "LEADTEK WinFast PxTV1200", + .porta = CX23885_ANALOG_VIDEO, + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .tuner_bus = 1, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN2_CH1 | + CX25840_VIN5_CH2 | + CX25840_NONE0_CH3, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_COMPOSITE1, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_SVIDEO_LUMA3 | + CX25840_SVIDEO_CHROMA4, + }, { + .type = CX23885_VMUX_COMPONENT, + .vmux = CX25840_VIN7_CH1 | + CX25840_VIN6_CH2 | + CX25840_VIN8_CH3 | + CX25840_COMPONENT_ON, + } }, + }, + [CX23885_BOARD_GOTVIEW_X5_3D_HYBRID] = { + .name = "GoTView X5 3D Hybrid", + .tuner_type = TUNER_XC5000, + .tuner_addr = 0x64, + .tuner_bus = 1, + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_DVB, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN2_CH1 | + CX25840_VIN5_CH2, + .gpio0 = 0x02, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX23885_VMUX_COMPOSITE1, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_SVIDEO_LUMA3 | + CX25840_SVIDEO_CHROMA4, + } }, + }, + [CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF] = { + .ci_type = 2, + .name = "NetUP Dual DVB-T/C-CI RF", + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + .num_fds_portb = 2, + .num_fds_portc = 2, + .tuner_type = TUNER_XC5000, + .tuner_addr = 0x64, + .input = { { + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_COMPOSITE1, + } }, + }, + [CX23885_BOARD_MPX885] = { + .name = "MPX-885", + .porta = CX23885_ANALOG_VIDEO, + .input = {{ + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_COMPOSITE1, + .amux = CX25840_AUDIO6, + .gpio0 = 0, + }, { + .type = CX23885_VMUX_COMPOSITE2, + .vmux = CX25840_COMPOSITE2, + .amux = CX25840_AUDIO6, + .gpio0 = 0, + }, { + .type = CX23885_VMUX_COMPOSITE3, + .vmux = CX25840_COMPOSITE3, + .amux = CX25840_AUDIO7, + .gpio0 = 0, + }, { + .type = CX23885_VMUX_COMPOSITE4, + .vmux = CX25840_COMPOSITE4, + .amux = CX25840_AUDIO7, + .gpio0 = 0, + } }, + }, + [CX23885_BOARD_MYGICA_X8507] = { + .name = "Mygica X8502/X8507 ISDB-T", + .tuner_type = TUNER_XC5000, + .tuner_addr = 0x61, + .tuner_bus = 1, + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_DVB, + .input = { + { + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_COMPOSITE2, + .amux = CX25840_AUDIO8, + }, + { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_COMPOSITE8, + .amux = CX25840_AUDIO7, + }, + { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_SVIDEO_LUMA3 | + CX25840_SVIDEO_CHROMA4, + .amux = CX25840_AUDIO7, + }, + { + .type = CX23885_VMUX_COMPONENT, + .vmux = CX25840_COMPONENT_ON | + CX25840_VIN1_CH1 | + CX25840_VIN6_CH2 | + CX25840_VIN7_CH3, + .amux = CX25840_AUDIO7, + }, + }, + }, + [CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL] = { + .name = "TerraTec Cinergy T PCIe Dual", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_TEVII_S471] = { + .name = "TeVii S471", + .portb = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_PROF_8000] = { + .name = "Prof Revolution DVB-S2 8000", + .portb = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR4400] = { + .name = "Hauppauge WinTV-HVR4400/HVR5500", + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_NXP_TDA18271, + .tuner_addr = 0x60, /* 0xc0 >> 1 */ + .tuner_bus = 1, + }, + [CX23885_BOARD_HAUPPAUGE_STARBURST] = { + .name = "Hauppauge WinTV Starburst", + .portb = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_AVERMEDIA_HC81R] = { + .name = "AVerTV Hybrid Express Slim HC81R", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, /* 0xc2 >> 1 */ + .tuner_bus = 1, + .porta = CX23885_ANALOG_VIDEO, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN2_CH1 | + CX25840_VIN5_CH2 | + CX25840_NONE0_CH3 | + CX25840_NONE1_CH3, + .amux = CX25840_AUDIO8, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN8_CH1 | + CX25840_NONE_CH2 | + CX25840_VIN7_CH3 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO6, + }, { + .type = CX23885_VMUX_COMPONENT, + .vmux = CX25840_VIN1_CH1 | + CX25840_NONE_CH2 | + CX25840_NONE0_CH3 | + CX25840_NONE1_CH3, + .amux = CX25840_AUDIO6, + } }, + }, + [CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP2] = { + .name = "DViCO FusionHDTV DVB-T Dual Express2", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_IMPACTVCBE] = { + .name = "Hauppauge ImpactVCB-e", + .tuner_type = TUNER_ABSENT, + .porta = CX23885_ANALOG_VIDEO, + .input = {{ + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_VIN6_CH1, + .amux = CX25840_AUDIO7, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN4_CH2 | + CX25840_VIN8_CH1 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO7, + } }, + }, + [CX23885_BOARD_DVBSKY_T9580] = { + .name = "DVBSky T9580", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_DVBSKY_T980C] = { + .name = "DVBSky T980C", + .portb = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_DVBSKY_S950C] = { + .name = "DVBSky S950C", + .portb = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_TT_CT2_4500_CI] = { + .name = "Technotrend TT-budget CT2-4500 CI", + .portb = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_DVBSKY_S950] = { + .name = "DVBSky S950", + .portb = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_DVBSKY_S952] = { + .name = "DVBSky S952", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_DVBSKY_T982] = { + .name = "DVBSky T982", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR5525] = { + .name = "Hauppauge WinTV-HVR5525", + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_ABSENT, + .force_bff = 1, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH2 | + CX25840_VIN2_CH1 | + CX25840_DIF_ON, + .amux = CX25840_AUDIO8, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_VIN6_CH1, + .amux = CX25840_AUDIO7, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN8_CH1 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO7, + } }, + }, + [CX23885_BOARD_VIEWCAST_260E] = { + .name = "ViewCast 260e", + .porta = CX23885_ANALOG_VIDEO, + .force_bff = 1, + .input = {{ + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_VIN6_CH1, + .amux = CX25840_AUDIO7, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH1 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO7, + }, { + .type = CX23885_VMUX_COMPONENT, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN6_CH2 | + CX25840_VIN5_CH1 | + CX25840_COMPONENT_ON, + .amux = CX25840_AUDIO7, + } }, + }, + [CX23885_BOARD_VIEWCAST_460E] = { + .name = "ViewCast 460e", + .porta = CX23885_ANALOG_VIDEO, + .force_bff = 1, + .input = {{ + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_VIN4_CH1, + .amux = CX25840_AUDIO7, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN6_CH1 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO7, + }, { + .type = CX23885_VMUX_COMPONENT, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN6_CH1 | + CX25840_VIN5_CH2 | + CX25840_COMPONENT_ON, + .amux = CX25840_AUDIO7, + }, { + .type = CX23885_VMUX_COMPOSITE2, + .vmux = CX25840_VIN6_CH1, + .amux = CX25840_AUDIO7, + } }, + }, + [CX23885_BOARD_HAUPPAUGE_QUADHD_DVB] = { + .name = "Hauppauge WinTV-QuadHD-DVB", + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_ABSENT, + .force_bff = 1, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH2 | + CX25840_VIN2_CH1 | + CX25840_DIF_ON, + .amux = CX25840_AUDIO8, + } }, + }, + [CX23885_BOARD_HAUPPAUGE_QUADHD_DVB_885] = { + .name = "Hauppauge WinTV-QuadHD-DVB(885)", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_ABSENT, + }, + [CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC] = { + .name = "Hauppauge WinTV-QuadHD-ATSC", + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_ABSENT, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH2 | + CX25840_VIN2_CH1 | + CX25840_DIF_ON, + .amux = CX25840_AUDIO8, + } }, + }, + [CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC_885] = { + .name = "Hauppauge WinTV-QuadHD-ATSC(885)", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_ABSENT, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1265_K4] = { + .name = "Hauppauge WinTV-HVR-1265(161111)", + .porta = CX23885_ANALOG_VIDEO, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_ABSENT, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH2 | + CX25840_VIN2_CH1 | + CX25840_DIF_ON, + .amux = CX25840_AUDIO8, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN4_CH2 | + CX25840_VIN6_CH1 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO7, + } }, + }, + [CX23885_BOARD_HAUPPAUGE_STARBURST2] = { + .name = "Hauppauge WinTV-Starburst2", + .portb = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_AVERMEDIA_CE310B] = { + .name = "AVerMedia CE310B", + .porta = CX23885_ANALOG_VIDEO, + .force_bff = 1, + .input = {{ + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_VIN1_CH1 | + CX25840_NONE_CH2 | + CX25840_NONE0_CH3, + .amux = CX25840_AUDIO7, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN8_CH1 | + CX25840_NONE_CH2 | + CX25840_VIN7_CH3 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO7, + } }, + }, + [CX23885_BOARD_AVERMEDIA_H789C] = { + .name = "AVerMedia H789-C", + .porta = CX23885_ANALOG_VIDEO, + .tuner_type = TUNER_NXP_TDA18271, + .tuner_addr = 0x63, /* 0xc0 >> 1 */ + .tuner_bus = 1, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH2 | + CX25840_VIN2_CH1 | + CX25840_DIF_ON, + .amux = CX25840_AUDIO8, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_VIN8_CH1, + .amux = CX25840_AUDIO7, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN8_CH1 | + CX25840_VIN7_CH3 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO7, + }, }, + }, +}; +const unsigned int cx23885_bcount = ARRAY_SIZE(cx23885_boards); + +/* ------------------------------------------------------------------ */ +/* PCI subsystem IDs */ + +struct cx23885_subid cx23885_subids[] = { + { + .subvendor = 0x0070, + .subdevice = 0x3400, + .card = CX23885_BOARD_UNKNOWN, + }, { + .subvendor = 0x0070, + .subdevice = 0x7600, + .card = CX23885_BOARD_HAUPPAUGE_HVR1800lp, + }, { + .subvendor = 0x0070, + .subdevice = 0x7800, + .card = CX23885_BOARD_HAUPPAUGE_HVR1800, + }, { + .subvendor = 0x0070, + .subdevice = 0x7801, + .card = CX23885_BOARD_HAUPPAUGE_HVR1800, + }, { + .subvendor = 0x0070, + .subdevice = 0x7809, + .card = CX23885_BOARD_HAUPPAUGE_HVR1800, + }, { + .subvendor = 0x0070, + .subdevice = 0x7911, + .card = CX23885_BOARD_HAUPPAUGE_HVR1250, + }, { + .subvendor = 0x18ac, + .subdevice = 0xd500, + .card = CX23885_BOARD_DVICO_FUSIONHDTV_5_EXP, + }, { + .subvendor = 0x0070, + .subdevice = 0x7790, + .card = CX23885_BOARD_HAUPPAUGE_HVR1500Q, + }, { + .subvendor = 0x0070, + .subdevice = 0x7797, + .card = CX23885_BOARD_HAUPPAUGE_HVR1500Q, + }, { + .subvendor = 0x0070, + .subdevice = 0x7710, + .card = CX23885_BOARD_HAUPPAUGE_HVR1500, + }, { + .subvendor = 0x0070, + .subdevice = 0x7717, + .card = CX23885_BOARD_HAUPPAUGE_HVR1500, + }, { + .subvendor = 0x0070, + .subdevice = 0x71d1, + .card = CX23885_BOARD_HAUPPAUGE_HVR1200, + }, { + .subvendor = 0x0070, + .subdevice = 0x71d3, + .card = CX23885_BOARD_HAUPPAUGE_HVR1200, + }, { + .subvendor = 0x0070, + .subdevice = 0x8101, + .card = CX23885_BOARD_HAUPPAUGE_HVR1700, + }, { + .subvendor = 0x0070, + .subdevice = 0x8010, + .card = CX23885_BOARD_HAUPPAUGE_HVR1400, + }, { + .subvendor = 0x18ac, + .subdevice = 0xd618, + .card = CX23885_BOARD_DVICO_FUSIONHDTV_7_DUAL_EXP, + }, { + .subvendor = 0x18ac, + .subdevice = 0xdb78, + .card = CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP, + }, { + .subvendor = 0x107d, + .subdevice = 0x6681, + .card = CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H, + }, { + .subvendor = 0x107d, + .subdevice = 0x6f21, + .card = CX23885_BOARD_LEADTEK_WINFAST_PXPVR2200, + }, { + .subvendor = 0x107d, + .subdevice = 0x6f39, + .card = CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000, + }, { + .subvendor = 0x185b, + .subdevice = 0xe800, + .card = CX23885_BOARD_COMPRO_VIDEOMATE_E650F, + }, { + .subvendor = 0x6920, + .subdevice = 0x8888, + .card = CX23885_BOARD_TBS_6920, + }, { + .subvendor = 0x6980, + .subdevice = 0x8888, + .card = CX23885_BOARD_TBS_6980, + }, { + .subvendor = 0x6981, + .subdevice = 0x8888, + .card = CX23885_BOARD_TBS_6981, + }, { + .subvendor = 0xd470, + .subdevice = 0x9022, + .card = CX23885_BOARD_TEVII_S470, + }, { + .subvendor = 0x0001, + .subdevice = 0x2005, + .card = CX23885_BOARD_DVBWORLD_2005, + }, { + .subvendor = 0x1b55, + .subdevice = 0x2a2c, + .card = CX23885_BOARD_NETUP_DUAL_DVBS2_CI, + }, { + .subvendor = 0x0070, + .subdevice = 0x2211, + .card = CX23885_BOARD_HAUPPAUGE_HVR1270, + }, { + .subvendor = 0x0070, + .subdevice = 0x2215, + .card = CX23885_BOARD_HAUPPAUGE_HVR1275, + }, { + .subvendor = 0x0070, + .subdevice = 0x221d, + .card = CX23885_BOARD_HAUPPAUGE_HVR1275, + }, { + .subvendor = 0x0070, + .subdevice = 0x2251, + .card = CX23885_BOARD_HAUPPAUGE_HVR1255, + }, { + .subvendor = 0x0070, + .subdevice = 0x2259, + .card = CX23885_BOARD_HAUPPAUGE_HVR1255_22111, + }, { + .subvendor = 0x0070, + .subdevice = 0x2291, + .card = CX23885_BOARD_HAUPPAUGE_HVR1210, + }, { + .subvendor = 0x0070, + .subdevice = 0x2295, + .card = CX23885_BOARD_HAUPPAUGE_HVR1210, + }, { + .subvendor = 0x0070, + .subdevice = 0x2299, + .card = CX23885_BOARD_HAUPPAUGE_HVR1210, + }, { + .subvendor = 0x0070, + .subdevice = 0x229d, + .card = CX23885_BOARD_HAUPPAUGE_HVR1210, /* HVR1215 */ + }, { + .subvendor = 0x0070, + .subdevice = 0x22f0, + .card = CX23885_BOARD_HAUPPAUGE_HVR1210, + }, { + .subvendor = 0x0070, + .subdevice = 0x22f1, + .card = CX23885_BOARD_HAUPPAUGE_HVR1255, + }, { + .subvendor = 0x0070, + .subdevice = 0x22f2, + .card = CX23885_BOARD_HAUPPAUGE_HVR1275, + }, { + .subvendor = 0x0070, + .subdevice = 0x22f3, + .card = CX23885_BOARD_HAUPPAUGE_HVR1210, /* HVR1215 */ + }, { + .subvendor = 0x0070, + .subdevice = 0x22f4, + .card = CX23885_BOARD_HAUPPAUGE_HVR1210, + }, { + .subvendor = 0x0070, + .subdevice = 0x22f5, + .card = CX23885_BOARD_HAUPPAUGE_HVR1210, /* HVR1215 */ + }, { + .subvendor = 0x14f1, + .subdevice = 0x8651, + .card = CX23885_BOARD_MYGICA_X8506, + }, { + .subvendor = 0x14f1, + .subdevice = 0x8657, + .card = CX23885_BOARD_MAGICPRO_PROHDTVE2, + }, { + .subvendor = 0x0070, + .subdevice = 0x8541, + .card = CX23885_BOARD_HAUPPAUGE_HVR1850, + }, { + .subvendor = 0x1858, + .subdevice = 0xe800, + .card = CX23885_BOARD_COMPRO_VIDEOMATE_E800, + }, { + .subvendor = 0x0070, + .subdevice = 0x8551, + .card = CX23885_BOARD_HAUPPAUGE_HVR1290, + }, { + .subvendor = 0x14f1, + .subdevice = 0x8578, + .card = CX23885_BOARD_MYGICA_X8558PRO, + }, { + .subvendor = 0x107d, + .subdevice = 0x6f22, + .card = CX23885_BOARD_LEADTEK_WINFAST_PXTV1200, + }, { + .subvendor = 0x5654, + .subdevice = 0x2390, + .card = CX23885_BOARD_GOTVIEW_X5_3D_HYBRID, + }, { + .subvendor = 0x1b55, + .subdevice = 0xe2e4, + .card = CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF, + }, { + .subvendor = 0x14f1, + .subdevice = 0x8502, + .card = CX23885_BOARD_MYGICA_X8507, + }, { + .subvendor = 0x153b, + .subdevice = 0x117e, + .card = CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL, + }, { + .subvendor = 0xd471, + .subdevice = 0x9022, + .card = CX23885_BOARD_TEVII_S471, + }, { + .subvendor = 0x8000, + .subdevice = 0x3034, + .card = CX23885_BOARD_PROF_8000, + }, { + .subvendor = 0x0070, + .subdevice = 0xc108, + .card = CX23885_BOARD_HAUPPAUGE_HVR4400, /* Hauppauge WinTV HVR-4400 (Model 121xxx, Hybrid DVB-T/S2, IR) */ + }, { + .subvendor = 0x0070, + .subdevice = 0xc138, + .card = CX23885_BOARD_HAUPPAUGE_HVR4400, /* Hauppauge WinTV HVR-5500 (Model 121xxx, Hybrid DVB-T/C/S2, IR) */ + }, { + .subvendor = 0x0070, + .subdevice = 0xc12a, + .card = CX23885_BOARD_HAUPPAUGE_STARBURST, /* Hauppauge WinTV Starburst (Model 121x00, DVB-S2, IR) */ + }, { + .subvendor = 0x0070, + .subdevice = 0xc1f8, + .card = CX23885_BOARD_HAUPPAUGE_HVR4400, /* Hauppauge WinTV HVR-5500 (Model 121xxx, Hybrid DVB-T/C/S2, IR) */ + }, { + .subvendor = 0x1461, + .subdevice = 0xd939, + .card = CX23885_BOARD_AVERMEDIA_HC81R, + }, { + .subvendor = 0x0070, + .subdevice = 0x7133, + .card = CX23885_BOARD_HAUPPAUGE_IMPACTVCBE, + }, { + .subvendor = 0x0070, + .subdevice = 0x7137, + .card = CX23885_BOARD_HAUPPAUGE_IMPACTVCBE, + }, { + .subvendor = 0x18ac, + .subdevice = 0xdb98, + .card = CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP2, + }, { + .subvendor = 0x4254, + .subdevice = 0x9580, + .card = CX23885_BOARD_DVBSKY_T9580, + }, { + .subvendor = 0x4254, + .subdevice = 0x980c, + .card = CX23885_BOARD_DVBSKY_T980C, + }, { + .subvendor = 0x4254, + .subdevice = 0x950c, + .card = CX23885_BOARD_DVBSKY_S950C, + }, { + .subvendor = 0x13c2, + .subdevice = 0x3013, + .card = CX23885_BOARD_TT_CT2_4500_CI, + }, { + .subvendor = 0x4254, + .subdevice = 0x0950, + .card = CX23885_BOARD_DVBSKY_S950, + }, { + .subvendor = 0x4254, + .subdevice = 0x0952, + .card = CX23885_BOARD_DVBSKY_S952, + }, { + .subvendor = 0x4254, + .subdevice = 0x0982, + .card = CX23885_BOARD_DVBSKY_T982, + }, { + .subvendor = 0x0070, + .subdevice = 0xf038, + .card = CX23885_BOARD_HAUPPAUGE_HVR5525, + }, { + .subvendor = 0x1576, + .subdevice = 0x0260, + .card = CX23885_BOARD_VIEWCAST_260E, + }, { + .subvendor = 0x1576, + .subdevice = 0x0460, + .card = CX23885_BOARD_VIEWCAST_460E, + }, { + .subvendor = 0x0070, + .subdevice = 0x6a28, + .card = CX23885_BOARD_HAUPPAUGE_QUADHD_DVB, /* Tuner Pair 1 */ + }, { + .subvendor = 0x0070, + .subdevice = 0x6b28, + .card = CX23885_BOARD_HAUPPAUGE_QUADHD_DVB, /* Tuner Pair 2 */ + }, { + .subvendor = 0x0070, + .subdevice = 0x6a18, + .card = CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC, /* Tuner Pair 1 */ + }, { + .subvendor = 0x0070, + .subdevice = 0x6b18, + .card = CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC, /* Tuner Pair 2 */ + }, { + .subvendor = 0x0070, + .subdevice = 0x2a18, + .card = CX23885_BOARD_HAUPPAUGE_HVR1265_K4, /* Hauppauge WinTV HVR-1265 (Model 161xx1, Hybrid ATSC/QAM-B) */ + }, { + .subvendor = 0x0070, + .subdevice = 0xf02a, + .card = CX23885_BOARD_HAUPPAUGE_STARBURST2, + }, { + .subvendor = 0x1461, + .subdevice = 0x3100, + .card = CX23885_BOARD_AVERMEDIA_CE310B, + }, { + .subvendor = 0x1461, + .subdevice = 0xe139, + .card = CX23885_BOARD_AVERMEDIA_H789C, + }, +}; +const unsigned int cx23885_idcount = ARRAY_SIZE(cx23885_subids); + +void cx23885_card_list(struct cx23885_dev *dev) +{ + int i; + + if (0 == dev->pci->subsystem_vendor && + 0 == dev->pci->subsystem_device) { + pr_info("%s: Board has no valid PCIe Subsystem ID and can't\n" + "%s: be autodetected. Pass card= insmod option\n" + "%s: to workaround that. Redirect complaints to the\n" + "%s: vendor of the TV card. Best regards,\n" + "%s: -- tux\n", + dev->name, dev->name, dev->name, dev->name, dev->name); + } else { + pr_info("%s: Your board isn't known (yet) to the driver.\n" + "%s: Try to pick one of the existing card configs via\n" + "%s: card= insmod option. Updating to the latest\n" + "%s: version might help as well.\n", + dev->name, dev->name, dev->name, dev->name); + } + pr_info("%s: Here is a list of valid choices for the card= insmod option:\n", + dev->name); + for (i = 0; i < cx23885_bcount; i++) + pr_info("%s: card=%d -> %s\n", + dev->name, i, cx23885_boards[i].name); +} + +static void viewcast_eeprom(struct cx23885_dev *dev, u8 *eeprom_data) +{ + u32 sn; + + /* The serial number record begins with tag 0x59 */ + if (*(eeprom_data + 0x00) != 0x59) { + pr_info("%s() eeprom records are undefined, no serial number\n", + __func__); + return; + } + + sn = (*(eeprom_data + 0x06) << 24) | + (*(eeprom_data + 0x05) << 16) | + (*(eeprom_data + 0x04) << 8) | + (*(eeprom_data + 0x03)); + + pr_info("%s: card '%s' sn# MM%d\n", + dev->name, + cx23885_boards[dev->board].name, + sn); +} + +static void hauppauge_eeprom(struct cx23885_dev *dev, u8 *eeprom_data) +{ + struct tveeprom tv; + + tveeprom_hauppauge_analog(&tv, eeprom_data); + + /* Make sure we support the board model */ + switch (tv.model) { + case 22001: + /* WinTV-HVR1270 (PCIe, Retail, half height) + * ATSC/QAM and basic analog, IR Blast */ + case 22009: + /* WinTV-HVR1210 (PCIe, Retail, half height) + * DVB-T and basic analog, IR Blast */ + case 22011: + /* WinTV-HVR1270 (PCIe, Retail, half height) + * ATSC/QAM and basic analog, IR Recv */ + case 22019: + /* WinTV-HVR1210 (PCIe, Retail, half height) + * DVB-T and basic analog, IR Recv */ + case 22021: + /* WinTV-HVR1275 (PCIe, Retail, half height) + * ATSC/QAM and basic analog, IR Recv */ + case 22029: + /* WinTV-HVR1210 (PCIe, Retail, half height) + * DVB-T and basic analog, IR Recv */ + case 22101: + /* WinTV-HVR1270 (PCIe, Retail, full height) + * ATSC/QAM and basic analog, IR Blast */ + case 22109: + /* WinTV-HVR1210 (PCIe, Retail, full height) + * DVB-T and basic analog, IR Blast */ + case 22111: + /* WinTV-HVR1270 (PCIe, Retail, full height) + * ATSC/QAM and basic analog, IR Recv */ + case 22119: + /* WinTV-HVR1210 (PCIe, Retail, full height) + * DVB-T and basic analog, IR Recv */ + case 22121: + /* WinTV-HVR1275 (PCIe, Retail, full height) + * ATSC/QAM and basic analog, IR Recv */ + case 22129: + /* WinTV-HVR1210 (PCIe, Retail, full height) + * DVB-T and basic analog, IR Recv */ + case 71009: + /* WinTV-HVR1200 (PCIe, Retail, full height) + * DVB-T and basic analog */ + case 71100: + /* WinTV-ImpactVCB-e (PCIe, Retail, half height) + * Basic analog */ + case 71359: + /* WinTV-HVR1200 (PCIe, OEM, half height) + * DVB-T and basic analog */ + case 71439: + /* WinTV-HVR1200 (PCIe, OEM, half height) + * DVB-T and basic analog */ + case 71449: + /* WinTV-HVR1200 (PCIe, OEM, full height) + * DVB-T and basic analog */ + case 71939: + /* WinTV-HVR1200 (PCIe, OEM, half height) + * DVB-T and basic analog */ + case 71949: + /* WinTV-HVR1200 (PCIe, OEM, full height) + * DVB-T and basic analog */ + case 71959: + /* WinTV-HVR1200 (PCIe, OEM, full height) + * DVB-T and basic analog */ + case 71979: + /* WinTV-HVR1200 (PCIe, OEM, half height) + * DVB-T and basic analog */ + case 71999: + /* WinTV-HVR1200 (PCIe, OEM, full height) + * DVB-T and basic analog */ + case 76601: + /* WinTV-HVR1800lp (PCIe, Retail, No IR, Dual + channel ATSC and MPEG2 HW Encoder */ + case 77001: + /* WinTV-HVR1500 (Express Card, OEM, No IR, ATSC + and Basic analog */ + case 77011: + /* WinTV-HVR1500 (Express Card, Retail, No IR, ATSC + and Basic analog */ + case 77041: + /* WinTV-HVR1500Q (Express Card, OEM, No IR, ATSC/QAM + and Basic analog */ + case 77051: + /* WinTV-HVR1500Q (Express Card, Retail, No IR, ATSC/QAM + and Basic analog */ + case 78011: + /* WinTV-HVR1800 (PCIe, Retail, 3.5mm in, IR, No FM, + Dual channel ATSC and MPEG2 HW Encoder */ + case 78501: + /* WinTV-HVR1800 (PCIe, OEM, RCA in, No IR, FM, + Dual channel ATSC and MPEG2 HW Encoder */ + case 78521: + /* WinTV-HVR1800 (PCIe, OEM, RCA in, No IR, FM, + Dual channel ATSC and MPEG2 HW Encoder */ + case 78531: + /* WinTV-HVR1800 (PCIe, OEM, RCA in, No IR, No FM, + Dual channel ATSC and MPEG2 HW Encoder */ + case 78631: + /* WinTV-HVR1800 (PCIe, OEM, No IR, No FM, + Dual channel ATSC and MPEG2 HW Encoder */ + case 79001: + /* WinTV-HVR1250 (PCIe, Retail, IR, full height, + ATSC and Basic analog */ + case 79101: + /* WinTV-HVR1250 (PCIe, Retail, IR, half height, + ATSC and Basic analog */ + case 79501: + /* WinTV-HVR1250 (PCIe, No IR, half height, + ATSC [at least] and Basic analog) */ + case 79561: + /* WinTV-HVR1250 (PCIe, OEM, No IR, half height, + ATSC and Basic analog */ + case 79571: + /* WinTV-HVR1250 (PCIe, OEM, No IR, full height, + ATSC and Basic analog */ + case 79671: + /* WinTV-HVR1250 (PCIe, OEM, No IR, half height, + ATSC and Basic analog */ + case 80019: + /* WinTV-HVR1400 (Express Card, Retail, IR, + * DVB-T and Basic analog */ + case 81509: + /* WinTV-HVR1700 (PCIe, OEM, No IR, half height) + * DVB-T and MPEG2 HW Encoder */ + case 81519: + /* WinTV-HVR1700 (PCIe, OEM, No IR, full height) + * DVB-T and MPEG2 HW Encoder */ + break; + case 85021: + /* WinTV-HVR1850 (PCIe, Retail, 3.5mm in, IR, FM, + Dual channel ATSC and MPEG2 HW Encoder */ + break; + case 85721: + /* WinTV-HVR1290 (PCIe, OEM, RCA in, IR, + Dual channel ATSC and Basic analog */ + case 121019: + /* WinTV-HVR4400 (PCIe, DVB-S2, DVB-C/T) */ + break; + case 121029: + /* WinTV-HVR5500 (PCIe, DVB-S2, DVB-C/T) */ + break; + case 150329: + /* WinTV-HVR5525 (PCIe, DVB-S/S2, DVB-T/T2/C) */ + break; + case 161111: + /* WinTV-HVR-1265 K4 (PCIe, Analog/ATSC/QAM-B) */ + break; + case 166100: /* 888 version, hybrid */ + case 166200: /* 885 version, DVB only */ + /* WinTV-QuadHD (DVB) Tuner Pair 1 (PCIe, IR, half height, + DVB-T/T2/C, DVB-T/T2/C */ + break; + case 166101: /* 888 version, hybrid */ + case 166201: /* 885 version, DVB only */ + /* WinTV-QuadHD (DVB) Tuner Pair 2 (PCIe, IR, half height, + DVB-T/T2/C, DVB-T/T2/C */ + break; + case 165100: /* 888 version, hybrid */ + case 165200: /* 885 version, digital only */ + /* WinTV-QuadHD (ATSC) Tuner Pair 1 (PCIe, IR, half height, + * ATSC/QAM-B, ATSC/QAM-B */ + break; + case 165101: /* 888 version, hybrid */ + case 165201: /* 885 version, digital only */ + /* WinTV-QuadHD (ATSC) Tuner Pair 2 (PCIe, IR, half height, + * ATSC/QAM-B, ATSC/QAM-B */ + break; + default: + pr_warn("%s: warning: unknown hauppauge model #%d\n", + dev->name, tv.model); + break; + } + + pr_info("%s: hauppauge eeprom: model=%d\n", + dev->name, tv.model); +} + +/* Some TBS cards require initing a chip using a bitbanged SPI attached + to the cx23885 gpio's. If this chip doesn't get init'ed the demod + doesn't respond to any command. */ +static void tbs_card_init(struct cx23885_dev *dev) +{ + int i; + static const u8 buf[] = { + 0xe0, 0x06, 0x66, 0x33, 0x65, + 0x01, 0x17, 0x06, 0xde}; + + switch (dev->board) { + case CX23885_BOARD_TBS_6980: + case CX23885_BOARD_TBS_6981: + cx_set(GP0_IO, 0x00070007); + usleep_range(1000, 10000); + cx_clear(GP0_IO, 2); + usleep_range(1000, 10000); + for (i = 0; i < 9 * 8; i++) { + cx_clear(GP0_IO, 7); + usleep_range(1000, 10000); + cx_set(GP0_IO, + ((buf[i >> 3] >> (7 - (i & 7))) & 1) | 4); + usleep_range(1000, 10000); + } + cx_set(GP0_IO, 7); + break; + } +} + +int cx23885_tuner_callback(void *priv, int component, int command, int arg) +{ + struct cx23885_tsport *port = priv; + struct cx23885_dev *dev = port->dev; + u32 bitmask = 0; + + if ((command == XC2028_RESET_CLK) || (command == XC2028_I2C_FLUSH)) + return 0; + + if (command != 0) { + pr_err("%s(): Unknown command 0x%x.\n", + __func__, command); + return -EINVAL; + } + + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1400: + case CX23885_BOARD_HAUPPAUGE_HVR1500: + case CX23885_BOARD_HAUPPAUGE_HVR1500Q: + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H: + case CX23885_BOARD_LEADTEK_WINFAST_PXPVR2200: + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000: + case CX23885_BOARD_COMPRO_VIDEOMATE_E650F: + case CX23885_BOARD_COMPRO_VIDEOMATE_E800: + case CX23885_BOARD_LEADTEK_WINFAST_PXTV1200: + /* Tuner Reset Command */ + bitmask = 0x04; + break; + case CX23885_BOARD_DVICO_FUSIONHDTV_7_DUAL_EXP: + case CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP: + case CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP2: + /* Two identical tuners on two different i2c buses, + * we need to reset the correct gpio. */ + if (port->nr == 1) + bitmask = 0x01; + else if (port->nr == 2) + bitmask = 0x04; + break; + case CX23885_BOARD_GOTVIEW_X5_3D_HYBRID: + /* Tuner Reset Command */ + bitmask = 0x02; + break; + case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: + altera_ci_tuner_reset(dev, port->nr); + break; + case CX23885_BOARD_AVERMEDIA_HC81R: + /* XC3028L Reset Command */ + bitmask = 1 << 2; + break; + } + + if (bitmask) { + /* Drive the tuner into reset and back out */ + cx_clear(GP0_IO, bitmask); + mdelay(200); + cx_set(GP0_IO, bitmask); + } + + return 0; +} + +void cx23885_gpio_setup(struct cx23885_dev *dev) +{ + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1250: + /* GPIO-0 cx24227 demodulator reset */ + cx_set(GP0_IO, 0x00010001); /* Bring the part out of reset */ + break; + case CX23885_BOARD_HAUPPAUGE_HVR1500: + /* GPIO-0 cx24227 demodulator */ + /* GPIO-2 xc3028 tuner */ + + /* Put the parts into reset */ + cx_set(GP0_IO, 0x00050000); + cx_clear(GP0_IO, 0x00000005); + msleep(5); + + /* Bring the parts out of reset */ + cx_set(GP0_IO, 0x00050005); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1500Q: + /* GPIO-0 cx24227 demodulator reset */ + /* GPIO-2 xc5000 tuner reset */ + cx_set(GP0_IO, 0x00050005); /* Bring the part out of reset */ + break; + case CX23885_BOARD_HAUPPAUGE_HVR1800: + /* GPIO-0 656_CLK */ + /* GPIO-1 656_D0 */ + /* GPIO-2 8295A Reset */ + /* GPIO-3-10 cx23417 data0-7 */ + /* GPIO-11-14 cx23417 addr0-3 */ + /* GPIO-15-18 cx23417 READY, CS, RD, WR */ + /* GPIO-19 IR_RX */ + + /* CX23417 GPIO's */ + /* EIO15 Zilog Reset */ + /* EIO14 S5H1409/CX24227 Reset */ + mc417_gpio_enable(dev, GPIO_15 | GPIO_14, 1); + + /* Put the demod into reset and protect the eeprom */ + mc417_gpio_clear(dev, GPIO_15 | GPIO_14); + msleep(100); + + /* Bring the demod and blaster out of reset */ + mc417_gpio_set(dev, GPIO_15 | GPIO_14); + msleep(100); + + /* Force the TDA8295A into reset and back */ + cx23885_gpio_enable(dev, GPIO_2, 1); + cx23885_gpio_set(dev, GPIO_2); + msleep(20); + cx23885_gpio_clear(dev, GPIO_2); + msleep(20); + cx23885_gpio_set(dev, GPIO_2); + msleep(20); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1200: + /* GPIO-0 tda10048 demodulator reset */ + /* GPIO-2 tda18271 tuner reset */ + + /* Put the parts into reset and back */ + cx_set(GP0_IO, 0x00050000); + msleep(20); + cx_clear(GP0_IO, 0x00000005); + msleep(20); + cx_set(GP0_IO, 0x00050005); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1700: + /* GPIO-0 TDA10048 demodulator reset */ + /* GPIO-2 TDA8295A Reset */ + /* GPIO-3-10 cx23417 data0-7 */ + /* GPIO-11-14 cx23417 addr0-3 */ + /* GPIO-15-18 cx23417 READY, CS, RD, WR */ + + /* The following GPIO's are on the interna AVCore (cx25840) */ + /* GPIO-19 IR_RX */ + /* GPIO-20 IR_TX 416/DVBT Select */ + /* GPIO-21 IIS DAT */ + /* GPIO-22 IIS WCLK */ + /* GPIO-23 IIS BCLK */ + + /* Put the parts into reset and back */ + cx_set(GP0_IO, 0x00050000); + msleep(20); + cx_clear(GP0_IO, 0x00000005); + msleep(20); + cx_set(GP0_IO, 0x00050005); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1400: + /* GPIO-0 Dibcom7000p demodulator reset */ + /* GPIO-2 xc3028L tuner reset */ + /* GPIO-13 LED */ + + /* Put the parts into reset and back */ + cx_set(GP0_IO, 0x00050000); + msleep(20); + cx_clear(GP0_IO, 0x00000005); + msleep(20); + cx_set(GP0_IO, 0x00050005); + break; + case CX23885_BOARD_DVICO_FUSIONHDTV_7_DUAL_EXP: + /* GPIO-0 xc5000 tuner reset i2c bus 0 */ + /* GPIO-1 s5h1409 demod reset i2c bus 0 */ + /* GPIO-2 xc5000 tuner reset i2c bus 1 */ + /* GPIO-3 s5h1409 demod reset i2c bus 0 */ + + /* Put the parts into reset and back */ + cx_set(GP0_IO, 0x000f0000); + msleep(20); + cx_clear(GP0_IO, 0x0000000f); + msleep(20); + cx_set(GP0_IO, 0x000f000f); + break; + case CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP: + case CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP2: + /* GPIO-0 portb xc3028 reset */ + /* GPIO-1 portb zl10353 reset */ + /* GPIO-2 portc xc3028 reset */ + /* GPIO-3 portc zl10353 reset */ + + /* Put the parts into reset and back */ + cx_set(GP0_IO, 0x000f0000); + msleep(20); + cx_clear(GP0_IO, 0x0000000f); + msleep(20); + cx_set(GP0_IO, 0x000f000f); + break; + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H: + case CX23885_BOARD_LEADTEK_WINFAST_PXPVR2200: + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000: + case CX23885_BOARD_COMPRO_VIDEOMATE_E650F: + case CX23885_BOARD_COMPRO_VIDEOMATE_E800: + case CX23885_BOARD_LEADTEK_WINFAST_PXTV1200: + /* GPIO-2 xc3028 tuner reset */ + + /* The following GPIO's are on the internal AVCore (cx25840) */ + /* GPIO-? zl10353 demod reset */ + + /* Put the parts into reset and back */ + cx_set(GP0_IO, 0x00040000); + msleep(20); + cx_clear(GP0_IO, 0x00000004); + msleep(20); + cx_set(GP0_IO, 0x00040004); + break; + case CX23885_BOARD_TBS_6920: + case CX23885_BOARD_TBS_6980: + case CX23885_BOARD_TBS_6981: + case CX23885_BOARD_PROF_8000: + cx_write(MC417_CTL, 0x00000036); + cx_write(MC417_OEN, 0x00001000); + cx_set(MC417_RWD, 0x00000002); + msleep(200); + cx_clear(MC417_RWD, 0x00000800); + msleep(200); + cx_set(MC417_RWD, 0x00000800); + msleep(200); + break; + case CX23885_BOARD_NETUP_DUAL_DVBS2_CI: + /* GPIO-0 INTA from CiMax1 + GPIO-1 INTB from CiMax2 + GPIO-2 reset chips + GPIO-3 to GPIO-10 data/addr for CA + GPIO-11 ~CS0 to CiMax1 + GPIO-12 ~CS1 to CiMax2 + GPIO-13 ADL0 load LSB addr + GPIO-14 ADL1 load MSB addr + GPIO-15 ~RDY from CiMax + GPIO-17 ~RD to CiMax + GPIO-18 ~WR to CiMax + */ + cx_set(GP0_IO, 0x00040000); /* GPIO as out */ + /* GPIO1 and GPIO2 as INTA and INTB from CiMaxes, reset low */ + cx_clear(GP0_IO, 0x00030004); + msleep(100);/* reset delay */ + cx_set(GP0_IO, 0x00040004); /* GPIO as out, reset high */ + cx_write(MC417_CTL, 0x00000037);/* enable GPIO3-18 pins */ + /* GPIO-15 IN as ~ACK, rest as OUT */ + cx_write(MC417_OEN, 0x00001000); + /* ~RD, ~WR high; ADL0, ADL1 low; ~CS0, ~CS1 high */ + cx_write(MC417_RWD, 0x0000c300); + /* enable irq */ + cx_write(GPIO_ISM, 0x00000000);/* INTERRUPTS active low*/ + break; + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1275: + case CX23885_BOARD_HAUPPAUGE_HVR1255: + case CX23885_BOARD_HAUPPAUGE_HVR1255_22111: + case CX23885_BOARD_HAUPPAUGE_HVR1210: + /* GPIO-5 RF Control: 0 = RF1 Terrestrial, 1 = RF2 Cable */ + /* GPIO-6 I2C Gate which can isolate the demod from the bus */ + /* GPIO-9 Demod reset */ + + /* Put the parts into reset and back */ + cx23885_gpio_enable(dev, GPIO_9 | GPIO_6 | GPIO_5, 1); + cx23885_gpio_set(dev, GPIO_9 | GPIO_6 | GPIO_5); + cx23885_gpio_clear(dev, GPIO_9); + msleep(20); + cx23885_gpio_set(dev, GPIO_9); + break; + case CX23885_BOARD_MYGICA_X8506: + case CX23885_BOARD_MAGICPRO_PROHDTVE2: + case CX23885_BOARD_MYGICA_X8507: + /* GPIO-0 (0)Analog / (1)Digital TV */ + /* GPIO-1 reset XC5000 */ + /* GPIO-2 demod reset */ + cx23885_gpio_enable(dev, GPIO_0 | GPIO_1 | GPIO_2, 1); + cx23885_gpio_clear(dev, GPIO_1 | GPIO_2); + msleep(100); + cx23885_gpio_set(dev, GPIO_0 | GPIO_1 | GPIO_2); + msleep(100); + break; + case CX23885_BOARD_MYGICA_X8558PRO: + /* GPIO-0 reset first ATBM8830 */ + /* GPIO-1 reset second ATBM8830 */ + cx23885_gpio_enable(dev, GPIO_0 | GPIO_1, 1); + cx23885_gpio_clear(dev, GPIO_0 | GPIO_1); + msleep(100); + cx23885_gpio_set(dev, GPIO_0 | GPIO_1); + msleep(100); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + /* GPIO-0 656_CLK */ + /* GPIO-1 656_D0 */ + /* GPIO-2 Wake# */ + /* GPIO-3-10 cx23417 data0-7 */ + /* GPIO-11-14 cx23417 addr0-3 */ + /* GPIO-15-18 cx23417 READY, CS, RD, WR */ + /* GPIO-19 IR_RX */ + /* GPIO-20 C_IR_TX */ + /* GPIO-21 I2S DAT */ + /* GPIO-22 I2S WCLK */ + /* GPIO-23 I2S BCLK */ + /* ALT GPIO: EXP GPIO LATCH */ + + /* CX23417 GPIO's */ + /* GPIO-14 S5H1411/CX24228 Reset */ + /* GPIO-13 EEPROM write protect */ + mc417_gpio_enable(dev, GPIO_14 | GPIO_13, 1); + + /* Put the demod into reset and protect the eeprom */ + mc417_gpio_clear(dev, GPIO_14 | GPIO_13); + msleep(100); + + /* Bring the demod out of reset */ + mc417_gpio_set(dev, GPIO_14); + msleep(100); + + /* CX24228 GPIO */ + /* Connected to IF / Mux */ + break; + case CX23885_BOARD_GOTVIEW_X5_3D_HYBRID: + cx_set(GP0_IO, 0x00010001); /* Bring the part out of reset */ + break; + case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: + /* GPIO-0 ~INT in + GPIO-1 TMS out + GPIO-2 ~reset chips out + GPIO-3 to GPIO-10 data/addr for CA in/out + GPIO-11 ~CS out + GPIO-12 ADDR out + GPIO-13 ~WR out + GPIO-14 ~RD out + GPIO-15 ~RDY in + GPIO-16 TCK out + GPIO-17 TDO in + GPIO-18 TDI out + */ + cx_set(GP0_IO, 0x00060000); /* GPIO-1,2 as out */ + /* GPIO-0 as INT, reset & TMS low */ + cx_clear(GP0_IO, 0x00010006); + msleep(100);/* reset delay */ + cx_set(GP0_IO, 0x00000004); /* reset high */ + cx_write(MC417_CTL, 0x00000037);/* enable GPIO-3..18 pins */ + /* GPIO-17 is TDO in, GPIO-15 is ~RDY in, rest is out */ + cx_write(MC417_OEN, 0x00005000); + /* ~RD, ~WR high; ADDR low; ~CS high */ + cx_write(MC417_RWD, 0x00000d00); + /* enable irq */ + cx_write(GPIO_ISM, 0x00000000);/* INTERRUPTS active low*/ + break; + case CX23885_BOARD_HAUPPAUGE_HVR4400: + case CX23885_BOARD_HAUPPAUGE_STARBURST: + /* GPIO-8 tda10071 demod reset */ + /* GPIO-9 si2165 demod reset (only HVR4400/HVR5500)*/ + + /* Put the parts into reset and back */ + cx23885_gpio_enable(dev, GPIO_8 | GPIO_9, 1); + + cx23885_gpio_clear(dev, GPIO_8 | GPIO_9); + msleep(100); + cx23885_gpio_set(dev, GPIO_8 | GPIO_9); + msleep(100); + + break; + case CX23885_BOARD_AVERMEDIA_HC81R: + cx_clear(MC417_CTL, 1); + /* GPIO-0,1,2 setup direction as output */ + cx_set(GP0_IO, 0x00070000); + usleep_range(10000, 11000); + /* AF9013 demod reset */ + cx_set(GP0_IO, 0x00010001); + usleep_range(10000, 11000); + cx_clear(GP0_IO, 0x00010001); + usleep_range(10000, 11000); + cx_set(GP0_IO, 0x00010001); + usleep_range(10000, 11000); + /* demod tune? */ + cx_clear(GP0_IO, 0x00030003); + usleep_range(10000, 11000); + cx_set(GP0_IO, 0x00020002); + usleep_range(10000, 11000); + cx_set(GP0_IO, 0x00010001); + usleep_range(10000, 11000); + cx_clear(GP0_IO, 0x00020002); + /* XC3028L tuner reset */ + cx_set(GP0_IO, 0x00040004); + cx_clear(GP0_IO, 0x00040004); + cx_set(GP0_IO, 0x00040004); + msleep(60); + break; + case CX23885_BOARD_DVBSKY_T9580: + case CX23885_BOARD_DVBSKY_S952: + case CX23885_BOARD_DVBSKY_T982: + /* enable GPIO3-18 pins */ + cx_write(MC417_CTL, 0x00000037); + cx23885_gpio_enable(dev, GPIO_2 | GPIO_11, 1); + cx23885_gpio_clear(dev, GPIO_2 | GPIO_11); + msleep(100); + cx23885_gpio_set(dev, GPIO_2 | GPIO_11); + break; + case CX23885_BOARD_DVBSKY_T980C: + case CX23885_BOARD_DVBSKY_S950C: + case CX23885_BOARD_TT_CT2_4500_CI: + /* + * GPIO-0 INTA from CiMax, input + * GPIO-1 reset CiMax, output, high active + * GPIO-2 reset demod, output, low active + * GPIO-3 to GPIO-10 data/addr for CAM + * GPIO-11 ~CS0 to CiMax1 + * GPIO-12 ~CS1 to CiMax2 + * GPIO-13 ADL0 load LSB addr + * GPIO-14 ADL1 load MSB addr + * GPIO-15 ~RDY from CiMax + * GPIO-17 ~RD to CiMax + * GPIO-18 ~WR to CiMax + */ + + cx_set(GP0_IO, 0x00060002); /* GPIO 1/2 as output */ + cx_clear(GP0_IO, 0x00010004); /* GPIO 0 as input */ + msleep(100); /* reset delay */ + cx_set(GP0_IO, 0x00060004); /* GPIO as out, reset high */ + cx_clear(GP0_IO, 0x00010002); + cx_write(MC417_CTL, 0x00000037); /* enable GPIO3-18 pins */ + + /* GPIO-15 IN as ~ACK, rest as OUT */ + cx_write(MC417_OEN, 0x00001000); + + /* ~RD, ~WR high; ADL0, ADL1 low; ~CS0, ~CS1 high */ + cx_write(MC417_RWD, 0x0000c300); + + /* enable irq */ + cx_write(GPIO_ISM, 0x00000000); /* INTERRUPTS active low */ + break; + case CX23885_BOARD_DVBSKY_S950: + cx23885_gpio_enable(dev, GPIO_2, 1); + cx23885_gpio_clear(dev, GPIO_2); + msleep(100); + cx23885_gpio_set(dev, GPIO_2); + break; + case CX23885_BOARD_HAUPPAUGE_HVR5525: + case CX23885_BOARD_HAUPPAUGE_STARBURST2: + /* + * HVR5525 GPIO Details: + * GPIO-00 IR_WIDE + * GPIO-02 wake# + * GPIO-03 VAUX Pres. + * GPIO-07 PROG# + * GPIO-08 SAT_RESN + * GPIO-09 TER_RESN + * GPIO-10 B2_SENSE + * GPIO-11 B1_SENSE + * GPIO-15 IR_LED_STATUS + * GPIO-19 IR_NARROW + * GPIO-20 Blauster1 + * ALTGPIO VAUX_SWITCH + * AUX_PLL_CLK : Blaster2 + */ + /* Put the parts into reset and back */ + cx23885_gpio_enable(dev, GPIO_8 | GPIO_9, 1); + cx23885_gpio_clear(dev, GPIO_8 | GPIO_9); + msleep(100); + cx23885_gpio_set(dev, GPIO_8 | GPIO_9); + msleep(100); + break; + case CX23885_BOARD_VIEWCAST_260E: + case CX23885_BOARD_VIEWCAST_460E: + /* For documentation purposes, it's worth noting that this + * card does not have any GPIO's connected to subcomponents. + */ + break; + case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB: + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB_885: + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC: + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC_885: + /* + * GPIO-08 TER1_RESN + * GPIO-09 TER2_RESN + */ + /* Put the parts into reset and back */ + cx23885_gpio_enable(dev, GPIO_8 | GPIO_9, 1); + cx23885_gpio_clear(dev, GPIO_8 | GPIO_9); + msleep(100); + cx23885_gpio_set(dev, GPIO_8 | GPIO_9); + msleep(100); + break; + } +} + +int cx23885_ir_init(struct cx23885_dev *dev) +{ + static struct v4l2_subdev_io_pin_config ir_rxtx_pin_cfg[] = { + { + .flags = BIT(V4L2_SUBDEV_IO_PIN_INPUT), + .pin = CX23885_PIN_IR_RX_GPIO19, + .function = CX23885_PAD_IR_RX, + .value = 0, + .strength = CX25840_PIN_DRIVE_MEDIUM, + }, { + .flags = BIT(V4L2_SUBDEV_IO_PIN_OUTPUT), + .pin = CX23885_PIN_IR_TX_GPIO20, + .function = CX23885_PAD_IR_TX, + .value = 0, + .strength = CX25840_PIN_DRIVE_MEDIUM, + } + }; + const size_t ir_rxtx_pin_cfg_count = ARRAY_SIZE(ir_rxtx_pin_cfg); + + static struct v4l2_subdev_io_pin_config ir_rx_pin_cfg[] = { + { + .flags = BIT(V4L2_SUBDEV_IO_PIN_INPUT), + .pin = CX23885_PIN_IR_RX_GPIO19, + .function = CX23885_PAD_IR_RX, + .value = 0, + .strength = CX25840_PIN_DRIVE_MEDIUM, + } + }; + const size_t ir_rx_pin_cfg_count = ARRAY_SIZE(ir_rx_pin_cfg); + + struct v4l2_subdev_ir_parameters params; + int ret = 0; + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1500: + case CX23885_BOARD_HAUPPAUGE_HVR1500Q: + case CX23885_BOARD_HAUPPAUGE_HVR1800: + case CX23885_BOARD_HAUPPAUGE_HVR1200: + case CX23885_BOARD_HAUPPAUGE_HVR1400: + case CX23885_BOARD_HAUPPAUGE_HVR1275: + case CX23885_BOARD_HAUPPAUGE_HVR1255: + case CX23885_BOARD_HAUPPAUGE_HVR1255_22111: + case CX23885_BOARD_HAUPPAUGE_HVR1210: + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB: + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC: + /* FIXME: Implement me */ + break; + case CX23885_BOARD_HAUPPAUGE_HVR1270: + ret = cx23888_ir_probe(dev); + if (ret) + break; + dev->sd_ir = cx23885_find_hw(dev, CX23885_HW_888_IR); + v4l2_subdev_call(dev->sd_cx25840, core, s_io_pin_config, + ir_rx_pin_cfg_count, ir_rx_pin_cfg); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + ret = cx23888_ir_probe(dev); + if (ret) + break; + dev->sd_ir = cx23885_find_hw(dev, CX23885_HW_888_IR); + v4l2_subdev_call(dev->sd_cx25840, core, s_io_pin_config, + ir_rxtx_pin_cfg_count, ir_rxtx_pin_cfg); + /* + * For these boards we need to invert the Tx output via the + * IR controller to have the LED off while idle + */ + v4l2_subdev_call(dev->sd_ir, ir, tx_g_parameters, ¶ms); + params.enable = false; + params.shutdown = false; + params.invert_level = true; + v4l2_subdev_call(dev->sd_ir, ir, tx_s_parameters, ¶ms); + params.shutdown = true; + v4l2_subdev_call(dev->sd_ir, ir, tx_s_parameters, ¶ms); + break; + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + case CX23885_BOARD_TEVII_S470: + case CX23885_BOARD_MYGICA_X8507: + case CX23885_BOARD_TBS_6980: + case CX23885_BOARD_TBS_6981: + case CX23885_BOARD_DVBSKY_T9580: + case CX23885_BOARD_DVBSKY_T980C: + case CX23885_BOARD_DVBSKY_S950C: + case CX23885_BOARD_TT_CT2_4500_CI: + case CX23885_BOARD_DVBSKY_S950: + case CX23885_BOARD_DVBSKY_S952: + case CX23885_BOARD_DVBSKY_T982: + if (!enable_885_ir) + break; + dev->sd_ir = cx23885_find_hw(dev, CX23885_HW_AV_CORE); + if (dev->sd_ir == NULL) { + ret = -ENODEV; + break; + } + v4l2_subdev_call(dev->sd_cx25840, core, s_io_pin_config, + ir_rx_pin_cfg_count, ir_rx_pin_cfg); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1250: + if (!enable_885_ir) + break; + dev->sd_ir = cx23885_find_hw(dev, CX23885_HW_AV_CORE); + if (dev->sd_ir == NULL) { + ret = -ENODEV; + break; + } + v4l2_subdev_call(dev->sd_cx25840, core, s_io_pin_config, + ir_rxtx_pin_cfg_count, ir_rxtx_pin_cfg); + break; + case CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP: + case CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP2: + request_module("ir-kbd-i2c"); + break; + } + + return ret; +} + +void cx23885_ir_fini(struct cx23885_dev *dev) +{ + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + cx23885_irq_remove(dev, PCI_MSK_IR); + cx23888_ir_remove(dev); + dev->sd_ir = NULL; + break; + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + case CX23885_BOARD_TEVII_S470: + case CX23885_BOARD_HAUPPAUGE_HVR1250: + case CX23885_BOARD_MYGICA_X8507: + case CX23885_BOARD_TBS_6980: + case CX23885_BOARD_TBS_6981: + case CX23885_BOARD_DVBSKY_T9580: + case CX23885_BOARD_DVBSKY_T980C: + case CX23885_BOARD_DVBSKY_S950C: + case CX23885_BOARD_TT_CT2_4500_CI: + case CX23885_BOARD_DVBSKY_S950: + case CX23885_BOARD_DVBSKY_S952: + case CX23885_BOARD_DVBSKY_T982: + cx23885_irq_remove(dev, PCI_MSK_AV_CORE); + /* sd_ir is a duplicate pointer to the AV Core, just clear it */ + dev->sd_ir = NULL; + break; + } +} + +static int netup_jtag_io(void *device, int tms, int tdi, int read_tdo) +{ + int data; + int tdo = 0; + struct cx23885_dev *dev = (struct cx23885_dev *)device; + /*TMS*/ + data = ((cx_read(GP0_IO)) & (~0x00000002)); + data |= (tms ? 0x00020002 : 0x00020000); + cx_write(GP0_IO, data); + + /*TDI*/ + data = ((cx_read(MC417_RWD)) & (~0x0000a000)); + data |= (tdi ? 0x00008000 : 0); + cx_write(MC417_RWD, data); + if (read_tdo) + tdo = (data & 0x00004000) ? 1 : 0; /*TDO*/ + + cx_write(MC417_RWD, data | 0x00002000); + udelay(1); + /*TCK*/ + cx_write(MC417_RWD, data); + + return tdo; +} + +void cx23885_ir_pci_int_enable(struct cx23885_dev *dev) +{ + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + if (dev->sd_ir) + cx23885_irq_add_enable(dev, PCI_MSK_IR); + break; + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + case CX23885_BOARD_TEVII_S470: + case CX23885_BOARD_HAUPPAUGE_HVR1250: + case CX23885_BOARD_MYGICA_X8507: + case CX23885_BOARD_TBS_6980: + case CX23885_BOARD_TBS_6981: + case CX23885_BOARD_DVBSKY_T9580: + case CX23885_BOARD_DVBSKY_T980C: + case CX23885_BOARD_DVBSKY_S950C: + case CX23885_BOARD_TT_CT2_4500_CI: + case CX23885_BOARD_DVBSKY_S950: + case CX23885_BOARD_DVBSKY_S952: + case CX23885_BOARD_DVBSKY_T982: + if (dev->sd_ir) + cx23885_irq_add_enable(dev, PCI_MSK_AV_CORE); + break; + } +} + +void cx23885_card_setup(struct cx23885_dev *dev) +{ + struct cx23885_tsport *ts1 = &dev->ts1; + struct cx23885_tsport *ts2 = &dev->ts2; + + static u8 eeprom[256]; + + if (dev->i2c_bus[0].i2c_rc == 0) { + dev->i2c_bus[0].i2c_client.addr = 0xa0 >> 1; + tveeprom_read(&dev->i2c_bus[0].i2c_client, + eeprom, sizeof(eeprom)); + } + + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1250: + if (dev->i2c_bus[0].i2c_rc == 0) { + if (eeprom[0x80] != 0x84) + hauppauge_eeprom(dev, eeprom+0xc0); + else + hauppauge_eeprom(dev, eeprom+0x80); + } + break; + case CX23885_BOARD_HAUPPAUGE_HVR1500: + case CX23885_BOARD_HAUPPAUGE_HVR1500Q: + case CX23885_BOARD_HAUPPAUGE_HVR1400: + if (dev->i2c_bus[0].i2c_rc == 0) + hauppauge_eeprom(dev, eeprom+0x80); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1800: + case CX23885_BOARD_HAUPPAUGE_HVR1800lp: + case CX23885_BOARD_HAUPPAUGE_HVR1200: + case CX23885_BOARD_HAUPPAUGE_HVR1700: + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1275: + case CX23885_BOARD_HAUPPAUGE_HVR1255: + case CX23885_BOARD_HAUPPAUGE_HVR1255_22111: + case CX23885_BOARD_HAUPPAUGE_HVR1210: + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + case CX23885_BOARD_HAUPPAUGE_HVR4400: + case CX23885_BOARD_HAUPPAUGE_STARBURST: + case CX23885_BOARD_HAUPPAUGE_IMPACTVCBE: + case CX23885_BOARD_HAUPPAUGE_HVR5525: + case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: + case CX23885_BOARD_HAUPPAUGE_STARBURST2: + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB: + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB_885: + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC: + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC_885: + if (dev->i2c_bus[0].i2c_rc == 0) + hauppauge_eeprom(dev, eeprom+0xc0); + break; + case CX23885_BOARD_VIEWCAST_260E: + case CX23885_BOARD_VIEWCAST_460E: + dev->i2c_bus[1].i2c_client.addr = 0xa0 >> 1; + tveeprom_read(&dev->i2c_bus[1].i2c_client, + eeprom, sizeof(eeprom)); + if (dev->i2c_bus[0].i2c_rc == 0) + viewcast_eeprom(dev, eeprom); + break; + } + + switch (dev->board) { + case CX23885_BOARD_AVERMEDIA_HC81R: + /* Defaults for VID B */ + ts1->gen_ctrl_val = 0x4; /* Parallel */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + /* Defaults for VID C */ + /* DREQ_POL, SMODE, PUNC_CLK, MCLK_POL Serial bus + punc clk */ + ts2->gen_ctrl_val = 0x10e; + ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_DVICO_FUSIONHDTV_7_DUAL_EXP: + case CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP: + case CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP2: + ts2->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + fallthrough; + case CX23885_BOARD_DVICO_FUSIONHDTV_5_EXP: + ts1->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1800: + /* Defaults for VID B - Analog encoder */ + /* DREQ_POL, SMODE, PUNC_CLK, MCLK_POL Serial bus + punc clk */ + ts1->gen_ctrl_val = 0x10e; + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + + /* APB_TSVALERR_POL (active low)*/ + ts1->vld_misc_val = 0x2000; + ts1->hw_sop_ctrl_val = (0x47 << 16 | 188 << 4 | 0xc); + cx_write(0x130184, 0xc); + + /* Defaults for VID C */ + ts2->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_TBS_6920: + ts1->gen_ctrl_val = 0x4; /* Parallel */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_TEVII_S470: + case CX23885_BOARD_TEVII_S471: + case CX23885_BOARD_DVBWORLD_2005: + case CX23885_BOARD_PROF_8000: + case CX23885_BOARD_DVBSKY_T980C: + case CX23885_BOARD_DVBSKY_S950C: + case CX23885_BOARD_TT_CT2_4500_CI: + case CX23885_BOARD_DVBSKY_S950: + ts1->gen_ctrl_val = 0x5; /* Parallel */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_NETUP_DUAL_DVBS2_CI: + case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + ts1->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + ts2->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_TBS_6980: + case CX23885_BOARD_TBS_6981: + ts1->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + ts2->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + tbs_card_init(dev); + break; + case CX23885_BOARD_MYGICA_X8506: + case CX23885_BOARD_MAGICPRO_PROHDTVE2: + case CX23885_BOARD_MYGICA_X8507: + ts1->gen_ctrl_val = 0x5; /* Parallel */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_MYGICA_X8558PRO: + ts1->gen_ctrl_val = 0x5; /* Parallel */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + ts2->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_HAUPPAUGE_HVR4400: + ts1->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + ts2->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_HAUPPAUGE_STARBURST: + ts1->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_DVBSKY_T9580: + case CX23885_BOARD_DVBSKY_T982: + ts1->gen_ctrl_val = 0x5; /* Parallel */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + ts2->gen_ctrl_val = 0x8; /* Serial bus */ + ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_DVBSKY_S952: + ts1->gen_ctrl_val = 0x5; /* Parallel */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + ts2->gen_ctrl_val = 0xe; /* Serial bus */ + ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_HAUPPAUGE_HVR5525: + case CX23885_BOARD_HAUPPAUGE_STARBURST2: + ts1->gen_ctrl_val = 0x5; /* Parallel */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + ts2->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB: + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB_885: + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC: + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC_885: + ts1->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + ts2->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_HAUPPAUGE_HVR1250: + case CX23885_BOARD_HAUPPAUGE_HVR1500: + case CX23885_BOARD_HAUPPAUGE_HVR1500Q: + case CX23885_BOARD_HAUPPAUGE_HVR1800lp: + case CX23885_BOARD_HAUPPAUGE_HVR1200: + case CX23885_BOARD_HAUPPAUGE_HVR1700: + case CX23885_BOARD_HAUPPAUGE_HVR1400: + case CX23885_BOARD_HAUPPAUGE_IMPACTVCBE: + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H: + case CX23885_BOARD_LEADTEK_WINFAST_PXPVR2200: + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000: + case CX23885_BOARD_COMPRO_VIDEOMATE_E650F: + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1275: + case CX23885_BOARD_HAUPPAUGE_HVR1255: + case CX23885_BOARD_HAUPPAUGE_HVR1255_22111: + case CX23885_BOARD_HAUPPAUGE_HVR1210: + case CX23885_BOARD_COMPRO_VIDEOMATE_E800: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + case CX23885_BOARD_GOTVIEW_X5_3D_HYBRID: + default: + ts2->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + } + + /* Certain boards support analog, or require the avcore to be + * loaded, ensure this happens. + */ + switch (dev->board) { + case CX23885_BOARD_TEVII_S470: + /* Currently only enabled for the integrated IR controller */ + if (!enable_885_ir) + break; + fallthrough; + case CX23885_BOARD_HAUPPAUGE_HVR1250: + case CX23885_BOARD_HAUPPAUGE_HVR1800: + case CX23885_BOARD_HAUPPAUGE_IMPACTVCBE: + case CX23885_BOARD_HAUPPAUGE_HVR1800lp: + case CX23885_BOARD_HAUPPAUGE_HVR1700: + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H: + case CX23885_BOARD_LEADTEK_WINFAST_PXPVR2200: + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000: + case CX23885_BOARD_COMPRO_VIDEOMATE_E650F: + case CX23885_BOARD_NETUP_DUAL_DVBS2_CI: + case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: + case CX23885_BOARD_COMPRO_VIDEOMATE_E800: + case CX23885_BOARD_HAUPPAUGE_HVR1255: + case CX23885_BOARD_HAUPPAUGE_HVR1255_22111: + case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB: + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC: + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR5525: + case CX23885_BOARD_MYGICA_X8506: + case CX23885_BOARD_MAGICPRO_PROHDTVE2: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + case CX23885_BOARD_LEADTEK_WINFAST_PXTV1200: + case CX23885_BOARD_GOTVIEW_X5_3D_HYBRID: + case CX23885_BOARD_HAUPPAUGE_HVR1500: + case CX23885_BOARD_MPX885: + case CX23885_BOARD_MYGICA_X8507: + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + case CX23885_BOARD_AVERMEDIA_HC81R: + case CX23885_BOARD_TBS_6980: + case CX23885_BOARD_TBS_6981: + case CX23885_BOARD_DVBSKY_T9580: + case CX23885_BOARD_DVBSKY_T980C: + case CX23885_BOARD_DVBSKY_S950C: + case CX23885_BOARD_TT_CT2_4500_CI: + case CX23885_BOARD_DVBSKY_S950: + case CX23885_BOARD_DVBSKY_S952: + case CX23885_BOARD_DVBSKY_T982: + case CX23885_BOARD_VIEWCAST_260E: + case CX23885_BOARD_VIEWCAST_460E: + case CX23885_BOARD_AVERMEDIA_CE310B: + case CX23885_BOARD_AVERMEDIA_H789C: + if (dev->disable_analog) + break; + + dev->sd_cx25840 = v4l2_i2c_new_subdev(&dev->v4l2_dev, + &dev->i2c_bus[2].i2c_adap, + "cx25840", 0x88 >> 1, NULL); + if (dev->sd_cx25840) { + /* set host data for clk_freq configuration */ + v4l2_set_subdev_hostdata(dev->sd_cx25840, + &dev->clk_freq); + + dev->sd_cx25840->grp_id = CX23885_HW_AV_CORE; + v4l2_subdev_call(dev->sd_cx25840, core, load_fw); + } + break; + } + + switch (dev->board) { + case CX23885_BOARD_VIEWCAST_260E: + v4l2_i2c_new_subdev(&dev->v4l2_dev, + &dev->i2c_bus[0].i2c_adap, + "cs3308", 0x82 >> 1, NULL); + break; + case CX23885_BOARD_VIEWCAST_460E: + /* This cs3308 controls the audio from the breakout cable */ + v4l2_i2c_new_subdev(&dev->v4l2_dev, + &dev->i2c_bus[0].i2c_adap, + "cs3308", 0x80 >> 1, NULL); + /* This cs3308 controls the audio from the onboard header */ + v4l2_i2c_new_subdev(&dev->v4l2_dev, + &dev->i2c_bus[0].i2c_adap, + "cs3308", 0x82 >> 1, NULL); + break; + } + + /* AUX-PLL 27MHz CLK */ + switch (dev->board) { + case CX23885_BOARD_NETUP_DUAL_DVBS2_CI: + netup_initialize(dev); + break; + case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: { + int ret; + const struct firmware *fw; + const char *filename = "dvb-netup-altera-01.fw"; + char *action = "configure"; + static struct netup_card_info cinfo; + struct altera_config netup_config = { + .dev = dev, + .action = action, + .jtag_io = netup_jtag_io, + }; + + netup_initialize(dev); + + netup_get_card_info(&dev->i2c_bus[0].i2c_adap, &cinfo); + if (netup_card_rev) + cinfo.rev = netup_card_rev; + + switch (cinfo.rev) { + case 0x4: + filename = "dvb-netup-altera-04.fw"; + break; + default: + filename = "dvb-netup-altera-01.fw"; + break; + } + pr_info("NetUP card rev=0x%x fw_filename=%s\n", + cinfo.rev, filename); + + ret = request_firmware(&fw, filename, &dev->pci->dev); + if (ret != 0) + pr_err("did not find the firmware file '%s'. You can use /scripts/get_dvb_firmware to get the firmware.", + filename); + else + altera_init(&netup_config, fw); + + release_firmware(fw); + break; + } + } +} diff --git a/drivers/media/pci/cx23885/cx23885-core.c b/drivers/media/pci/cx23885/cx23885-core.c new file mode 100644 index 000000000..5fb26285e --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-core.c @@ -0,0 +1,2315 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX23885 PCIe bridge + * + * Copyright (c) 2006 Steven Toth + */ + +#include "cx23885.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cimax2.h" +#include "altera-ci.h" +#include "cx23888-ir.h" +#include "cx23885-ir.h" +#include "cx23885-av.h" +#include "cx23885-input.h" + +MODULE_DESCRIPTION("Driver for cx23885 based TV cards"); +MODULE_AUTHOR("Steven Toth "); +MODULE_LICENSE("GPL"); +MODULE_VERSION(CX23885_VERSION); + +/* + * Some platforms have been found to require periodic resetting of the DMA + * engine. Ryzen and XEON platforms are known to be affected. The symptom + * encountered is "mpeg risc op code error". Only Ryzen platforms employ + * this workaround if the option equals 1. The workaround can be explicitly + * disabled for all platforms by setting to 0, the workaround can be forced + * on for any platform by setting to 2. + */ +static unsigned int dma_reset_workaround = 1; +module_param(dma_reset_workaround, int, 0644); +MODULE_PARM_DESC(dma_reset_workaround, "periodic RiSC dma engine reset; 0-force disable, 1-driver detect (default), 2-force enable"); + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "enable debug messages"); + +static unsigned int disable_analog_video[8] = { 0, 0, 0, 0, 0, 0, 0, 0}; +static int disable_analog_argc; +module_param_array(disable_analog_video, int, &disable_analog_argc, 0644); +MODULE_PARM_DESC(disable_analog_video, "disable analog video for card type"); + +static unsigned int card[] = {[0 ... (CX23885_MAXBOARDS - 1)] = UNSET }; +module_param_array(card, int, NULL, 0444); +MODULE_PARM_DESC(card, "card type"); + +#define dprintk(level, fmt, arg...)\ + do { if (debug >= level)\ + printk(KERN_DEBUG pr_fmt("%s: " fmt), \ + __func__, ##arg); \ + } while (0) + +static unsigned int cx23885_devcount; + +#define NO_SYNC_LINE (-1U) + +/* FIXME, these allocations will change when + * analog arrives. The be reviewed. + * CX23887 Assumptions + * 1 line = 16 bytes of CDT + * cmds size = 80 + * cdt size = 16 * linesize + * iqsize = 64 + * maxlines = 6 + * + * Address Space: + * 0x00000000 0x00008fff FIFO clusters + * 0x00010000 0x000104af Channel Management Data Structures + * 0x000104b0 0x000104ff Free + * 0x00010500 0x000108bf 15 channels * iqsize + * 0x000108c0 0x000108ff Free + * 0x00010900 0x00010e9f IQ's + Cluster Descriptor Tables + * 15 channels * (iqsize + (maxlines * linesize)) + * 0x00010ea0 0x00010xxx Free + */ + +static struct sram_channel cx23885_sram_channels[] = { + [SRAM_CH01] = { + .name = "VID A", + .cmds_start = 0x10000, + .ctrl_start = 0x10380, + .cdt = 0x104c0, + .fifo_start = 0x40, + .fifo_size = 0x2800, + .ptr1_reg = DMA1_PTR1, + .ptr2_reg = DMA1_PTR2, + .cnt1_reg = DMA1_CNT1, + .cnt2_reg = DMA1_CNT2, + }, + [SRAM_CH02] = { + .name = "ch2", + .cmds_start = 0x0, + .ctrl_start = 0x0, + .cdt = 0x0, + .fifo_start = 0x0, + .fifo_size = 0x0, + .ptr1_reg = DMA2_PTR1, + .ptr2_reg = DMA2_PTR2, + .cnt1_reg = DMA2_CNT1, + .cnt2_reg = DMA2_CNT2, + }, + [SRAM_CH03] = { + .name = "TS1 B", + .cmds_start = 0x100A0, + .ctrl_start = 0x10400, + .cdt = 0x10580, + .fifo_start = 0x5000, + .fifo_size = 0x1000, + .ptr1_reg = DMA3_PTR1, + .ptr2_reg = DMA3_PTR2, + .cnt1_reg = DMA3_CNT1, + .cnt2_reg = DMA3_CNT2, + }, + [SRAM_CH04] = { + .name = "ch4", + .cmds_start = 0x0, + .ctrl_start = 0x0, + .cdt = 0x0, + .fifo_start = 0x0, + .fifo_size = 0x0, + .ptr1_reg = DMA4_PTR1, + .ptr2_reg = DMA4_PTR2, + .cnt1_reg = DMA4_CNT1, + .cnt2_reg = DMA4_CNT2, + }, + [SRAM_CH05] = { + .name = "ch5", + .cmds_start = 0x0, + .ctrl_start = 0x0, + .cdt = 0x0, + .fifo_start = 0x0, + .fifo_size = 0x0, + .ptr1_reg = DMA5_PTR1, + .ptr2_reg = DMA5_PTR2, + .cnt1_reg = DMA5_CNT1, + .cnt2_reg = DMA5_CNT2, + }, + [SRAM_CH06] = { + .name = "TS2 C", + .cmds_start = 0x10140, + .ctrl_start = 0x10440, + .cdt = 0x105e0, + .fifo_start = 0x6000, + .fifo_size = 0x1000, + .ptr1_reg = DMA5_PTR1, + .ptr2_reg = DMA5_PTR2, + .cnt1_reg = DMA5_CNT1, + .cnt2_reg = DMA5_CNT2, + }, + [SRAM_CH07] = { + .name = "TV Audio", + .cmds_start = 0x10190, + .ctrl_start = 0x10480, + .cdt = 0x10a00, + .fifo_start = 0x7000, + .fifo_size = 0x1000, + .ptr1_reg = DMA6_PTR1, + .ptr2_reg = DMA6_PTR2, + .cnt1_reg = DMA6_CNT1, + .cnt2_reg = DMA6_CNT2, + }, + [SRAM_CH08] = { + .name = "ch8", + .cmds_start = 0x0, + .ctrl_start = 0x0, + .cdt = 0x0, + .fifo_start = 0x0, + .fifo_size = 0x0, + .ptr1_reg = DMA7_PTR1, + .ptr2_reg = DMA7_PTR2, + .cnt1_reg = DMA7_CNT1, + .cnt2_reg = DMA7_CNT2, + }, + [SRAM_CH09] = { + .name = "ch9", + .cmds_start = 0x0, + .ctrl_start = 0x0, + .cdt = 0x0, + .fifo_start = 0x0, + .fifo_size = 0x0, + .ptr1_reg = DMA8_PTR1, + .ptr2_reg = DMA8_PTR2, + .cnt1_reg = DMA8_CNT1, + .cnt2_reg = DMA8_CNT2, + }, +}; + +static struct sram_channel cx23887_sram_channels[] = { + [SRAM_CH01] = { + .name = "VID A", + .cmds_start = 0x10000, + .ctrl_start = 0x105b0, + .cdt = 0x107b0, + .fifo_start = 0x40, + .fifo_size = 0x2800, + .ptr1_reg = DMA1_PTR1, + .ptr2_reg = DMA1_PTR2, + .cnt1_reg = DMA1_CNT1, + .cnt2_reg = DMA1_CNT2, + }, + [SRAM_CH02] = { + .name = "VID A (VBI)", + .cmds_start = 0x10050, + .ctrl_start = 0x105F0, + .cdt = 0x10810, + .fifo_start = 0x3000, + .fifo_size = 0x1000, + .ptr1_reg = DMA2_PTR1, + .ptr2_reg = DMA2_PTR2, + .cnt1_reg = DMA2_CNT1, + .cnt2_reg = DMA2_CNT2, + }, + [SRAM_CH03] = { + .name = "TS1 B", + .cmds_start = 0x100A0, + .ctrl_start = 0x10630, + .cdt = 0x10870, + .fifo_start = 0x5000, + .fifo_size = 0x1000, + .ptr1_reg = DMA3_PTR1, + .ptr2_reg = DMA3_PTR2, + .cnt1_reg = DMA3_CNT1, + .cnt2_reg = DMA3_CNT2, + }, + [SRAM_CH04] = { + .name = "ch4", + .cmds_start = 0x0, + .ctrl_start = 0x0, + .cdt = 0x0, + .fifo_start = 0x0, + .fifo_size = 0x0, + .ptr1_reg = DMA4_PTR1, + .ptr2_reg = DMA4_PTR2, + .cnt1_reg = DMA4_CNT1, + .cnt2_reg = DMA4_CNT2, + }, + [SRAM_CH05] = { + .name = "ch5", + .cmds_start = 0x0, + .ctrl_start = 0x0, + .cdt = 0x0, + .fifo_start = 0x0, + .fifo_size = 0x0, + .ptr1_reg = DMA5_PTR1, + .ptr2_reg = DMA5_PTR2, + .cnt1_reg = DMA5_CNT1, + .cnt2_reg = DMA5_CNT2, + }, + [SRAM_CH06] = { + .name = "TS2 C", + .cmds_start = 0x10140, + .ctrl_start = 0x10670, + .cdt = 0x108d0, + .fifo_start = 0x6000, + .fifo_size = 0x1000, + .ptr1_reg = DMA5_PTR1, + .ptr2_reg = DMA5_PTR2, + .cnt1_reg = DMA5_CNT1, + .cnt2_reg = DMA5_CNT2, + }, + [SRAM_CH07] = { + .name = "TV Audio", + .cmds_start = 0x10190, + .ctrl_start = 0x106B0, + .cdt = 0x10930, + .fifo_start = 0x7000, + .fifo_size = 0x1000, + .ptr1_reg = DMA6_PTR1, + .ptr2_reg = DMA6_PTR2, + .cnt1_reg = DMA6_CNT1, + .cnt2_reg = DMA6_CNT2, + }, + [SRAM_CH08] = { + .name = "ch8", + .cmds_start = 0x0, + .ctrl_start = 0x0, + .cdt = 0x0, + .fifo_start = 0x0, + .fifo_size = 0x0, + .ptr1_reg = DMA7_PTR1, + .ptr2_reg = DMA7_PTR2, + .cnt1_reg = DMA7_CNT1, + .cnt2_reg = DMA7_CNT2, + }, + [SRAM_CH09] = { + .name = "ch9", + .cmds_start = 0x0, + .ctrl_start = 0x0, + .cdt = 0x0, + .fifo_start = 0x0, + .fifo_size = 0x0, + .ptr1_reg = DMA8_PTR1, + .ptr2_reg = DMA8_PTR2, + .cnt1_reg = DMA8_CNT1, + .cnt2_reg = DMA8_CNT2, + }, +}; + +static void cx23885_irq_add(struct cx23885_dev *dev, u32 mask) +{ + unsigned long flags; + spin_lock_irqsave(&dev->pci_irqmask_lock, flags); + + dev->pci_irqmask |= mask; + + spin_unlock_irqrestore(&dev->pci_irqmask_lock, flags); +} + +void cx23885_irq_add_enable(struct cx23885_dev *dev, u32 mask) +{ + unsigned long flags; + spin_lock_irqsave(&dev->pci_irqmask_lock, flags); + + dev->pci_irqmask |= mask; + cx_set(PCI_INT_MSK, mask); + + spin_unlock_irqrestore(&dev->pci_irqmask_lock, flags); +} + +void cx23885_irq_enable(struct cx23885_dev *dev, u32 mask) +{ + u32 v; + unsigned long flags; + spin_lock_irqsave(&dev->pci_irqmask_lock, flags); + + v = mask & dev->pci_irqmask; + if (v) + cx_set(PCI_INT_MSK, v); + + spin_unlock_irqrestore(&dev->pci_irqmask_lock, flags); +} + +static inline void cx23885_irq_enable_all(struct cx23885_dev *dev) +{ + cx23885_irq_enable(dev, 0xffffffff); +} + +void cx23885_irq_disable(struct cx23885_dev *dev, u32 mask) +{ + unsigned long flags; + spin_lock_irqsave(&dev->pci_irqmask_lock, flags); + + cx_clear(PCI_INT_MSK, mask); + + spin_unlock_irqrestore(&dev->pci_irqmask_lock, flags); +} + +static inline void cx23885_irq_disable_all(struct cx23885_dev *dev) +{ + cx23885_irq_disable(dev, 0xffffffff); +} + +void cx23885_irq_remove(struct cx23885_dev *dev, u32 mask) +{ + unsigned long flags; + spin_lock_irqsave(&dev->pci_irqmask_lock, flags); + + dev->pci_irqmask &= ~mask; + cx_clear(PCI_INT_MSK, mask); + + spin_unlock_irqrestore(&dev->pci_irqmask_lock, flags); +} + +static u32 cx23885_irq_get_mask(struct cx23885_dev *dev) +{ + u32 v; + unsigned long flags; + spin_lock_irqsave(&dev->pci_irqmask_lock, flags); + + v = cx_read(PCI_INT_MSK); + + spin_unlock_irqrestore(&dev->pci_irqmask_lock, flags); + return v; +} + +static int cx23885_risc_decode(u32 risc) +{ + static char *instr[16] = { + [RISC_SYNC >> 28] = "sync", + [RISC_WRITE >> 28] = "write", + [RISC_WRITEC >> 28] = "writec", + [RISC_READ >> 28] = "read", + [RISC_READC >> 28] = "readc", + [RISC_JUMP >> 28] = "jump", + [RISC_SKIP >> 28] = "skip", + [RISC_WRITERM >> 28] = "writerm", + [RISC_WRITECM >> 28] = "writecm", + [RISC_WRITECR >> 28] = "writecr", + }; + static int incr[16] = { + [RISC_WRITE >> 28] = 3, + [RISC_JUMP >> 28] = 3, + [RISC_SKIP >> 28] = 1, + [RISC_SYNC >> 28] = 1, + [RISC_WRITERM >> 28] = 3, + [RISC_WRITECM >> 28] = 3, + [RISC_WRITECR >> 28] = 4, + }; + static char *bits[] = { + "12", "13", "14", "resync", + "cnt0", "cnt1", "18", "19", + "20", "21", "22", "23", + "irq1", "irq2", "eol", "sol", + }; + int i; + + printk(KERN_DEBUG "0x%08x [ %s", risc, + instr[risc >> 28] ? instr[risc >> 28] : "INVALID"); + for (i = ARRAY_SIZE(bits) - 1; i >= 0; i--) + if (risc & (1 << (i + 12))) + pr_cont(" %s", bits[i]); + pr_cont(" count=%d ]\n", risc & 0xfff); + return incr[risc >> 28] ? incr[risc >> 28] : 1; +} + +static void cx23885_wakeup(struct cx23885_tsport *port, + struct cx23885_dmaqueue *q, u32 count) +{ + struct cx23885_buffer *buf; + int count_delta; + int max_buf_done = 5; /* service maximum five buffers */ + + do { + if (list_empty(&q->active)) + return; + buf = list_entry(q->active.next, + struct cx23885_buffer, queue); + + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + buf->vb.sequence = q->count++; + if (count != (q->count % 65536)) { + dprintk(1, "[%p/%d] wakeup reg=%d buf=%d\n", buf, + buf->vb.vb2_buf.index, count, q->count); + } else { + dprintk(7, "[%p/%d] wakeup reg=%d buf=%d\n", buf, + buf->vb.vb2_buf.index, count, q->count); + } + list_del(&buf->queue); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + max_buf_done--; + /* count register is 16 bits so apply modulo appropriately */ + count_delta = ((int)count - (int)(q->count % 65536)); + } while ((count_delta > 0) && (max_buf_done > 0)); +} + +int cx23885_sram_channel_setup(struct cx23885_dev *dev, + struct sram_channel *ch, + unsigned int bpl, u32 risc) +{ + unsigned int i, lines; + u32 cdt; + + if (ch->cmds_start == 0) { + dprintk(1, "%s() Erasing channel [%s]\n", __func__, + ch->name); + cx_write(ch->ptr1_reg, 0); + cx_write(ch->ptr2_reg, 0); + cx_write(ch->cnt2_reg, 0); + cx_write(ch->cnt1_reg, 0); + return 0; + } else { + dprintk(1, "%s() Configuring channel [%s]\n", __func__, + ch->name); + } + + bpl = (bpl + 7) & ~7; /* alignment */ + cdt = ch->cdt; + lines = ch->fifo_size / bpl; + if (lines > 6) + lines = 6; + BUG_ON(lines < 2); + + cx_write(8 + 0, RISC_JUMP | RISC_CNT_RESET); + cx_write(8 + 4, 12); + cx_write(8 + 8, 0); + + /* write CDT */ + for (i = 0; i < lines; i++) { + dprintk(2, "%s() 0x%08x <- 0x%08x\n", __func__, cdt + 16*i, + ch->fifo_start + bpl*i); + cx_write(cdt + 16*i, ch->fifo_start + bpl*i); + cx_write(cdt + 16*i + 4, 0); + cx_write(cdt + 16*i + 8, 0); + cx_write(cdt + 16*i + 12, 0); + } + + /* write CMDS */ + if (ch->jumponly) + cx_write(ch->cmds_start + 0, 8); + else + cx_write(ch->cmds_start + 0, risc); + cx_write(ch->cmds_start + 4, 0); /* 64 bits 63-32 */ + cx_write(ch->cmds_start + 8, cdt); + cx_write(ch->cmds_start + 12, (lines*16) >> 3); + cx_write(ch->cmds_start + 16, ch->ctrl_start); + if (ch->jumponly) + cx_write(ch->cmds_start + 20, 0x80000000 | (64 >> 2)); + else + cx_write(ch->cmds_start + 20, 64 >> 2); + for (i = 24; i < 80; i += 4) + cx_write(ch->cmds_start + i, 0); + + /* fill registers */ + cx_write(ch->ptr1_reg, ch->fifo_start); + cx_write(ch->ptr2_reg, cdt); + cx_write(ch->cnt2_reg, (lines*16) >> 3); + cx_write(ch->cnt1_reg, (bpl >> 3) - 1); + + dprintk(2, "[bridge %d] sram setup %s: bpl=%d lines=%d\n", + dev->bridge, + ch->name, + bpl, + lines); + + return 0; +} + +void cx23885_sram_channel_dump(struct cx23885_dev *dev, + struct sram_channel *ch) +{ + static char *name[] = { + "init risc lo", + "init risc hi", + "cdt base", + "cdt size", + "iq base", + "iq size", + "risc pc lo", + "risc pc hi", + "iq wr ptr", + "iq rd ptr", + "cdt current", + "pci target lo", + "pci target hi", + "line / byte", + }; + u32 risc; + unsigned int i, j, n; + + pr_warn("%s: %s - dma channel status dump\n", + dev->name, ch->name); + for (i = 0; i < ARRAY_SIZE(name); i++) + pr_warn("%s: cmds: %-15s: 0x%08x\n", + dev->name, name[i], + cx_read(ch->cmds_start + 4*i)); + + for (i = 0; i < 4; i++) { + risc = cx_read(ch->cmds_start + 4 * (i + 14)); + pr_warn("%s: risc%d:", dev->name, i); + cx23885_risc_decode(risc); + } + for (i = 0; i < (64 >> 2); i += n) { + risc = cx_read(ch->ctrl_start + 4 * i); + /* No consideration for bits 63-32 */ + + pr_warn("%s: (0x%08x) iq %x:", dev->name, + ch->ctrl_start + 4 * i, i); + n = cx23885_risc_decode(risc); + for (j = 1; j < n; j++) { + risc = cx_read(ch->ctrl_start + 4 * (i + j)); + pr_warn("%s: iq %x: 0x%08x [ arg #%d ]\n", + dev->name, i+j, risc, j); + } + } + + pr_warn("%s: fifo: 0x%08x -> 0x%x\n", + dev->name, ch->fifo_start, ch->fifo_start+ch->fifo_size); + pr_warn("%s: ctrl: 0x%08x -> 0x%x\n", + dev->name, ch->ctrl_start, ch->ctrl_start + 6*16); + pr_warn("%s: ptr1_reg: 0x%08x\n", + dev->name, cx_read(ch->ptr1_reg)); + pr_warn("%s: ptr2_reg: 0x%08x\n", + dev->name, cx_read(ch->ptr2_reg)); + pr_warn("%s: cnt1_reg: 0x%08x\n", + dev->name, cx_read(ch->cnt1_reg)); + pr_warn("%s: cnt2_reg: 0x%08x\n", + dev->name, cx_read(ch->cnt2_reg)); +} + +static void cx23885_risc_disasm(struct cx23885_tsport *port, + struct cx23885_riscmem *risc) +{ + struct cx23885_dev *dev = port->dev; + unsigned int i, j, n; + + pr_info("%s: risc disasm: %p [dma=0x%08lx]\n", + dev->name, risc->cpu, (unsigned long)risc->dma); + for (i = 0; i < (risc->size >> 2); i += n) { + pr_info("%s: %04d:", dev->name, i); + n = cx23885_risc_decode(le32_to_cpu(risc->cpu[i])); + for (j = 1; j < n; j++) + pr_info("%s: %04d: 0x%08x [ arg #%d ]\n", + dev->name, i + j, risc->cpu[i + j], j); + if (risc->cpu[i] == cpu_to_le32(RISC_JUMP)) + break; + } +} + +static void cx23885_clear_bridge_error(struct cx23885_dev *dev) +{ + uint32_t reg1_val, reg2_val; + + if (!dev->need_dma_reset) + return; + + reg1_val = cx_read(TC_REQ); /* read-only */ + reg2_val = cx_read(TC_REQ_SET); + + if (reg1_val && reg2_val) { + cx_write(TC_REQ, reg1_val); + cx_write(TC_REQ_SET, reg2_val); + cx_read(VID_B_DMA); + cx_read(VBI_B_DMA); + cx_read(VID_C_DMA); + cx_read(VBI_C_DMA); + + dev_info(&dev->pci->dev, + "dma in progress detected 0x%08x 0x%08x, clearing\n", + reg1_val, reg2_val); + } +} + +static void cx23885_shutdown(struct cx23885_dev *dev) +{ + /* disable RISC controller */ + cx_write(DEV_CNTRL2, 0); + + /* Disable all IR activity */ + cx_write(IR_CNTRL_REG, 0); + + /* Disable Video A/B activity */ + cx_write(VID_A_DMA_CTL, 0); + cx_write(VID_B_DMA_CTL, 0); + cx_write(VID_C_DMA_CTL, 0); + + /* Disable Audio activity */ + cx_write(AUD_INT_DMA_CTL, 0); + cx_write(AUD_EXT_DMA_CTL, 0); + + /* Disable Serial port */ + cx_write(UART_CTL, 0); + + /* Disable Interrupts */ + cx23885_irq_disable_all(dev); + cx_write(VID_A_INT_MSK, 0); + cx_write(VID_B_INT_MSK, 0); + cx_write(VID_C_INT_MSK, 0); + cx_write(AUDIO_INT_INT_MSK, 0); + cx_write(AUDIO_EXT_INT_MSK, 0); + +} + +static void cx23885_reset(struct cx23885_dev *dev) +{ + dprintk(1, "%s()\n", __func__); + + cx23885_shutdown(dev); + + cx_write(PCI_INT_STAT, 0xffffffff); + cx_write(VID_A_INT_STAT, 0xffffffff); + cx_write(VID_B_INT_STAT, 0xffffffff); + cx_write(VID_C_INT_STAT, 0xffffffff); + cx_write(AUDIO_INT_INT_STAT, 0xffffffff); + cx_write(AUDIO_EXT_INT_STAT, 0xffffffff); + cx_write(CLK_DELAY, cx_read(CLK_DELAY) & 0x80000000); + cx_write(PAD_CTRL, 0x00500300); + + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); + msleep(100); + + cx23885_sram_channel_setup(dev, &dev->sram_channels[SRAM_CH01], + 720*4, 0); + cx23885_sram_channel_setup(dev, &dev->sram_channels[SRAM_CH02], 128, 0); + cx23885_sram_channel_setup(dev, &dev->sram_channels[SRAM_CH03], + 188*4, 0); + cx23885_sram_channel_setup(dev, &dev->sram_channels[SRAM_CH04], 128, 0); + cx23885_sram_channel_setup(dev, &dev->sram_channels[SRAM_CH05], 128, 0); + cx23885_sram_channel_setup(dev, &dev->sram_channels[SRAM_CH06], + 188*4, 0); + cx23885_sram_channel_setup(dev, &dev->sram_channels[SRAM_CH07], 128, 0); + cx23885_sram_channel_setup(dev, &dev->sram_channels[SRAM_CH08], 128, 0); + cx23885_sram_channel_setup(dev, &dev->sram_channels[SRAM_CH09], 128, 0); + + cx23885_gpio_setup(dev); + + cx23885_irq_get_mask(dev); + + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); +} + + +static int cx23885_pci_quirks(struct cx23885_dev *dev) +{ + dprintk(1, "%s()\n", __func__); + + /* The cx23885 bridge has a weird bug which causes NMI to be asserted + * when DMA begins if RDR_TLCTL0 bit4 is not cleared. It does not + * occur on the cx23887 bridge. + */ + if (dev->bridge == CX23885_BRIDGE_885) + cx_clear(RDR_TLCTL0, 1 << 4); + + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); + return 0; +} + +static int get_resources(struct cx23885_dev *dev) +{ + if (request_mem_region(pci_resource_start(dev->pci, 0), + pci_resource_len(dev->pci, 0), + dev->name)) + return 0; + + pr_err("%s: can't get MMIO memory @ 0x%llx\n", + dev->name, (unsigned long long)pci_resource_start(dev->pci, 0)); + + return -EBUSY; +} + +static int cx23885_init_tsport(struct cx23885_dev *dev, + struct cx23885_tsport *port, int portno) +{ + dprintk(1, "%s(portno=%d)\n", __func__, portno); + + /* Transport bus init dma queue - Common settings */ + port->dma_ctl_val = 0x11; /* Enable RISC controller and Fifo */ + port->ts_int_msk_val = 0x1111; /* TS port bits for RISC */ + port->vld_misc_val = 0x0; + port->hw_sop_ctrl_val = (0x47 << 16 | 188 << 4); + + spin_lock_init(&port->slock); + port->dev = dev; + port->nr = portno; + + INIT_LIST_HEAD(&port->mpegq.active); + mutex_init(&port->frontends.lock); + INIT_LIST_HEAD(&port->frontends.felist); + port->frontends.active_fe_id = 0; + + /* This should be hardcoded allow a single frontend + * attachment to this tsport, keeping the -dvb.c + * code clean and safe. + */ + if (!port->num_frontends) + port->num_frontends = 1; + + switch (portno) { + case 1: + port->reg_gpcnt = VID_B_GPCNT; + port->reg_gpcnt_ctl = VID_B_GPCNT_CTL; + port->reg_dma_ctl = VID_B_DMA_CTL; + port->reg_lngth = VID_B_LNGTH; + port->reg_hw_sop_ctrl = VID_B_HW_SOP_CTL; + port->reg_gen_ctrl = VID_B_GEN_CTL; + port->reg_bd_pkt_status = VID_B_BD_PKT_STATUS; + port->reg_sop_status = VID_B_SOP_STATUS; + port->reg_fifo_ovfl_stat = VID_B_FIFO_OVFL_STAT; + port->reg_vld_misc = VID_B_VLD_MISC; + port->reg_ts_clk_en = VID_B_TS_CLK_EN; + port->reg_src_sel = VID_B_SRC_SEL; + port->reg_ts_int_msk = VID_B_INT_MSK; + port->reg_ts_int_stat = VID_B_INT_STAT; + port->sram_chno = SRAM_CH03; /* VID_B */ + port->pci_irqmask = 0x02; /* VID_B bit1 */ + break; + case 2: + port->reg_gpcnt = VID_C_GPCNT; + port->reg_gpcnt_ctl = VID_C_GPCNT_CTL; + port->reg_dma_ctl = VID_C_DMA_CTL; + port->reg_lngth = VID_C_LNGTH; + port->reg_hw_sop_ctrl = VID_C_HW_SOP_CTL; + port->reg_gen_ctrl = VID_C_GEN_CTL; + port->reg_bd_pkt_status = VID_C_BD_PKT_STATUS; + port->reg_sop_status = VID_C_SOP_STATUS; + port->reg_fifo_ovfl_stat = VID_C_FIFO_OVFL_STAT; + port->reg_vld_misc = VID_C_VLD_MISC; + port->reg_ts_clk_en = VID_C_TS_CLK_EN; + port->reg_src_sel = 0; + port->reg_ts_int_msk = VID_C_INT_MSK; + port->reg_ts_int_stat = VID_C_INT_STAT; + port->sram_chno = SRAM_CH06; /* VID_C */ + port->pci_irqmask = 0x04; /* VID_C bit2 */ + break; + default: + BUG(); + } + + return 0; +} + +static void cx23885_dev_checkrevision(struct cx23885_dev *dev) +{ + switch (cx_read(RDR_CFG2) & 0xff) { + case 0x00: + /* cx23885 */ + dev->hwrevision = 0xa0; + break; + case 0x01: + /* CX23885-12Z */ + dev->hwrevision = 0xa1; + break; + case 0x02: + /* CX23885-13Z/14Z */ + dev->hwrevision = 0xb0; + break; + case 0x03: + if (dev->pci->device == 0x8880) { + /* CX23888-21Z/22Z */ + dev->hwrevision = 0xc0; + } else { + /* CX23885-14Z */ + dev->hwrevision = 0xa4; + } + break; + case 0x04: + if (dev->pci->device == 0x8880) { + /* CX23888-31Z */ + dev->hwrevision = 0xd0; + } else { + /* CX23885-15Z, CX23888-31Z */ + dev->hwrevision = 0xa5; + } + break; + case 0x0e: + /* CX23887-15Z */ + dev->hwrevision = 0xc0; + break; + case 0x0f: + /* CX23887-14Z */ + dev->hwrevision = 0xb1; + break; + default: + pr_err("%s() New hardware revision found 0x%x\n", + __func__, dev->hwrevision); + } + if (dev->hwrevision) + pr_info("%s() Hardware revision = 0x%02x\n", + __func__, dev->hwrevision); + else + pr_err("%s() Hardware revision unknown 0x%x\n", + __func__, dev->hwrevision); +} + +/* Find the first v4l2_subdev member of the group id in hw */ +struct v4l2_subdev *cx23885_find_hw(struct cx23885_dev *dev, u32 hw) +{ + struct v4l2_subdev *result = NULL; + struct v4l2_subdev *sd; + + spin_lock(&dev->v4l2_dev.lock); + v4l2_device_for_each_subdev(sd, &dev->v4l2_dev) { + if (sd->grp_id == hw) { + result = sd; + break; + } + } + spin_unlock(&dev->v4l2_dev.lock); + return result; +} + +static int cx23885_dev_setup(struct cx23885_dev *dev) +{ + int i; + + spin_lock_init(&dev->pci_irqmask_lock); + spin_lock_init(&dev->slock); + + mutex_init(&dev->lock); + mutex_init(&dev->gpio_lock); + + atomic_inc(&dev->refcount); + + dev->nr = cx23885_devcount++; + sprintf(dev->name, "cx23885[%d]", dev->nr); + + /* Configure the internal memory */ + if (dev->pci->device == 0x8880) { + /* Could be 887 or 888, assume an 888 default */ + dev->bridge = CX23885_BRIDGE_888; + /* Apply a sensible clock frequency for the PCIe bridge */ + dev->clk_freq = 50000000; + dev->sram_channels = cx23887_sram_channels; + } else + if (dev->pci->device == 0x8852) { + dev->bridge = CX23885_BRIDGE_885; + /* Apply a sensible clock frequency for the PCIe bridge */ + dev->clk_freq = 28000000; + dev->sram_channels = cx23885_sram_channels; + } else + BUG(); + + dprintk(1, "%s() Memory configured for PCIe bridge type %d\n", + __func__, dev->bridge); + + /* board config */ + dev->board = UNSET; + if (card[dev->nr] < cx23885_bcount) + dev->board = card[dev->nr]; + for (i = 0; UNSET == dev->board && i < cx23885_idcount; i++) + if (dev->pci->subsystem_vendor == cx23885_subids[i].subvendor && + dev->pci->subsystem_device == cx23885_subids[i].subdevice) + dev->board = cx23885_subids[i].card; + if (UNSET == dev->board) { + dev->board = CX23885_BOARD_UNKNOWN; + cx23885_card_list(dev); + } + + if (dev->pci->device == 0x8852) { + /* no DIF on cx23885, so no analog tuner support possible */ + if (dev->board == CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC) + dev->board = CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC_885; + else if (dev->board == CX23885_BOARD_HAUPPAUGE_QUADHD_DVB) + dev->board = CX23885_BOARD_HAUPPAUGE_QUADHD_DVB_885; + } + + for (i = 0; i < disable_analog_argc; i++) { + if (disable_analog_video[i] == dev->board) { + pr_warn("Disabling analog for board %d\n", dev->board); + dev->disable_analog = 1; + } + } + + /* If the user specific a clk freq override, apply it */ + if (cx23885_boards[dev->board].clk_freq > 0) + dev->clk_freq = cx23885_boards[dev->board].clk_freq; + + if (dev->board == CX23885_BOARD_HAUPPAUGE_IMPACTVCBE && + dev->pci->subsystem_device == 0x7137) { + /* Hauppauge ImpactVCBe device ID 0x7137 is populated + * with an 888, and a 25Mhz crystal, instead of the + * usual third overtone 50Mhz. The default clock rate must + * be overridden so the cx25840 is properly configured + */ + dev->clk_freq = 25000000; + } + + dev->pci_bus = dev->pci->bus->number; + dev->pci_slot = PCI_SLOT(dev->pci->devfn); + cx23885_irq_add(dev, 0x001f00); + + /* External Master 1 Bus */ + dev->i2c_bus[0].nr = 0; + dev->i2c_bus[0].dev = dev; + dev->i2c_bus[0].reg_stat = I2C1_STAT; + dev->i2c_bus[0].reg_ctrl = I2C1_CTRL; + dev->i2c_bus[0].reg_addr = I2C1_ADDR; + dev->i2c_bus[0].reg_rdata = I2C1_RDATA; + dev->i2c_bus[0].reg_wdata = I2C1_WDATA; + dev->i2c_bus[0].i2c_period = (0x9d << 24); /* 100kHz */ + + /* External Master 2 Bus */ + dev->i2c_bus[1].nr = 1; + dev->i2c_bus[1].dev = dev; + dev->i2c_bus[1].reg_stat = I2C2_STAT; + dev->i2c_bus[1].reg_ctrl = I2C2_CTRL; + dev->i2c_bus[1].reg_addr = I2C2_ADDR; + dev->i2c_bus[1].reg_rdata = I2C2_RDATA; + dev->i2c_bus[1].reg_wdata = I2C2_WDATA; + dev->i2c_bus[1].i2c_period = (0x9d << 24); /* 100kHz */ + + /* Internal Master 3 Bus */ + dev->i2c_bus[2].nr = 2; + dev->i2c_bus[2].dev = dev; + dev->i2c_bus[2].reg_stat = I2C3_STAT; + dev->i2c_bus[2].reg_ctrl = I2C3_CTRL; + dev->i2c_bus[2].reg_addr = I2C3_ADDR; + dev->i2c_bus[2].reg_rdata = I2C3_RDATA; + dev->i2c_bus[2].reg_wdata = I2C3_WDATA; + dev->i2c_bus[2].i2c_period = (0x07 << 24); /* 1.95MHz */ + + if ((cx23885_boards[dev->board].portb == CX23885_MPEG_DVB) || + (cx23885_boards[dev->board].portb == CX23885_MPEG_ENCODER)) + cx23885_init_tsport(dev, &dev->ts1, 1); + + if ((cx23885_boards[dev->board].portc == CX23885_MPEG_DVB) || + (cx23885_boards[dev->board].portc == CX23885_MPEG_ENCODER)) + cx23885_init_tsport(dev, &dev->ts2, 2); + + if (get_resources(dev) < 0) { + pr_err("CORE %s No more PCIe resources for subsystem: %04x:%04x\n", + dev->name, dev->pci->subsystem_vendor, + dev->pci->subsystem_device); + + cx23885_devcount--; + return -ENODEV; + } + + /* PCIe stuff */ + dev->lmmio = pci_ioremap_bar(dev->pci, 0); + if (!dev->lmmio) { + dev_err(&dev->pci->dev, "CORE %s: can't ioremap MMIO memory\n", + dev->name); + goto err_release_region; + } + + dev->bmmio = (u8 __iomem *)dev->lmmio; + + pr_info("CORE %s: subsystem: %04x:%04x, board: %s [card=%d,%s]\n", + dev->name, dev->pci->subsystem_vendor, + dev->pci->subsystem_device, cx23885_boards[dev->board].name, + dev->board, card[dev->nr] == dev->board ? + "insmod option" : "autodetected"); + + cx23885_pci_quirks(dev); + + /* Assume some sensible defaults */ + dev->tuner_type = cx23885_boards[dev->board].tuner_type; + dev->tuner_addr = cx23885_boards[dev->board].tuner_addr; + dev->tuner_bus = cx23885_boards[dev->board].tuner_bus; + dev->radio_type = cx23885_boards[dev->board].radio_type; + dev->radio_addr = cx23885_boards[dev->board].radio_addr; + + dprintk(1, "%s() tuner_type = 0x%x tuner_addr = 0x%x tuner_bus = %d\n", + __func__, dev->tuner_type, dev->tuner_addr, dev->tuner_bus); + dprintk(1, "%s() radio_type = 0x%x radio_addr = 0x%x\n", + __func__, dev->radio_type, dev->radio_addr); + + /* The cx23417 encoder has GPIO's that need to be initialised + * before DVB, so that demodulators and tuners are out of + * reset before DVB uses them. + */ + if ((cx23885_boards[dev->board].portb == CX23885_MPEG_ENCODER) || + (cx23885_boards[dev->board].portc == CX23885_MPEG_ENCODER)) + cx23885_mc417_init(dev); + + /* init hardware */ + cx23885_reset(dev); + + cx23885_i2c_register(&dev->i2c_bus[0]); + cx23885_i2c_register(&dev->i2c_bus[1]); + cx23885_i2c_register(&dev->i2c_bus[2]); + cx23885_card_setup(dev); + call_all(dev, tuner, standby); + cx23885_ir_init(dev); + + if (dev->board == CX23885_BOARD_VIEWCAST_460E) { + /* + * GPIOs 9/8 are input detection bits for the breakout video + * (gpio 8) and audio (gpio 9) cables. When they're attached, + * this gpios are pulled high. Make sure these GPIOs are marked + * as inputs. + */ + cx23885_gpio_enable(dev, 0x300, 0); + } + + if (cx23885_boards[dev->board].porta == CX23885_ANALOG_VIDEO && + !dev->disable_analog) { + if (cx23885_video_register(dev) < 0) { + pr_err("%s() Failed to register analog video adapters on VID_A\n", + __func__); + } + } + + if (cx23885_boards[dev->board].portb == CX23885_MPEG_DVB) { + if (cx23885_boards[dev->board].num_fds_portb) + dev->ts1.num_frontends = + cx23885_boards[dev->board].num_fds_portb; + if (cx23885_dvb_register(&dev->ts1) < 0) { + pr_err("%s() Failed to register dvb adapters on VID_B\n", + __func__); + } + } else + if (cx23885_boards[dev->board].portb == CX23885_MPEG_ENCODER) { + if (cx23885_417_register(dev) < 0) { + pr_err("%s() Failed to register 417 on VID_B\n", + __func__); + } + } + + if (cx23885_boards[dev->board].portc == CX23885_MPEG_DVB) { + if (cx23885_boards[dev->board].num_fds_portc) + dev->ts2.num_frontends = + cx23885_boards[dev->board].num_fds_portc; + if (cx23885_dvb_register(&dev->ts2) < 0) { + pr_err("%s() Failed to register dvb on VID_C\n", + __func__); + } + } else + if (cx23885_boards[dev->board].portc == CX23885_MPEG_ENCODER) { + if (cx23885_417_register(dev) < 0) { + pr_err("%s() Failed to register 417 on VID_C\n", + __func__); + } + } + + cx23885_dev_checkrevision(dev); + + /* disable MSI for NetUP cards, otherwise CI is not working */ + if (cx23885_boards[dev->board].ci_type > 0) + cx_clear(RDR_RDRCTL1, 1 << 8); + + switch (dev->board) { + case CX23885_BOARD_TEVII_S470: + case CX23885_BOARD_TEVII_S471: + cx_clear(RDR_RDRCTL1, 1 << 8); + break; + } + + return 0; + +err_release_region: + release_mem_region(pci_resource_start(dev->pci, 0), + pci_resource_len(dev->pci, 0)); + cx23885_devcount--; + return -ENODEV; +} + +static void cx23885_dev_unregister(struct cx23885_dev *dev) +{ + release_mem_region(pci_resource_start(dev->pci, 0), + pci_resource_len(dev->pci, 0)); + + if (!atomic_dec_and_test(&dev->refcount)) + return; + + if (cx23885_boards[dev->board].porta == CX23885_ANALOG_VIDEO) + cx23885_video_unregister(dev); + + if (cx23885_boards[dev->board].portb == CX23885_MPEG_DVB) + cx23885_dvb_unregister(&dev->ts1); + + if (cx23885_boards[dev->board].portb == CX23885_MPEG_ENCODER) + cx23885_417_unregister(dev); + + if (cx23885_boards[dev->board].portc == CX23885_MPEG_DVB) + cx23885_dvb_unregister(&dev->ts2); + + if (cx23885_boards[dev->board].portc == CX23885_MPEG_ENCODER) + cx23885_417_unregister(dev); + + cx23885_i2c_unregister(&dev->i2c_bus[2]); + cx23885_i2c_unregister(&dev->i2c_bus[1]); + cx23885_i2c_unregister(&dev->i2c_bus[0]); + + iounmap(dev->lmmio); +} + +static __le32 *cx23885_risc_field(__le32 *rp, struct scatterlist *sglist, + unsigned int offset, u32 sync_line, + unsigned int bpl, unsigned int padding, + unsigned int lines, unsigned int lpi, bool jump) +{ + struct scatterlist *sg; + unsigned int line, todo, sol; + + + if (jump) { + *(rp++) = cpu_to_le32(RISC_JUMP); + *(rp++) = cpu_to_le32(0); + *(rp++) = cpu_to_le32(0); /* bits 63-32 */ + } + + /* sync instruction */ + if (sync_line != NO_SYNC_LINE) + *(rp++) = cpu_to_le32(RISC_RESYNC | sync_line); + + /* scan lines */ + sg = sglist; + for (line = 0; line < lines; line++) { + while (offset && offset >= sg_dma_len(sg)) { + offset -= sg_dma_len(sg); + sg = sg_next(sg); + } + + if (lpi && line > 0 && !(line % lpi)) + sol = RISC_SOL | RISC_IRQ1 | RISC_CNT_INC; + else + sol = RISC_SOL; + + if (bpl <= sg_dma_len(sg)-offset) { + /* fits into current chunk */ + *(rp++) = cpu_to_le32(RISC_WRITE|sol|RISC_EOL|bpl); + *(rp++) = cpu_to_le32(sg_dma_address(sg)+offset); + *(rp++) = cpu_to_le32(0); /* bits 63-32 */ + offset += bpl; + } else { + /* scanline needs to be split */ + todo = bpl; + *(rp++) = cpu_to_le32(RISC_WRITE|sol| + (sg_dma_len(sg)-offset)); + *(rp++) = cpu_to_le32(sg_dma_address(sg)+offset); + *(rp++) = cpu_to_le32(0); /* bits 63-32 */ + todo -= (sg_dma_len(sg)-offset); + offset = 0; + sg = sg_next(sg); + while (todo > sg_dma_len(sg)) { + *(rp++) = cpu_to_le32(RISC_WRITE| + sg_dma_len(sg)); + *(rp++) = cpu_to_le32(sg_dma_address(sg)); + *(rp++) = cpu_to_le32(0); /* bits 63-32 */ + todo -= sg_dma_len(sg); + sg = sg_next(sg); + } + *(rp++) = cpu_to_le32(RISC_WRITE|RISC_EOL|todo); + *(rp++) = cpu_to_le32(sg_dma_address(sg)); + *(rp++) = cpu_to_le32(0); /* bits 63-32 */ + offset += todo; + } + offset += padding; + } + + return rp; +} + +int cx23885_risc_buffer(struct pci_dev *pci, struct cx23885_riscmem *risc, + struct scatterlist *sglist, unsigned int top_offset, + unsigned int bottom_offset, unsigned int bpl, + unsigned int padding, unsigned int lines) +{ + u32 instructions, fields; + __le32 *rp; + + fields = 0; + if (UNSET != top_offset) + fields++; + if (UNSET != bottom_offset) + fields++; + + /* estimate risc mem: worst case is one write per page border + + one write per scan line + syncs + jump (all 2 dwords). Padding + can cause next bpl to start close to a page border. First DMA + region may be smaller than PAGE_SIZE */ + /* write and jump need and extra dword */ + instructions = fields * (1 + ((bpl + padding) * lines) + / PAGE_SIZE + lines); + instructions += 5; + risc->size = instructions * 12; + risc->cpu = dma_alloc_coherent(&pci->dev, risc->size, &risc->dma, + GFP_KERNEL); + if (risc->cpu == NULL) + return -ENOMEM; + + /* write risc instructions */ + rp = risc->cpu; + if (UNSET != top_offset) + rp = cx23885_risc_field(rp, sglist, top_offset, 0, + bpl, padding, lines, 0, true); + if (UNSET != bottom_offset) + rp = cx23885_risc_field(rp, sglist, bottom_offset, 0x200, + bpl, padding, lines, 0, UNSET == top_offset); + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + BUG_ON((risc->jmp - risc->cpu + 2) * sizeof(*risc->cpu) > risc->size); + return 0; +} + +int cx23885_risc_databuffer(struct pci_dev *pci, + struct cx23885_riscmem *risc, + struct scatterlist *sglist, + unsigned int bpl, + unsigned int lines, unsigned int lpi) +{ + u32 instructions; + __le32 *rp; + + /* estimate risc mem: worst case is one write per page border + + one write per scan line + syncs + jump (all 2 dwords). Here + there is no padding and no sync. First DMA region may be smaller + than PAGE_SIZE */ + /* Jump and write need an extra dword */ + instructions = 1 + (bpl * lines) / PAGE_SIZE + lines; + instructions += 4; + + risc->size = instructions * 12; + risc->cpu = dma_alloc_coherent(&pci->dev, risc->size, &risc->dma, + GFP_KERNEL); + if (risc->cpu == NULL) + return -ENOMEM; + + /* write risc instructions */ + rp = risc->cpu; + rp = cx23885_risc_field(rp, sglist, 0, NO_SYNC_LINE, + bpl, 0, lines, lpi, lpi == 0); + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + BUG_ON((risc->jmp - risc->cpu + 2) * sizeof(*risc->cpu) > risc->size); + return 0; +} + +int cx23885_risc_vbibuffer(struct pci_dev *pci, struct cx23885_riscmem *risc, + struct scatterlist *sglist, unsigned int top_offset, + unsigned int bottom_offset, unsigned int bpl, + unsigned int padding, unsigned int lines) +{ + u32 instructions, fields; + __le32 *rp; + + fields = 0; + if (UNSET != top_offset) + fields++; + if (UNSET != bottom_offset) + fields++; + + /* estimate risc mem: worst case is one write per page border + + one write per scan line + syncs + jump (all 2 dwords). Padding + can cause next bpl to start close to a page border. First DMA + region may be smaller than PAGE_SIZE */ + /* write and jump need and extra dword */ + instructions = fields * (1 + ((bpl + padding) * lines) + / PAGE_SIZE + lines); + instructions += 5; + risc->size = instructions * 12; + risc->cpu = dma_alloc_coherent(&pci->dev, risc->size, &risc->dma, + GFP_KERNEL); + if (risc->cpu == NULL) + return -ENOMEM; + /* write risc instructions */ + rp = risc->cpu; + + /* Sync to line 6, so US CC line 21 will appear in line '12' + * in the userland vbi payload */ + if (UNSET != top_offset) + rp = cx23885_risc_field(rp, sglist, top_offset, 0, + bpl, padding, lines, 0, true); + + if (UNSET != bottom_offset) + rp = cx23885_risc_field(rp, sglist, bottom_offset, 0x200, + bpl, padding, lines, 0, UNSET == top_offset); + + + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + BUG_ON((risc->jmp - risc->cpu + 2) * sizeof(*risc->cpu) > risc->size); + return 0; +} + + +void cx23885_free_buffer(struct cx23885_dev *dev, struct cx23885_buffer *buf) +{ + struct cx23885_riscmem *risc = &buf->risc; + + if (risc->cpu) + dma_free_coherent(&dev->pci->dev, risc->size, risc->cpu, risc->dma); + memset(risc, 0, sizeof(*risc)); +} + +static void cx23885_tsport_reg_dump(struct cx23885_tsport *port) +{ + struct cx23885_dev *dev = port->dev; + + dprintk(1, "%s() Register Dump\n", __func__); + dprintk(1, "%s() DEV_CNTRL2 0x%08X\n", __func__, + cx_read(DEV_CNTRL2)); + dprintk(1, "%s() PCI_INT_MSK 0x%08X\n", __func__, + cx23885_irq_get_mask(dev)); + dprintk(1, "%s() AUD_INT_INT_MSK 0x%08X\n", __func__, + cx_read(AUDIO_INT_INT_MSK)); + dprintk(1, "%s() AUD_INT_DMA_CTL 0x%08X\n", __func__, + cx_read(AUD_INT_DMA_CTL)); + dprintk(1, "%s() AUD_EXT_INT_MSK 0x%08X\n", __func__, + cx_read(AUDIO_EXT_INT_MSK)); + dprintk(1, "%s() AUD_EXT_DMA_CTL 0x%08X\n", __func__, + cx_read(AUD_EXT_DMA_CTL)); + dprintk(1, "%s() PAD_CTRL 0x%08X\n", __func__, + cx_read(PAD_CTRL)); + dprintk(1, "%s() ALT_PIN_OUT_SEL 0x%08X\n", __func__, + cx_read(ALT_PIN_OUT_SEL)); + dprintk(1, "%s() GPIO2 0x%08X\n", __func__, + cx_read(GPIO2)); + dprintk(1, "%s() gpcnt(0x%08X) 0x%08X\n", __func__, + port->reg_gpcnt, cx_read(port->reg_gpcnt)); + dprintk(1, "%s() gpcnt_ctl(0x%08X) 0x%08x\n", __func__, + port->reg_gpcnt_ctl, cx_read(port->reg_gpcnt_ctl)); + dprintk(1, "%s() dma_ctl(0x%08X) 0x%08x\n", __func__, + port->reg_dma_ctl, cx_read(port->reg_dma_ctl)); + if (port->reg_src_sel) + dprintk(1, "%s() src_sel(0x%08X) 0x%08x\n", __func__, + port->reg_src_sel, cx_read(port->reg_src_sel)); + dprintk(1, "%s() lngth(0x%08X) 0x%08x\n", __func__, + port->reg_lngth, cx_read(port->reg_lngth)); + dprintk(1, "%s() hw_sop_ctrl(0x%08X) 0x%08x\n", __func__, + port->reg_hw_sop_ctrl, cx_read(port->reg_hw_sop_ctrl)); + dprintk(1, "%s() gen_ctrl(0x%08X) 0x%08x\n", __func__, + port->reg_gen_ctrl, cx_read(port->reg_gen_ctrl)); + dprintk(1, "%s() bd_pkt_status(0x%08X) 0x%08x\n", __func__, + port->reg_bd_pkt_status, cx_read(port->reg_bd_pkt_status)); + dprintk(1, "%s() sop_status(0x%08X) 0x%08x\n", __func__, + port->reg_sop_status, cx_read(port->reg_sop_status)); + dprintk(1, "%s() fifo_ovfl_stat(0x%08X) 0x%08x\n", __func__, + port->reg_fifo_ovfl_stat, cx_read(port->reg_fifo_ovfl_stat)); + dprintk(1, "%s() vld_misc(0x%08X) 0x%08x\n", __func__, + port->reg_vld_misc, cx_read(port->reg_vld_misc)); + dprintk(1, "%s() ts_clk_en(0x%08X) 0x%08x\n", __func__, + port->reg_ts_clk_en, cx_read(port->reg_ts_clk_en)); + dprintk(1, "%s() ts_int_msk(0x%08X) 0x%08x\n", __func__, + port->reg_ts_int_msk, cx_read(port->reg_ts_int_msk)); + dprintk(1, "%s() ts_int_status(0x%08X) 0x%08x\n", __func__, + port->reg_ts_int_stat, cx_read(port->reg_ts_int_stat)); + dprintk(1, "%s() PCI_INT_STAT 0x%08X\n", __func__, + cx_read(PCI_INT_STAT)); + dprintk(1, "%s() VID_B_INT_MSTAT 0x%08X\n", __func__, + cx_read(VID_B_INT_MSTAT)); + dprintk(1, "%s() VID_B_INT_SSTAT 0x%08X\n", __func__, + cx_read(VID_B_INT_SSTAT)); + dprintk(1, "%s() VID_C_INT_MSTAT 0x%08X\n", __func__, + cx_read(VID_C_INT_MSTAT)); + dprintk(1, "%s() VID_C_INT_SSTAT 0x%08X\n", __func__, + cx_read(VID_C_INT_SSTAT)); +} + +int cx23885_start_dma(struct cx23885_tsport *port, + struct cx23885_dmaqueue *q, + struct cx23885_buffer *buf) +{ + struct cx23885_dev *dev = port->dev; + u32 reg; + + dprintk(1, "%s() w: %d, h: %d, f: %d\n", __func__, + dev->width, dev->height, dev->field); + + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); + + /* Stop the fifo and risc engine for this port */ + cx_clear(port->reg_dma_ctl, port->dma_ctl_val); + + /* setup fifo + format */ + cx23885_sram_channel_setup(dev, + &dev->sram_channels[port->sram_chno], + port->ts_packet_size, buf->risc.dma); + if (debug > 5) { + cx23885_sram_channel_dump(dev, + &dev->sram_channels[port->sram_chno]); + cx23885_risc_disasm(port, &buf->risc); + } + + /* write TS length to chip */ + cx_write(port->reg_lngth, port->ts_packet_size); + + if ((!(cx23885_boards[dev->board].portb & CX23885_MPEG_DVB)) && + (!(cx23885_boards[dev->board].portc & CX23885_MPEG_DVB))) { + pr_err("%s() Unsupported .portb/c (0x%08x)/(0x%08x)\n", + __func__, + cx23885_boards[dev->board].portb, + cx23885_boards[dev->board].portc); + return -EINVAL; + } + + if (cx23885_boards[dev->board].portb == CX23885_MPEG_ENCODER) + cx23885_av_clk(dev, 0); + + udelay(100); + + /* If the port supports SRC SELECT, configure it */ + if (port->reg_src_sel) + cx_write(port->reg_src_sel, port->src_sel_val); + + cx_write(port->reg_hw_sop_ctrl, port->hw_sop_ctrl_val); + cx_write(port->reg_ts_clk_en, port->ts_clk_en_val); + cx_write(port->reg_vld_misc, port->vld_misc_val); + cx_write(port->reg_gen_ctrl, port->gen_ctrl_val); + udelay(100); + + /* NOTE: this is 2 (reserved) for portb, does it matter? */ + /* reset counter to zero */ + cx_write(port->reg_gpcnt_ctl, 3); + q->count = 0; + + /* Set VIDB pins to input */ + if (cx23885_boards[dev->board].portb == CX23885_MPEG_DVB) { + reg = cx_read(PAD_CTRL); + reg &= ~0x3; /* Clear TS1_OE & TS1_SOP_OE */ + cx_write(PAD_CTRL, reg); + } + + /* Set VIDC pins to input */ + if (cx23885_boards[dev->board].portc == CX23885_MPEG_DVB) { + reg = cx_read(PAD_CTRL); + reg &= ~0x4; /* Clear TS2_SOP_OE */ + cx_write(PAD_CTRL, reg); + } + + if (cx23885_boards[dev->board].portb == CX23885_MPEG_ENCODER) { + + reg = cx_read(PAD_CTRL); + reg = reg & ~0x1; /* Clear TS1_OE */ + + /* FIXME, bit 2 writing here is questionable */ + /* set TS1_SOP_OE and TS1_OE_HI */ + reg = reg | 0xa; + cx_write(PAD_CTRL, reg); + + /* Sets MOE_CLK_DIS to disable MoE clock */ + /* sets MCLK_DLY_SEL/BCLK_DLY_SEL to 1 buffer delay each */ + cx_write(CLK_DELAY, cx_read(CLK_DELAY) | 0x80000011); + + /* ALT_GPIO_ALT_SET: GPIO[0] + * IR_ALT_TX_SEL: GPIO[1] + * GPIO1_ALT_SEL: VIP_656_DATA[0] + * GPIO0_ALT_SEL: VIP_656_CLK + */ + cx_write(ALT_PIN_OUT_SEL, 0x10100045); + } + + switch (dev->bridge) { + case CX23885_BRIDGE_885: + case CX23885_BRIDGE_887: + case CX23885_BRIDGE_888: + /* enable irqs */ + dprintk(1, "%s() enabling TS int's and DMA\n", __func__); + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); + cx_set(port->reg_ts_int_msk, port->ts_int_msk_val); + cx_set(port->reg_dma_ctl, port->dma_ctl_val); + + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); + cx23885_irq_add(dev, port->pci_irqmask); + cx23885_irq_enable_all(dev); + + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); + break; + default: + BUG(); + } + + cx_set(DEV_CNTRL2, (1<<5)); /* Enable RISC controller */ + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); + + if (cx23885_boards[dev->board].portb == CX23885_MPEG_ENCODER) + cx23885_av_clk(dev, 1); + + if (debug > 4) + cx23885_tsport_reg_dump(port); + + cx23885_irq_get_mask(dev); + + /* clear dma in progress */ + cx23885_clear_bridge_error(dev); + + return 0; +} + +static int cx23885_stop_dma(struct cx23885_tsport *port) +{ + struct cx23885_dev *dev = port->dev; + u32 reg; + int delay = 0; + uint32_t reg1_val; + uint32_t reg2_val; + + dprintk(1, "%s()\n", __func__); + + /* Stop interrupts and DMA */ + cx_clear(port->reg_ts_int_msk, port->ts_int_msk_val); + cx_clear(port->reg_dma_ctl, port->dma_ctl_val); + /* just in case wait for any dma to complete before allowing dealloc */ + mdelay(20); + for (delay = 0; delay < 100; delay++) { + reg1_val = cx_read(TC_REQ); + reg2_val = cx_read(TC_REQ_SET); + if (reg1_val == 0 || reg2_val == 0) + break; + mdelay(1); + } + dev_dbg(&dev->pci->dev, "delay=%d reg1=0x%08x reg2=0x%08x\n", + delay, reg1_val, reg2_val); + + if (cx23885_boards[dev->board].portb == CX23885_MPEG_ENCODER) { + reg = cx_read(PAD_CTRL); + + /* Set TS1_OE */ + reg = reg | 0x1; + + /* clear TS1_SOP_OE and TS1_OE_HI */ + reg = reg & ~0xa; + cx_write(PAD_CTRL, reg); + cx_write(port->reg_src_sel, 0); + cx_write(port->reg_gen_ctrl, 8); + } + + if (cx23885_boards[dev->board].portb == CX23885_MPEG_ENCODER) + cx23885_av_clk(dev, 0); + + return 0; +} + +/* ------------------------------------------------------------------ */ + +int cx23885_buf_prepare(struct cx23885_buffer *buf, struct cx23885_tsport *port) +{ + struct cx23885_dev *dev = port->dev; + int size = port->ts_packet_size * port->ts_packet_count; + struct sg_table *sgt = vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0); + + dprintk(1, "%s: %p\n", __func__, buf); + if (vb2_plane_size(&buf->vb.vb2_buf, 0) < size) + return -EINVAL; + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size); + + cx23885_risc_databuffer(dev->pci, &buf->risc, + sgt->sgl, + port->ts_packet_size, port->ts_packet_count, 0); + return 0; +} + +/* + * The risc program for each buffer works as follows: it starts with a simple + * 'JUMP to addr + 12', which is effectively a NOP. Then the code to DMA the + * buffer follows and at the end we have a JUMP back to the start + 12 (skipping + * the initial JUMP). + * + * This is the risc program of the first buffer to be queued if the active list + * is empty and it just keeps DMAing this buffer without generating any + * interrupts. + * + * If a new buffer is added then the initial JUMP in the code for that buffer + * will generate an interrupt which signals that the previous buffer has been + * DMAed successfully and that it can be returned to userspace. + * + * It also sets the final jump of the previous buffer to the start of the new + * buffer, thus chaining the new buffer into the DMA chain. This is a single + * atomic u32 write, so there is no race condition. + * + * The end-result of all this that you only get an interrupt when a buffer + * is ready, so the control flow is very easy. + */ +void cx23885_buf_queue(struct cx23885_tsport *port, struct cx23885_buffer *buf) +{ + struct cx23885_buffer *prev; + struct cx23885_dev *dev = port->dev; + struct cx23885_dmaqueue *cx88q = &port->mpegq; + unsigned long flags; + + buf->risc.cpu[1] = cpu_to_le32(buf->risc.dma + 12); + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma + 12); + buf->risc.jmp[2] = cpu_to_le32(0); /* bits 63-32 */ + + spin_lock_irqsave(&dev->slock, flags); + if (list_empty(&cx88q->active)) { + list_add_tail(&buf->queue, &cx88q->active); + dprintk(1, "[%p/%d] %s - first active\n", + buf, buf->vb.vb2_buf.index, __func__); + } else { + buf->risc.cpu[0] |= cpu_to_le32(RISC_IRQ1); + prev = list_entry(cx88q->active.prev, struct cx23885_buffer, + queue); + list_add_tail(&buf->queue, &cx88q->active); + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(1, "[%p/%d] %s - append to active\n", + buf, buf->vb.vb2_buf.index, __func__); + } + spin_unlock_irqrestore(&dev->slock, flags); +} + +/* ----------------------------------------------------------- */ + +static void do_cancel_buffers(struct cx23885_tsport *port, char *reason) +{ + struct cx23885_dmaqueue *q = &port->mpegq; + struct cx23885_buffer *buf; + unsigned long flags; + + spin_lock_irqsave(&port->slock, flags); + while (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx23885_buffer, + queue); + list_del(&buf->queue); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + dprintk(1, "[%p/%d] %s - dma=0x%08lx\n", + buf, buf->vb.vb2_buf.index, reason, + (unsigned long)buf->risc.dma); + } + spin_unlock_irqrestore(&port->slock, flags); +} + +void cx23885_cancel_buffers(struct cx23885_tsport *port) +{ + dprintk(1, "%s()\n", __func__); + cx23885_stop_dma(port); + do_cancel_buffers(port, "cancel"); +} + +int cx23885_irq_417(struct cx23885_dev *dev, u32 status) +{ + /* FIXME: port1 assumption here. */ + struct cx23885_tsport *port = &dev->ts1; + int count = 0; + int handled = 0; + + if (status == 0) + return handled; + + count = cx_read(port->reg_gpcnt); + dprintk(7, "status: 0x%08x mask: 0x%08x count: 0x%x\n", + status, cx_read(port->reg_ts_int_msk), count); + + if ((status & VID_B_MSK_BAD_PKT) || + (status & VID_B_MSK_OPC_ERR) || + (status & VID_B_MSK_VBI_OPC_ERR) || + (status & VID_B_MSK_SYNC) || + (status & VID_B_MSK_VBI_SYNC) || + (status & VID_B_MSK_OF) || + (status & VID_B_MSK_VBI_OF)) { + pr_err("%s: V4L mpeg risc op code error, status = 0x%x\n", + dev->name, status); + if (status & VID_B_MSK_BAD_PKT) + dprintk(1, " VID_B_MSK_BAD_PKT\n"); + if (status & VID_B_MSK_OPC_ERR) + dprintk(1, " VID_B_MSK_OPC_ERR\n"); + if (status & VID_B_MSK_VBI_OPC_ERR) + dprintk(1, " VID_B_MSK_VBI_OPC_ERR\n"); + if (status & VID_B_MSK_SYNC) + dprintk(1, " VID_B_MSK_SYNC\n"); + if (status & VID_B_MSK_VBI_SYNC) + dprintk(1, " VID_B_MSK_VBI_SYNC\n"); + if (status & VID_B_MSK_OF) + dprintk(1, " VID_B_MSK_OF\n"); + if (status & VID_B_MSK_VBI_OF) + dprintk(1, " VID_B_MSK_VBI_OF\n"); + + cx_clear(port->reg_dma_ctl, port->dma_ctl_val); + cx23885_sram_channel_dump(dev, + &dev->sram_channels[port->sram_chno]); + cx23885_417_check_encoder(dev); + } else if (status & VID_B_MSK_RISCI1) { + dprintk(7, " VID_B_MSK_RISCI1\n"); + spin_lock(&port->slock); + cx23885_wakeup(port, &port->mpegq, count); + spin_unlock(&port->slock); + } + if (status) { + cx_write(port->reg_ts_int_stat, status); + handled = 1; + } + + return handled; +} + +static int cx23885_irq_ts(struct cx23885_tsport *port, u32 status) +{ + struct cx23885_dev *dev = port->dev; + int handled = 0; + u32 count; + + if ((status & VID_BC_MSK_OPC_ERR) || + (status & VID_BC_MSK_BAD_PKT) || + (status & VID_BC_MSK_SYNC) || + (status & VID_BC_MSK_OF)) { + + if (status & VID_BC_MSK_OPC_ERR) + dprintk(7, " (VID_BC_MSK_OPC_ERR 0x%08x)\n", + VID_BC_MSK_OPC_ERR); + + if (status & VID_BC_MSK_BAD_PKT) + dprintk(7, " (VID_BC_MSK_BAD_PKT 0x%08x)\n", + VID_BC_MSK_BAD_PKT); + + if (status & VID_BC_MSK_SYNC) + dprintk(7, " (VID_BC_MSK_SYNC 0x%08x)\n", + VID_BC_MSK_SYNC); + + if (status & VID_BC_MSK_OF) + dprintk(7, " (VID_BC_MSK_OF 0x%08x)\n", + VID_BC_MSK_OF); + + pr_err("%s: mpeg risc op code error\n", dev->name); + + cx_clear(port->reg_dma_ctl, port->dma_ctl_val); + cx23885_sram_channel_dump(dev, + &dev->sram_channels[port->sram_chno]); + + } else if (status & VID_BC_MSK_RISCI1) { + + dprintk(7, " (RISCI1 0x%08x)\n", VID_BC_MSK_RISCI1); + + spin_lock(&port->slock); + count = cx_read(port->reg_gpcnt); + cx23885_wakeup(port, &port->mpegq, count); + spin_unlock(&port->slock); + + } + if (status) { + cx_write(port->reg_ts_int_stat, status); + handled = 1; + } + + return handled; +} + +static irqreturn_t cx23885_irq(int irq, void *dev_id) +{ + struct cx23885_dev *dev = dev_id; + struct cx23885_tsport *ts1 = &dev->ts1; + struct cx23885_tsport *ts2 = &dev->ts2; + u32 pci_status, pci_mask; + u32 vida_status, vida_mask; + u32 audint_status, audint_mask; + u32 ts1_status, ts1_mask; + u32 ts2_status, ts2_mask; + int vida_count = 0, ts1_count = 0, ts2_count = 0, handled = 0; + int audint_count = 0; + bool subdev_handled; + + pci_status = cx_read(PCI_INT_STAT); + pci_mask = cx23885_irq_get_mask(dev); + if ((pci_status & pci_mask) == 0) { + dprintk(7, "pci_status: 0x%08x pci_mask: 0x%08x\n", + pci_status, pci_mask); + goto out; + } + + vida_status = cx_read(VID_A_INT_STAT); + vida_mask = cx_read(VID_A_INT_MSK); + audint_status = cx_read(AUDIO_INT_INT_STAT); + audint_mask = cx_read(AUDIO_INT_INT_MSK); + ts1_status = cx_read(VID_B_INT_STAT); + ts1_mask = cx_read(VID_B_INT_MSK); + ts2_status = cx_read(VID_C_INT_STAT); + ts2_mask = cx_read(VID_C_INT_MSK); + + if (((pci_status & pci_mask) == 0) && + ((ts2_status & ts2_mask) == 0) && + ((ts1_status & ts1_mask) == 0)) + goto out; + + vida_count = cx_read(VID_A_GPCNT); + audint_count = cx_read(AUD_INT_A_GPCNT); + ts1_count = cx_read(ts1->reg_gpcnt); + ts2_count = cx_read(ts2->reg_gpcnt); + dprintk(7, "pci_status: 0x%08x pci_mask: 0x%08x\n", + pci_status, pci_mask); + dprintk(7, "vida_status: 0x%08x vida_mask: 0x%08x count: 0x%x\n", + vida_status, vida_mask, vida_count); + dprintk(7, "audint_status: 0x%08x audint_mask: 0x%08x count: 0x%x\n", + audint_status, audint_mask, audint_count); + dprintk(7, "ts1_status: 0x%08x ts1_mask: 0x%08x count: 0x%x\n", + ts1_status, ts1_mask, ts1_count); + dprintk(7, "ts2_status: 0x%08x ts2_mask: 0x%08x count: 0x%x\n", + ts2_status, ts2_mask, ts2_count); + + if (pci_status & (PCI_MSK_RISC_RD | PCI_MSK_RISC_WR | + PCI_MSK_AL_RD | PCI_MSK_AL_WR | PCI_MSK_APB_DMA | + PCI_MSK_VID_C | PCI_MSK_VID_B | PCI_MSK_VID_A | + PCI_MSK_AUD_INT | PCI_MSK_AUD_EXT | + PCI_MSK_GPIO0 | PCI_MSK_GPIO1 | + PCI_MSK_AV_CORE | PCI_MSK_IR)) { + + if (pci_status & PCI_MSK_RISC_RD) + dprintk(7, " (PCI_MSK_RISC_RD 0x%08x)\n", + PCI_MSK_RISC_RD); + + if (pci_status & PCI_MSK_RISC_WR) + dprintk(7, " (PCI_MSK_RISC_WR 0x%08x)\n", + PCI_MSK_RISC_WR); + + if (pci_status & PCI_MSK_AL_RD) + dprintk(7, " (PCI_MSK_AL_RD 0x%08x)\n", + PCI_MSK_AL_RD); + + if (pci_status & PCI_MSK_AL_WR) + dprintk(7, " (PCI_MSK_AL_WR 0x%08x)\n", + PCI_MSK_AL_WR); + + if (pci_status & PCI_MSK_APB_DMA) + dprintk(7, " (PCI_MSK_APB_DMA 0x%08x)\n", + PCI_MSK_APB_DMA); + + if (pci_status & PCI_MSK_VID_C) + dprintk(7, " (PCI_MSK_VID_C 0x%08x)\n", + PCI_MSK_VID_C); + + if (pci_status & PCI_MSK_VID_B) + dprintk(7, " (PCI_MSK_VID_B 0x%08x)\n", + PCI_MSK_VID_B); + + if (pci_status & PCI_MSK_VID_A) + dprintk(7, " (PCI_MSK_VID_A 0x%08x)\n", + PCI_MSK_VID_A); + + if (pci_status & PCI_MSK_AUD_INT) + dprintk(7, " (PCI_MSK_AUD_INT 0x%08x)\n", + PCI_MSK_AUD_INT); + + if (pci_status & PCI_MSK_AUD_EXT) + dprintk(7, " (PCI_MSK_AUD_EXT 0x%08x)\n", + PCI_MSK_AUD_EXT); + + if (pci_status & PCI_MSK_GPIO0) + dprintk(7, " (PCI_MSK_GPIO0 0x%08x)\n", + PCI_MSK_GPIO0); + + if (pci_status & PCI_MSK_GPIO1) + dprintk(7, " (PCI_MSK_GPIO1 0x%08x)\n", + PCI_MSK_GPIO1); + + if (pci_status & PCI_MSK_AV_CORE) + dprintk(7, " (PCI_MSK_AV_CORE 0x%08x)\n", + PCI_MSK_AV_CORE); + + if (pci_status & PCI_MSK_IR) + dprintk(7, " (PCI_MSK_IR 0x%08x)\n", + PCI_MSK_IR); + } + + if (cx23885_boards[dev->board].ci_type == 1 && + (pci_status & (PCI_MSK_GPIO1 | PCI_MSK_GPIO0))) + handled += netup_ci_slot_status(dev, pci_status); + + if (cx23885_boards[dev->board].ci_type == 2 && + (pci_status & PCI_MSK_GPIO0)) + handled += altera_ci_irq(dev); + + if (ts1_status) { + if (cx23885_boards[dev->board].portb == CX23885_MPEG_DVB) + handled += cx23885_irq_ts(ts1, ts1_status); + else + if (cx23885_boards[dev->board].portb == CX23885_MPEG_ENCODER) + handled += cx23885_irq_417(dev, ts1_status); + } + + if (ts2_status) { + if (cx23885_boards[dev->board].portc == CX23885_MPEG_DVB) + handled += cx23885_irq_ts(ts2, ts2_status); + else + if (cx23885_boards[dev->board].portc == CX23885_MPEG_ENCODER) + handled += cx23885_irq_417(dev, ts2_status); + } + + if (vida_status) + handled += cx23885_video_irq(dev, vida_status); + + if (audint_status) + handled += cx23885_audio_irq(dev, audint_status, audint_mask); + + if (pci_status & PCI_MSK_IR) { + subdev_handled = false; + v4l2_subdev_call(dev->sd_ir, core, interrupt_service_routine, + pci_status, &subdev_handled); + if (subdev_handled) + handled++; + } + + if ((pci_status & pci_mask) & PCI_MSK_AV_CORE) { + cx23885_irq_disable(dev, PCI_MSK_AV_CORE); + schedule_work(&dev->cx25840_work); + handled++; + } + + if (handled) + cx_write(PCI_INT_STAT, pci_status & pci_mask); +out: + return IRQ_RETVAL(handled); +} + +static void cx23885_v4l2_dev_notify(struct v4l2_subdev *sd, + unsigned int notification, void *arg) +{ + struct cx23885_dev *dev; + + if (sd == NULL) + return; + + dev = to_cx23885(sd->v4l2_dev); + + switch (notification) { + case V4L2_SUBDEV_IR_RX_NOTIFY: /* Possibly called in an IRQ context */ + if (sd == dev->sd_ir) + cx23885_ir_rx_v4l2_dev_notify(sd, *(u32 *)arg); + break; + case V4L2_SUBDEV_IR_TX_NOTIFY: /* Possibly called in an IRQ context */ + if (sd == dev->sd_ir) + cx23885_ir_tx_v4l2_dev_notify(sd, *(u32 *)arg); + break; + } +} + +static void cx23885_v4l2_dev_notify_init(struct cx23885_dev *dev) +{ + INIT_WORK(&dev->cx25840_work, cx23885_av_work_handler); + INIT_WORK(&dev->ir_rx_work, cx23885_ir_rx_work_handler); + INIT_WORK(&dev->ir_tx_work, cx23885_ir_tx_work_handler); + dev->v4l2_dev.notify = cx23885_v4l2_dev_notify; +} + +static inline int encoder_on_portb(struct cx23885_dev *dev) +{ + return cx23885_boards[dev->board].portb == CX23885_MPEG_ENCODER; +} + +static inline int encoder_on_portc(struct cx23885_dev *dev) +{ + return cx23885_boards[dev->board].portc == CX23885_MPEG_ENCODER; +} + +/* Mask represents 32 different GPIOs, GPIO's are split into multiple + * registers depending on the board configuration (and whether the + * 417 encoder (wi it's own GPIO's) are present. Each GPIO bit will + * be pushed into the correct hardware register, regardless of the + * physical location. Certain registers are shared so we sanity check + * and report errors if we think we're tampering with a GPIo that might + * be assigned to the encoder (and used for the host bus). + * + * GPIO 2 through 0 - On the cx23885 bridge + * GPIO 18 through 3 - On the cx23417 host bus interface + * GPIO 23 through 19 - On the cx25840 a/v core + */ +void cx23885_gpio_set(struct cx23885_dev *dev, u32 mask) +{ + if (mask & 0x7) + cx_set(GP0_IO, mask & 0x7); + + if (mask & 0x0007fff8) { + if (encoder_on_portb(dev) || encoder_on_portc(dev)) + pr_err("%s: Setting GPIO on encoder ports\n", + dev->name); + cx_set(MC417_RWD, (mask & 0x0007fff8) >> 3); + } + + /* TODO: 23-19 */ + if (mask & 0x00f80000) + pr_info("%s: Unsupported\n", dev->name); +} + +void cx23885_gpio_clear(struct cx23885_dev *dev, u32 mask) +{ + if (mask & 0x00000007) + cx_clear(GP0_IO, mask & 0x7); + + if (mask & 0x0007fff8) { + if (encoder_on_portb(dev) || encoder_on_portc(dev)) + pr_err("%s: Clearing GPIO moving on encoder ports\n", + dev->name); + cx_clear(MC417_RWD, (mask & 0x7fff8) >> 3); + } + + /* TODO: 23-19 */ + if (mask & 0x00f80000) + pr_info("%s: Unsupported\n", dev->name); +} + +u32 cx23885_gpio_get(struct cx23885_dev *dev, u32 mask) +{ + if (mask & 0x00000007) + return (cx_read(GP0_IO) >> 8) & mask & 0x7; + + if (mask & 0x0007fff8) { + if (encoder_on_portb(dev) || encoder_on_portc(dev)) + pr_err("%s: Reading GPIO moving on encoder ports\n", + dev->name); + return (cx_read(MC417_RWD) & ((mask & 0x7fff8) >> 3)) << 3; + } + + /* TODO: 23-19 */ + if (mask & 0x00f80000) + pr_info("%s: Unsupported\n", dev->name); + + return 0; +} + +void cx23885_gpio_enable(struct cx23885_dev *dev, u32 mask, int asoutput) +{ + if ((mask & 0x00000007) && asoutput) + cx_set(GP0_IO, (mask & 0x7) << 16); + else if ((mask & 0x00000007) && !asoutput) + cx_clear(GP0_IO, (mask & 0x7) << 16); + + if (mask & 0x0007fff8) { + if (encoder_on_portb(dev) || encoder_on_portc(dev)) + pr_err("%s: Enabling GPIO on encoder ports\n", + dev->name); + } + + /* MC417_OEN is active low for output, write 1 for an input */ + if ((mask & 0x0007fff8) && asoutput) + cx_clear(MC417_OEN, (mask & 0x7fff8) >> 3); + + else if ((mask & 0x0007fff8) && !asoutput) + cx_set(MC417_OEN, (mask & 0x7fff8) >> 3); + + /* TODO: 23-19 */ +} + +static struct { + int vendor, dev; +} const broken_dev_id[] = { + /* According with + * https://openbenchmarking.org/system/1703021-RI-AMDZEN08075/Ryzen%207%201800X/lspci, + * 0x1451 is PCI ID for the IOMMU found on Ryzen + */ + { PCI_VENDOR_ID_AMD, 0x1451 }, + /* According to sudo lspci -nn, + * 0x1423 is the PCI ID for the IOMMU found on Kaveri + */ + { PCI_VENDOR_ID_AMD, 0x1423 }, + /* 0x1481 is the PCI ID for the IOMMU found on Starship/Matisse + */ + { PCI_VENDOR_ID_AMD, 0x1481 }, + /* 0x1419 is the PCI ID for the IOMMU found on 15h (Models 10h-1fh) family + */ + { PCI_VENDOR_ID_AMD, 0x1419 }, + /* 0x1631 is the PCI ID for the IOMMU found on Renoir/Cezanne + */ + { PCI_VENDOR_ID_AMD, 0x1631 }, + /* 0x5a23 is the PCI ID for the IOMMU found on RD890S/RD990 + */ + { PCI_VENDOR_ID_ATI, 0x5a23 }, +}; + +static bool cx23885_does_need_dma_reset(void) +{ + int i; + struct pci_dev *pdev = NULL; + + if (dma_reset_workaround == 0) + return false; + else if (dma_reset_workaround == 2) + return true; + + for (i = 0; i < ARRAY_SIZE(broken_dev_id); i++) { + pdev = pci_get_device(broken_dev_id[i].vendor, + broken_dev_id[i].dev, NULL); + if (pdev) { + pci_dev_put(pdev); + return true; + } + } + return false; +} + +static int cx23885_initdev(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct cx23885_dev *dev; + struct v4l2_ctrl_handler *hdl; + int err; + + dev = kzalloc_obj(*dev); + if (NULL == dev) + return -ENOMEM; + + dev->need_dma_reset = cx23885_does_need_dma_reset(); + + err = v4l2_device_register(&pci_dev->dev, &dev->v4l2_dev); + if (err < 0) + goto fail_free; + + hdl = &dev->ctrl_handler; + v4l2_ctrl_handler_init(hdl, 6); + if (hdl->error) { + err = hdl->error; + goto fail_ctrl; + } + dev->v4l2_dev.ctrl_handler = hdl; + + /* Prepare to handle notifications from subdevices */ + cx23885_v4l2_dev_notify_init(dev); + + /* pci init */ + dev->pci = pci_dev; + if (pci_enable_device(pci_dev)) { + err = -EIO; + goto fail_ctrl; + } + + if (cx23885_dev_setup(dev) < 0) { + err = -EINVAL; + goto fail_ctrl; + } + + /* print pci info */ + dev->pci_rev = pci_dev->revision; + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat); + pr_info("%s/0: found at %s, rev: %d, irq: %d, latency: %d, mmio: 0x%llx\n", + dev->name, + pci_name(pci_dev), dev->pci_rev, pci_dev->irq, + dev->pci_lat, + (unsigned long long)pci_resource_start(pci_dev, 0)); + + pci_set_master(pci_dev); + err = dma_set_mask(&pci_dev->dev, 0xffffffff); + if (err) { + pr_err("%s/0: Oops: no 32bit PCI DMA ???\n", dev->name); + goto fail_dma_set_mask; + } + + err = request_irq(pci_dev->irq, cx23885_irq, + IRQF_SHARED, dev->name, dev); + if (err < 0) { + pr_err("%s: can't get IRQ %d\n", + dev->name, pci_dev->irq); + goto fail_dma_set_mask; + } + + switch (dev->board) { + case CX23885_BOARD_NETUP_DUAL_DVBS2_CI: + cx23885_irq_add_enable(dev, PCI_MSK_GPIO1 | PCI_MSK_GPIO0); + break; + case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: + cx23885_irq_add_enable(dev, PCI_MSK_GPIO0); + break; + } + + /* + * The CX2388[58] IR controller can start firing interrupts when + * enabled, so these have to take place after the cx23885_irq() handler + * is hooked up by the call to request_irq() above. + */ + cx23885_ir_pci_int_enable(dev); + cx23885_input_init(dev); + + return 0; + +fail_dma_set_mask: + cx23885_dev_unregister(dev); +fail_ctrl: + v4l2_ctrl_handler_free(hdl); + v4l2_device_unregister(&dev->v4l2_dev); +fail_free: + kfree(dev); + return err; +} + +static void cx23885_finidev(struct pci_dev *pci_dev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct cx23885_dev *dev = to_cx23885(v4l2_dev); + + cx23885_input_fini(dev); + cx23885_ir_fini(dev); + + cx23885_shutdown(dev); + + /* unregister stuff */ + free_irq(pci_dev->irq, dev); + + pci_disable_device(pci_dev); + + cx23885_dev_unregister(dev); + v4l2_ctrl_handler_free(&dev->ctrl_handler); + v4l2_device_unregister(v4l2_dev); + kfree(dev); +} + +static int __maybe_unused cx23885_suspend(struct device *dev_d) +{ + struct pci_dev *pci_dev = to_pci_dev(dev_d); + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct cx23885_dev *dev = to_cx23885(v4l2_dev); + + cx23885_shutdown(dev); + + return 0; +} + +static int __maybe_unused cx23885_resume(struct device *dev_d) +{ + struct pci_dev *pci_dev = to_pci_dev(dev_d); + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct cx23885_dev *dev = to_cx23885(v4l2_dev); + + cx23885_reset(dev); + + return 0; +} + +static const struct pci_device_id cx23885_pci_tbl[] = { + { + /* CX23885 */ + PCI_DEVICE(0x14f1, 0x8852), + }, { + /* CX23887 Rev 2 */ + PCI_DEVICE(0x14f1, 0x8880), + }, { + /* --- end of list --- */ + } +}; +MODULE_DEVICE_TABLE(pci, cx23885_pci_tbl); + +static SIMPLE_DEV_PM_OPS(cx23885_pm_ops, cx23885_suspend, cx23885_resume); + +static struct pci_driver cx23885_pci_driver = { + .name = "cx23885", + .id_table = cx23885_pci_tbl, + .probe = cx23885_initdev, + .remove = cx23885_finidev, + .driver.pm = &cx23885_pm_ops, +}; + +static int __init cx23885_init(void) +{ + pr_info("cx23885 driver version %s loaded\n", + CX23885_VERSION); + return pci_register_driver(&cx23885_pci_driver); +} + +static void __exit cx23885_fini(void) +{ + pci_unregister_driver(&cx23885_pci_driver); +} + +module_init(cx23885_init); +module_exit(cx23885_fini); diff --git a/drivers/media/pci/cx23885/cx23885-dvb.c b/drivers/media/pci/cx23885/cx23885-dvb.c new file mode 100644 index 000000000..f240ccda4 --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-dvb.c @@ -0,0 +1,2741 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX23885 PCIe bridge + * + * Copyright (c) 2006 Steven Toth + */ + +#include "cx23885.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "s5h1409.h" +#include "s5h1411.h" +#include "mt2131.h" +#include "tda8290.h" +#include "tda18271.h" +#include "lgdt330x.h" +#include "xc4000.h" +#include "xc5000.h" +#include "max2165.h" +#include "tda10048.h" +#include "xc2028.h" +#include "tuner-simple.h" +#include "dib7000p.h" +#include "dib0070.h" +#include "dibx000_common.h" +#include "zl10353.h" +#include "stv0900.h" +#include "stv0900_reg.h" +#include "stv6110.h" +#include "lnbh24.h" +#include "cx24116.h" +#include "cx24117.h" +#include "cimax2.h" +#include "lgs8gxx.h" +#include "netup-eeprom.h" +#include "netup-init.h" +#include "lgdt3305.h" +#include "atbm8830.h" +#include "ts2020.h" +#include "ds3000.h" +#include "cx23885-f300.h" +#include "altera-ci.h" +#include "stv0367.h" +#include "drxk.h" +#include "mt2063.h" +#include "stv090x.h" +#include "stb6100.h" +#include "stb6100_cfg.h" +#include "tda10071.h" +#include "a8293.h" +#include "mb86a20s.h" +#include "si2165.h" +#include "si2168.h" +#include "si2157.h" +#include "sp2.h" +#include "m88ds3103.h" +#include "m88rs6000t.h" +#include "lgdt3306a.h" + +static unsigned int debug; + +#define dprintk(level, fmt, arg...)\ + do { if (debug >= level)\ + printk(KERN_DEBUG pr_fmt("%s dvb: " fmt), \ + __func__, ##arg); \ + } while (0) + +/* ------------------------------------------------------------------ */ + +static unsigned int alt_tuner; +module_param(alt_tuner, int, 0644); +MODULE_PARM_DESC(alt_tuner, "Enable alternate tuner configuration"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +/* ------------------------------------------------------------------ */ + +static int queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct cx23885_tsport *port = q->drv_priv; + + port->ts_packet_size = 188 * 4; + port->ts_packet_count = 32; + *num_planes = 1; + sizes[0] = port->ts_packet_size * port->ts_packet_count; + *num_buffers = 32; + return 0; +} + + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx23885_tsport *port = vb->vb2_queue->drv_priv; + struct cx23885_buffer *buf = + container_of(vbuf, struct cx23885_buffer, vb); + + return cx23885_buf_prepare(buf, port); +} + +static void buffer_finish(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx23885_tsport *port = vb->vb2_queue->drv_priv; + struct cx23885_dev *dev = port->dev; + struct cx23885_buffer *buf = container_of(vbuf, + struct cx23885_buffer, vb); + + cx23885_free_buffer(dev, buf); +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx23885_tsport *port = vb->vb2_queue->drv_priv; + struct cx23885_buffer *buf = container_of(vbuf, + struct cx23885_buffer, vb); + + cx23885_buf_queue(port, buf); +} + +static void cx23885_dvb_gate_ctrl(struct cx23885_tsport *port, int open) +{ + struct vb2_dvb_frontends *f; + struct vb2_dvb_frontend *fe; + + f = &port->frontends; + + if (f->gate <= 1) /* undefined or fe0 */ + fe = vb2_dvb_get_frontend(f, 1); + else + fe = vb2_dvb_get_frontend(f, f->gate); + + if (fe && fe->dvb.frontend && fe->dvb.frontend->ops.i2c_gate_ctrl) + fe->dvb.frontend->ops.i2c_gate_ctrl(fe->dvb.frontend, open); +} + +static int cx23885_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct cx23885_tsport *port = q->drv_priv; + struct cx23885_dmaqueue *dmaq = &port->mpegq; + struct cx23885_buffer *buf = list_entry(dmaq->active.next, + struct cx23885_buffer, queue); + + cx23885_start_dma(port, dmaq, buf); + return 0; +} + +static void cx23885_stop_streaming(struct vb2_queue *q) +{ + struct cx23885_tsport *port = q->drv_priv; + + cx23885_cancel_buffers(port); +} + +static const struct vb2_ops dvb_qops = { + .queue_setup = queue_setup, + .buf_prepare = buffer_prepare, + .buf_finish = buffer_finish, + .buf_queue = buffer_queue, + .start_streaming = cx23885_start_streaming, + .stop_streaming = cx23885_stop_streaming, +}; + +static struct s5h1409_config hauppauge_generic_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_ON, + .qam_if = 44000, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, +}; + +static struct tda10048_config hauppauge_hvr1200_config = { + .demod_address = 0x10 >> 1, + .output_mode = TDA10048_SERIAL_OUTPUT, + .fwbulkwritelen = TDA10048_BULKWRITE_200, + .inversion = TDA10048_INVERSION_ON, + .dtv6_if_freq_khz = TDA10048_IF_3300, + .dtv7_if_freq_khz = TDA10048_IF_3800, + .dtv8_if_freq_khz = TDA10048_IF_4300, + .clk_freq_khz = TDA10048_CLK_16000, +}; + +static struct tda10048_config hauppauge_hvr1210_config = { + .demod_address = 0x10 >> 1, + .output_mode = TDA10048_SERIAL_OUTPUT, + .fwbulkwritelen = TDA10048_BULKWRITE_200, + .inversion = TDA10048_INVERSION_ON, + .dtv6_if_freq_khz = TDA10048_IF_3300, + .dtv7_if_freq_khz = TDA10048_IF_3500, + .dtv8_if_freq_khz = TDA10048_IF_4000, + .clk_freq_khz = TDA10048_CLK_16000, +}; + +static struct s5h1409_config hauppauge_ezqam_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_OFF, + .qam_if = 4000, + .inversion = S5H1409_INVERSION_ON, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, +}; + +static struct s5h1409_config hauppauge_hvr1800lp_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_OFF, + .qam_if = 44000, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, +}; + +static struct s5h1409_config hauppauge_hvr1500_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_OFF, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, +}; + +static struct mt2131_config hauppauge_generic_tunerconfig = { + 0x61 +}; + +static struct lgdt330x_config fusionhdtv_5_express = { + .demod_chip = LGDT3303, + .serial_mpeg = 0x40, +}; + +static struct s5h1409_config hauppauge_hvr1500q_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_ON, + .qam_if = 44000, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, +}; + +static struct s5h1409_config dvico_s5h1409_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_ON, + .qam_if = 44000, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, +}; + +static struct s5h1411_config dvico_s5h1411_config = { + .output_mode = S5H1411_SERIAL_OUTPUT, + .gpio = S5H1411_GPIO_ON, + .qam_if = S5H1411_IF_44000, + .vsb_if = S5H1411_IF_44000, + .inversion = S5H1411_INVERSION_OFF, + .status_mode = S5H1411_DEMODLOCKING, + .mpeg_timing = S5H1411_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, +}; + +static struct s5h1411_config hcw_s5h1411_config = { + .output_mode = S5H1411_SERIAL_OUTPUT, + .gpio = S5H1411_GPIO_OFF, + .vsb_if = S5H1411_IF_44000, + .qam_if = S5H1411_IF_4000, + .inversion = S5H1411_INVERSION_ON, + .status_mode = S5H1411_DEMODLOCKING, + .mpeg_timing = S5H1411_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, +}; + +static struct xc5000_config hauppauge_hvr1500q_tunerconfig = { + .i2c_address = 0x61, + .if_khz = 5380, +}; + +static struct xc5000_config dvico_xc5000_tunerconfig = { + .i2c_address = 0x64, + .if_khz = 5380, +}; + +static struct tda829x_config tda829x_no_probe = { + .probe_tuner = TDA829X_DONT_PROBE, +}; + +static struct tda18271_std_map hauppauge_tda18271_std_map = { + .atsc_6 = { .if_freq = 5380, .agc_mode = 3, .std = 3, + .if_lvl = 6, .rfagc_top = 0x37 }, + .qam_6 = { .if_freq = 4000, .agc_mode = 3, .std = 0, + .if_lvl = 6, .rfagc_top = 0x37 }, +}; + +static struct tda18271_std_map hauppauge_hvr1200_tda18271_std_map = { + .dvbt_6 = { .if_freq = 3300, .agc_mode = 3, .std = 4, + .if_lvl = 1, .rfagc_top = 0x37, }, + .dvbt_7 = { .if_freq = 3800, .agc_mode = 3, .std = 5, + .if_lvl = 1, .rfagc_top = 0x37, }, + .dvbt_8 = { .if_freq = 4300, .agc_mode = 3, .std = 6, + .if_lvl = 1, .rfagc_top = 0x37, }, +}; + +static struct tda18271_config hauppauge_tda18271_config = { + .std_map = &hauppauge_tda18271_std_map, + .gate = TDA18271_GATE_ANALOG, + .output_opt = TDA18271_OUTPUT_LT_OFF, +}; + +static struct tda18271_config hauppauge_hvr1200_tuner_config = { + .std_map = &hauppauge_hvr1200_tda18271_std_map, + .gate = TDA18271_GATE_ANALOG, + .output_opt = TDA18271_OUTPUT_LT_OFF, +}; + +static struct tda18271_config hauppauge_hvr1210_tuner_config = { + .gate = TDA18271_GATE_DIGITAL, + .output_opt = TDA18271_OUTPUT_LT_OFF, +}; + +static struct tda18271_config hauppauge_hvr4400_tuner_config = { + .gate = TDA18271_GATE_DIGITAL, + .output_opt = TDA18271_OUTPUT_LT_OFF, +}; + +static struct tda18271_std_map hauppauge_hvr127x_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 hauppauge_hvr127x_config = { + .std_map = &hauppauge_hvr127x_std_map, + .output_opt = TDA18271_OUTPUT_LT_OFF, +}; + +static struct lgdt3305_config hauppauge_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 dibx000_agc_config xc3028_agc_config = { + BAND_VHF | BAND_UHF, /* band_caps */ + + /* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=0, + * P_agc_inv_pwm1=0, P_agc_inv_pwm2=0, + * P_agc_inh_dc_rv_est=0, P_agc_time_est=3, P_agc_freeze=0, + * P_agc_nb_est=2, P_agc_write=0 + */ + (0 << 15) | (0 << 14) | (0 << 11) | (0 << 10) | (0 << 9) | (0 << 8) | + (3 << 5) | (0 << 4) | (2 << 1) | (0 << 0), /* setup */ + + 712, /* inv_gain */ + 21, /* time_stabiliz */ + + 0, /* alpha_level */ + 118, /* thlock */ + + 0, /* wbd_inv */ + 2867, /* wbd_ref */ + 0, /* wbd_sel */ + 2, /* wbd_alpha */ + + 0, /* agc1_max */ + 0, /* agc1_min */ + 39718, /* agc2_max */ + 9930, /* agc2_min */ + 0, /* agc1_pt1 */ + 0, /* agc1_pt2 */ + 0, /* agc1_pt3 */ + 0, /* agc1_slope1 */ + 0, /* agc1_slope2 */ + 0, /* agc2_pt1 */ + 128, /* agc2_pt2 */ + 29, /* agc2_slope1 */ + 29, /* agc2_slope2 */ + + 17, /* alpha_mant */ + 27, /* alpha_exp */ + 23, /* beta_mant */ + 51, /* beta_exp */ + + 1, /* perform_agc_softsplit */ +}; + +/* PLL Configuration for COFDM BW_MHz = 8.000000 + * With external clock = 30.000000 */ +static struct dibx000_bandwidth_config xc3028_bw_config = { + 60000, /* internal */ + 30000, /* sampling */ + 1, /* pll_cfg: prediv */ + 8, /* pll_cfg: ratio */ + 3, /* pll_cfg: range */ + 1, /* pll_cfg: reset */ + 0, /* pll_cfg: bypass */ + 0, /* misc: refdiv */ + 0, /* misc: bypclk_div */ + 1, /* misc: IO_CLK_en_core */ + 1, /* misc: ADClkSrc */ + 0, /* misc: modulo */ + (3 << 14) | (1 << 12) | (524 << 0), /* sad_cfg: refsel, sel, freq_15k */ + (1 << 25) | 5816102, /* ifreq = 5.200000 MHz */ + 20452225, /* timf */ + 30000000 /* xtal_hz */ +}; + +static struct dib7000p_config hauppauge_hvr1400_dib7000_config = { + .output_mpeg2_in_188_bytes = 1, + .hostbus_diversity = 1, + .tuner_is_baseband = 0, + .update_lna = NULL, + + .agc_config_count = 1, + .agc = &xc3028_agc_config, + .bw = &xc3028_bw_config, + + .gpio_dir = DIB7000P_GPIO_DEFAULT_DIRECTIONS, + .gpio_val = DIB7000P_GPIO_DEFAULT_VALUES, + .gpio_pwm_pos = DIB7000P_GPIO_DEFAULT_PWM_POS, + + .pwm_freq_div = 0, + .agc_control = NULL, + .spur_protect = 0, + + .output_mode = OUTMODE_MPEG2_SERIAL, +}; + +static struct zl10353_config dvico_fusionhdtv_xc3028 = { + .demod_address = 0x0f, + .if2 = 45600, + .no_tuner = 1, + .disable_i2c_gate_ctrl = 1, +}; + +static struct stv0900_reg stv0900_ts_regs[] = { + { R0900_TSGENERAL, 0x00 }, + { R0900_P1_TSSPEED, 0x40 }, + { R0900_P2_TSSPEED, 0x40 }, + { R0900_P1_TSCFGM, 0xc0 }, + { R0900_P2_TSCFGM, 0xc0 }, + { R0900_P1_TSCFGH, 0xe0 }, + { R0900_P2_TSCFGH, 0xe0 }, + { R0900_P1_TSCFGL, 0x20 }, + { R0900_P2_TSCFGL, 0x20 }, + { 0xffff, 0xff }, /* terminate */ +}; + +static struct stv0900_config netup_stv0900_config = { + .demod_address = 0x68, + .demod_mode = 1, /* dual */ + .xtal = 8000000, + .clkmode = 3,/* 0-CLKI, 2-XTALI, else AUTO */ + .diseqc_mode = 2,/* 2/3 PWM */ + .ts_config_regs = stv0900_ts_regs, + .tun1_maddress = 0,/* 0x60 */ + .tun2_maddress = 3,/* 0x63 */ + .tun1_adc = 1,/* 1 Vpp */ + .tun2_adc = 1,/* 1 Vpp */ +}; + +static struct stv6110_config netup_stv6110_tunerconfig_a = { + .i2c_address = 0x60, + .mclk = 16000000, + .clk_div = 1, + .gain = 8, /* +16 dB - maximum gain */ +}; + +static struct stv6110_config netup_stv6110_tunerconfig_b = { + .i2c_address = 0x63, + .mclk = 16000000, + .clk_div = 1, + .gain = 8, /* +16 dB - maximum gain */ +}; + +static struct cx24116_config tbs_cx24116_config = { + .demod_address = 0x55, +}; + +static struct cx24117_config tbs_cx24117_config = { + .demod_address = 0x55, +}; + +static struct ds3000_config tevii_ds3000_config = { + .demod_address = 0x68, +}; + +static struct ts2020_config tevii_ts2020_config = { + .tuner_address = 0x60, + .clk_out_div = 1, + .frequency_div = 1146000, +}; + +static struct cx24116_config dvbworld_cx24116_config = { + .demod_address = 0x05, +}; + +static struct lgs8gxx_config mygica_x8506_lgs8gl5_config = { + .prod = LGS8GXX_PROD_LGS8GL5, + .demod_address = 0x19, + .serial_ts = 0, + .ts_clk_pol = 1, + .ts_clk_gated = 1, + .if_clk_freq = 30400, /* 30.4 MHz */ + .if_freq = 5380, /* 5.38 MHz */ + .if_neg_center = 1, + .ext_adc = 0, + .adc_signed = 0, + .if_neg_edge = 0, +}; + +static struct xc5000_config mygica_x8506_xc5000_config = { + .i2c_address = 0x61, + .if_khz = 5380, +}; + +static struct mb86a20s_config mygica_x8507_mb86a20s_config = { + .demod_address = 0x10, +}; + +static struct xc5000_config mygica_x8507_xc5000_config = { + .i2c_address = 0x61, + .if_khz = 4000, +}; + +static struct stv090x_config prof_8000_stv090x_config = { + .device = STV0903, + .demod_mode = STV090x_SINGLE, + .clk_mode = STV090x_CLK_EXT, + .xtal = 27000000, + .address = 0x6A, + .ts1_mode = STV090x_TSMODE_PARALLEL_PUNCTURED, + .repeater_level = STV090x_RPTLEVEL_64, + .adc1_range = STV090x_ADC_2Vpp, + .diseqc_envelope_mode = false, + + .tuner_get_frequency = stb6100_get_frequency, + .tuner_set_frequency = stb6100_set_frequency, + .tuner_set_bandwidth = stb6100_set_bandwidth, + .tuner_get_bandwidth = stb6100_get_bandwidth, +}; + +static struct stb6100_config prof_8000_stb6100_config = { + .tuner_address = 0x60, + .refclock = 27000000, +}; + +static struct lgdt3306a_config hauppauge_quadHD_ATSC_a_config = { + .i2c_addr = 0x59, + .qam_if_khz = 4000, + .vsb_if_khz = 3250, + .deny_i2c_rptr = 1, /* Disabled */ + .spectral_inversion = 0, /* Disabled */ + .mpeg_mode = LGDT3306A_MPEG_SERIAL, + .tpclk_edge = LGDT3306A_TPCLK_RISING_EDGE, + .tpvalid_polarity = LGDT3306A_TP_VALID_HIGH, + .xtalMHz = 25, /* 24 or 25 */ +}; + +static struct lgdt3306a_config hauppauge_quadHD_ATSC_b_config = { + .i2c_addr = 0x0e, + .qam_if_khz = 4000, + .vsb_if_khz = 3250, + .deny_i2c_rptr = 1, /* Disabled */ + .spectral_inversion = 0, /* Disabled */ + .mpeg_mode = LGDT3306A_MPEG_SERIAL, + .tpclk_edge = LGDT3306A_TPCLK_RISING_EDGE, + .tpvalid_polarity = LGDT3306A_TP_VALID_HIGH, + .xtalMHz = 25, /* 24 or 25 */ +}; + +static int p8000_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage voltage) +{ + struct cx23885_tsport *port = fe->dvb->priv; + struct cx23885_dev *dev = port->dev; + + if (voltage == SEC_VOLTAGE_18) + cx_write(MC417_RWD, 0x00001e00); + else if (voltage == SEC_VOLTAGE_13) + cx_write(MC417_RWD, 0x00001a00); + else + cx_write(MC417_RWD, 0x00001800); + return 0; +} + +static int dvbsky_t9580_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage voltage) +{ + struct cx23885_tsport *port = fe->dvb->priv; + struct cx23885_dev *dev = port->dev; + + cx23885_gpio_enable(dev, GPIO_0 | GPIO_1, 1); + + switch (voltage) { + case SEC_VOLTAGE_13: + cx23885_gpio_set(dev, GPIO_1); + cx23885_gpio_clear(dev, GPIO_0); + break; + case SEC_VOLTAGE_18: + cx23885_gpio_set(dev, GPIO_1); + cx23885_gpio_set(dev, GPIO_0); + break; + case SEC_VOLTAGE_OFF: + cx23885_gpio_clear(dev, GPIO_1); + cx23885_gpio_clear(dev, GPIO_0); + break; + } + + /* call the frontend set_voltage function */ + port->fe_set_voltage(fe, voltage); + + return 0; +} + +static int dvbsky_s952_portc_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage voltage) +{ + struct cx23885_tsport *port = fe->dvb->priv; + struct cx23885_dev *dev = port->dev; + + cx23885_gpio_enable(dev, GPIO_12 | GPIO_13, 1); + + switch (voltage) { + case SEC_VOLTAGE_13: + cx23885_gpio_set(dev, GPIO_13); + cx23885_gpio_clear(dev, GPIO_12); + break; + case SEC_VOLTAGE_18: + cx23885_gpio_set(dev, GPIO_13); + cx23885_gpio_set(dev, GPIO_12); + break; + case SEC_VOLTAGE_OFF: + cx23885_gpio_clear(dev, GPIO_13); + cx23885_gpio_clear(dev, GPIO_12); + break; + } + /* call the frontend set_voltage function */ + return port->fe_set_voltage(fe, voltage); +} + +static int cx23885_sp2_ci_ctrl(void *priv, u8 read, int addr, + u8 data, int *mem) +{ + /* MC417 */ + #define SP2_DATA 0x000000ff + #define SP2_WR 0x00008000 + #define SP2_RD 0x00004000 + #define SP2_ACK 0x00001000 + #define SP2_ADHI 0x00000800 + #define SP2_ADLO 0x00000400 + #define SP2_CS1 0x00000200 + #define SP2_CS0 0x00000100 + #define SP2_EN_ALL 0x00001000 + #define SP2_CTRL_OFF (SP2_CS1 | SP2_CS0 | SP2_WR | SP2_RD) + + struct cx23885_tsport *port = priv; + struct cx23885_dev *dev = port->dev; + int ret; + int tmp = 0; + unsigned long timeout; + + mutex_lock(&dev->gpio_lock); + + /* write addr */ + cx_write(MC417_OEN, SP2_EN_ALL); + cx_write(MC417_RWD, SP2_CTRL_OFF | + SP2_ADLO | (0xff & addr)); + cx_clear(MC417_RWD, SP2_ADLO); + cx_write(MC417_RWD, SP2_CTRL_OFF | + SP2_ADHI | (0xff & (addr >> 8))); + cx_clear(MC417_RWD, SP2_ADHI); + + if (read) + /* data in */ + cx_write(MC417_OEN, SP2_EN_ALL | SP2_DATA); + else + /* data out */ + cx_write(MC417_RWD, SP2_CTRL_OFF | data); + + /* chip select 0 */ + cx_clear(MC417_RWD, SP2_CS0); + + /* read/write */ + cx_clear(MC417_RWD, (read) ? SP2_RD : SP2_WR); + + /* wait for a maximum of 1 msec */ + timeout = jiffies + msecs_to_jiffies(1); + while (!time_after(jiffies, timeout)) { + tmp = cx_read(MC417_RWD); + if ((tmp & SP2_ACK) == 0) + break; + usleep_range(50, 100); + } + + cx_set(MC417_RWD, SP2_CTRL_OFF); + *mem = tmp & 0xff; + + mutex_unlock(&dev->gpio_lock); + + if (!read) { + if (*mem < 0) { + ret = -EREMOTEIO; + goto err; + } + } + + return 0; +err: + return ret; +} + +static int cx23885_dvb_set_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct cx23885_tsport *port = fe->dvb->priv; + struct cx23885_dev *dev = port->dev; + + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1275: + switch (p->modulation) { + case VSB_8: + cx23885_gpio_clear(dev, GPIO_5); + break; + case QAM_64: + case QAM_256: + default: + cx23885_gpio_set(dev, GPIO_5); + break; + } + break; + case CX23885_BOARD_MYGICA_X8506: + case CX23885_BOARD_MYGICA_X8507: + case CX23885_BOARD_MAGICPRO_PROHDTVE2: + /* Select Digital TV */ + cx23885_gpio_set(dev, GPIO_0); + break; + } + + /* Call the real set_frontend */ + if (port->set_frontend) + return port->set_frontend(fe); + + return 0; +} + +static void cx23885_set_frontend_hook(struct cx23885_tsport *port, + struct dvb_frontend *fe) +{ + port->set_frontend = fe->ops.set_frontend; + fe->ops.set_frontend = cx23885_dvb_set_frontend; +} + +static struct lgs8gxx_config magicpro_prohdtve2_lgs8g75_config = { + .prod = LGS8GXX_PROD_LGS8G75, + .demod_address = 0x19, + .serial_ts = 0, + .ts_clk_pol = 1, + .ts_clk_gated = 1, + .if_clk_freq = 30400, /* 30.4 MHz */ + .if_freq = 6500, /* 6.50 MHz */ + .if_neg_center = 1, + .ext_adc = 0, + .adc_signed = 1, + .adc_vpp = 2, /* 1.6 Vpp */ + .if_neg_edge = 1, +}; + +static struct xc5000_config magicpro_prohdtve2_xc5000_config = { + .i2c_address = 0x61, + .if_khz = 6500, +}; + +static struct atbm8830_config mygica_x8558pro_atbm8830_cfg1 = { + .prod = ATBM8830_PROD_8830, + .demod_address = 0x44, + .serial_ts = 0, + .ts_sampling_edge = 1, + .ts_clk_gated = 0, + .osc_clk_freq = 30400, /* in kHz */ + .if_freq = 0, /* zero IF */ + .zif_swap_iq = 1, + .agc_min = 0x2E, + .agc_max = 0xFF, + .agc_hold_loop = 0, +}; + +static struct max2165_config mygic_x8558pro_max2165_cfg1 = { + .i2c_address = 0x60, + .osc_clk = 20 +}; + +static struct atbm8830_config mygica_x8558pro_atbm8830_cfg2 = { + .prod = ATBM8830_PROD_8830, + .demod_address = 0x44, + .serial_ts = 1, + .ts_sampling_edge = 1, + .ts_clk_gated = 0, + .osc_clk_freq = 30400, /* in kHz */ + .if_freq = 0, /* zero IF */ + .zif_swap_iq = 1, + .agc_min = 0x2E, + .agc_max = 0xFF, + .agc_hold_loop = 0, +}; + +static struct max2165_config mygic_x8558pro_max2165_cfg2 = { + .i2c_address = 0x60, + .osc_clk = 20 +}; +static struct stv0367_config netup_stv0367_config[] = { + { + .demod_address = 0x1c, + .xtal = 27000000, + .if_khz = 4500, + .if_iq_mode = 0, + .ts_mode = 1, + .clk_pol = 0, + }, { + .demod_address = 0x1d, + .xtal = 27000000, + .if_khz = 4500, + .if_iq_mode = 0, + .ts_mode = 1, + .clk_pol = 0, + }, +}; + +static struct xc5000_config netup_xc5000_config[] = { + { + .i2c_address = 0x61, + .if_khz = 4500, + }, { + .i2c_address = 0x64, + .if_khz = 4500, + }, +}; + +static struct drxk_config terratec_drxk_config[] = { + { + .adr = 0x29, + .no_i2c_bridge = 1, + }, { + .adr = 0x2a, + .no_i2c_bridge = 1, + }, +}; + +static struct mt2063_config terratec_mt2063_config[] = { + { + .tuner_address = 0x60, + }, { + .tuner_address = 0x67, + }, +}; + +static const struct tda10071_platform_data hauppauge_tda10071_pdata = { + .clk = 40444000, /* 40.444 MHz */ + .i2c_wr_max = 64, + .ts_mode = TDA10071_TS_SERIAL, + .pll_multiplier = 20, + .tuner_i2c_addr = 0x54, +}; + +static const struct m88ds3103_config dvbsky_t9580_m88ds3103_config = { + .i2c_addr = 0x68, + .clock = 27000000, + .i2c_wr_max = 33, + .clock_out = 0, + .ts_mode = M88DS3103_TS_PARALLEL, + .ts_clk = 16000, + .ts_clk_pol = 1, + .lnb_en_pol = 1, + .lnb_hv_pol = 0, + .agc = 0x99, +}; + +static const struct m88ds3103_config dvbsky_s950c_m88ds3103_config = { + .i2c_addr = 0x68, + .clock = 27000000, + .i2c_wr_max = 33, + .clock_out = 0, + .ts_mode = M88DS3103_TS_CI, + .ts_clk = 10000, + .ts_clk_pol = 1, + .lnb_en_pol = 1, + .lnb_hv_pol = 0, + .agc = 0x99, +}; + +static const struct m88ds3103_config hauppauge_hvr5525_m88ds3103_config = { + .i2c_addr = 0x69, + .clock = 27000000, + .i2c_wr_max = 33, + .ts_mode = M88DS3103_TS_PARALLEL, + .ts_clk = 16000, + .ts_clk_pol = 1, + .agc = 0x99, +}; + +static struct lgdt3306a_config hauppauge_hvr1265k4_config = { + .i2c_addr = 0x59, + .qam_if_khz = 4000, + .vsb_if_khz = 3250, + .deny_i2c_rptr = 1, /* Disabled */ + .spectral_inversion = 0, /* Disabled */ + .mpeg_mode = LGDT3306A_MPEG_SERIAL, + .tpclk_edge = LGDT3306A_TPCLK_RISING_EDGE, + .tpvalid_polarity = LGDT3306A_TP_VALID_HIGH, + .xtalMHz = 25, /* 24 or 25 */ +}; + +static int netup_altera_fpga_rw(void *device, int flag, int data, int read) +{ + struct cx23885_dev *dev = (struct cx23885_dev *)device; + unsigned long timeout = jiffies + msecs_to_jiffies(1); + uint32_t mem = 0; + + mem = cx_read(MC417_RWD); + if (read) + cx_set(MC417_OEN, ALT_DATA); + else { + cx_clear(MC417_OEN, ALT_DATA);/* D0-D7 out */ + mem &= ~ALT_DATA; + mem |= (data & ALT_DATA); + } + + if (flag) + mem |= ALT_AD_RG; + else + mem &= ~ALT_AD_RG; + + mem &= ~ALT_CS; + if (read) + mem = (mem & ~ALT_RD) | ALT_WR; + else + mem = (mem & ~ALT_WR) | ALT_RD; + + cx_write(MC417_RWD, mem); /* start RW cycle */ + + for (;;) { + mem = cx_read(MC417_RWD); + if ((mem & ALT_RDY) == 0) + break; + if (time_after(jiffies, timeout)) + break; + udelay(1); + } + + cx_set(MC417_RWD, ALT_RD | ALT_WR | ALT_CS); + if (read) + return mem & ALT_DATA; + + return 0; +}; + +static int dib7070_tuner_reset(struct dvb_frontend *fe, int onoff) +{ + struct dib7000p_ops *dib7000p_ops = fe->sec_priv; + + return dib7000p_ops->set_gpio(fe, 8, 0, !onoff); +} + +static int dib7070_tuner_sleep(struct dvb_frontend *fe, int onoff) +{ + return 0; +} + +static struct dib0070_config dib7070p_dib0070_config = { + .i2c_address = DEFAULT_DIB0070_I2C_ADDRESS, + .reset = dib7070_tuner_reset, + .sleep = dib7070_tuner_sleep, + .clock_khz = 12000, + .freq_offset_khz_vhf = 550, + /* .flip_chip = 1, */ +}; + +/* DIB7070 generic */ +static struct dibx000_agc_config dib7070_agc_config = { + .band_caps = BAND_UHF | BAND_VHF | BAND_LBAND | BAND_SBAND, + + /* + * P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=5, + * P_agc_inv_pwm1=0, P_agc_inv_pwm2=0, P_agc_inh_dc_rv_est=0, + * P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=5, P_agc_write=0 + */ + .setup = (0 << 15) | (0 << 14) | (5 << 11) | (0 << 10) | (0 << 9) | + (0 << 8) | (3 << 5) | (0 << 4) | (5 << 1) | (0 << 0), + .inv_gain = 600, + .time_stabiliz = 10, + .alpha_level = 0, + .thlock = 118, + .wbd_inv = 0, + .wbd_ref = 3530, + .wbd_sel = 1, + .wbd_alpha = 5, + .agc1_max = 65535, + .agc1_min = 0, + .agc2_max = 65535, + .agc2_min = 0, + .agc1_pt1 = 0, + .agc1_pt2 = 40, + .agc1_pt3 = 183, + .agc1_slope1 = 206, + .agc1_slope2 = 255, + .agc2_pt1 = 72, + .agc2_pt2 = 152, + .agc2_slope1 = 88, + .agc2_slope2 = 90, + .alpha_mant = 17, + .alpha_exp = 27, + .beta_mant = 23, + .beta_exp = 51, + .perform_agc_softsplit = 0, +}; + +static struct dibx000_bandwidth_config dib7070_bw_config_12_mhz = { + .internal = 60000, + .sampling = 15000, + .pll_prediv = 1, + .pll_ratio = 20, + .pll_range = 3, + .pll_reset = 1, + .pll_bypass = 0, + .enable_refdiv = 0, + .bypclk_div = 0, + .IO_CLK_en_core = 1, + .ADClkSrc = 1, + .modulo = 2, + /* refsel, sel, freq_15k */ + .sad_cfg = (3 << 14) | (1 << 12) | (524 << 0), + .ifreq = (0 << 25) | 0, + .timf = 20452225, + .xtal_hz = 12000000, +}; + +static struct dib7000p_config dib7070p_dib7000p_config = { + /* .output_mode = OUTMODE_MPEG2_FIFO, */ + .output_mode = OUTMODE_MPEG2_SERIAL, + /* .output_mode = OUTMODE_MPEG2_PAR_GATED_CLK, */ + .output_mpeg2_in_188_bytes = 1, + + .agc_config_count = 1, + .agc = &dib7070_agc_config, + .bw = &dib7070_bw_config_12_mhz, + .tuner_is_baseband = 1, + .spur_protect = 1, + + .gpio_dir = 0xfcef, /* DIB7000P_GPIO_DEFAULT_DIRECTIONS, */ + .gpio_val = 0x0110, /* DIB7000P_GPIO_DEFAULT_VALUES, */ + .gpio_pwm_pos = DIB7000P_GPIO_DEFAULT_PWM_POS, + + .hostbus_diversity = 1, +}; + +static int dvb_register_ci_mac(struct cx23885_tsport *port) +{ + struct cx23885_dev *dev = port->dev; + struct i2c_client *client_ci = NULL; + struct vb2_dvb_frontend *fe0; + + fe0 = vb2_dvb_get_frontend(&port->frontends, 1); + if (!fe0) + return -EINVAL; + + switch (dev->board) { + case CX23885_BOARD_NETUP_DUAL_DVBS2_CI: { + static struct netup_card_info cinfo; + + netup_get_card_info(&dev->i2c_bus[0].i2c_adap, &cinfo); + memcpy(port->frontends.adapter.proposed_mac, + cinfo.port[port->nr - 1].mac, 6); + pr_info("NetUP Dual DVB-S2 CI card port%d MAC=%pM\n", + port->nr, port->frontends.adapter.proposed_mac); + + netup_ci_init(port); + return 0; + } + case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: { + struct altera_ci_config netup_ci_cfg = { + .dev = dev,/* magic number to identify*/ + .adapter = &port->frontends.adapter,/* for CI */ + .demux = &fe0->dvb.demux,/* for hw pid filter */ + .fpga_rw = netup_altera_fpga_rw, + }; + + altera_ci_init(&netup_ci_cfg, port->nr); + return 0; + } + case CX23885_BOARD_TEVII_S470: { + u8 eeprom[256]; /* 24C02 i2c eeprom */ + + if (port->nr != 1) + return 0; + + /* Read entire EEPROM */ + dev->i2c_bus[0].i2c_client.addr = 0xa0 >> 1; + tveeprom_read(&dev->i2c_bus[0].i2c_client, eeprom, sizeof(eeprom)); + pr_info("TeVii S470 MAC= %pM\n", eeprom + 0xa0); + memcpy(port->frontends.adapter.proposed_mac, eeprom + 0xa0, 6); + return 0; + } + case CX23885_BOARD_DVBSKY_T9580: + case CX23885_BOARD_DVBSKY_S950: + case CX23885_BOARD_DVBSKY_S952: + case CX23885_BOARD_DVBSKY_T982: { + u8 eeprom[256]; /* 24C02 i2c eeprom */ + + if (port->nr > 2) + return 0; + + /* Read entire EEPROM */ + dev->i2c_bus[0].i2c_client.addr = 0xa0 >> 1; + tveeprom_read(&dev->i2c_bus[0].i2c_client, eeprom, + sizeof(eeprom)); + pr_info("%s port %d MAC address: %pM\n", + cx23885_boards[dev->board].name, port->nr, + eeprom + 0xc0 + (port->nr-1) * 8); + memcpy(port->frontends.adapter.proposed_mac, eeprom + 0xc0 + + (port->nr-1) * 8, 6); + return 0; + } + case CX23885_BOARD_DVBSKY_S950C: + case CX23885_BOARD_DVBSKY_T980C: + case CX23885_BOARD_TT_CT2_4500_CI: { + u8 eeprom[256]; /* 24C02 i2c eeprom */ + struct sp2_config sp2_config; + struct i2c_board_info info; + struct cx23885_i2c *i2c_bus = &dev->i2c_bus[0]; + + /* attach CI */ + memset(&sp2_config, 0, sizeof(sp2_config)); + sp2_config.dvb_adap = &port->frontends.adapter; + sp2_config.priv = port; + sp2_config.ci_control = cx23885_sp2_ci_ctrl; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "sp2", I2C_NAME_SIZE); + info.addr = 0x40; + info.platform_data = &sp2_config; + request_module(info.type); + client_ci = i2c_new_client_device(&i2c_bus->i2c_adap, &info); + if (!i2c_client_has_driver(client_ci)) + return -ENODEV; + if (!try_module_get(client_ci->dev.driver->owner)) { + i2c_unregister_device(client_ci); + return -ENODEV; + } + port->i2c_client_ci = client_ci; + + if (port->nr != 1) + return 0; + + /* Read entire EEPROM */ + dev->i2c_bus[0].i2c_client.addr = 0xa0 >> 1; + tveeprom_read(&dev->i2c_bus[0].i2c_client, eeprom, + sizeof(eeprom)); + pr_info("%s MAC address: %pM\n", + cx23885_boards[dev->board].name, eeprom + 0xc0); + memcpy(port->frontends.adapter.proposed_mac, eeprom + 0xc0, 6); + return 0; + } + } + return 0; +} + +static int dvb_register(struct cx23885_tsport *port) +{ + struct dib7000p_ops dib7000p_ops; + struct cx23885_dev *dev = port->dev; + struct cx23885_i2c *i2c_bus = NULL, *i2c_bus2 = NULL; + struct vb2_dvb_frontend *fe0, *fe1 = NULL; + struct si2168_config si2168_config; + struct si2165_platform_data si2165_pdata; + struct si2157_config si2157_config; + struct ts2020_config ts2020_config; + struct m88ds3103_platform_data m88ds3103_pdata; + struct m88rs6000t_config m88rs6000t_config = {}; + struct a8293_platform_data a8293_pdata = {}; + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client_demod = NULL, *client_tuner = NULL; + struct i2c_client *client_sec = NULL; + int (*p_set_voltage)(struct dvb_frontend *fe, + enum fe_sec_voltage voltage) = NULL; + int mfe_shared = 0; /* bus not shared by default */ + int ret; + + /* Get the first frontend */ + fe0 = vb2_dvb_get_frontend(&port->frontends, 1); + if (!fe0) + return -EINVAL; + + /* init struct vb2_dvb */ + fe0->dvb.name = dev->name; + + /* multi-frontend gate control is undefined or defaults to fe0 */ + port->frontends.gate = 0; + + /* Sets the gate control callback to be used by i2c command calls */ + port->gate_ctrl = cx23885_dvb_gate_ctrl; + + /* init frontend */ + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1250: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(s5h1409_attach, + &hauppauge_generic_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + dvb_attach(mt2131_attach, fe0->dvb.frontend, + &i2c_bus->i2c_adap, + &hauppauge_generic_tunerconfig, 0); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1275: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(lgdt3305_attach, + &hauppauge_lgdt3305_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_bus[1].i2c_adap, + &hauppauge_hvr127x_config); + if (dev->board == CX23885_BOARD_HAUPPAUGE_HVR1275) + cx23885_set_frontend_hook(port, fe0->dvb.frontend); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1255: + case CX23885_BOARD_HAUPPAUGE_HVR1255_22111: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(s5h1411_attach, + &hcw_s5h1411_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_bus[1].i2c_adap, + &hauppauge_tda18271_config); + + tda18271_attach(&dev->ts1.analog_fe, + 0x60, &dev->i2c_bus[1].i2c_adap, + &hauppauge_tda18271_config); + + break; + case CX23885_BOARD_HAUPPAUGE_HVR1800: + i2c_bus = &dev->i2c_bus[0]; + switch (alt_tuner) { + case 1: + fe0->dvb.frontend = + dvb_attach(s5h1409_attach, + &hauppauge_ezqam_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_bus[1].i2c_adap, 0x42, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_bus[1].i2c_adap, + &hauppauge_tda18271_config); + break; + case 0: + default: + fe0->dvb.frontend = + dvb_attach(s5h1409_attach, + &hauppauge_generic_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + dvb_attach(mt2131_attach, fe0->dvb.frontend, + &i2c_bus->i2c_adap, + &hauppauge_generic_tunerconfig, 0); + } + break; + case CX23885_BOARD_HAUPPAUGE_HVR1800lp: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(s5h1409_attach, + &hauppauge_hvr1800lp_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + dvb_attach(mt2131_attach, fe0->dvb.frontend, + &i2c_bus->i2c_adap, + &hauppauge_generic_tunerconfig, 0); + break; + case CX23885_BOARD_DVICO_FUSIONHDTV_5_EXP: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(lgdt330x_attach, + &fusionhdtv_5_express, + 0x0e, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &i2c_bus->i2c_adap, 0x61, + TUNER_LG_TDVS_H06XF); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1500Q: + i2c_bus = &dev->i2c_bus[1]; + fe0->dvb.frontend = dvb_attach(s5h1409_attach, + &hauppauge_hvr1500q_config, + &dev->i2c_bus[0].i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + dvb_attach(xc5000_attach, fe0->dvb.frontend, + &i2c_bus->i2c_adap, + &hauppauge_hvr1500q_tunerconfig); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1500: + i2c_bus = &dev->i2c_bus[1]; + fe0->dvb.frontend = dvb_attach(s5h1409_attach, + &hauppauge_hvr1500_config, + &dev->i2c_bus[0].i2c_adap); + if (fe0->dvb.frontend != NULL) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &i2c_bus->i2c_adap, + .i2c_addr = 0x61, + }; + static struct xc2028_ctrl ctl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + .demod = XC3028_FE_OREN538, + }; + + fe = dvb_attach(xc2028_attach, + fe0->dvb.frontend, &cfg); + if (fe != NULL && fe->ops.tuner_ops.set_config != NULL) + fe->ops.tuner_ops.set_config(fe, &ctl); + } + break; + case CX23885_BOARD_HAUPPAUGE_HVR1200: + case CX23885_BOARD_HAUPPAUGE_HVR1700: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(tda10048_attach, + &hauppauge_hvr1200_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_bus[1].i2c_adap, 0x42, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_bus[1].i2c_adap, + &hauppauge_hvr1200_tuner_config); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1210: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(tda10048_attach, + &hauppauge_hvr1210_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_bus[1].i2c_adap, + &hauppauge_hvr1210_tuner_config); + } + break; + case CX23885_BOARD_HAUPPAUGE_HVR1400: + i2c_bus = &dev->i2c_bus[0]; + + if (!dvb_attach(dib7000p_attach, &dib7000p_ops)) + return -ENODEV; + + fe0->dvb.frontend = dib7000p_ops.init(&i2c_bus->i2c_adap, + 0x12, &hauppauge_hvr1400_dib7000_config); + if (fe0->dvb.frontend != NULL) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &dev->i2c_bus[1].i2c_adap, + .i2c_addr = 0x64, + }; + static struct xc2028_ctrl ctl = { + .fname = XC3028L_DEFAULT_FIRMWARE, + .max_len = 64, + .demod = XC3028_FE_DIBCOM52, + /* This is true for all demods with + v36 firmware? */ + .type = XC2028_D2633, + }; + + fe = dvb_attach(xc2028_attach, + fe0->dvb.frontend, &cfg); + if (fe != NULL && fe->ops.tuner_ops.set_config != NULL) + fe->ops.tuner_ops.set_config(fe, &ctl); + } + break; + case CX23885_BOARD_DVICO_FUSIONHDTV_7_DUAL_EXP: + i2c_bus = &dev->i2c_bus[port->nr - 1]; + + fe0->dvb.frontend = dvb_attach(s5h1409_attach, + &dvico_s5h1409_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + fe0->dvb.frontend = dvb_attach(s5h1411_attach, + &dvico_s5h1411_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) + dvb_attach(xc5000_attach, fe0->dvb.frontend, + &i2c_bus->i2c_adap, + &dvico_xc5000_tunerconfig); + break; + case CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP: { + i2c_bus = &dev->i2c_bus[port->nr - 1]; + + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &dvico_fusionhdtv_xc3028, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &i2c_bus->i2c_adap, + .i2c_addr = 0x61, + }; + static struct xc2028_ctrl ctl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + .demod = XC3028_FE_ZARLINK456, + }; + + fe = dvb_attach(xc2028_attach, fe0->dvb.frontend, + &cfg); + if (fe != NULL && fe->ops.tuner_ops.set_config != NULL) + fe->ops.tuner_ops.set_config(fe, &ctl); + } + break; + } + case CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP2: { + i2c_bus = &dev->i2c_bus[port->nr - 1]; + /* cxusb_ctrl_msg(adap->dev, CMD_DIGITAL, NULL, 0, NULL, 0); */ + /* cxusb_bluebird_gpio_pulse(adap->dev, 0x02, 1); */ + + if (!dvb_attach(dib7000p_attach, &dib7000p_ops)) + return -ENODEV; + + if (dib7000p_ops.i2c_enumeration(&i2c_bus->i2c_adap, 1, 0x12, &dib7070p_dib7000p_config) < 0) { + pr_warn("Unable to enumerate dib7000p\n"); + return -ENODEV; + } + fe0->dvb.frontend = dib7000p_ops.init(&i2c_bus->i2c_adap, 0x80, &dib7070p_dib7000p_config); + if (fe0->dvb.frontend != NULL) { + struct i2c_adapter *tun_i2c; + + fe0->dvb.frontend->sec_priv = kmemdup(&dib7000p_ops, sizeof(dib7000p_ops), GFP_KERNEL); + if (!fe0->dvb.frontend->sec_priv) + return -ENOMEM; + tun_i2c = dib7000p_ops.get_i2c_master(fe0->dvb.frontend, DIBX000_I2C_INTERFACE_TUNER, 1); + if (!dvb_attach(dib0070_attach, fe0->dvb.frontend, tun_i2c, &dib7070p_dib0070_config)) + return -ENODEV; + } + break; + } + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H: + case CX23885_BOARD_COMPRO_VIDEOMATE_E650F: + case CX23885_BOARD_COMPRO_VIDEOMATE_E800: + i2c_bus = &dev->i2c_bus[0]; + + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &dvico_fusionhdtv_xc3028, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &dev->i2c_bus[1].i2c_adap, + .i2c_addr = 0x61, + }; + static struct xc2028_ctrl ctl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + .demod = XC3028_FE_ZARLINK456, + }; + + fe = dvb_attach(xc2028_attach, fe0->dvb.frontend, + &cfg); + if (fe != NULL && fe->ops.tuner_ops.set_config != NULL) + fe->ops.tuner_ops.set_config(fe, &ctl); + } + break; + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000: + i2c_bus = &dev->i2c_bus[0]; + + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &dvico_fusionhdtv_xc3028, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + struct dvb_frontend *fe; + struct xc4000_config cfg = { + .i2c_address = 0x61, + .default_pm = 0, + .dvb_amplitude = 134, + .set_smoothedcvbs = 1, + .if_khz = 4560 + }; + + fe = dvb_attach(xc4000_attach, fe0->dvb.frontend, + &dev->i2c_bus[1].i2c_adap, &cfg); + if (!fe) { + pr_err("%s/2: xc4000 attach failed\n", + dev->name); + goto frontend_detach; + } + } + break; + case CX23885_BOARD_TBS_6920: + i2c_bus = &dev->i2c_bus[1]; + + fe0->dvb.frontend = dvb_attach(cx24116_attach, + &tbs_cx24116_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) + fe0->dvb.frontend->ops.set_voltage = f300_set_voltage; + + break; + case CX23885_BOARD_TBS_6980: + case CX23885_BOARD_TBS_6981: + i2c_bus = &dev->i2c_bus[1]; + + switch (port->nr) { + /* PORT B */ + case 1: + fe0->dvb.frontend = dvb_attach(cx24117_attach, + &tbs_cx24117_config, + &i2c_bus->i2c_adap); + break; + /* PORT C */ + case 2: + fe0->dvb.frontend = dvb_attach(cx24117_attach, + &tbs_cx24117_config, + &i2c_bus->i2c_adap); + break; + } + break; + case CX23885_BOARD_TEVII_S470: + i2c_bus = &dev->i2c_bus[1]; + + fe0->dvb.frontend = dvb_attach(ds3000_attach, + &tevii_ds3000_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(ts2020_attach, fe0->dvb.frontend, + &tevii_ts2020_config, &i2c_bus->i2c_adap); + fe0->dvb.frontend->ops.set_voltage = f300_set_voltage; + } + + break; + case CX23885_BOARD_DVBWORLD_2005: + i2c_bus = &dev->i2c_bus[1]; + + fe0->dvb.frontend = dvb_attach(cx24116_attach, + &dvbworld_cx24116_config, + &i2c_bus->i2c_adap); + break; + case CX23885_BOARD_NETUP_DUAL_DVBS2_CI: + i2c_bus = &dev->i2c_bus[0]; + switch (port->nr) { + /* port B */ + case 1: + fe0->dvb.frontend = dvb_attach(stv0900_attach, + &netup_stv0900_config, + &i2c_bus->i2c_adap, 0); + if (fe0->dvb.frontend != NULL) { + if (dvb_attach(stv6110_attach, + fe0->dvb.frontend, + &netup_stv6110_tunerconfig_a, + &i2c_bus->i2c_adap)) { + if (!dvb_attach(lnbh24_attach, + fe0->dvb.frontend, + &i2c_bus->i2c_adap, + LNBH24_PCL | LNBH24_TTX, + LNBH24_TEN, 0x09)) + pr_err("No LNBH24 found!\n"); + + } + } + break; + /* port C */ + case 2: + fe0->dvb.frontend = dvb_attach(stv0900_attach, + &netup_stv0900_config, + &i2c_bus->i2c_adap, 1); + if (fe0->dvb.frontend != NULL) { + if (dvb_attach(stv6110_attach, + fe0->dvb.frontend, + &netup_stv6110_tunerconfig_b, + &i2c_bus->i2c_adap)) { + if (!dvb_attach(lnbh24_attach, + fe0->dvb.frontend, + &i2c_bus->i2c_adap, + LNBH24_PCL | LNBH24_TTX, + LNBH24_TEN, 0x0a)) + pr_err("No LNBH24 found!\n"); + + } + } + break; + } + break; + case CX23885_BOARD_MYGICA_X8506: + i2c_bus = &dev->i2c_bus[0]; + i2c_bus2 = &dev->i2c_bus[1]; + fe0->dvb.frontend = dvb_attach(lgs8gxx_attach, + &mygica_x8506_lgs8gl5_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + dvb_attach(xc5000_attach, fe0->dvb.frontend, + &i2c_bus2->i2c_adap, &mygica_x8506_xc5000_config); + cx23885_set_frontend_hook(port, fe0->dvb.frontend); + break; + case CX23885_BOARD_MYGICA_X8507: + i2c_bus = &dev->i2c_bus[0]; + i2c_bus2 = &dev->i2c_bus[1]; + fe0->dvb.frontend = dvb_attach(mb86a20s_attach, + &mygica_x8507_mb86a20s_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + + dvb_attach(xc5000_attach, fe0->dvb.frontend, + &i2c_bus2->i2c_adap, + &mygica_x8507_xc5000_config); + cx23885_set_frontend_hook(port, fe0->dvb.frontend); + break; + case CX23885_BOARD_MAGICPRO_PROHDTVE2: + i2c_bus = &dev->i2c_bus[0]; + i2c_bus2 = &dev->i2c_bus[1]; + fe0->dvb.frontend = dvb_attach(lgs8gxx_attach, + &magicpro_prohdtve2_lgs8g75_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + dvb_attach(xc5000_attach, fe0->dvb.frontend, + &i2c_bus2->i2c_adap, + &magicpro_prohdtve2_xc5000_config); + cx23885_set_frontend_hook(port, fe0->dvb.frontend); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1850: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(s5h1411_attach, + &hcw_s5h1411_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_bus[0].i2c_adap, + &hauppauge_tda18271_config); + + tda18271_attach(&dev->ts1.analog_fe, + 0x60, &dev->i2c_bus[1].i2c_adap, + &hauppauge_tda18271_config); + + break; + case CX23885_BOARD_HAUPPAUGE_HVR1290: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(s5h1411_attach, + &hcw_s5h1411_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_bus[0].i2c_adap, + &hauppauge_tda18271_config); + break; + case CX23885_BOARD_MYGICA_X8558PRO: + switch (port->nr) { + /* port B */ + case 1: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(atbm8830_attach, + &mygica_x8558pro_atbm8830_cfg1, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + dvb_attach(max2165_attach, fe0->dvb.frontend, + &i2c_bus->i2c_adap, + &mygic_x8558pro_max2165_cfg1); + break; + /* port C */ + case 2: + i2c_bus = &dev->i2c_bus[1]; + fe0->dvb.frontend = dvb_attach(atbm8830_attach, + &mygica_x8558pro_atbm8830_cfg2, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + dvb_attach(max2165_attach, fe0->dvb.frontend, + &i2c_bus->i2c_adap, + &mygic_x8558pro_max2165_cfg2); + } + break; + case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: + if (port->nr > 2) + return 0; + + i2c_bus = &dev->i2c_bus[0]; + mfe_shared = 1;/* MFE */ + port->frontends.gate = 0;/* not clear for me yet */ + /* ports B, C */ + /* MFE frontend 1 DVB-T */ + fe0->dvb.frontend = dvb_attach(stv0367ter_attach, + &netup_stv0367_config[port->nr - 1], + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + if (NULL == dvb_attach(xc5000_attach, fe0->dvb.frontend, + &i2c_bus->i2c_adap, + &netup_xc5000_config[port->nr - 1])) + goto frontend_detach; + /* load xc5000 firmware */ + fe0->dvb.frontend->ops.tuner_ops.init(fe0->dvb.frontend); + + /* MFE frontend 2 */ + fe1 = vb2_dvb_get_frontend(&port->frontends, 2); + if (fe1 == NULL) + goto frontend_detach; + /* DVB-C init */ + fe1->dvb.frontend = dvb_attach(stv0367cab_attach, + &netup_stv0367_config[port->nr - 1], + &i2c_bus->i2c_adap); + if (fe1->dvb.frontend == NULL) + break; + + fe1->dvb.frontend->id = 1; + if (NULL == dvb_attach(xc5000_attach, + fe1->dvb.frontend, + &i2c_bus->i2c_adap, + &netup_xc5000_config[port->nr - 1])) + goto frontend_detach; + break; + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + i2c_bus = &dev->i2c_bus[0]; + i2c_bus2 = &dev->i2c_bus[1]; + + switch (port->nr) { + /* port b */ + case 1: + fe0->dvb.frontend = dvb_attach(drxk_attach, + &terratec_drxk_config[0], + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + if (!dvb_attach(mt2063_attach, + fe0->dvb.frontend, + &terratec_mt2063_config[0], + &i2c_bus2->i2c_adap)) + goto frontend_detach; + break; + /* port c */ + case 2: + fe0->dvb.frontend = dvb_attach(drxk_attach, + &terratec_drxk_config[1], + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + if (!dvb_attach(mt2063_attach, + fe0->dvb.frontend, + &terratec_mt2063_config[1], + &i2c_bus2->i2c_adap)) + goto frontend_detach; + break; + } + break; + case CX23885_BOARD_TEVII_S471: + i2c_bus = &dev->i2c_bus[1]; + + fe0->dvb.frontend = dvb_attach(ds3000_attach, + &tevii_ds3000_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + dvb_attach(ts2020_attach, fe0->dvb.frontend, + &tevii_ts2020_config, &i2c_bus->i2c_adap); + break; + case CX23885_BOARD_PROF_8000: + i2c_bus = &dev->i2c_bus[0]; + + fe0->dvb.frontend = dvb_attach(stv090x_attach, + &prof_8000_stv090x_config, + &i2c_bus->i2c_adap, + STV090x_DEMODULATOR_0); + if (fe0->dvb.frontend == NULL) + break; + if (!dvb_attach(stb6100_attach, + fe0->dvb.frontend, + &prof_8000_stb6100_config, + &i2c_bus->i2c_adap)) + goto frontend_detach; + + fe0->dvb.frontend->ops.set_voltage = p8000_set_voltage; + break; + case CX23885_BOARD_HAUPPAUGE_HVR4400: { + struct tda10071_platform_data tda10071_pdata = hauppauge_tda10071_pdata; + struct a8293_platform_data a8293_pdata = {}; + + i2c_bus = &dev->i2c_bus[0]; + i2c_bus2 = &dev->i2c_bus[1]; + switch (port->nr) { + /* port b */ + case 1: + /* attach demod + tuner combo */ + memset(&info, 0, sizeof(info)); + strscpy(info.type, "tda10071_cx24118", I2C_NAME_SIZE); + info.addr = 0x05; + info.platform_data = &tda10071_pdata; + request_module("tda10071"); + client_demod = i2c_new_client_device(&i2c_bus->i2c_adap, &info); + if (!i2c_client_has_driver(client_demod)) + goto frontend_detach; + if (!try_module_get(client_demod->dev.driver->owner)) { + i2c_unregister_device(client_demod); + goto frontend_detach; + } + fe0->dvb.frontend = tda10071_pdata.get_dvb_frontend(client_demod); + port->i2c_client_demod = client_demod; + + /* attach SEC */ + a8293_pdata.dvb_frontend = fe0->dvb.frontend; + memset(&info, 0, sizeof(info)); + strscpy(info.type, "a8293", I2C_NAME_SIZE); + info.addr = 0x0b; + info.platform_data = &a8293_pdata; + request_module("a8293"); + client_sec = i2c_new_client_device(&i2c_bus->i2c_adap, &info); + if (!i2c_client_has_driver(client_sec)) + goto frontend_detach; + if (!try_module_get(client_sec->dev.driver->owner)) { + i2c_unregister_device(client_sec); + goto frontend_detach; + } + port->i2c_client_sec = client_sec; + break; + /* port c */ + case 2: + /* attach frontend */ + memset(&si2165_pdata, 0, sizeof(si2165_pdata)); + si2165_pdata.fe = &fe0->dvb.frontend; + si2165_pdata.chip_mode = SI2165_MODE_PLL_XTAL; + si2165_pdata.ref_freq_hz = 16000000; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2165", I2C_NAME_SIZE); + info.addr = 0x64; + info.platform_data = &si2165_pdata; + request_module(info.type); + client_demod = i2c_new_client_device(&i2c_bus->i2c_adap, &info); + if (!i2c_client_has_driver(client_demod)) + goto frontend_detach; + if (!try_module_get(client_demod->dev.driver->owner)) { + i2c_unregister_device(client_demod); + goto frontend_detach; + } + port->i2c_client_demod = client_demod; + + if (fe0->dvb.frontend == NULL) + break; + fe0->dvb.frontend->ops.i2c_gate_ctrl = NULL; + if (!dvb_attach(tda18271_attach, + fe0->dvb.frontend, + 0x60, &i2c_bus2->i2c_adap, + &hauppauge_hvr4400_tuner_config)) + goto frontend_detach; + break; + } + break; + } + case CX23885_BOARD_HAUPPAUGE_STARBURST: { + struct tda10071_platform_data tda10071_pdata = hauppauge_tda10071_pdata; + struct a8293_platform_data a8293_pdata = {}; + + i2c_bus = &dev->i2c_bus[0]; + + /* attach demod + tuner combo */ + memset(&info, 0, sizeof(info)); + strscpy(info.type, "tda10071_cx24118", I2C_NAME_SIZE); + info.addr = 0x05; + info.platform_data = &tda10071_pdata; + request_module("tda10071"); + client_demod = i2c_new_client_device(&i2c_bus->i2c_adap, &info); + if (!i2c_client_has_driver(client_demod)) + goto frontend_detach; + if (!try_module_get(client_demod->dev.driver->owner)) { + i2c_unregister_device(client_demod); + goto frontend_detach; + } + fe0->dvb.frontend = tda10071_pdata.get_dvb_frontend(client_demod); + port->i2c_client_demod = client_demod; + + /* attach SEC */ + a8293_pdata.dvb_frontend = fe0->dvb.frontend; + memset(&info, 0, sizeof(info)); + strscpy(info.type, "a8293", I2C_NAME_SIZE); + info.addr = 0x0b; + info.platform_data = &a8293_pdata; + request_module("a8293"); + client_sec = i2c_new_client_device(&i2c_bus->i2c_adap, &info); + if (!i2c_client_has_driver(client_sec)) + goto frontend_detach; + if (!try_module_get(client_sec->dev.driver->owner)) { + i2c_unregister_device(client_sec); + goto frontend_detach; + } + port->i2c_client_sec = client_sec; + break; + } + case CX23885_BOARD_DVBSKY_T9580: + case CX23885_BOARD_DVBSKY_S950: + i2c_bus = &dev->i2c_bus[0]; + i2c_bus2 = &dev->i2c_bus[1]; + switch (port->nr) { + /* port b - satellite */ + case 1: + /* attach frontend */ + fe0->dvb.frontend = dvb_attach(m88ds3103_attach, + &dvbsky_t9580_m88ds3103_config, + &i2c_bus2->i2c_adap, &adapter); + if (fe0->dvb.frontend == NULL) + break; + + /* attach tuner */ + memset(&ts2020_config, 0, sizeof(ts2020_config)); + ts2020_config.fe = fe0->dvb.frontend; + ts2020_config.get_agc_pwm = m88ds3103_get_agc_pwm; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "ts2020", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &ts2020_config; + request_module(info.type); + client_tuner = i2c_new_client_device(adapter, &info); + if (!i2c_client_has_driver(client_tuner)) + goto frontend_detach; + if (!try_module_get(client_tuner->dev.driver->owner)) { + i2c_unregister_device(client_tuner); + goto frontend_detach; + } + + /* delegate signal strength measurement to tuner */ + fe0->dvb.frontend->ops.read_signal_strength = + fe0->dvb.frontend->ops.tuner_ops.get_rf_strength; + + /* + * for setting the voltage we need to set GPIOs on + * the card. + */ + port->fe_set_voltage = + fe0->dvb.frontend->ops.set_voltage; + fe0->dvb.frontend->ops.set_voltage = + dvbsky_t9580_set_voltage; + + port->i2c_client_tuner = client_tuner; + + break; + /* port c - terrestrial/cable */ + case 2: + /* attach frontend */ + memset(&si2168_config, 0, sizeof(si2168_config)); + si2168_config.i2c_adapter = &adapter; + si2168_config.fe = &fe0->dvb.frontend; + si2168_config.ts_mode = SI2168_TS_SERIAL; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2168", I2C_NAME_SIZE); + info.addr = 0x64; + info.platform_data = &si2168_config; + request_module(info.type); + client_demod = i2c_new_client_device(&i2c_bus->i2c_adap, &info); + if (!i2c_client_has_driver(client_demod)) + goto frontend_detach; + if (!try_module_get(client_demod->dev.driver->owner)) { + i2c_unregister_device(client_demod); + goto frontend_detach; + } + port->i2c_client_demod = client_demod; + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = fe0->dvb.frontend; + si2157_config.if_port = 1; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2157", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &si2157_config; + request_module(info.type); + client_tuner = i2c_new_client_device(adapter, &info); + if (!i2c_client_has_driver(client_tuner)) + goto frontend_detach; + + if (!try_module_get(client_tuner->dev.driver->owner)) { + i2c_unregister_device(client_tuner); + goto frontend_detach; + } + port->i2c_client_tuner = client_tuner; + break; + } + break; + case CX23885_BOARD_DVBSKY_T980C: + case CX23885_BOARD_TT_CT2_4500_CI: + i2c_bus = &dev->i2c_bus[0]; + i2c_bus2 = &dev->i2c_bus[1]; + + /* attach frontend */ + memset(&si2168_config, 0, sizeof(si2168_config)); + si2168_config.i2c_adapter = &adapter; + si2168_config.fe = &fe0->dvb.frontend; + si2168_config.ts_mode = SI2168_TS_PARALLEL; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2168", I2C_NAME_SIZE); + info.addr = 0x64; + info.platform_data = &si2168_config; + request_module(info.type); + client_demod = i2c_new_client_device(&i2c_bus2->i2c_adap, &info); + if (!i2c_client_has_driver(client_demod)) + goto frontend_detach; + if (!try_module_get(client_demod->dev.driver->owner)) { + i2c_unregister_device(client_demod); + goto frontend_detach; + } + port->i2c_client_demod = client_demod; + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = fe0->dvb.frontend; + si2157_config.if_port = 1; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2157", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &si2157_config; + request_module(info.type); + client_tuner = i2c_new_client_device(adapter, &info); + if (!i2c_client_has_driver(client_tuner)) + goto frontend_detach; + if (!try_module_get(client_tuner->dev.driver->owner)) { + i2c_unregister_device(client_tuner); + goto frontend_detach; + } + port->i2c_client_tuner = client_tuner; + break; + case CX23885_BOARD_DVBSKY_S950C: + i2c_bus = &dev->i2c_bus[0]; + i2c_bus2 = &dev->i2c_bus[1]; + + /* attach frontend */ + fe0->dvb.frontend = dvb_attach(m88ds3103_attach, + &dvbsky_s950c_m88ds3103_config, + &i2c_bus2->i2c_adap, &adapter); + if (fe0->dvb.frontend == NULL) + break; + + /* attach tuner */ + memset(&ts2020_config, 0, sizeof(ts2020_config)); + ts2020_config.fe = fe0->dvb.frontend; + ts2020_config.get_agc_pwm = m88ds3103_get_agc_pwm; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "ts2020", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &ts2020_config; + request_module(info.type); + client_tuner = i2c_new_client_device(adapter, &info); + if (!i2c_client_has_driver(client_tuner)) + goto frontend_detach; + if (!try_module_get(client_tuner->dev.driver->owner)) { + i2c_unregister_device(client_tuner); + goto frontend_detach; + } + + /* delegate signal strength measurement to tuner */ + fe0->dvb.frontend->ops.read_signal_strength = + fe0->dvb.frontend->ops.tuner_ops.get_rf_strength; + + port->i2c_client_tuner = client_tuner; + break; + case CX23885_BOARD_DVBSKY_S952: + /* attach frontend */ + memset(&m88ds3103_pdata, 0, sizeof(m88ds3103_pdata)); + m88ds3103_pdata.clk = 27000000; + m88ds3103_pdata.i2c_wr_max = 33; + m88ds3103_pdata.agc = 0x99; + m88ds3103_pdata.clk_out = M88DS3103_CLOCK_OUT_DISABLED; + m88ds3103_pdata.lnb_en_pol = 1; + + switch (port->nr) { + /* port b */ + case 1: + i2c_bus = &dev->i2c_bus[1]; + m88ds3103_pdata.ts_mode = M88DS3103_TS_PARALLEL; + m88ds3103_pdata.ts_clk = 16000; + m88ds3103_pdata.ts_clk_pol = 1; + p_set_voltage = dvbsky_t9580_set_voltage; + break; + /* port c */ + case 2: + i2c_bus = &dev->i2c_bus[0]; + m88ds3103_pdata.ts_mode = M88DS3103_TS_SERIAL; + m88ds3103_pdata.ts_clk = 96000; + m88ds3103_pdata.ts_clk_pol = 0; + p_set_voltage = dvbsky_s952_portc_set_voltage; + break; + default: + return 0; + } + + memset(&info, 0, sizeof(info)); + strscpy(info.type, "m88ds3103", I2C_NAME_SIZE); + info.addr = 0x68; + info.platform_data = &m88ds3103_pdata; + request_module(info.type); + client_demod = i2c_new_client_device(&i2c_bus->i2c_adap, &info); + if (!i2c_client_has_driver(client_demod)) + goto frontend_detach; + if (!try_module_get(client_demod->dev.driver->owner)) { + i2c_unregister_device(client_demod); + goto frontend_detach; + } + port->i2c_client_demod = client_demod; + adapter = m88ds3103_pdata.get_i2c_adapter(client_demod); + fe0->dvb.frontend = m88ds3103_pdata.get_dvb_frontend(client_demod); + + /* attach tuner */ + memset(&ts2020_config, 0, sizeof(ts2020_config)); + ts2020_config.fe = fe0->dvb.frontend; + ts2020_config.get_agc_pwm = m88ds3103_get_agc_pwm; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "ts2020", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &ts2020_config; + request_module(info.type); + client_tuner = i2c_new_client_device(adapter, &info); + if (!i2c_client_has_driver(client_tuner)) + goto frontend_detach; + if (!try_module_get(client_tuner->dev.driver->owner)) { + i2c_unregister_device(client_tuner); + goto frontend_detach; + } + + /* delegate signal strength measurement to tuner */ + fe0->dvb.frontend->ops.read_signal_strength = + fe0->dvb.frontend->ops.tuner_ops.get_rf_strength; + + /* + * for setting the voltage we need to set GPIOs on + * the card. + */ + port->fe_set_voltage = + fe0->dvb.frontend->ops.set_voltage; + fe0->dvb.frontend->ops.set_voltage = p_set_voltage; + + port->i2c_client_tuner = client_tuner; + break; + case CX23885_BOARD_DVBSKY_T982: + memset(&si2168_config, 0, sizeof(si2168_config)); + switch (port->nr) { + /* port b */ + case 1: + i2c_bus = &dev->i2c_bus[1]; + si2168_config.ts_mode = SI2168_TS_PARALLEL; + break; + /* port c */ + case 2: + i2c_bus = &dev->i2c_bus[0]; + si2168_config.ts_mode = SI2168_TS_SERIAL; + break; + } + + /* attach frontend */ + si2168_config.i2c_adapter = &adapter; + si2168_config.fe = &fe0->dvb.frontend; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2168", I2C_NAME_SIZE); + info.addr = 0x64; + info.platform_data = &si2168_config; + request_module(info.type); + client_demod = i2c_new_client_device(&i2c_bus->i2c_adap, &info); + if (!i2c_client_has_driver(client_demod)) + goto frontend_detach; + if (!try_module_get(client_demod->dev.driver->owner)) { + i2c_unregister_device(client_demod); + goto frontend_detach; + } + port->i2c_client_demod = client_demod; + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = fe0->dvb.frontend; + si2157_config.if_port = 1; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2157", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &si2157_config; + request_module(info.type); + client_tuner = i2c_new_client_device(adapter, &info); + if (!i2c_client_has_driver(client_tuner)) + goto frontend_detach; + if (!try_module_get(client_tuner->dev.driver->owner)) { + i2c_unregister_device(client_tuner); + goto frontend_detach; + } + port->i2c_client_tuner = client_tuner; + break; + case CX23885_BOARD_HAUPPAUGE_STARBURST2: + case CX23885_BOARD_HAUPPAUGE_HVR5525: + i2c_bus = &dev->i2c_bus[0]; + i2c_bus2 = &dev->i2c_bus[1]; + + switch (port->nr) { + + /* port b - satellite */ + case 1: + /* attach frontend */ + fe0->dvb.frontend = dvb_attach(m88ds3103_attach, + &hauppauge_hvr5525_m88ds3103_config, + &i2c_bus->i2c_adap, &adapter); + if (fe0->dvb.frontend == NULL) + break; + + /* attach SEC */ + a8293_pdata.dvb_frontend = fe0->dvb.frontend; + memset(&info, 0, sizeof(info)); + strscpy(info.type, "a8293", I2C_NAME_SIZE); + info.addr = 0x0b; + info.platform_data = &a8293_pdata; + request_module("a8293"); + client_sec = i2c_new_client_device(&i2c_bus->i2c_adap, &info); + if (!i2c_client_has_driver(client_sec)) + goto frontend_detach; + if (!try_module_get(client_sec->dev.driver->owner)) { + i2c_unregister_device(client_sec); + goto frontend_detach; + } + port->i2c_client_sec = client_sec; + + /* attach tuner */ + memset(&m88rs6000t_config, 0, sizeof(m88rs6000t_config)); + m88rs6000t_config.fe = fe0->dvb.frontend; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "m88rs6000t", I2C_NAME_SIZE); + info.addr = 0x21; + info.platform_data = &m88rs6000t_config; + request_module("%s", info.type); + client_tuner = i2c_new_client_device(adapter, &info); + if (!i2c_client_has_driver(client_tuner)) + goto frontend_detach; + if (!try_module_get(client_tuner->dev.driver->owner)) { + i2c_unregister_device(client_tuner); + goto frontend_detach; + } + port->i2c_client_tuner = client_tuner; + + /* delegate signal strength measurement to tuner */ + fe0->dvb.frontend->ops.read_signal_strength = + fe0->dvb.frontend->ops.tuner_ops.get_rf_strength; + break; + /* port c - terrestrial/cable */ + case 2: + /* attach frontend */ + memset(&si2168_config, 0, sizeof(si2168_config)); + si2168_config.i2c_adapter = &adapter; + si2168_config.fe = &fe0->dvb.frontend; + si2168_config.ts_mode = SI2168_TS_SERIAL; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2168", I2C_NAME_SIZE); + info.addr = 0x64; + info.platform_data = &si2168_config; + request_module("%s", info.type); + client_demod = i2c_new_client_device(&i2c_bus->i2c_adap, &info); + if (!i2c_client_has_driver(client_demod)) + goto frontend_detach; + if (!try_module_get(client_demod->dev.driver->owner)) { + i2c_unregister_device(client_demod); + goto frontend_detach; + } + port->i2c_client_demod = client_demod; + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = fe0->dvb.frontend; + si2157_config.if_port = 1; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2157", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &si2157_config; + request_module("%s", info.type); + client_tuner = i2c_new_client_device(&i2c_bus2->i2c_adap, &info); + if (!i2c_client_has_driver(client_tuner)) { + module_put(client_demod->dev.driver->owner); + i2c_unregister_device(client_demod); + port->i2c_client_demod = NULL; + goto frontend_detach; + } + if (!try_module_get(client_tuner->dev.driver->owner)) { + i2c_unregister_device(client_tuner); + module_put(client_demod->dev.driver->owner); + i2c_unregister_device(client_demod); + port->i2c_client_demod = NULL; + goto frontend_detach; + } + port->i2c_client_tuner = client_tuner; + + dev->ts1.analog_fe.tuner_priv = client_tuner; + memcpy(&dev->ts1.analog_fe.ops.tuner_ops, + &fe0->dvb.frontend->ops.tuner_ops, + sizeof(struct dvb_tuner_ops)); + + break; + } + break; + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB: + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB_885: + pr_info("%s(): board=%d port=%d\n", __func__, + dev->board, port->nr); + switch (port->nr) { + /* port b - Terrestrial/cable */ + case 1: + /* attach frontend */ + memset(&si2168_config, 0, sizeof(si2168_config)); + si2168_config.i2c_adapter = &adapter; + si2168_config.fe = &fe0->dvb.frontend; + si2168_config.ts_mode = SI2168_TS_SERIAL; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2168", I2C_NAME_SIZE); + info.addr = 0x64; + info.platform_data = &si2168_config; + request_module("%s", info.type); + client_demod = i2c_new_client_device(&dev->i2c_bus[0].i2c_adap, &info); + if (!i2c_client_has_driver(client_demod)) + goto frontend_detach; + if (!try_module_get(client_demod->dev.driver->owner)) { + i2c_unregister_device(client_demod); + goto frontend_detach; + } + port->i2c_client_demod = client_demod; + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = fe0->dvb.frontend; + si2157_config.if_port = 1; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2157", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &si2157_config; + request_module("%s", info.type); + client_tuner = i2c_new_client_device(&dev->i2c_bus[1].i2c_adap, &info); + if (!i2c_client_has_driver(client_tuner)) { + module_put(client_demod->dev.driver->owner); + i2c_unregister_device(client_demod); + port->i2c_client_demod = NULL; + goto frontend_detach; + } + if (!try_module_get(client_tuner->dev.driver->owner)) { + i2c_unregister_device(client_tuner); + module_put(client_demod->dev.driver->owner); + i2c_unregister_device(client_demod); + port->i2c_client_demod = NULL; + goto frontend_detach; + } + port->i2c_client_tuner = client_tuner; + + /* we only attach tuner for analog on the 888 version */ + if (dev->board == CX23885_BOARD_HAUPPAUGE_QUADHD_DVB && + !dev->disable_analog) { + pr_info("%s(): QUADHD_DVB analog setup\n", + __func__); + dev->ts1.analog_fe.tuner_priv = client_tuner; + memcpy(&dev->ts1.analog_fe.ops.tuner_ops, + &fe0->dvb.frontend->ops.tuner_ops, + sizeof(struct dvb_tuner_ops)); + } + break; + + /* port c - terrestrial/cable */ + case 2: + /* attach frontend */ + memset(&si2168_config, 0, sizeof(si2168_config)); + si2168_config.i2c_adapter = &adapter; + si2168_config.fe = &fe0->dvb.frontend; + si2168_config.ts_mode = SI2168_TS_SERIAL; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2168", I2C_NAME_SIZE); + info.addr = 0x66; + info.platform_data = &si2168_config; + request_module("%s", info.type); + client_demod = i2c_new_client_device(&dev->i2c_bus[0].i2c_adap, &info); + if (!i2c_client_has_driver(client_demod)) + goto frontend_detach; + if (!try_module_get(client_demod->dev.driver->owner)) { + i2c_unregister_device(client_demod); + goto frontend_detach; + } + port->i2c_client_demod = client_demod; + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = fe0->dvb.frontend; + si2157_config.if_port = 1; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2157", I2C_NAME_SIZE); + info.addr = 0x62; + info.platform_data = &si2157_config; + request_module("%s", info.type); + client_tuner = i2c_new_client_device(&dev->i2c_bus[1].i2c_adap, &info); + if (!i2c_client_has_driver(client_tuner)) { + module_put(client_demod->dev.driver->owner); + i2c_unregister_device(client_demod); + port->i2c_client_demod = NULL; + goto frontend_detach; + } + if (!try_module_get(client_tuner->dev.driver->owner)) { + i2c_unregister_device(client_tuner); + module_put(client_demod->dev.driver->owner); + i2c_unregister_device(client_demod); + port->i2c_client_demod = NULL; + goto frontend_detach; + } + port->i2c_client_tuner = client_tuner; + break; + } + break; + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC: + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC_885: + pr_info("%s(): board=%d port=%d\n", __func__, + dev->board, port->nr); + switch (port->nr) { + /* port b - Terrestrial/cable */ + case 1: + /* attach frontend */ + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(lgdt3306a_attach, + &hauppauge_quadHD_ATSC_a_config, &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = fe0->dvb.frontend; + si2157_config.if_port = 1; + si2157_config.inversion = 1; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2157", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &si2157_config; + request_module("%s", info.type); + client_tuner = i2c_new_client_device(&dev->i2c_bus[1].i2c_adap, &info); + if (!i2c_client_has_driver(client_tuner)) { + goto frontend_detach; + } + if (!try_module_get(client_tuner->dev.driver->owner)) { + i2c_unregister_device(client_tuner); + goto frontend_detach; + } + port->i2c_client_tuner = client_tuner; + + /* we only attach tuner for analog on the 888 version */ + if (dev->board == CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC && + !dev->disable_analog) { + pr_info("%s(): QUADHD_ATSC analog setup\n", + __func__); + dev->ts1.analog_fe.tuner_priv = client_tuner; + memcpy(&dev->ts1.analog_fe.ops.tuner_ops, + &fe0->dvb.frontend->ops.tuner_ops, + sizeof(struct dvb_tuner_ops)); + } + break; + + /* port c - terrestrial/cable */ + case 2: + /* attach frontend */ + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(lgdt3306a_attach, + &hauppauge_quadHD_ATSC_b_config, &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = fe0->dvb.frontend; + si2157_config.if_port = 1; + si2157_config.inversion = 1; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2157", I2C_NAME_SIZE); + info.addr = 0x62; + info.platform_data = &si2157_config; + request_module("%s", info.type); + client_tuner = i2c_new_client_device(&dev->i2c_bus[1].i2c_adap, &info); + if (!i2c_client_has_driver(client_tuner)) { + goto frontend_detach; + } + if (!try_module_get(client_tuner->dev.driver->owner)) { + i2c_unregister_device(client_tuner); + goto frontend_detach; + } + port->i2c_client_tuner = client_tuner; + break; + } + break; + case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: + switch (port->nr) { + /* port c - Terrestrial/cable */ + case 2: + /* attach frontend */ + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(lgdt3306a_attach, + &hauppauge_hvr1265k4_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + break; + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = fe0->dvb.frontend; + si2157_config.if_port = 1; + si2157_config.inversion = 1; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2157", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &si2157_config; + request_module("%s", info.type); + client_tuner = i2c_new_client_device(&dev->i2c_bus[1].i2c_adap, &info); + if (!i2c_client_has_driver(client_tuner)) + goto frontend_detach; + + if (!try_module_get(client_tuner->dev.driver->owner)) { + i2c_unregister_device(client_tuner); + client_tuner = NULL; + goto frontend_detach; + } + port->i2c_client_tuner = client_tuner; + + dev->ts1.analog_fe.tuner_priv = client_tuner; + memcpy(&dev->ts1.analog_fe.ops.tuner_ops, + &fe0->dvb.frontend->ops.tuner_ops, + sizeof(struct dvb_tuner_ops)); + break; + } + break; + default: + pr_info("%s: The frontend of your DVB/ATSC card isn't supported yet\n", + dev->name); + break; + } + + if ((NULL == fe0->dvb.frontend) || (fe1 && NULL == fe1->dvb.frontend)) { + pr_err("%s: frontend initialization failed\n", + dev->name); + goto frontend_detach; + } + + /* define general-purpose callback pointer */ + fe0->dvb.frontend->callback = cx23885_tuner_callback; + if (fe1) + fe1->dvb.frontend->callback = cx23885_tuner_callback; +#if 0 + /* Ensure all frontends negotiate bus access */ + fe0->dvb.frontend->ops.ts_bus_ctrl = cx23885_dvb_bus_ctrl; + if (fe1) + fe1->dvb.frontend->ops.ts_bus_ctrl = cx23885_dvb_bus_ctrl; +#endif + + /* Put the tuner in standby to keep it quiet */ + call_all(dev, tuner, standby); + + if (fe0->dvb.frontend->ops.analog_ops.standby) + fe0->dvb.frontend->ops.analog_ops.standby(fe0->dvb.frontend); + + /* register everything */ + ret = vb2_dvb_register_bus(&port->frontends, THIS_MODULE, port, + &dev->pci->dev, NULL, + adapter_nr, mfe_shared); + if (ret) + goto frontend_detach; + + ret = dvb_register_ci_mac(port); + if (ret) + goto frontend_detach; + + return 0; + +frontend_detach: + /* remove I2C client for SEC */ + client_sec = port->i2c_client_sec; + if (client_sec) { + module_put(client_sec->dev.driver->owner); + i2c_unregister_device(client_sec); + port->i2c_client_sec = NULL; + } + + /* remove I2C client for tuner */ + client_tuner = port->i2c_client_tuner; + if (client_tuner) { + module_put(client_tuner->dev.driver->owner); + i2c_unregister_device(client_tuner); + port->i2c_client_tuner = NULL; + } + + /* remove I2C client for demodulator */ + client_demod = port->i2c_client_demod; + if (client_demod) { + module_put(client_demod->dev.driver->owner); + i2c_unregister_device(client_demod); + port->i2c_client_demod = NULL; + } + + port->gate_ctrl = NULL; + vb2_dvb_dealloc_frontends(&port->frontends); + return -EINVAL; +} + +int cx23885_dvb_register(struct cx23885_tsport *port) +{ + + struct vb2_dvb_frontend *fe0; + struct cx23885_dev *dev = port->dev; + int err, i; + + /* Here we need to allocate the correct number of frontends, + * as reflected in the cards struct. The reality is that currently + * no cx23885 boards support this - yet. But, if we don't modify this + * code then the second frontend would never be allocated (later) + * and fail with error before the attach in dvb_register(). + * Without these changes we risk an OOPS later. The changes here + * are for safety, and should provide a good foundation for the + * future addition of any multi-frontend cx23885 based boards. + */ + pr_info("%s() allocating %d frontend(s)\n", __func__, + port->num_frontends); + + for (i = 1; i <= port->num_frontends; i++) { + struct vb2_queue *q; + + if (vb2_dvb_alloc_frontend( + &port->frontends, i) == NULL) { + pr_err("%s() failed to alloc\n", __func__); + return -ENOMEM; + } + + fe0 = vb2_dvb_get_frontend(&port->frontends, i); + if (!fe0) + return -EINVAL; + + dprintk(1, "%s\n", __func__); + dprintk(1, " ->probed by Card=%d Name=%s, PCI %02x:%02x\n", + dev->board, + dev->name, + dev->pci_bus, + dev->pci_slot); + + /* dvb stuff */ + /* We have to init the queue for each frontend on a port. */ + pr_info("%s: cx23885 based dvb card\n", dev->name); + q = &fe0->dvb.dvbq; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ; + q->gfp_flags = GFP_DMA32; + q->min_queued_buffers = 2; + q->drv_priv = port; + q->buf_struct_size = sizeof(struct cx23885_buffer); + q->ops = &dvb_qops; + q->mem_ops = &vb2_dma_sg_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &dev->lock; + q->dev = &dev->pci->dev; + + err = vb2_queue_init(q); + if (err < 0) + return err; + } + err = dvb_register(port); + if (err != 0) + pr_err("%s() dvb_register failed err = %d\n", + __func__, err); + + return err; +} + +int cx23885_dvb_unregister(struct cx23885_tsport *port) +{ + struct vb2_dvb_frontend *fe0; + struct i2c_client *client; + + fe0 = vb2_dvb_get_frontend(&port->frontends, 1); + + if (fe0 && fe0->dvb.frontend) + vb2_dvb_unregister_bus(&port->frontends); + + /* remove I2C client for CI */ + client = port->i2c_client_ci; + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); + } + + /* remove I2C client for SEC */ + client = port->i2c_client_sec; + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); + } + + /* remove I2C client for tuner */ + client = port->i2c_client_tuner; + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); + } + + /* remove I2C client for demodulator */ + client = port->i2c_client_demod; + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); + } + + switch (port->dev->board) { + case CX23885_BOARD_NETUP_DUAL_DVBS2_CI: + netup_ci_exit(port); + break; + case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: + altera_ci_release(port->dev, port->nr); + break; + } + + port->gate_ctrl = NULL; + + return 0; +} diff --git a/drivers/media/pci/cx23885/cx23885-f300.c b/drivers/media/pci/cx23885/cx23885-f300.c new file mode 100644 index 000000000..ac1c434e8 --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-f300.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Silicon Labs C8051F300 microcontroller. + * + * It is used for LNB power control in TeVii S470, + * TBS 6920 PCIe DVB-S2 cards. + * + * Microcontroller connected to cx23885 GPIO pins: + * GPIO0 - data - P0.3 F300 + * GPIO1 - reset - P0.2 F300 + * GPIO2 - clk - P0.1 F300 + * GPIO3 - busy - P0.0 F300 + * + * Copyright (C) 2009 Igor M. Liplianin + */ + +#include "cx23885.h" +#include "cx23885-f300.h" + +#define F300_DATA GPIO_0 +#define F300_RESET GPIO_1 +#define F300_CLK GPIO_2 +#define F300_BUSY GPIO_3 + +static void f300_set_line(struct cx23885_dev *dev, u32 line, u8 lvl) +{ + cx23885_gpio_enable(dev, line, 1); + if (lvl == 1) + cx23885_gpio_set(dev, line); + else + cx23885_gpio_clear(dev, line); +} + +static u8 f300_get_line(struct cx23885_dev *dev, u32 line) +{ + cx23885_gpio_enable(dev, line, 0); + + return cx23885_gpio_get(dev, line); +} + +static void f300_send_byte(struct cx23885_dev *dev, u8 dta) +{ + u8 i; + + for (i = 0; i < 8; i++) { + f300_set_line(dev, F300_CLK, 0); + udelay(30); + f300_set_line(dev, F300_DATA, (dta & 0x80) >> 7);/* msb first */ + udelay(30); + dta <<= 1; + f300_set_line(dev, F300_CLK, 1); + udelay(30); + } +} + +static u8 f300_get_byte(struct cx23885_dev *dev) +{ + u8 i, dta = 0; + + for (i = 0; i < 8; i++) { + f300_set_line(dev, F300_CLK, 0); + udelay(30); + dta <<= 1; + f300_set_line(dev, F300_CLK, 1); + udelay(30); + dta |= f300_get_line(dev, F300_DATA);/* msb first */ + + } + + return dta; +} + +static u8 f300_xfer(struct dvb_frontend *fe, u8 *buf) +{ + struct cx23885_tsport *port = fe->dvb->priv; + struct cx23885_dev *dev = port->dev; + u8 i, temp, ret = 0; + + temp = buf[0]; + for (i = 0; i < buf[0]; i++) + temp += buf[i + 1]; + temp = (~temp + 1);/* get check sum */ + buf[1 + buf[0]] = temp; + + f300_set_line(dev, F300_RESET, 1); + f300_set_line(dev, F300_CLK, 1); + udelay(30); + f300_set_line(dev, F300_DATA, 1); + msleep(1); + + /* question: */ + f300_set_line(dev, F300_RESET, 0);/* begin to send data */ + msleep(1); + + f300_send_byte(dev, 0xe0);/* the slave address is 0xe0, write */ + msleep(1); + + temp = buf[0]; + temp += 2; + for (i = 0; i < temp; i++) + f300_send_byte(dev, buf[i]); + + f300_set_line(dev, F300_RESET, 1);/* sent data over */ + f300_set_line(dev, F300_DATA, 1); + + /* answer: */ + temp = 0; + for (i = 0; ((i < 8) & (temp == 0)); i++) { + msleep(1); + if (f300_get_line(dev, F300_BUSY) == 0) + temp = 1; + } + + if (i > 7) { + pr_err("%s: timeout, the slave no response\n", + __func__); + ret = 1; /* timeout, the slave no response */ + } else { /* the slave not busy, prepare for getting data */ + f300_set_line(dev, F300_RESET, 0);/*ready...*/ + msleep(1); + f300_send_byte(dev, 0xe1);/* 0xe1 is Read */ + msleep(1); + temp = f300_get_byte(dev);/*get the data length */ + if (temp > 14) + temp = 14; + + for (i = 0; i < (temp + 1); i++) + f300_get_byte(dev);/* get data to empty buffer */ + + f300_set_line(dev, F300_RESET, 1);/* received data over */ + f300_set_line(dev, F300_DATA, 1); + } + + return ret; +} + +int f300_set_voltage(struct dvb_frontend *fe, enum fe_sec_voltage voltage) +{ + u8 buf[16]; + + buf[0] = 0x05; + buf[1] = 0x38;/* write port */ + buf[2] = 0x01;/* A port, lnb power */ + + switch (voltage) { + case SEC_VOLTAGE_13: + buf[3] = 0x01;/* power on */ + buf[4] = 0x02;/* B port, H/V */ + buf[5] = 0x00;/*13V v*/ + break; + case SEC_VOLTAGE_18: + buf[3] = 0x01; + buf[4] = 0x02; + buf[5] = 0x01;/* 18V h*/ + break; + case SEC_VOLTAGE_OFF: + buf[3] = 0x00;/* power off */ + buf[4] = 0x00; + buf[5] = 0x00; + break; + } + + return f300_xfer(fe, buf); +} diff --git a/drivers/media/pci/cx23885/cx23885-f300.h b/drivers/media/pci/cx23885/cx23885-f300.h new file mode 100644 index 000000000..34aef3610 --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-f300.h @@ -0,0 +1,3 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +extern int f300_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage voltage); diff --git a/drivers/media/pci/cx23885/cx23885-i2c.c b/drivers/media/pci/cx23885/cx23885-i2c.c new file mode 100644 index 000000000..f51fad33d --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-i2c.c @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX23885 PCIe bridge + * + * Copyright (c) 2006 Steven Toth + */ + +#include "cx23885.h" + +#include +#include +#include +#include + +#include + +static unsigned int i2c_debug; +module_param(i2c_debug, int, 0644); +MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]"); + +static unsigned int i2c_scan; +module_param(i2c_scan, int, 0444); +MODULE_PARM_DESC(i2c_scan, "scan i2c bus at insmod time"); + +#define dprintk(level, fmt, arg...)\ + do { if (i2c_debug >= level)\ + printk(KERN_DEBUG pr_fmt("%s: i2c:" fmt), \ + __func__, ##arg); \ + } while (0) + +#define I2C_WAIT_DELAY 32 +#define I2C_WAIT_RETRY 64 + +#define I2C_EXTEND (1 << 3) +#define I2C_NOSTOP (1 << 4) + +static inline int i2c_slave_did_ack(struct i2c_adapter *i2c_adap) +{ + struct cx23885_i2c *bus = i2c_adap->algo_data; + struct cx23885_dev *dev = bus->dev; + return cx_read(bus->reg_stat) & 0x01; +} + +static inline int i2c_is_busy(struct i2c_adapter *i2c_adap) +{ + struct cx23885_i2c *bus = i2c_adap->algo_data; + struct cx23885_dev *dev = bus->dev; + return cx_read(bus->reg_stat) & 0x02 ? 1 : 0; +} + +static int i2c_wait_done(struct i2c_adapter *i2c_adap) +{ + int count; + + for (count = 0; count < I2C_WAIT_RETRY; count++) { + if (!i2c_is_busy(i2c_adap)) + break; + udelay(I2C_WAIT_DELAY); + } + + if (I2C_WAIT_RETRY == count) + return 0; + + return 1; +} + +static int i2c_sendbytes(struct i2c_adapter *i2c_adap, + const struct i2c_msg *msg, int joined_rlen) +{ + struct cx23885_i2c *bus = i2c_adap->algo_data; + struct cx23885_dev *dev = bus->dev; + u32 wdata, addr, ctrl; + int retval, cnt; + + if (joined_rlen) + dprintk(1, "%s(msg->wlen=%d, nextmsg->rlen=%d)\n", __func__, + msg->len, joined_rlen); + else + dprintk(1, "%s(msg->len=%d)\n", __func__, msg->len); + + /* Deal with i2c probe functions with zero payload */ + if (msg->len == 0) { + cx_write(bus->reg_addr, msg->addr << 25); + cx_write(bus->reg_ctrl, bus->i2c_period | (1 << 2)); + if (!i2c_wait_done(i2c_adap)) + return -EIO; + if (!i2c_slave_did_ack(i2c_adap)) + return -ENXIO; + + dprintk(1, "%s() returns 0\n", __func__); + return 0; + } + + + /* dev, reg + first byte */ + addr = (msg->addr << 25) | msg->buf[0]; + wdata = msg->buf[0]; + ctrl = bus->i2c_period | (1 << 12) | (1 << 2); + + if (msg->len > 1) + ctrl |= I2C_NOSTOP | I2C_EXTEND; + else if (joined_rlen) + ctrl |= I2C_NOSTOP; + + cx_write(bus->reg_addr, addr); + cx_write(bus->reg_wdata, wdata); + cx_write(bus->reg_ctrl, ctrl); + + if (!i2c_wait_done(i2c_adap)) + goto eio; + if (i2c_debug) { + printk(KERN_DEBUG " addr << 1, msg->buf[0]); + if (!(ctrl & I2C_NOSTOP)) + pr_cont(" >\n"); + } + + for (cnt = 1; cnt < msg->len; cnt++) { + /* following bytes */ + wdata = msg->buf[cnt]; + ctrl = bus->i2c_period | (1 << 12) | (1 << 2); + + if (cnt < msg->len - 1) + ctrl |= I2C_NOSTOP | I2C_EXTEND; + else if (joined_rlen) + ctrl |= I2C_NOSTOP; + + cx_write(bus->reg_addr, addr); + cx_write(bus->reg_wdata, wdata); + cx_write(bus->reg_ctrl, ctrl); + + if (!i2c_wait_done(i2c_adap)) + goto eio; + if (i2c_debug) { + pr_cont(" %02x", msg->buf[cnt]); + if (!(ctrl & I2C_NOSTOP)) + pr_cont(" >\n"); + } + } + return msg->len; + + eio: + retval = -EIO; + if (i2c_debug) + pr_err(" ERR: %d\n", retval); + return retval; +} + +static int i2c_readbytes(struct i2c_adapter *i2c_adap, + const struct i2c_msg *msg, int joined) +{ + struct cx23885_i2c *bus = i2c_adap->algo_data; + struct cx23885_dev *dev = bus->dev; + u32 ctrl, cnt; + int retval; + + + if (i2c_debug && !joined) + dprintk(1, "%s(msg->len=%d)\n", __func__, msg->len); + + /* Deal with i2c probe functions with zero payload */ + if (msg->len == 0) { + cx_write(bus->reg_addr, msg->addr << 25); + cx_write(bus->reg_ctrl, bus->i2c_period | (1 << 2) | 1); + if (!i2c_wait_done(i2c_adap)) + return -EIO; + if (!i2c_slave_did_ack(i2c_adap)) + return -ENXIO; + + + dprintk(1, "%s() returns 0\n", __func__); + return 0; + } + + if (i2c_debug) { + if (joined) + dprintk(1, " R"); + else + dprintk(1, " addr << 1) + 1); + } + + for (cnt = 0; cnt < msg->len; cnt++) { + + ctrl = bus->i2c_period | (1 << 12) | (1 << 2) | 1; + + if (cnt < msg->len - 1) + ctrl |= I2C_NOSTOP | I2C_EXTEND; + + cx_write(bus->reg_addr, msg->addr << 25); + cx_write(bus->reg_ctrl, ctrl); + + if (!i2c_wait_done(i2c_adap)) + goto eio; + msg->buf[cnt] = cx_read(bus->reg_rdata) & 0xff; + if (i2c_debug) { + dprintk(1, " %02x", msg->buf[cnt]); + if (!(ctrl & I2C_NOSTOP)) + dprintk(1, " >\n"); + } + } + return msg->len; + + eio: + retval = -EIO; + if (i2c_debug) + pr_err(" ERR: %d\n", retval); + return retval; +} + +static int i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg *msgs, int num) +{ + int i, retval = 0; + + dprintk(1, "%s(num = %d)\n", __func__, num); + + for (i = 0 ; i < num; i++) { + dprintk(1, "%s(num = %d) addr = 0x%02x len = 0x%x\n", + __func__, num, msgs[i].addr, msgs[i].len); + if (msgs[i].flags & I2C_M_RD) { + /* read */ + retval = i2c_readbytes(i2c_adap, &msgs[i], 0); + } else if (i + 1 < num && (msgs[i + 1].flags & I2C_M_RD) && + msgs[i].addr == msgs[i + 1].addr) { + /* write then read from same address */ + retval = i2c_sendbytes(i2c_adap, &msgs[i], + msgs[i + 1].len); + if (retval < 0) + goto err; + i++; + retval = i2c_readbytes(i2c_adap, &msgs[i], 1); + } else { + /* write */ + retval = i2c_sendbytes(i2c_adap, &msgs[i], 0); + } + if (retval < 0) + goto err; + } + return num; + + err: + return retval; +} + +static u32 cx23885_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C; +} + +static const struct i2c_algorithm cx23885_i2c_algo_template = { + .master_xfer = i2c_xfer, + .functionality = cx23885_functionality, +}; + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_adapter cx23885_i2c_adap_template = { + .name = "cx23885", + .owner = THIS_MODULE, + .algo = &cx23885_i2c_algo_template, +}; + +static const struct i2c_client cx23885_i2c_client_template = { + .name = "cx23885 internal", +}; + +static char *i2c_devs[128] = { + [0x10 >> 1] = "tda10048", + [0x12 >> 1] = "dib7000pc", + [0x1c >> 1] = "lgdt3303", + [0x80 >> 1] = "cs3308", + [0x82 >> 1] = "cs3308", + [0x86 >> 1] = "tda9887", + [0x32 >> 1] = "cx24227", + [0x88 >> 1] = "cx25837", + [0x84 >> 1] = "tda8295", + [0x98 >> 1] = "flatiron", + [0xa0 >> 1] = "eeprom", + [0xc0 >> 1] = "tuner/mt2131/tda8275", + [0xc2 >> 1] = "tuner/mt2131/tda8275/xc5000/xc3028", + [0xc8 >> 1] = "tuner/xc3028L", +}; + +static void do_i2c_scan(char *name, struct i2c_client *c) +{ + unsigned char buf; + int i, rc; + + for (i = 0; i < 128; i++) { + c->addr = i; + rc = i2c_master_recv(c, &buf, 0); + if (rc < 0) + continue; + pr_info("%s: i2c scan: found device @ 0x%04x [%s]\n", + name, i, i2c_devs[i] ? i2c_devs[i] : "???"); + } +} + +/* init + register i2c adapter */ +int cx23885_i2c_register(struct cx23885_i2c *bus) +{ + struct cx23885_dev *dev = bus->dev; + + dprintk(1, "%s(bus = %d)\n", __func__, bus->nr); + + bus->i2c_adap = cx23885_i2c_adap_template; + bus->i2c_client = cx23885_i2c_client_template; + bus->i2c_adap.dev.parent = &dev->pci->dev; + + strscpy(bus->i2c_adap.name, bus->dev->name, + sizeof(bus->i2c_adap.name)); + + bus->i2c_adap.algo_data = bus; + i2c_set_adapdata(&bus->i2c_adap, &dev->v4l2_dev); + i2c_add_adapter(&bus->i2c_adap); + + bus->i2c_client.adapter = &bus->i2c_adap; + + if (0 == bus->i2c_rc) { + dprintk(1, "%s: i2c bus %d registered\n", dev->name, bus->nr); + if (i2c_scan) { + pr_info("%s: scan bus %d:\n", + dev->name, bus->nr); + do_i2c_scan(dev->name, &bus->i2c_client); + } + } else + pr_warn("%s: i2c bus %d register FAILED\n", + dev->name, bus->nr); + + /* Instantiate the IR receiver device, if present */ + if (0 == bus->i2c_rc) { + struct i2c_board_info info; + static const unsigned short addr_list[] = { + 0x6b, I2C_CLIENT_END + }; + + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "ir_video", I2C_NAME_SIZE); + /* Use quick read command for probe, some IR chips don't + * support writes */ + i2c_new_scanned_device(&bus->i2c_adap, &info, addr_list, + i2c_probe_func_quick_read); + } + + return bus->i2c_rc; +} + +int cx23885_i2c_unregister(struct cx23885_i2c *bus) +{ + i2c_del_adapter(&bus->i2c_adap); + return 0; +} + +void cx23885_av_clk(struct cx23885_dev *dev, int enable) +{ + /* write 0 to bus 2 addr 0x144 via i2x_xfer() */ + char buffer[3]; + struct i2c_msg msg; + dprintk(1, "%s(enabled = %d)\n", __func__, enable); + + /* Register 0x144 */ + buffer[0] = 0x01; + buffer[1] = 0x44; + if (enable == 1) + buffer[2] = 0x05; + else + buffer[2] = 0x00; + + msg.addr = 0x44; + msg.flags = I2C_M_TEN; + msg.len = 3; + msg.buf = buffer; + + i2c_xfer(&dev->i2c_bus[2].i2c_adap, &msg, 1); +} diff --git a/drivers/media/pci/cx23885/cx23885-input.c b/drivers/media/pci/cx23885/cx23885-input.c new file mode 100644 index 000000000..554767b8e --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-input.c @@ -0,0 +1,410 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX23885/7/8 PCIe bridge + * + * Infrared remote control input device + * + * Most of this file is + * + * Copyright (C) 2009 Andy Walls + * + * However, the cx23885_input_{init,fini} functions contained herein are + * derived from Linux kernel files linux/media/video/.../...-input.c marked as: + * + * Copyright (C) 2008 + * Copyright (C) 2005 Ludovico Cavedon + * Markus Rechberger + * Mauro Carvalho Chehab + * Sascha Sommer + * Copyright (C) 2004, 2005 Chris Pascoe + * Copyright (C) 2003, 2004 Gerd Knorr + * Copyright (C) 2003 Pavel Machek + */ + +#include "cx23885.h" +#include "cx23885-input.h" + +#include +#include +#include + +#define MODULE_NAME "cx23885" + +static void cx23885_input_process_measurements(struct cx23885_dev *dev, + bool overrun) +{ + struct cx23885_kernel_ir *kernel_ir = dev->kernel_ir; + + ssize_t num; + int count, i; + bool handle = false; + struct ir_raw_event ir_core_event[64]; + + do { + num = 0; + v4l2_subdev_call(dev->sd_ir, ir, rx_read, (u8 *) ir_core_event, + sizeof(ir_core_event), &num); + + count = num / sizeof(struct ir_raw_event); + + for (i = 0; i < count; i++) { + ir_raw_event_store(kernel_ir->rc, + &ir_core_event[i]); + handle = true; + } + } while (num != 0); + + if (overrun) + ir_raw_event_overflow(kernel_ir->rc); + else if (handle) + ir_raw_event_handle(kernel_ir->rc); +} + +void cx23885_input_rx_work_handler(struct cx23885_dev *dev, u32 events) +{ + struct v4l2_subdev_ir_parameters params; + int overrun, data_available; + + if (dev->sd_ir == NULL || events == 0) + return; + + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + case CX23885_BOARD_TEVII_S470: + case CX23885_BOARD_HAUPPAUGE_HVR1250: + case CX23885_BOARD_MYGICA_X8507: + case CX23885_BOARD_TBS_6980: + case CX23885_BOARD_TBS_6981: + case CX23885_BOARD_DVBSKY_T9580: + case CX23885_BOARD_DVBSKY_T980C: + case CX23885_BOARD_DVBSKY_S950C: + case CX23885_BOARD_TT_CT2_4500_CI: + case CX23885_BOARD_DVBSKY_S950: + case CX23885_BOARD_DVBSKY_S952: + case CX23885_BOARD_DVBSKY_T982: + case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: + /* + * The only boards we handle right now. However other boards + * using the CX2388x integrated IR controller should be similar + */ + break; + default: + return; + } + + overrun = events & (V4L2_SUBDEV_IR_RX_SW_FIFO_OVERRUN | + V4L2_SUBDEV_IR_RX_HW_FIFO_OVERRUN); + + data_available = events & (V4L2_SUBDEV_IR_RX_END_OF_RX_DETECTED | + V4L2_SUBDEV_IR_RX_FIFO_SERVICE_REQ); + + if (overrun) { + /* If there was a FIFO overrun, stop the device */ + v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, ¶ms); + params.enable = false; + /* Mitigate race with cx23885_input_ir_stop() */ + params.shutdown = atomic_read(&dev->ir_input_stopping); + v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, ¶ms); + } + + if (data_available) + cx23885_input_process_measurements(dev, overrun); + + if (overrun) { + /* If there was a FIFO overrun, clear & restart the device */ + params.enable = true; + /* Mitigate race with cx23885_input_ir_stop() */ + params.shutdown = atomic_read(&dev->ir_input_stopping); + v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, ¶ms); + } +} + +static int cx23885_input_ir_start(struct cx23885_dev *dev) +{ + struct v4l2_subdev_ir_parameters params; + + if (dev->sd_ir == NULL) + return -ENODEV; + + atomic_set(&dev->ir_input_stopping, 0); + + v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, ¶ms); + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + case CX23885_BOARD_HAUPPAUGE_HVR1250: + case CX23885_BOARD_MYGICA_X8507: + case CX23885_BOARD_DVBSKY_T9580: + case CX23885_BOARD_DVBSKY_T980C: + case CX23885_BOARD_DVBSKY_S950C: + case CX23885_BOARD_TT_CT2_4500_CI: + case CX23885_BOARD_DVBSKY_S950: + case CX23885_BOARD_DVBSKY_S952: + case CX23885_BOARD_DVBSKY_T982: + case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: + /* + * The IR controller on this board only returns pulse widths. + * Any other mode setting will fail to set up the device. + */ + params.mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH; + params.enable = true; + params.interrupt_enable = true; + params.shutdown = false; + + /* Setup for baseband compatible with both RC-5 and RC-6A */ + params.modulation = false; + /* RC-5: 2,222,222 ns = 1/36 kHz * 32 cycles * 2 marks * 1.25*/ + /* RC-6A: 3,333,333 ns = 1/36 kHz * 16 cycles * 6 marks * 1.25*/ + params.max_pulse_width = 3333333; /* ns */ + /* RC-5: 666,667 ns = 1/36 kHz * 32 cycles * 1 mark * 0.75 */ + /* RC-6A: 333,333 ns = 1/36 kHz * 16 cycles * 1 mark * 0.75 */ + params.noise_filter_min_width = 333333; /* ns */ + /* + * This board has inverted receive sense: + * mark is received as low logic level; + * falling edges are detected as rising edges; etc. + */ + params.invert_level = true; + break; + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + case CX23885_BOARD_TEVII_S470: + case CX23885_BOARD_TBS_6980: + case CX23885_BOARD_TBS_6981: + /* + * The IR controller on this board only returns pulse widths. + * Any other mode setting will fail to set up the device. + */ + params.mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH; + params.enable = true; + params.interrupt_enable = true; + params.shutdown = false; + + /* Setup for a standard NEC protocol */ + params.carrier_freq = 37917; /* Hz, 455 kHz/12 for NEC */ + params.carrier_range_lower = 33000; /* Hz */ + params.carrier_range_upper = 43000; /* Hz */ + params.duty_cycle = 33; /* percent, 33 percent for NEC */ + + /* + * NEC max pulse width: (64/3)/(455 kHz/12) * 16 nec_units + * (64/3)/(455 kHz/12) * 16 nec_units * 1.375 = 12378022 ns + */ + params.max_pulse_width = 12378022; /* ns */ + + /* + * NEC noise filter min width: (64/3)/(455 kHz/12) * 1 nec_unit + * (64/3)/(455 kHz/12) * 1 nec_units * 0.625 = 351648 ns + */ + params.noise_filter_min_width = 351648; /* ns */ + + params.modulation = false; + params.invert_level = true; + break; + } + v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, ¶ms); + return 0; +} + +static int cx23885_input_ir_open(struct rc_dev *rc) +{ + struct cx23885_kernel_ir *kernel_ir = rc->priv; + + if (kernel_ir->cx == NULL) + return -ENODEV; + + return cx23885_input_ir_start(kernel_ir->cx); +} + +static void cx23885_input_ir_stop(struct cx23885_dev *dev) +{ + struct v4l2_subdev_ir_parameters params; + + if (dev->sd_ir == NULL) + return; + + /* + * Stop the sd_ir subdevice from generating notifications and + * scheduling work. + * It is shutdown this way in order to mitigate a race with + * cx23885_input_rx_work_handler() in the overrun case, which could + * re-enable the subdevice. + */ + atomic_set(&dev->ir_input_stopping, 1); + v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, ¶ms); + while (params.shutdown == false) { + params.enable = false; + params.interrupt_enable = false; + params.shutdown = true; + v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, ¶ms); + v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, ¶ms); + } + flush_work(&dev->cx25840_work); + flush_work(&dev->ir_rx_work); + flush_work(&dev->ir_tx_work); +} + +static void cx23885_input_ir_close(struct rc_dev *rc) +{ + struct cx23885_kernel_ir *kernel_ir = rc->priv; + + if (kernel_ir->cx != NULL) + cx23885_input_ir_stop(kernel_ir->cx); +} + +int cx23885_input_init(struct cx23885_dev *dev) +{ + struct cx23885_kernel_ir *kernel_ir; + struct rc_dev *rc; + char *rc_map; + u64 allowed_protos; + + int ret; + + /* + * If the IR device (hardware registers, chip, GPIO lines, etc.) isn't + * encapsulated in a v4l2_subdev, then I'm not going to deal with it. + */ + if (dev->sd_ir == NULL) + return -ENODEV; + + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + case CX23885_BOARD_HAUPPAUGE_HVR1250: + case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: + /* Integrated CX2388[58] IR controller */ + allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER; + /* The grey Hauppauge RC-5 remote */ + rc_map = RC_MAP_HAUPPAUGE; + break; + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + /* Integrated CX23885 IR controller */ + allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER; + /* The grey Terratec remote with orange buttons */ + rc_map = RC_MAP_NEC_TERRATEC_CINERGY_XS; + break; + case CX23885_BOARD_TEVII_S470: + /* Integrated CX23885 IR controller */ + allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER; + /* A guess at the remote */ + rc_map = RC_MAP_TEVII_NEC; + break; + case CX23885_BOARD_MYGICA_X8507: + /* Integrated CX23885 IR controller */ + allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER; + /* A guess at the remote */ + rc_map = RC_MAP_TOTAL_MEDIA_IN_HAND_02; + break; + case CX23885_BOARD_TBS_6980: + case CX23885_BOARD_TBS_6981: + /* Integrated CX23885 IR controller */ + allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER; + /* A guess at the remote */ + rc_map = RC_MAP_TBS_NEC; + break; + case CX23885_BOARD_DVBSKY_T9580: + case CX23885_BOARD_DVBSKY_T980C: + case CX23885_BOARD_DVBSKY_S950C: + case CX23885_BOARD_DVBSKY_S950: + case CX23885_BOARD_DVBSKY_S952: + case CX23885_BOARD_DVBSKY_T982: + /* Integrated CX23885 IR controller */ + allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER; + rc_map = RC_MAP_DVBSKY; + break; + case CX23885_BOARD_TT_CT2_4500_CI: + /* Integrated CX23885 IR controller */ + allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER; + rc_map = RC_MAP_TT_1500; + break; + default: + return -ENODEV; + } + + /* cx23885 board instance kernel IR state */ + kernel_ir = kzalloc_obj(struct cx23885_kernel_ir); + if (kernel_ir == NULL) + return -ENOMEM; + + kernel_ir->cx = dev; + kernel_ir->name = kasprintf(GFP_KERNEL, "cx23885 IR (%s)", + cx23885_boards[dev->board].name); + if (!kernel_ir->name) { + ret = -ENOMEM; + goto err_out_free; + } + + kernel_ir->phys = kasprintf(GFP_KERNEL, "pci-%s/ir0", + pci_name(dev->pci)); + if (!kernel_ir->phys) { + ret = -ENOMEM; + goto err_out_free_name; + } + + /* input device */ + rc = rc_allocate_device(RC_DRIVER_IR_RAW); + if (!rc) { + ret = -ENOMEM; + goto err_out_free_phys; + } + + kernel_ir->rc = rc; + rc->device_name = kernel_ir->name; + rc->input_phys = kernel_ir->phys; + rc->input_id.bustype = BUS_PCI; + rc->input_id.version = 1; + if (dev->pci->subsystem_vendor) { + rc->input_id.vendor = dev->pci->subsystem_vendor; + rc->input_id.product = dev->pci->subsystem_device; + } else { + rc->input_id.vendor = dev->pci->vendor; + rc->input_id.product = dev->pci->device; + } + rc->dev.parent = &dev->pci->dev; + rc->allowed_protocols = allowed_protos; + rc->priv = kernel_ir; + rc->open = cx23885_input_ir_open; + rc->close = cx23885_input_ir_close; + rc->map_name = rc_map; + rc->driver_name = MODULE_NAME; + + /* Go */ + dev->kernel_ir = kernel_ir; + ret = rc_register_device(rc); + if (ret) + goto err_out_stop; + + return 0; + +err_out_stop: + cx23885_input_ir_stop(dev); + dev->kernel_ir = NULL; + rc_free_device(rc); +err_out_free_phys: + kfree(kernel_ir->phys); +err_out_free_name: + kfree(kernel_ir->name); +err_out_free: + kfree(kernel_ir); + return ret; +} + +void cx23885_input_fini(struct cx23885_dev *dev) +{ + /* Always stop the IR hardware from generating interrupts */ + cx23885_input_ir_stop(dev); + + if (dev->kernel_ir == NULL) + return; + rc_unregister_device(dev->kernel_ir->rc); + rc_free_device(dev->kernel_ir->rc); + kfree(dev->kernel_ir->phys); + kfree(dev->kernel_ir->name); + kfree(dev->kernel_ir); + dev->kernel_ir = NULL; +} diff --git a/drivers/media/pci/cx23885/cx23885-input.h b/drivers/media/pci/cx23885/cx23885-input.h new file mode 100644 index 000000000..5b261e0ee --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-input.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the Conexant CX23885/7/8 PCIe bridge + * + * Infrared remote control input device + * + * Copyright (C) 2009 Andy Walls + */ + +#ifndef _CX23885_INPUT_H_ +#define _CX23885_INPUT_H_ +void cx23885_input_rx_work_handler(struct cx23885_dev *dev, u32 events); + +int cx23885_input_init(struct cx23885_dev *dev); +void cx23885_input_fini(struct cx23885_dev *dev); +#endif diff --git a/drivers/media/pci/cx23885/cx23885-ioctl.c b/drivers/media/pci/cx23885/cx23885-ioctl.c new file mode 100644 index 000000000..a8ccad07c --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-ioctl.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX23885/7/8 PCIe bridge + * + * Various common ioctl() support functions + * + * Copyright (c) 2009 Andy Walls + */ + +#include "cx23885.h" +#include "cx23885-ioctl.h" + +#ifdef CONFIG_VIDEO_ADV_DEBUG +int cx23885_g_chip_info(struct file *file, void *fh, + struct v4l2_dbg_chip_info *chip) +{ + struct cx23885_dev *dev = video_drvdata(file); + + if (chip->match.addr > 1) + return -EINVAL; + if (chip->match.addr == 1) { + if (dev->v4l_device == NULL) + return -EINVAL; + strscpy(chip->name, "cx23417", sizeof(chip->name)); + } else { + strscpy(chip->name, dev->v4l2_dev.name, sizeof(chip->name)); + } + return 0; +} + +static int cx23417_g_register(struct cx23885_dev *dev, + struct v4l2_dbg_register *reg) +{ + u32 value; + + if (dev->v4l_device == NULL) + return -EINVAL; + + if ((reg->reg & 0x3) != 0 || reg->reg >= 0x10000) + return -EINVAL; + + if (mc417_register_read(dev, (u16) reg->reg, &value)) + return -EINVAL; /* V4L2 spec, but -EREMOTEIO really */ + + reg->size = 4; + reg->val = value; + return 0; +} + +int cx23885_g_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + struct cx23885_dev *dev = video_drvdata(file); + + if (reg->match.addr > 1) + return -EINVAL; + if (reg->match.addr) + return cx23417_g_register(dev, reg); + + if ((reg->reg & 0x3) != 0 || reg->reg >= pci_resource_len(dev->pci, 0)) + return -EINVAL; + + reg->size = 4; + reg->val = cx_read(reg->reg); + return 0; +} + +static int cx23417_s_register(struct cx23885_dev *dev, + const struct v4l2_dbg_register *reg) +{ + if (dev->v4l_device == NULL) + return -EINVAL; + + if ((reg->reg & 0x3) != 0 || reg->reg >= 0x10000) + return -EINVAL; + + if (mc417_register_write(dev, (u16) reg->reg, (u32) reg->val)) + return -EINVAL; /* V4L2 spec, but -EREMOTEIO really */ + return 0; +} + +int cx23885_s_register(struct file *file, void *fh, + const struct v4l2_dbg_register *reg) +{ + struct cx23885_dev *dev = video_drvdata(file); + + if (reg->match.addr > 1) + return -EINVAL; + if (reg->match.addr) + return cx23417_s_register(dev, reg); + + if ((reg->reg & 0x3) != 0 || reg->reg >= pci_resource_len(dev->pci, 0)) + return -EINVAL; + + cx_write(reg->reg, reg->val); + return 0; +} +#endif diff --git a/drivers/media/pci/cx23885/cx23885-ioctl.h b/drivers/media/pci/cx23885/cx23885-ioctl.h new file mode 100644 index 000000000..14bfe9e4e --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-ioctl.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the Conexant CX23885/7/8 PCIe bridge + * + * Various common ioctl() support functions + * + * Copyright (c) 2009 Andy Walls + */ + +#ifndef _CX23885_IOCTL_H_ +#define _CX23885_IOCTL_H_ + +int cx23885_g_chip_info(struct file *file, void *fh, + struct v4l2_dbg_chip_info *chip); + +#ifdef CONFIG_VIDEO_ADV_DEBUG +int cx23885_g_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg); + + +int cx23885_s_register(struct file *file, void *fh, + const struct v4l2_dbg_register *reg); + +#endif +#endif diff --git a/drivers/media/pci/cx23885/cx23885-ir.c b/drivers/media/pci/cx23885/cx23885-ir.c new file mode 100644 index 000000000..45befc272 --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-ir.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX23885/7/8 PCIe bridge + * + * Infrared device support routines - non-input, non-vl42_subdev routines + * + * Copyright (C) 2009 Andy Walls + */ + +#include "cx23885.h" +#include "cx23885-ir.h" +#include "cx23885-input.h" + +#include + +#define CX23885_IR_RX_FIFO_SERVICE_REQ 0 +#define CX23885_IR_RX_END_OF_RX_DETECTED 1 +#define CX23885_IR_RX_HW_FIFO_OVERRUN 2 +#define CX23885_IR_RX_SW_FIFO_OVERRUN 3 + +#define CX23885_IR_TX_FIFO_SERVICE_REQ 0 + + +void cx23885_ir_rx_work_handler(struct work_struct *work) +{ + struct cx23885_dev *dev = + container_of(work, struct cx23885_dev, ir_rx_work); + u32 events = 0; + unsigned long *notifications = &dev->ir_rx_notifications; + + if (test_and_clear_bit(CX23885_IR_RX_SW_FIFO_OVERRUN, notifications)) + events |= V4L2_SUBDEV_IR_RX_SW_FIFO_OVERRUN; + if (test_and_clear_bit(CX23885_IR_RX_HW_FIFO_OVERRUN, notifications)) + events |= V4L2_SUBDEV_IR_RX_HW_FIFO_OVERRUN; + if (test_and_clear_bit(CX23885_IR_RX_END_OF_RX_DETECTED, notifications)) + events |= V4L2_SUBDEV_IR_RX_END_OF_RX_DETECTED; + if (test_and_clear_bit(CX23885_IR_RX_FIFO_SERVICE_REQ, notifications)) + events |= V4L2_SUBDEV_IR_RX_FIFO_SERVICE_REQ; + + if (events == 0) + return; + + if (dev->kernel_ir) + cx23885_input_rx_work_handler(dev, events); +} + +void cx23885_ir_tx_work_handler(struct work_struct *work) +{ + struct cx23885_dev *dev = + container_of(work, struct cx23885_dev, ir_tx_work); + u32 events = 0; + unsigned long *notifications = &dev->ir_tx_notifications; + + if (test_and_clear_bit(CX23885_IR_TX_FIFO_SERVICE_REQ, notifications)) + events |= V4L2_SUBDEV_IR_TX_FIFO_SERVICE_REQ; + + if (events == 0) + return; + +} + +/* Possibly called in an IRQ context */ +void cx23885_ir_rx_v4l2_dev_notify(struct v4l2_subdev *sd, u32 events) +{ + struct cx23885_dev *dev = to_cx23885(sd->v4l2_dev); + unsigned long *notifications = &dev->ir_rx_notifications; + + if (events & V4L2_SUBDEV_IR_RX_FIFO_SERVICE_REQ) + set_bit(CX23885_IR_RX_FIFO_SERVICE_REQ, notifications); + if (events & V4L2_SUBDEV_IR_RX_END_OF_RX_DETECTED) + set_bit(CX23885_IR_RX_END_OF_RX_DETECTED, notifications); + if (events & V4L2_SUBDEV_IR_RX_HW_FIFO_OVERRUN) + set_bit(CX23885_IR_RX_HW_FIFO_OVERRUN, notifications); + if (events & V4L2_SUBDEV_IR_RX_SW_FIFO_OVERRUN) + set_bit(CX23885_IR_RX_SW_FIFO_OVERRUN, notifications); + + /* + * For the integrated AV core, we are already in a workqueue context. + * For the CX23888 integrated IR, we are in an interrupt context. + */ + if (sd == dev->sd_cx25840) + cx23885_ir_rx_work_handler(&dev->ir_rx_work); + else + schedule_work(&dev->ir_rx_work); +} + +/* Possibly called in an IRQ context */ +void cx23885_ir_tx_v4l2_dev_notify(struct v4l2_subdev *sd, u32 events) +{ + struct cx23885_dev *dev = to_cx23885(sd->v4l2_dev); + unsigned long *notifications = &dev->ir_tx_notifications; + + if (events & V4L2_SUBDEV_IR_TX_FIFO_SERVICE_REQ) + set_bit(CX23885_IR_TX_FIFO_SERVICE_REQ, notifications); + + /* + * For the integrated AV core, we are already in a workqueue context. + * For the CX23888 integrated IR, we are in an interrupt context. + */ + if (sd == dev->sd_cx25840) + cx23885_ir_tx_work_handler(&dev->ir_tx_work); + else + schedule_work(&dev->ir_tx_work); +} diff --git a/drivers/media/pci/cx23885/cx23885-ir.h b/drivers/media/pci/cx23885/cx23885-ir.h new file mode 100644 index 000000000..32b9378b2 --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-ir.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the Conexant CX23885/7/8 PCIe bridge + * + * Infrared device support routines - non-input, non-vl42_subdev routines + * + * Copyright (C) 2009 Andy Walls + */ + +#ifndef _CX23885_IR_H_ +#define _CX23885_IR_H_ +void cx23885_ir_rx_v4l2_dev_notify(struct v4l2_subdev *sd, u32 events); +void cx23885_ir_tx_v4l2_dev_notify(struct v4l2_subdev *sd, u32 events); + +void cx23885_ir_rx_work_handler(struct work_struct *work); +void cx23885_ir_tx_work_handler(struct work_struct *work); +#endif diff --git a/drivers/media/pci/cx23885/cx23885-reg.h b/drivers/media/pci/cx23885/cx23885-reg.h new file mode 100644 index 000000000..125a79a8c --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-reg.h @@ -0,0 +1,452 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the Conexant CX23885 PCIe bridge + * + * Copyright (c) 2006 Steven Toth + */ + +#ifndef _CX23885_REG_H_ +#define _CX23885_REG_H_ + +/* +Address Map +0x00000000 -> 0x00009000 TX SRAM (Fifos) +0x00010000 -> 0x00013c00 RX SRAM CMDS + CDT + +EACH CMDS struct is 0x80 bytes long + +DMAx_PTR1 = 0x03040 address of first cluster +DMAx_PTR2 = 0x10600 address of the CDT +DMAx_CNT1 = cluster size in (bytes >> 4) -1 +DMAx_CNT2 = total cdt size for all entries >> 3 + +Cluster Descriptor entry = 4 DWORDS + DWORD 0 -> ptr to cluster + DWORD 1 Reserved + DWORD 2 Reserved + DWORD 3 Reserved + +Channel manager Data Structure entry = 20 DWORD + 0 IntialProgramCounterLow + 1 IntialProgramCounterHigh + 2 ClusterDescriptorTableBase + 3 ClusterDescriptorTableSize + 4 InstructionQueueBase + 5 InstructionQueueSize +... Reserved + 19 Reserved +*/ + +/* Risc Instructions */ +#define RISC_CNT_INC 0x00010000 +#define RISC_CNT_RESET 0x00030000 +#define RISC_IRQ1 0x01000000 +#define RISC_IRQ2 0x02000000 +#define RISC_EOL 0x04000000 +#define RISC_SOL 0x08000000 +#define RISC_WRITE 0x10000000 +#define RISC_SKIP 0x20000000 +#define RISC_JUMP 0x70000000 +#define RISC_SYNC 0x80000000 +#define RISC_RESYNC 0x80008000 +#define RISC_READ 0x90000000 +#define RISC_WRITERM 0xB0000000 +#define RISC_WRITECM 0xC0000000 +#define RISC_WRITECR 0xD0000000 +#define RISC_WRITEC 0x50000000 +#define RISC_READC 0xA0000000 + + +/* Audio and Video Core */ +#define HOST_REG1 0x00000000 +#define HOST_REG2 0x00000001 +#define HOST_REG3 0x00000002 + +/* Chip Configuration Registers */ +#define CHIP_CTRL 0x00000100 +#define AFE_CTRL 0x00000104 +#define VID_PLL_INT_POST 0x00000108 +#define VID_PLL_FRAC 0x0000010C +#define AUX_PLL_INT_POST 0x00000110 +#define AUX_PLL_FRAC 0x00000114 +#define SYS_PLL_INT_POST 0x00000118 +#define SYS_PLL_FRAC 0x0000011C +#define PIN_CTRL 0x00000120 +#define AUD_IO_CTRL 0x00000124 +#define AUD_LOCK1 0x00000128 +#define AUD_LOCK2 0x0000012C +#define POWER_CTRL 0x00000130 +#define AFE_DIAG_CTRL1 0x00000134 +#define AFE_DIAG_CTRL3 0x0000013C +#define PLL_DIAG_CTRL 0x00000140 +#define AFE_CLK_OUT_CTRL 0x00000144 +#define DLL1_DIAG_CTRL 0x0000015C + +/* GPIO[23:19] Output Enable */ +#define GPIO2_OUT_EN_REG 0x00000160 +/* GPIO[23:19] Data Registers */ +#define GPIO2 0x00000164 + +#define IFADC_CTRL 0x00000180 + +/* Infrared Remote Registers */ +#define IR_CNTRL_REG 0x00000200 +#define IR_TXCLK_REG 0x00000204 +#define IR_RXCLK_REG 0x00000208 +#define IR_CDUTY_REG 0x0000020C +#define IR_STAT_REG 0x00000210 +#define IR_IRQEN_REG 0x00000214 +#define IR_FILTR_REG 0x00000218 +#define IR_FIFO_REG 0x0000023C + +/* Video Decoder Registers */ +#define MODE_CTRL 0x00000400 +#define OUT_CTRL1 0x00000404 +#define OUT_CTRL2 0x00000408 +#define GEN_STAT 0x0000040C +#define INT_STAT_MASK 0x00000410 +#define LUMA_CTRL 0x00000414 +#define HSCALE_CTRL 0x00000418 +#define VSCALE_CTRL 0x0000041C +#define CHROMA_CTRL 0x00000420 +#define VBI_LINE_CTRL1 0x00000424 +#define VBI_LINE_CTRL2 0x00000428 +#define VBI_LINE_CTRL3 0x0000042C +#define VBI_LINE_CTRL4 0x00000430 +#define VBI_LINE_CTRL5 0x00000434 +#define VBI_FC_CFG 0x00000438 +#define VBI_MISC_CFG1 0x0000043C +#define VBI_MISC_CFG2 0x00000440 +#define VBI_PAY1 0x00000444 +#define VBI_PAY2 0x00000448 +#define VBI_CUST1_CFG1 0x0000044C +#define VBI_CUST1_CFG2 0x00000450 +#define VBI_CUST1_CFG3 0x00000454 +#define VBI_CUST2_CFG1 0x00000458 +#define VBI_CUST2_CFG2 0x0000045C +#define VBI_CUST2_CFG3 0x00000460 +#define VBI_CUST3_CFG1 0x00000464 +#define VBI_CUST3_CFG2 0x00000468 +#define VBI_CUST3_CFG3 0x0000046C +#define HORIZ_TIM_CTRL 0x00000470 +#define VERT_TIM_CTRL 0x00000474 +#define SRC_COMB_CFG 0x00000478 +#define CHROMA_VBIOFF_CFG 0x0000047C +#define FIELD_COUNT 0x00000480 +#define MISC_TIM_CTRL 0x00000484 +#define DFE_CTRL1 0x00000488 +#define DFE_CTRL2 0x0000048C +#define DFE_CTRL3 0x00000490 +#define PLL_CTRL 0x00000494 +#define HTL_CTRL 0x00000498 +#define COMB_CTRL 0x0000049C +#define CRUSH_CTRL 0x000004A0 +#define SOFT_RST_CTRL 0x000004A4 +#define CX885_VERSION 0x000004B4 +#define VBI_PASS_CTRL 0x000004BC + +/* Audio Decoder Registers */ +/* 8051 Configuration */ +#define DL_CTL 0x00000800 +#define STD_DET_STATUS 0x00000804 +#define STD_DET_CTL 0x00000808 +#define DW8051_INT 0x0000080C +#define GENERAL_CTL 0x00000810 +#define AAGC_CTL 0x00000814 +#define DEMATRIX_CTL 0x000008CC +#define PATH1_CTL1 0x000008D0 +#define PATH1_VOL_CTL 0x000008D4 +#define PATH1_EQ_CTL 0x000008D8 +#define PATH1_SC_CTL 0x000008DC +#define PATH2_CTL1 0x000008E0 +#define PATH2_VOL_CTL 0x000008E4 +#define PATH2_EQ_CTL 0x000008E8 +#define PATH2_SC_CTL 0x000008EC + +/* Sample Rate Converter */ +#define SRC_CTL 0x000008F0 +#define SRC_LF_COEF 0x000008F4 +#define SRC1_CTL 0x000008F8 +#define SRC2_CTL 0x000008FC +#define SRC3_CTL 0x00000900 +#define SRC4_CTL 0x00000904 +#define SRC5_CTL 0x00000908 +#define SRC6_CTL 0x0000090C +#define BAND_OUT_SEL 0x00000910 +#define I2S_N_CTL 0x00000914 +#define I2S_OUT_CTL 0x00000918 +#define AUTOCONFIG_REG 0x000009C4 + +/* Audio ADC Registers */ +#define DSM_CTRL1 0x00000000 +#define DSM_CTRL2 0x00000001 +#define CHP_EN_CTRL 0x00000002 +#define CHP_CLK_CTRL1 0x00000004 +#define CHP_CLK_CTRL2 0x00000005 +#define BG_REF_CTRL 0x00000006 +#define SD2_SW_CTRL1 0x00000008 +#define SD2_SW_CTRL2 0x00000009 +#define SD2_BIAS_CTRL 0x0000000A +#define AMP_BIAS_CTRL 0x0000000C +#define CH_PWR_CTRL1 0x0000000E +#define FLD_CH_SEL (1 << 3) +#define CH_PWR_CTRL2 0x0000000F +#define DSM_STATUS1 0x00000010 +#define DSM_STATUS2 0x00000011 +#define DIG_CTL1 0x00000012 +#define DIG_CTL2 0x00000013 +#define I2S_TX_CFG 0x0000001A + +#define DEV_CNTRL2 0x00040000 + +#define PCI_MSK_IR (1 << 28) +#define PCI_MSK_AV_CORE (1 << 27) +#define PCI_MSK_GPIO1 (1 << 24) +#define PCI_MSK_GPIO0 (1 << 23) +#define PCI_MSK_APB_DMA (1 << 12) +#define PCI_MSK_AL_WR (1 << 11) +#define PCI_MSK_AL_RD (1 << 10) +#define PCI_MSK_RISC_WR (1 << 9) +#define PCI_MSK_RISC_RD (1 << 8) +#define PCI_MSK_AUD_EXT (1 << 4) +#define PCI_MSK_AUD_INT (1 << 3) +#define PCI_MSK_VID_C (1 << 2) +#define PCI_MSK_VID_B (1 << 1) +#define PCI_MSK_VID_A 1 +#define PCI_INT_MSK 0x00040010 + +#define PCI_INT_STAT 0x00040014 +#define PCI_INT_MSTAT 0x00040018 + +#define VID_A_INT_MSK 0x00040020 +#define VID_A_INT_STAT 0x00040024 +#define VID_A_INT_MSTAT 0x00040028 +#define VID_A_INT_SSTAT 0x0004002C + +#define VID_B_INT_MSK 0x00040030 +#define VID_B_MSK_BAD_PKT (1 << 20) +#define VID_B_MSK_VBI_OPC_ERR (1 << 17) +#define VID_B_MSK_OPC_ERR (1 << 16) +#define VID_B_MSK_VBI_SYNC (1 << 13) +#define VID_B_MSK_SYNC (1 << 12) +#define VID_B_MSK_VBI_OF (1 << 9) +#define VID_B_MSK_OF (1 << 8) +#define VID_B_MSK_VBI_RISCI2 (1 << 5) +#define VID_B_MSK_RISCI2 (1 << 4) +#define VID_B_MSK_VBI_RISCI1 (1 << 1) +#define VID_B_MSK_RISCI1 1 +#define VID_B_INT_STAT 0x00040034 +#define VID_B_INT_MSTAT 0x00040038 +#define VID_B_INT_SSTAT 0x0004003C + +#define VID_B_MSK_BAD_PKT (1 << 20) +#define VID_B_MSK_OPC_ERR (1 << 16) +#define VID_B_MSK_SYNC (1 << 12) +#define VID_B_MSK_OF (1 << 8) +#define VID_B_MSK_RISCI2 (1 << 4) +#define VID_B_MSK_RISCI1 1 + +#define VID_C_MSK_BAD_PKT (1 << 20) +#define VID_C_MSK_OPC_ERR (1 << 16) +#define VID_C_MSK_SYNC (1 << 12) +#define VID_C_MSK_OF (1 << 8) +#define VID_C_MSK_RISCI2 (1 << 4) +#define VID_C_MSK_RISCI1 1 + +/* A superset for testing purposes */ +#define VID_BC_MSK_BAD_PKT (1 << 20) +#define VID_BC_MSK_OPC_ERR (1 << 16) +#define VID_BC_MSK_SYNC (1 << 12) +#define VID_BC_MSK_OF (1 << 8) +#define VID_BC_MSK_VBI_RISCI2 (1 << 5) +#define VID_BC_MSK_RISCI2 (1 << 4) +#define VID_BC_MSK_VBI_RISCI1 (1 << 1) +#define VID_BC_MSK_RISCI1 1 + +#define VID_C_INT_MSK 0x00040040 +#define VID_C_INT_STAT 0x00040044 +#define VID_C_INT_MSTAT 0x00040048 +#define VID_C_INT_SSTAT 0x0004004C + +#define AUDIO_INT_INT_MSK 0x00040050 +#define AUDIO_INT_INT_STAT 0x00040054 +#define AUDIO_INT_INT_MSTAT 0x00040058 +#define AUDIO_INT_INT_SSTAT 0x0004005C + +#define AUDIO_EXT_INT_MSK 0x00040060 +#define AUDIO_EXT_INT_STAT 0x00040064 +#define AUDIO_EXT_INT_MSTAT 0x00040068 +#define AUDIO_EXT_INT_SSTAT 0x0004006C + +/* Bits [7:0] set in both TC_REQ and TC_REQ_SET + * indicate a stall in the RISC engine for a + * particular rider traffic class. This causes + * the 885 and 888 bridges (unknown about 887) + * to become inoperable. Setting bits in + * TC_REQ_SET resets the corresponding bits + * in TC_REQ (and TC_REQ_SET) allowing + * operation to continue. + */ +#define TC_REQ 0x00040090 +#define TC_REQ_SET 0x00040094 + +#define RDR_CFG0 0x00050000 +#define RDR_CFG1 0x00050004 +#define RDR_CFG2 0x00050008 +#define RDR_RDRCTL1 0x0005030c +#define RDR_TLCTL0 0x00050318 + +/* APB DMAC Current Buffer Pointer */ +#define DMA1_PTR1 0x00100000 +#define DMA2_PTR1 0x00100004 +#define DMA3_PTR1 0x00100008 +#define DMA4_PTR1 0x0010000C +#define DMA5_PTR1 0x00100010 +#define DMA6_PTR1 0x00100014 +#define DMA7_PTR1 0x00100018 +#define DMA8_PTR1 0x0010001C + +/* APB DMAC Current Table Pointer */ +#define DMA1_PTR2 0x00100040 +#define DMA2_PTR2 0x00100044 +#define DMA3_PTR2 0x00100048 +#define DMA4_PTR2 0x0010004C +#define DMA5_PTR2 0x00100050 +#define DMA6_PTR2 0x00100054 +#define DMA7_PTR2 0x00100058 +#define DMA8_PTR2 0x0010005C + +/* APB DMAC Buffer Limit */ +#define DMA1_CNT1 0x00100080 +#define DMA2_CNT1 0x00100084 +#define DMA3_CNT1 0x00100088 +#define DMA4_CNT1 0x0010008C +#define DMA5_CNT1 0x00100090 +#define DMA6_CNT1 0x00100094 +#define DMA7_CNT1 0x00100098 +#define DMA8_CNT1 0x0010009C + +/* APB DMAC Table Size */ +#define DMA1_CNT2 0x001000C0 +#define DMA2_CNT2 0x001000C4 +#define DMA3_CNT2 0x001000C8 +#define DMA4_CNT2 0x001000CC +#define DMA5_CNT2 0x001000D0 +#define DMA6_CNT2 0x001000D4 +#define DMA7_CNT2 0x001000D8 +#define DMA8_CNT2 0x001000DC + +/* Timer Counters */ +#define TM_CNT_LDW 0x00110000 +#define TM_CNT_UW 0x00110004 +#define TM_LMT_LDW 0x00110008 +#define TM_LMT_UW 0x0011000C + +/* GPIO */ +#define GP0_IO 0x00110010 +#define GPIO_ISM 0x00110014 +#define SOFT_RESET 0x0011001C + +/* GPIO (417 Microsoftcontroller) RW Data */ +#define MC417_RWD 0x00110020 + +/* GPIO (417 Microsoftcontroller) Output Enable, Low Active */ +#define MC417_OEN 0x00110024 +#define MC417_CTL 0x00110028 +#define ALT_PIN_OUT_SEL 0x0011002C +#define CLK_DELAY 0x00110048 +#define PAD_CTRL 0x0011004C + +/* Video A Interface */ +#define VID_A_GPCNT 0x00130020 +#define VBI_A_GPCNT 0x00130024 +#define VID_A_GPCNT_CTL 0x00130030 +#define VBI_A_GPCNT_CTL 0x00130034 +#define VID_A_DMA_CTL 0x00130040 +#define VID_A_VIP_CTRL 0x00130080 +#define VID_A_PIXEL_FRMT 0x00130084 +#define VID_A_VBI_CTRL 0x00130088 + +/* Video B Interface */ +#define VID_B_DMA 0x00130100 +#define VBI_B_DMA 0x00130108 +#define VID_B_GPCNT 0x00130120 +#define VBI_B_GPCNT 0x00130124 +#define VID_B_GPCNT_CTL 0x00130134 +#define VBI_B_GPCNT_CTL 0x00130138 +#define VID_B_DMA_CTL 0x00130140 +#define VID_B_SRC_SEL 0x00130144 +#define VID_B_LNGTH 0x00130150 +#define VID_B_HW_SOP_CTL 0x00130154 +#define VID_B_GEN_CTL 0x00130158 +#define VID_B_BD_PKT_STATUS 0x0013015C +#define VID_B_SOP_STATUS 0x00130160 +#define VID_B_FIFO_OVFL_STAT 0x00130164 +#define VID_B_VLD_MISC 0x00130168 +#define VID_B_TS_CLK_EN 0x0013016C +#define VID_B_VIP_CTRL 0x00130180 +#define VID_B_PIXEL_FRMT 0x00130184 + +/* Video C Interface */ +#define VID_C_DMA 0x00130200 +#define VBI_C_DMA 0x00130208 +#define VID_C_GPCNT 0x00130220 +#define VID_C_GPCNT_CTL 0x00130230 +#define VBI_C_GPCNT_CTL 0x00130234 +#define VID_C_DMA_CTL 0x00130240 +#define VID_C_LNGTH 0x00130250 +#define VID_C_HW_SOP_CTL 0x00130254 +#define VID_C_GEN_CTL 0x00130258 +#define VID_C_BD_PKT_STATUS 0x0013025C +#define VID_C_SOP_STATUS 0x00130260 +#define VID_C_FIFO_OVFL_STAT 0x00130264 +#define VID_C_VLD_MISC 0x00130268 +#define VID_C_TS_CLK_EN 0x0013026C + +/* Internal Audio Interface */ +#define AUD_INT_A_GPCNT 0x00140020 +#define AUD_INT_B_GPCNT 0x00140024 +#define AUD_INT_A_GPCNT_CTL 0x00140030 +#define AUD_INT_B_GPCNT_CTL 0x00140034 +#define AUD_INT_DMA_CTL 0x00140040 +#define AUD_INT_A_LNGTH 0x00140050 +#define AUD_INT_B_LNGTH 0x00140054 +#define AUD_INT_A_MODE 0x00140058 +#define AUD_INT_B_MODE 0x0014005C + +/* External Audio Interface */ +#define AUD_EXT_DMA 0x00140100 +#define AUD_EXT_GPCNT 0x00140120 +#define AUD_EXT_GPCNT_CTL 0x00140130 +#define AUD_EXT_DMA_CTL 0x00140140 +#define AUD_EXT_LNGTH 0x00140150 +#define AUD_EXT_A_MODE 0x00140158 + +/* I2C Bus 1 */ +#define I2C1_ADDR 0x00180000 +#define I2C1_WDATA 0x00180004 +#define I2C1_CTRL 0x00180008 +#define I2C1_RDATA 0x0018000C +#define I2C1_STAT 0x00180010 + +/* I2C Bus 2 */ +#define I2C2_ADDR 0x00190000 +#define I2C2_WDATA 0x00190004 +#define I2C2_CTRL 0x00190008 +#define I2C2_RDATA 0x0019000C +#define I2C2_STAT 0x00190010 + +/* I2C Bus 3 */ +#define I2C3_ADDR 0x001A0000 +#define I2C3_WDATA 0x001A0004 +#define I2C3_CTRL 0x001A0008 +#define I2C3_RDATA 0x001A000C +#define I2C3_STAT 0x001A0010 + +/* UART */ +#define UART_CTL 0x001B0000 +#define UART_BRD 0x001B0004 +#define UART_ISR 0x001B000C +#define UART_CNT 0x001B0010 + +#endif /* _CX23885_REG_H_ */ diff --git a/drivers/media/pci/cx23885/cx23885-vbi.c b/drivers/media/pci/cx23885/cx23885-vbi.c new file mode 100644 index 000000000..40817cc52 --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-vbi.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX23885 PCIe bridge + * + * Copyright (c) 2007 Steven Toth + */ + +#include "cx23885.h" + +#include +#include +#include +#include + +static unsigned int vbibufs = 4; +module_param(vbibufs, int, 0644); +MODULE_PARM_DESC(vbibufs, "number of vbi buffers, range 2-32"); + +static unsigned int vbi_debug; +module_param(vbi_debug, int, 0644); +MODULE_PARM_DESC(vbi_debug, "enable debug messages [vbi]"); + +#define dprintk(level, fmt, arg...)\ + do { if (vbi_debug >= level)\ + printk(KERN_DEBUG pr_fmt("%s: vbi:" fmt), \ + __func__, ##arg); \ + } while (0) + +/* ------------------------------------------------------------------ */ + +#define VBI_LINE_LENGTH 1440 +#define VBI_NTSC_LINE_COUNT 12 +#define VBI_PAL_LINE_COUNT 18 + + +int cx23885_vbi_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx23885_dev *dev = video_drvdata(file); + + f->fmt.vbi.sampling_rate = 27000000; + f->fmt.vbi.samples_per_line = VBI_LINE_LENGTH; + f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + f->fmt.vbi.offset = 0; + f->fmt.vbi.flags = 0; + if (dev->tvnorm & V4L2_STD_525_60) { + /* ntsc */ + f->fmt.vbi.start[0] = V4L2_VBI_ITU_525_F1_START + 9; + f->fmt.vbi.start[1] = V4L2_VBI_ITU_525_F2_START + 9; + f->fmt.vbi.count[0] = VBI_NTSC_LINE_COUNT; + f->fmt.vbi.count[1] = VBI_NTSC_LINE_COUNT; + } else if (dev->tvnorm & V4L2_STD_625_50) { + /* pal */ + f->fmt.vbi.start[0] = V4L2_VBI_ITU_625_F1_START + 5; + f->fmt.vbi.start[1] = V4L2_VBI_ITU_625_F2_START + 5; + f->fmt.vbi.count[0] = VBI_PAL_LINE_COUNT; + f->fmt.vbi.count[1] = VBI_PAL_LINE_COUNT; + } + + return 0; +} + +/* We're given the Video Interrupt status register. + * The cx23885_video_irq() func has already validated + * the potential error bits, we just need to + * deal with vbi payload and return indication if + * we actually processed any payload. + */ +int cx23885_vbi_irq(struct cx23885_dev *dev, u32 status) +{ + u32 count; + int handled = 0; + + if (status & VID_BC_MSK_VBI_RISCI1) { + dprintk(1, "%s() VID_BC_MSK_VBI_RISCI1\n", __func__); + spin_lock(&dev->slock); + count = cx_read(VBI_A_GPCNT); + cx23885_video_wakeup(dev, &dev->vbiq, count); + spin_unlock(&dev->slock); + handled++; + } + + return handled; +} + +static int cx23885_start_vbi_dma(struct cx23885_dev *dev, + struct cx23885_dmaqueue *q, + struct cx23885_buffer *buf) +{ + dprintk(1, "%s()\n", __func__); + + /* setup fifo + format */ + cx23885_sram_channel_setup(dev, &dev->sram_channels[SRAM_CH02], + VBI_LINE_LENGTH, buf->risc.dma); + + /* reset counter */ + cx_write(VID_A_VBI_CTRL, 3); + cx_write(VBI_A_GPCNT_CTL, 3); + q->count = 0; + + /* enable irq */ + cx23885_irq_add_enable(dev, 0x01); + cx_set(VID_A_INT_MSK, 0x000022); + + /* start dma */ + cx_set(DEV_CNTRL2, (1<<5)); + cx_set(VID_A_DMA_CTL, 0x22); /* FIFO and RISC enable */ + + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct cx23885_dev *dev = q->drv_priv; + unsigned lines = VBI_PAL_LINE_COUNT; + + if (dev->tvnorm & V4L2_STD_525_60) + lines = VBI_NTSC_LINE_COUNT; + *num_planes = 1; + sizes[0] = lines * VBI_LINE_LENGTH * 2; + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx23885_dev *dev = vb->vb2_queue->drv_priv; + struct cx23885_buffer *buf = container_of(vbuf, + struct cx23885_buffer, vb); + struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, 0); + unsigned lines = VBI_PAL_LINE_COUNT; + + if (dev->tvnorm & V4L2_STD_525_60) + lines = VBI_NTSC_LINE_COUNT; + + if (vb2_plane_size(vb, 0) < lines * VBI_LINE_LENGTH * 2) + return -EINVAL; + vb2_set_plane_payload(vb, 0, lines * VBI_LINE_LENGTH * 2); + + cx23885_risc_vbibuffer(dev->pci, &buf->risc, + sgt->sgl, + 0, VBI_LINE_LENGTH * lines, + VBI_LINE_LENGTH, 0, + lines); + return 0; +} + +static void buffer_finish(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx23885_buffer *buf = container_of(vbuf, + struct cx23885_buffer, vb); + + cx23885_free_buffer(vb->vb2_queue->drv_priv, buf); +} + +/* + * The risc program for each buffer works as follows: it starts with a simple + * 'JUMP to addr + 12', which is effectively a NOP. Then the code to DMA the + * buffer follows and at the end we have a JUMP back to the start + 12 (skipping + * the initial JUMP). + * + * This is the risc program of the first buffer to be queued if the active list + * is empty and it just keeps DMAing this buffer without generating any + * interrupts. + * + * If a new buffer is added then the initial JUMP in the code for that buffer + * will generate an interrupt which signals that the previous buffer has been + * DMAed successfully and that it can be returned to userspace. + * + * It also sets the final jump of the previous buffer to the start of the new + * buffer, thus chaining the new buffer into the DMA chain. This is a single + * atomic u32 write, so there is no race condition. + * + * The end-result of all this that you only get an interrupt when a buffer + * is ready, so the control flow is very easy. + */ +static void buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx23885_dev *dev = vb->vb2_queue->drv_priv; + struct cx23885_buffer *buf = container_of(vbuf, + struct cx23885_buffer, vb); + struct cx23885_buffer *prev; + struct cx23885_dmaqueue *q = &dev->vbiq; + unsigned long flags; + + buf->risc.cpu[1] = cpu_to_le32(buf->risc.dma + 12); + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma + 12); + buf->risc.jmp[2] = cpu_to_le32(0); /* bits 63-32 */ + + if (list_empty(&q->active)) { + spin_lock_irqsave(&dev->slock, flags); + list_add_tail(&buf->queue, &q->active); + spin_unlock_irqrestore(&dev->slock, flags); + dprintk(2, "[%p/%d] vbi_queue - first active\n", + buf, buf->vb.vb2_buf.index); + + } else { + buf->risc.cpu[0] |= cpu_to_le32(RISC_IRQ1); + prev = list_entry(q->active.prev, struct cx23885_buffer, + queue); + spin_lock_irqsave(&dev->slock, flags); + list_add_tail(&buf->queue, &q->active); + spin_unlock_irqrestore(&dev->slock, flags); + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(2, "[%p/%d] buffer_queue - append to active\n", + buf, buf->vb.vb2_buf.index); + } +} + +static int cx23885_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct cx23885_dev *dev = q->drv_priv; + struct cx23885_dmaqueue *dmaq = &dev->vbiq; + struct cx23885_buffer *buf = list_entry(dmaq->active.next, + struct cx23885_buffer, queue); + + cx23885_start_vbi_dma(dev, dmaq, buf); + return 0; +} + +static void cx23885_stop_streaming(struct vb2_queue *q) +{ + struct cx23885_dev *dev = q->drv_priv; + struct cx23885_dmaqueue *dmaq = &dev->vbiq; + unsigned long flags; + + cx_clear(VID_A_DMA_CTL, 0x22); /* FIFO and RISC enable */ + spin_lock_irqsave(&dev->slock, flags); + while (!list_empty(&dmaq->active)) { + struct cx23885_buffer *buf = list_entry(dmaq->active.next, + struct cx23885_buffer, queue); + + list_del(&buf->queue); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&dev->slock, flags); +} + + +const struct vb2_ops cx23885_vbi_qops = { + .queue_setup = queue_setup, + .buf_prepare = buffer_prepare, + .buf_finish = buffer_finish, + .buf_queue = buffer_queue, + .start_streaming = cx23885_start_streaming, + .stop_streaming = cx23885_stop_streaming, +}; diff --git a/drivers/media/pci/cx23885/cx23885-video.c b/drivers/media/pci/cx23885/cx23885-video.c new file mode 100644 index 000000000..14d219fd1 --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-video.c @@ -0,0 +1,1424 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX23885 PCIe bridge + * + * Copyright (c) 2007 Steven Toth + */ + +#include "cx23885.h" +#include "cx23885-video.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "cx23885-ioctl.h" +#include "xc2028.h" + +#include + +MODULE_DESCRIPTION("v4l2 driver module for cx23885 based TV cards"); +MODULE_AUTHOR("Steven Toth "); +MODULE_LICENSE("GPL"); + +/* ------------------------------------------------------------------ */ + +static unsigned int video_nr[] = {[0 ... (CX23885_MAXBOARDS - 1)] = UNSET }; +static unsigned int vbi_nr[] = {[0 ... (CX23885_MAXBOARDS - 1)] = UNSET }; + +module_param_array(video_nr, int, NULL, 0444); +module_param_array(vbi_nr, int, NULL, 0444); + +MODULE_PARM_DESC(video_nr, "video device numbers"); +MODULE_PARM_DESC(vbi_nr, "vbi device numbers"); + +static unsigned int video_debug; +module_param(video_debug, int, 0644); +MODULE_PARM_DESC(video_debug, "enable debug messages [video]"); + +static unsigned int irq_debug; +module_param(irq_debug, int, 0644); +MODULE_PARM_DESC(irq_debug, "enable debug messages [IRQ handler]"); + +static unsigned int vid_limit = 16; +module_param(vid_limit, int, 0644); +MODULE_PARM_DESC(vid_limit, "capture memory limit in megabytes"); + +#define dprintk(level, fmt, arg...)\ + do { if (video_debug >= level)\ + printk(KERN_DEBUG pr_fmt("%s: video:" fmt), \ + __func__, ##arg); \ + } while (0) + +/* ------------------------------------------------------------------- */ +/* static data */ + +#define FORMAT_FLAGS_PACKED 0x01 +static struct cx23885_fmt formats[] = { + { + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + } +}; + +static struct cx23885_fmt *format_by_fourcc(unsigned int fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) + if (formats[i].fourcc == fourcc) + return formats+i; + return NULL; +} + +/* ------------------------------------------------------------------- */ + +void cx23885_video_wakeup(struct cx23885_dev *dev, + struct cx23885_dmaqueue *q, u32 count) +{ + struct cx23885_buffer *buf; + + if (list_empty(&q->active)) + return; + buf = list_entry(q->active.next, + struct cx23885_buffer, queue); + + buf->vb.sequence = q->count++; + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + dprintk(2, "[%p/%d] wakeup reg=%d buf=%d\n", buf, + buf->vb.vb2_buf.index, count, q->count); + list_del(&buf->queue); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); +} + +int cx23885_set_tvnorm(struct cx23885_dev *dev, v4l2_std_id norm) +{ + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .format.code = MEDIA_BUS_FMT_FIXED, + }; + + dprintk(1, "%s(norm = 0x%08x) name: [%s]\n", + __func__, + (unsigned int)norm, + v4l2_norm_to_name(norm)); + + if (dev->tvnorm == norm) + return 0; + + if (dev->tvnorm != norm) { + if (vb2_is_busy(&dev->vb2_vidq) || vb2_is_busy(&dev->vb2_vbiq) || + vb2_is_busy(&dev->vb2_mpegq)) + return -EBUSY; + } + + dev->tvnorm = norm; + dev->width = 720; + dev->height = norm_maxh(norm); + dev->field = V4L2_FIELD_INTERLACED; + + call_all(dev, video, s_std, norm); + + format.format.width = dev->width; + format.format.height = dev->height; + format.format.field = dev->field; + call_all(dev, pad, set_fmt, NULL, &format); + + return 0; +} + +static struct video_device *cx23885_vdev_init(struct cx23885_dev *dev, + struct pci_dev *pci, + struct video_device *template, + char *type) +{ + struct video_device *vfd; + dprintk(1, "%s()\n", __func__); + + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + *vfd = *template; + vfd->v4l2_dev = &dev->v4l2_dev; + vfd->release = video_device_release; + vfd->lock = &dev->lock; + snprintf(vfd->name, sizeof(vfd->name), "%s (%s)", + cx23885_boards[dev->board].name, type); + video_set_drvdata(vfd, dev); + return vfd; +} + +int cx23885_flatiron_write(struct cx23885_dev *dev, u8 reg, u8 data) +{ + /* 8 bit registers, 8 bit values */ + u8 buf[] = { reg, data }; + + struct i2c_msg msg = { .addr = 0x98 >> 1, + .flags = 0, .buf = buf, .len = 2 }; + + return i2c_transfer(&dev->i2c_bus[2].i2c_adap, &msg, 1); +} + +u8 cx23885_flatiron_read(struct cx23885_dev *dev, u8 reg) +{ + /* 8 bit registers, 8 bit values */ + int ret; + u8 b0[] = { reg }; + u8 b1[] = { 0 }; + + struct i2c_msg msg[] = { + { .addr = 0x98 >> 1, .flags = 0, .buf = b0, .len = 1 }, + { .addr = 0x98 >> 1, .flags = I2C_M_RD, .buf = b1, .len = 1 } + }; + + ret = i2c_transfer(&dev->i2c_bus[2].i2c_adap, &msg[0], 2); + if (ret != 2) + pr_err("%s() error\n", __func__); + + return b1[0]; +} + +static void cx23885_flatiron_dump(struct cx23885_dev *dev) +{ + int i; + dprintk(1, "Flatiron dump\n"); + for (i = 0; i < 0x24; i++) { + dprintk(1, "FI[%02x] = %02x\n", i, + cx23885_flatiron_read(dev, i)); + } +} + +static int cx23885_flatiron_mux(struct cx23885_dev *dev, int input) +{ + u8 val; + dprintk(1, "%s(input = %d)\n", __func__, input); + + if (input == 1) + val = cx23885_flatiron_read(dev, CH_PWR_CTRL1) & ~FLD_CH_SEL; + else if (input == 2) + val = cx23885_flatiron_read(dev, CH_PWR_CTRL1) | FLD_CH_SEL; + else + return -EINVAL; + + val |= 0x20; /* Enable clock to delta-sigma and dec filter */ + + cx23885_flatiron_write(dev, CH_PWR_CTRL1, val); + + /* Wake up */ + cx23885_flatiron_write(dev, CH_PWR_CTRL2, 0); + + if (video_debug) + cx23885_flatiron_dump(dev); + + return 0; +} + +static int cx23885_video_mux(struct cx23885_dev *dev, unsigned int input) +{ + dprintk(1, "%s() video_mux: %d [vmux=%d, gpio=0x%x,0x%x,0x%x,0x%x]\n", + __func__, + input, INPUT(input)->vmux, + INPUT(input)->gpio0, INPUT(input)->gpio1, + INPUT(input)->gpio2, INPUT(input)->gpio3); + dev->input = input; + + if (dev->board == CX23885_BOARD_MYGICA_X8506 || + dev->board == CX23885_BOARD_MAGICPRO_PROHDTVE2 || + dev->board == CX23885_BOARD_MYGICA_X8507) { + /* Select Analog TV */ + if (INPUT(input)->type == CX23885_VMUX_TELEVISION) + cx23885_gpio_clear(dev, GPIO_0); + } + + /* Tell the internal A/V decoder */ + v4l2_subdev_call(dev->sd_cx25840, video, s_routing, + INPUT(input)->vmux, 0, 0); + + if ((dev->board == CX23885_BOARD_HAUPPAUGE_HVR1800) || + (dev->board == CX23885_BOARD_MPX885) || + (dev->board == CX23885_BOARD_HAUPPAUGE_HVR1250) || + (dev->board == CX23885_BOARD_HAUPPAUGE_IMPACTVCBE) || + (dev->board == CX23885_BOARD_HAUPPAUGE_HVR1255) || + (dev->board == CX23885_BOARD_HAUPPAUGE_HVR1255_22111) || + (dev->board == CX23885_BOARD_HAUPPAUGE_HVR1265_K4) || + (dev->board == CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC) || + (dev->board == CX23885_BOARD_HAUPPAUGE_QUADHD_DVB) || + (dev->board == CX23885_BOARD_HAUPPAUGE_HVR1850) || + (dev->board == CX23885_BOARD_HAUPPAUGE_HVR5525) || + (dev->board == CX23885_BOARD_MYGICA_X8507) || + (dev->board == CX23885_BOARD_AVERMEDIA_HC81R) || + (dev->board == CX23885_BOARD_VIEWCAST_260E) || + (dev->board == CX23885_BOARD_VIEWCAST_460E) || + (dev->board == CX23885_BOARD_AVERMEDIA_CE310B) || + (dev->board == CX23885_BOARD_AVERMEDIA_H789C)) { + /* Configure audio routing */ + v4l2_subdev_call(dev->sd_cx25840, audio, s_routing, + INPUT(input)->amux, 0, 0); + + if (INPUT(input)->amux == CX25840_AUDIO7) + cx23885_flatiron_mux(dev, 1); + else if (INPUT(input)->amux == CX25840_AUDIO6) + cx23885_flatiron_mux(dev, 2); + } + + return 0; +} + +static int cx23885_audio_mux(struct cx23885_dev *dev, unsigned int input) +{ + dprintk(1, "%s(input=%d)\n", __func__, input); + + /* The baseband video core of the cx23885 has two audio inputs. + * LR1 and LR2. In almost every single case so far only HVR1xxx + * cards we've only ever supported LR1. Time to support LR2, + * which is available via the optional white breakout header on + * the board. + * We'll use a could of existing enums in the card struct to allow + * devs to specify which baseband input they need, or just default + * to what we've always used. + */ + if (INPUT(input)->amux == CX25840_AUDIO7) + cx23885_flatiron_mux(dev, 1); + else if (INPUT(input)->amux == CX25840_AUDIO6) + cx23885_flatiron_mux(dev, 2); + else { + /* Not specifically defined, assume the default. */ + cx23885_flatiron_mux(dev, 1); + } + + return 0; +} + +/* ------------------------------------------------------------------ */ +static int cx23885_start_video_dma(struct cx23885_dev *dev, + struct cx23885_dmaqueue *q, + struct cx23885_buffer *buf) +{ + dprintk(1, "%s()\n", __func__); + + /* Stop the dma/fifo before we tamper with it's risc programs */ + cx_clear(VID_A_DMA_CTL, 0x11); + + /* setup fifo + format */ + cx23885_sram_channel_setup(dev, &dev->sram_channels[SRAM_CH01], + buf->bpl, buf->risc.dma); + + /* reset counter */ + cx_write(VID_A_GPCNT_CTL, 3); + q->count = 0; + + /* enable irq */ + cx23885_irq_add_enable(dev, 0x01); + cx_set(VID_A_INT_MSK, 0x000011); + + /* start dma */ + cx_set(DEV_CNTRL2, (1<<5)); + cx_set(VID_A_DMA_CTL, 0x11); /* FIFO and RISC enable */ + + return 0; +} + +static int queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct cx23885_dev *dev = q->drv_priv; + + *num_planes = 1; + sizes[0] = (dev->fmt->depth * dev->width * dev->height) >> 3; + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + int ret; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx23885_dev *dev = vb->vb2_queue->drv_priv; + struct cx23885_buffer *buf = + container_of(vbuf, struct cx23885_buffer, vb); + u32 line0_offset, line1_offset; + struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, 0); + int field_tff; + + buf->bpl = (dev->width * dev->fmt->depth) >> 3; + + if (vb2_plane_size(vb, 0) < dev->height * buf->bpl) + return -EINVAL; + vb2_set_plane_payload(vb, 0, dev->height * buf->bpl); + + switch (dev->field) { + case V4L2_FIELD_TOP: + ret = cx23885_risc_buffer(dev->pci, &buf->risc, + sgt->sgl, 0, UNSET, + buf->bpl, 0, dev->height); + break; + case V4L2_FIELD_BOTTOM: + ret = cx23885_risc_buffer(dev->pci, &buf->risc, + sgt->sgl, UNSET, 0, + buf->bpl, 0, dev->height); + break; + case V4L2_FIELD_INTERLACED: + if (dev->tvnorm & V4L2_STD_525_60) + /* NTSC or */ + field_tff = 1; + else + field_tff = 0; + + if (cx23885_boards[dev->board].force_bff) + /* PAL / SECAM OR 888 in NTSC MODE */ + field_tff = 0; + + if (field_tff) { + /* cx25840 transmits NTSC bottom field first */ + dprintk(1, "%s() Creating TFF/NTSC risc\n", + __func__); + line0_offset = buf->bpl; + line1_offset = 0; + } else { + /* All other formats are top field first */ + dprintk(1, "%s() Creating BFF/PAL/SECAM risc\n", + __func__); + line0_offset = 0; + line1_offset = buf->bpl; + } + ret = cx23885_risc_buffer(dev->pci, &buf->risc, + sgt->sgl, line0_offset, + line1_offset, + buf->bpl, buf->bpl, + dev->height >> 1); + break; + case V4L2_FIELD_SEQ_TB: + ret = cx23885_risc_buffer(dev->pci, &buf->risc, + sgt->sgl, + 0, buf->bpl * (dev->height >> 1), + buf->bpl, 0, + dev->height >> 1); + break; + case V4L2_FIELD_SEQ_BT: + ret = cx23885_risc_buffer(dev->pci, &buf->risc, + sgt->sgl, + buf->bpl * (dev->height >> 1), 0, + buf->bpl, 0, + dev->height >> 1); + break; + default: + return -EINVAL; /* should not happen */ + } + dprintk(2, "[%p/%d] buffer_init - %dx%d %dbpp 0x%08x - dma=0x%08lx\n", + buf, buf->vb.vb2_buf.index, + dev->width, dev->height, dev->fmt->depth, dev->fmt->fourcc, + (unsigned long)buf->risc.dma); + return ret; +} + +static void buffer_finish(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx23885_buffer *buf = container_of(vbuf, + struct cx23885_buffer, vb); + + cx23885_free_buffer(vb->vb2_queue->drv_priv, buf); +} + +/* + * The risc program for each buffer works as follows: it starts with a simple + * 'JUMP to addr + 12', which is effectively a NOP. Then the code to DMA the + * buffer follows and at the end we have a JUMP back to the start + 12 (skipping + * the initial JUMP). + * + * This is the risc program of the first buffer to be queued if the active list + * is empty and it just keeps DMAing this buffer without generating any + * interrupts. + * + * If a new buffer is added then the initial JUMP in the code for that buffer + * will generate an interrupt which signals that the previous buffer has been + * DMAed successfully and that it can be returned to userspace. + * + * It also sets the final jump of the previous buffer to the start of the new + * buffer, thus chaining the new buffer into the DMA chain. This is a single + * atomic u32 write, so there is no race condition. + * + * The end-result of all this that you only get an interrupt when a buffer + * is ready, so the control flow is very easy. + */ +static void buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx23885_dev *dev = vb->vb2_queue->drv_priv; + struct cx23885_buffer *buf = container_of(vbuf, + struct cx23885_buffer, vb); + struct cx23885_buffer *prev; + struct cx23885_dmaqueue *q = &dev->vidq; + unsigned long flags; + + /* add jump to start */ + buf->risc.cpu[1] = cpu_to_le32(buf->risc.dma + 12); + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma + 12); + buf->risc.jmp[2] = cpu_to_le32(0); /* bits 63-32 */ + + spin_lock_irqsave(&dev->slock, flags); + if (list_empty(&q->active)) { + list_add_tail(&buf->queue, &q->active); + dprintk(2, "[%p/%d] buffer_queue - first active\n", + buf, buf->vb.vb2_buf.index); + } else { + buf->risc.cpu[0] |= cpu_to_le32(RISC_IRQ1); + prev = list_entry(q->active.prev, struct cx23885_buffer, + queue); + list_add_tail(&buf->queue, &q->active); + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(2, "[%p/%d] buffer_queue - append to active\n", + buf, buf->vb.vb2_buf.index); + } + spin_unlock_irqrestore(&dev->slock, flags); +} + +static int cx23885_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct cx23885_dev *dev = q->drv_priv; + struct cx23885_dmaqueue *dmaq = &dev->vidq; + struct cx23885_buffer *buf = list_entry(dmaq->active.next, + struct cx23885_buffer, queue); + + cx23885_start_video_dma(dev, dmaq, buf); + return 0; +} + +static void cx23885_stop_streaming(struct vb2_queue *q) +{ + struct cx23885_dev *dev = q->drv_priv; + struct cx23885_dmaqueue *dmaq = &dev->vidq; + unsigned long flags; + + cx_clear(VID_A_DMA_CTL, 0x11); + spin_lock_irqsave(&dev->slock, flags); + while (!list_empty(&dmaq->active)) { + struct cx23885_buffer *buf = list_entry(dmaq->active.next, + struct cx23885_buffer, queue); + + list_del(&buf->queue); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&dev->slock, flags); +} + +static const struct vb2_ops cx23885_video_qops = { + .queue_setup = queue_setup, + .buf_prepare = buffer_prepare, + .buf_finish = buffer_finish, + .buf_queue = buffer_queue, + .start_streaming = cx23885_start_streaming, + .stop_streaming = cx23885_stop_streaming, +}; + +/* ------------------------------------------------------------------ */ +/* VIDEO IOCTLS */ + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx23885_dev *dev = video_drvdata(file); + + f->fmt.pix.width = dev->width; + f->fmt.pix.height = dev->height; + f->fmt.pix.field = dev->field; + f->fmt.pix.pixelformat = dev->fmt->fourcc; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * dev->fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx23885_dev *dev = video_drvdata(file); + struct cx23885_fmt *fmt; + enum v4l2_field field; + unsigned int maxw, maxh; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + maxw = 720; + maxh = norm_maxh(dev->tvnorm); + + if (V4L2_FIELD_ANY == field) { + field = (f->fmt.pix.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + } + + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_SEQ_TB: + case V4L2_FIELD_SEQ_BT: + break; + default: + field = V4L2_FIELD_INTERLACED; + break; + } + + f->fmt.pix.field = field; + v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2, + &f->fmt.pix.height, 32, maxh, 0, 0); + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx23885_dev *dev = video_drvdata(file); + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int err; + + dprintk(2, "%s()\n", __func__); + err = vidioc_try_fmt_vid_cap(file, priv, f); + + if (0 != err) + return err; + + if (vb2_is_busy(&dev->vb2_vidq) || vb2_is_busy(&dev->vb2_vbiq) || + vb2_is_busy(&dev->vb2_mpegq)) + return -EBUSY; + + dev->fmt = format_by_fourcc(f->fmt.pix.pixelformat); + dev->width = f->fmt.pix.width; + dev->height = f->fmt.pix.height; + dev->field = f->fmt.pix.field; + dprintk(2, "%s() width=%d height=%d field=%d\n", __func__, + dev->width, dev->height, dev->field); + v4l2_fill_mbus_format(&format.format, &f->fmt.pix, MEDIA_BUS_FMT_FIXED); + call_all(dev, pad, set_fmt, NULL, &format); + v4l2_fill_pix_format(&f->fmt.pix, &format.format); + /* set_fmt overwrites f->fmt.pix.field, restore it */ + f->fmt.pix.field = dev->field; + return 0; +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct cx23885_dev *dev = video_drvdata(file); + + strscpy(cap->driver, "cx23885", sizeof(cap->driver)); + strscpy(cap->card, cx23885_boards[dev->board].name, + sizeof(cap->card)); + sprintf(cap->bus_info, "PCIe:%s", pci_name(dev->pci)); + cap->capabilities = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | + V4L2_CAP_AUDIO | V4L2_CAP_VBI_CAPTURE | + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_DEVICE_CAPS; + switch (dev->board) { /* i2c device tuners */ + case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: + case CX23885_BOARD_HAUPPAUGE_HVR5525: + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB: + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC: + cap->capabilities |= V4L2_CAP_TUNER; + break; + default: + if (dev->tuner_type != TUNER_ABSENT) + cap->capabilities |= V4L2_CAP_TUNER; + break; + } + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (unlikely(f->index >= ARRAY_SIZE(formats))) + return -EINVAL; + + f->pixelformat = formats[f->index].fourcc; + + return 0; +} + +static int vidioc_g_pixelaspect(struct file *file, void *priv, + int type, struct v4l2_fract *f) +{ + struct cx23885_dev *dev = video_drvdata(file); + bool is_50hz = dev->tvnorm & V4L2_STD_625_50; + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + f->numerator = is_50hz ? 54 : 11; + f->denominator = is_50hz ? 59 : 10; + + return 0; +} + +static int vidioc_g_selection(struct file *file, void *fh, + struct v4l2_selection *sel) +{ + struct cx23885_dev *dev = video_drvdata(file); + + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = 720; + sel->r.height = norm_maxh(dev->tvnorm); + break; + default: + return -EINVAL; + } + return 0; +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct cx23885_dev *dev = video_drvdata(file); + dprintk(1, "%s()\n", __func__); + + *id = dev->tvnorm; + return 0; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id tvnorms) +{ + struct cx23885_dev *dev = video_drvdata(file); + dprintk(1, "%s()\n", __func__); + + return cx23885_set_tvnorm(dev, tvnorms); +} + +int cx23885_enum_input(struct cx23885_dev *dev, struct v4l2_input *i) +{ + static const char *iname[] = { + [CX23885_VMUX_COMPOSITE1] = "Composite1", + [CX23885_VMUX_COMPOSITE2] = "Composite2", + [CX23885_VMUX_COMPOSITE3] = "Composite3", + [CX23885_VMUX_COMPOSITE4] = "Composite4", + [CX23885_VMUX_SVIDEO] = "S-Video", + [CX23885_VMUX_COMPONENT] = "Component", + [CX23885_VMUX_TELEVISION] = "Television", + [CX23885_VMUX_CABLE] = "Cable TV", + [CX23885_VMUX_DVB] = "DVB", + [CX23885_VMUX_DEBUG] = "for debug only", + }; + unsigned int n; + dprintk(1, "%s()\n", __func__); + + n = i->index; + if (n >= MAX_CX23885_INPUT) + return -EINVAL; + + if (0 == INPUT(n)->type) + return -EINVAL; + + i->index = n; + i->type = V4L2_INPUT_TYPE_CAMERA; + strscpy(i->name, iname[INPUT(n)->type], sizeof(i->name)); + i->std = CX23885_NORMS; + if ((CX23885_VMUX_TELEVISION == INPUT(n)->type) || + (CX23885_VMUX_CABLE == INPUT(n)->type)) { + i->type = V4L2_INPUT_TYPE_TUNER; + i->audioset = 4; + } else { + /* Two selectable audio inputs for non-tv inputs */ + i->audioset = 3; + } + + if (dev->input == n) { + /* enum'd input matches our configured input. + * Ask the video decoder to process the call + * and give it an oppertunity to update the + * status field. + */ + call_all(dev, video, g_input_status, &i->status); + } + + return 0; +} + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct cx23885_dev *dev = video_drvdata(file); + dprintk(1, "%s()\n", __func__); + return cx23885_enum_input(dev, i); +} + +int cx23885_get_input(struct file *file, void *priv, unsigned int *i) +{ + struct cx23885_dev *dev = video_drvdata(file); + + *i = dev->input; + dprintk(1, "%s() returns %d\n", __func__, *i); + return 0; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + return cx23885_get_input(file, priv, i); +} + +int cx23885_set_input(struct file *file, void *priv, unsigned int i) +{ + struct cx23885_dev *dev = video_drvdata(file); + + dprintk(1, "%s(%d)\n", __func__, i); + + if (i >= MAX_CX23885_INPUT) { + dprintk(1, "%s() -EINVAL\n", __func__); + return -EINVAL; + } + + if (INPUT(i)->type == 0) + return -EINVAL; + + cx23885_video_mux(dev, i); + + /* By default establish the default audio input for the card also */ + /* Caller is free to use VIDIOC_S_AUDIO to override afterwards */ + cx23885_audio_mux(dev, i); + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + return cx23885_set_input(file, priv, i); +} + +static int vidioc_log_status(struct file *file, void *priv) +{ + struct cx23885_dev *dev = video_drvdata(file); + + call_all(dev, core, log_status); + return 0; +} + +static int cx23885_query_audinput(struct file *file, void *priv, + struct v4l2_audio *i) +{ + static const char *iname[] = { + [0] = "Baseband L/R 1", + [1] = "Baseband L/R 2", + [2] = "TV", + }; + unsigned int n; + dprintk(1, "%s()\n", __func__); + + n = i->index; + if (n >= 3) + return -EINVAL; + + memset(i, 0, sizeof(*i)); + i->index = n; + strscpy(i->name, iname[n], sizeof(i->name)); + i->capability = V4L2_AUDCAP_STEREO; + return 0; + +} + +static int vidioc_enum_audinput(struct file *file, void *priv, + struct v4l2_audio *i) +{ + return cx23885_query_audinput(file, priv, i); +} + +static int vidioc_g_audinput(struct file *file, void *priv, + struct v4l2_audio *i) +{ + struct cx23885_dev *dev = video_drvdata(file); + + if ((CX23885_VMUX_TELEVISION == INPUT(dev->input)->type) || + (CX23885_VMUX_CABLE == INPUT(dev->input)->type)) + i->index = 2; + else + i->index = dev->audinput; + dprintk(1, "%s(input=%d)\n", __func__, i->index); + + return cx23885_query_audinput(file, priv, i); +} + +static int vidioc_s_audinput(struct file *file, void *priv, + const struct v4l2_audio *i) +{ + struct cx23885_dev *dev = video_drvdata(file); + + if ((CX23885_VMUX_TELEVISION == INPUT(dev->input)->type) || + (CX23885_VMUX_CABLE == INPUT(dev->input)->type)) { + return i->index != 2 ? -EINVAL : 0; + } + if (i->index > 1) + return -EINVAL; + + dprintk(1, "%s(%d)\n", __func__, i->index); + + dev->audinput = i->index; + + /* Skip the audio defaults from the cards struct, caller wants + * directly touch the audio mux hardware. */ + cx23885_flatiron_mux(dev, dev->audinput + 1); + return 0; +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx23885_dev *dev = video_drvdata(file); + + switch (dev->board) { /* i2c device tuners */ + case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: + case CX23885_BOARD_HAUPPAUGE_HVR5525: + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB: + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC: + break; + default: + if (dev->tuner_type == TUNER_ABSENT) + return -EINVAL; + break; + } + if (0 != t->index) + return -EINVAL; + + strscpy(t->name, "Television", sizeof(t->name)); + + call_all(dev, tuner, g_tuner, t); + return 0; +} + +static int vidioc_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *t) +{ + struct cx23885_dev *dev = video_drvdata(file); + + switch (dev->board) { /* i2c device tuners */ + case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: + case CX23885_BOARD_HAUPPAUGE_HVR5525: + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB: + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC: + break; + default: + if (dev->tuner_type == TUNER_ABSENT) + return -EINVAL; + break; + } + if (0 != t->index) + return -EINVAL; + /* Update the A/V core */ + call_all(dev, tuner, s_tuner, t); + + return 0; +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct cx23885_dev *dev = video_drvdata(file); + + switch (dev->board) { /* i2c device tuners */ + case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: + case CX23885_BOARD_HAUPPAUGE_HVR5525: + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB: + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC: + break; + default: + if (dev->tuner_type == TUNER_ABSENT) + return -EINVAL; + break; + } + f->type = V4L2_TUNER_ANALOG_TV; + f->frequency = dev->freq; + + call_all(dev, tuner, g_frequency, f); + + return 0; +} + +static int cx23885_set_freq(struct cx23885_dev *dev, const struct v4l2_frequency *f) +{ + struct v4l2_ctrl *mute; + int old_mute_val = 1; + + switch (dev->board) { /* i2c device tuners */ + case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: + case CX23885_BOARD_HAUPPAUGE_HVR5525: + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB: + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC: + break; + default: + if (dev->tuner_type == TUNER_ABSENT) + return -EINVAL; + break; + } + if (unlikely(f->tuner != 0)) + return -EINVAL; + + dev->freq = f->frequency; + + /* I need to mute audio here */ + mute = v4l2_ctrl_find(&dev->ctrl_handler, V4L2_CID_AUDIO_MUTE); + if (mute) { + old_mute_val = v4l2_ctrl_g_ctrl(mute); + if (!old_mute_val) + v4l2_ctrl_s_ctrl(mute, 1); + } + + call_all(dev, tuner, s_frequency, f); + + /* When changing channels it is required to reset TVAUDIO */ + msleep(100); + + /* I need to unmute audio here */ + if (old_mute_val == 0) + v4l2_ctrl_s_ctrl(mute, old_mute_val); + + return 0; +} + +static int cx23885_set_freq_via_ops(struct cx23885_dev *dev, + const struct v4l2_frequency *f) +{ + struct v4l2_ctrl *mute; + int old_mute_val = 1; + struct vb2_dvb_frontend *vfe; + struct dvb_frontend *fe; + + struct analog_parameters params = { + .mode = V4L2_TUNER_ANALOG_TV, + .audmode = V4L2_TUNER_MODE_STEREO, + .std = dev->tvnorm, + .frequency = f->frequency + }; + + dev->freq = f->frequency; + + /* I need to mute audio here */ + mute = v4l2_ctrl_find(&dev->ctrl_handler, V4L2_CID_AUDIO_MUTE); + if (mute) { + old_mute_val = v4l2_ctrl_g_ctrl(mute); + if (!old_mute_val) + v4l2_ctrl_s_ctrl(mute, 1); + } + + /* If HVR1850 */ + dprintk(1, "%s() frequency=%d tuner=%d std=0x%llx\n", __func__, + params.frequency, f->tuner, params.std); + + vfe = vb2_dvb_get_frontend(&dev->ts2.frontends, 1); + if (!vfe) { + return -EINVAL; + } + + fe = vfe->dvb.frontend; + + if ((dev->board == CX23885_BOARD_HAUPPAUGE_HVR1850) || + (dev->board == CX23885_BOARD_HAUPPAUGE_HVR1255) || + (dev->board == CX23885_BOARD_HAUPPAUGE_HVR1255_22111) || + (dev->board == CX23885_BOARD_HAUPPAUGE_HVR1265_K4) || + (dev->board == CX23885_BOARD_HAUPPAUGE_HVR5525) || + (dev->board == CX23885_BOARD_HAUPPAUGE_QUADHD_DVB) || + (dev->board == CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC)) + fe = &dev->ts1.analog_fe; + + if (fe && fe->ops.tuner_ops.set_analog_params) { + call_all(dev, video, s_std, dev->tvnorm); + fe->ops.tuner_ops.set_analog_params(fe, ¶ms); + } + else + pr_err("%s() No analog tuner, aborting\n", __func__); + + /* When changing channels it is required to reset TVAUDIO */ + msleep(100); + + /* I need to unmute audio here */ + if (old_mute_val == 0) + v4l2_ctrl_s_ctrl(mute, old_mute_val); + + return 0; +} + +int cx23885_set_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct cx23885_dev *dev = video_drvdata(file); + int ret; + + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1255: + case CX23885_BOARD_HAUPPAUGE_HVR1255_22111: + case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR5525: + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB: + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC: + ret = cx23885_set_freq_via_ops(dev, f); + break; + default: + ret = cx23885_set_freq(dev, f); + } + + return ret; +} + +static int vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + return cx23885_set_frequency(file, priv, f); +} + +/* ----------------------------------------------------------- */ + +int cx23885_video_irq(struct cx23885_dev *dev, u32 status) +{ + u32 mask, count; + int handled = 0; + + mask = cx_read(VID_A_INT_MSK); + if (0 == (status & mask)) + return handled; + + cx_write(VID_A_INT_STAT, status); + + /* risc op code error, fifo overflow or line sync detection error */ + if ((status & VID_BC_MSK_OPC_ERR) || + (status & VID_BC_MSK_SYNC) || + (status & VID_BC_MSK_OF)) { + + if (status & VID_BC_MSK_OPC_ERR) { + dprintk(7, " (VID_BC_MSK_OPC_ERR 0x%08x)\n", + VID_BC_MSK_OPC_ERR); + pr_warn("%s: video risc op code error\n", + dev->name); + cx23885_sram_channel_dump(dev, + &dev->sram_channels[SRAM_CH01]); + } + + if (status & VID_BC_MSK_SYNC) + dprintk(7, " (VID_BC_MSK_SYNC 0x%08x) video lines miss-match\n", + VID_BC_MSK_SYNC); + + if (status & VID_BC_MSK_OF) + dprintk(7, " (VID_BC_MSK_OF 0x%08x) fifo overflow\n", + VID_BC_MSK_OF); + + } + + /* Video */ + if (status & VID_BC_MSK_RISCI1) { + spin_lock(&dev->slock); + count = cx_read(VID_A_GPCNT); + cx23885_video_wakeup(dev, &dev->vidq, count); + spin_unlock(&dev->slock); + handled++; + } + + /* Allow the VBI framework to process it's payload */ + handled += cx23885_vbi_irq(dev, status); + + return handled; +} + +/* ----------------------------------------------------------- */ +/* exported stuff */ + +static const struct v4l2_file_operations video_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_g_fmt_vbi_cap = cx23885_vbi_fmt, + .vidioc_try_fmt_vbi_cap = cx23885_vbi_fmt, + .vidioc_s_fmt_vbi_cap = cx23885_vbi_fmt, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_g_pixelaspect = vidioc_g_pixelaspect, + .vidioc_g_selection = vidioc_g_selection, + .vidioc_s_std = vidioc_s_std, + .vidioc_g_std = vidioc_g_std, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_log_status = vidioc_log_status, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_chip_info = cx23885_g_chip_info, + .vidioc_g_register = cx23885_g_register, + .vidioc_s_register = cx23885_s_register, +#endif + .vidioc_enumaudio = vidioc_enum_audinput, + .vidioc_g_audio = vidioc_g_audinput, + .vidioc_s_audio = vidioc_s_audinput, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static struct video_device cx23885_vbi_template; +static struct video_device cx23885_video_template = { + .name = "cx23885-video", + .fops = &video_fops, + .ioctl_ops = &video_ioctl_ops, + .tvnorms = CX23885_NORMS, +}; + +void cx23885_video_unregister(struct cx23885_dev *dev) +{ + dprintk(1, "%s()\n", __func__); + cx23885_irq_remove(dev, 0x01); + + if (dev->vbi_dev) { + if (video_is_registered(dev->vbi_dev)) + video_unregister_device(dev->vbi_dev); + else + video_device_release(dev->vbi_dev); + dev->vbi_dev = NULL; + } + if (dev->video_dev) { + if (video_is_registered(dev->video_dev)) + video_unregister_device(dev->video_dev); + else + video_device_release(dev->video_dev); + dev->video_dev = NULL; + } + + if (dev->audio_dev) + cx23885_audio_unregister(dev); +} + +int cx23885_video_register(struct cx23885_dev *dev) +{ + struct vb2_queue *q; + int err; + + dprintk(1, "%s()\n", __func__); + + /* Initialize VBI template */ + cx23885_vbi_template = cx23885_video_template; + strscpy(cx23885_vbi_template.name, "cx23885-vbi", + sizeof(cx23885_vbi_template.name)); + + dev->tvnorm = V4L2_STD_NTSC_M; + dev->fmt = format_by_fourcc(V4L2_PIX_FMT_YUYV); + dev->field = V4L2_FIELD_INTERLACED; + dev->width = 720; + dev->height = norm_maxh(dev->tvnorm); + + /* init video dma queues */ + INIT_LIST_HEAD(&dev->vidq.active); + + /* init vbi dma queues */ + INIT_LIST_HEAD(&dev->vbiq.active); + + cx23885_irq_add_enable(dev, 0x01); + + if ((TUNER_ABSENT != dev->tuner_type) && + ((dev->tuner_bus == 0) || (dev->tuner_bus == 1))) { + struct v4l2_subdev *sd = NULL; + + if (dev->tuner_addr) + sd = v4l2_i2c_new_subdev(&dev->v4l2_dev, + &dev->i2c_bus[dev->tuner_bus].i2c_adap, + "tuner", dev->tuner_addr, NULL); + else + sd = v4l2_i2c_new_subdev(&dev->v4l2_dev, + &dev->i2c_bus[dev->tuner_bus].i2c_adap, + "tuner", 0, v4l2_i2c_tuner_addrs(ADDRS_TV)); + if (sd) { + struct tuner_setup tun_setup; + + memset(&tun_setup, 0, sizeof(tun_setup)); + tun_setup.mode_mask = T_ANALOG_TV; + tun_setup.type = dev->tuner_type; + tun_setup.addr = v4l2_i2c_subdev_addr(sd); + tun_setup.tuner_callback = cx23885_tuner_callback; + + v4l2_subdev_call(sd, tuner, s_type_addr, &tun_setup); + + if ((dev->board == CX23885_BOARD_LEADTEK_WINFAST_PXTV1200) || + (dev->board == CX23885_BOARD_LEADTEK_WINFAST_PXPVR2200)) { + struct xc2028_ctrl ctrl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64 + }; + struct v4l2_priv_tun_config cfg = { + .tuner = dev->tuner_type, + .priv = &ctrl + }; + v4l2_subdev_call(sd, tuner, s_config, &cfg); + } + + if (dev->board == CX23885_BOARD_AVERMEDIA_HC81R) { + struct xc2028_ctrl ctrl = { + .fname = "xc3028L-v36.fw", + .max_len = 64 + }; + struct v4l2_priv_tun_config cfg = { + .tuner = dev->tuner_type, + .priv = &ctrl + }; + v4l2_subdev_call(sd, tuner, s_config, &cfg); + } + } + } + + /* initial device configuration */ + mutex_lock(&dev->lock); + cx23885_set_tvnorm(dev, dev->tvnorm); + cx23885_video_mux(dev, 0); + cx23885_audio_mux(dev, 0); + mutex_unlock(&dev->lock); + + q = &dev->vb2_vidq; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ; + q->gfp_flags = GFP_DMA32; + q->min_queued_buffers = 2; + q->drv_priv = dev; + q->buf_struct_size = sizeof(struct cx23885_buffer); + q->ops = &cx23885_video_qops; + q->mem_ops = &vb2_dma_sg_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &dev->lock; + q->dev = &dev->pci->dev; + + err = vb2_queue_init(q); + if (err < 0) + goto fail_unreg; + + q = &dev->vb2_vbiq; + q->type = V4L2_BUF_TYPE_VBI_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ; + q->gfp_flags = GFP_DMA32; + q->min_queued_buffers = 2; + q->drv_priv = dev; + q->buf_struct_size = sizeof(struct cx23885_buffer); + q->ops = &cx23885_vbi_qops; + q->mem_ops = &vb2_dma_sg_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &dev->lock; + q->dev = &dev->pci->dev; + + err = vb2_queue_init(q); + if (err < 0) + goto fail_unreg; + + /* register Video device */ + dev->video_dev = cx23885_vdev_init(dev, dev->pci, + &cx23885_video_template, "video"); + if (!dev->video_dev) { + err = -ENOMEM; + goto fail_unreg; + } + dev->video_dev->queue = &dev->vb2_vidq; + dev->video_dev->device_caps = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | + V4L2_CAP_AUDIO | V4L2_CAP_VIDEO_CAPTURE; + switch (dev->board) { /* i2c device tuners */ + case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: + case CX23885_BOARD_HAUPPAUGE_HVR5525: + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB: + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC: + dev->video_dev->device_caps |= V4L2_CAP_TUNER; + break; + default: + if (dev->tuner_type != TUNER_ABSENT) + dev->video_dev->device_caps |= V4L2_CAP_TUNER; + } + + err = video_register_device(dev->video_dev, VFL_TYPE_VIDEO, + video_nr[dev->nr]); + if (err < 0) { + pr_info("%s: can't register video device\n", + dev->name); + goto fail_unreg; + } + pr_info("%s: registered device %s [v4l2]\n", + dev->name, video_device_node_name(dev->video_dev)); + + /* register VBI device */ + dev->vbi_dev = cx23885_vdev_init(dev, dev->pci, + &cx23885_vbi_template, "vbi"); + if (!dev->vbi_dev) { + err = -ENOMEM; + goto fail_unreg; + } + dev->vbi_dev->queue = &dev->vb2_vbiq; + dev->vbi_dev->device_caps = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | + V4L2_CAP_AUDIO | V4L2_CAP_VBI_CAPTURE; + switch (dev->board) { /* i2c device tuners */ + case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: + case CX23885_BOARD_HAUPPAUGE_HVR5525: + case CX23885_BOARD_HAUPPAUGE_QUADHD_DVB: + case CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC: + dev->vbi_dev->device_caps |= V4L2_CAP_TUNER; + break; + default: + if (dev->tuner_type != TUNER_ABSENT) + dev->vbi_dev->device_caps |= V4L2_CAP_TUNER; + } + err = video_register_device(dev->vbi_dev, VFL_TYPE_VBI, + vbi_nr[dev->nr]); + if (err < 0) { + pr_info("%s: can't register vbi device\n", + dev->name); + goto fail_unreg; + } + pr_info("%s: registered device %s\n", + dev->name, video_device_node_name(dev->vbi_dev)); + + /* Register ALSA audio device */ + dev->audio_dev = cx23885_audio_register(dev); + + return 0; + +fail_unreg: + cx23885_video_unregister(dev); + return err; +} diff --git a/drivers/media/pci/cx23885/cx23885-video.h b/drivers/media/pci/cx23885/cx23885-video.h new file mode 100644 index 000000000..c6c8a8605 --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-video.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the Conexant CX23885/7/8 PCIe bridge + * + * Copyright (C) 2010 Andy Walls + */ + +#ifndef _CX23885_VIDEO_H_ +#define _CX23885_VIDEO_H_ +int cx23885_flatiron_write(struct cx23885_dev *dev, u8 reg, u8 data); +u8 cx23885_flatiron_read(struct cx23885_dev *dev, u8 reg); +#endif diff --git a/drivers/media/pci/cx23885/cx23885.h b/drivers/media/pci/cx23885/cx23885.h new file mode 100644 index 000000000..8653ee1d9 --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885.h @@ -0,0 +1,636 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the Conexant CX23885 PCIe bridge + * + * Copyright (c) 2006 Steven Toth + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cx23885-reg.h" +#include "media/drv-intf/cx2341x.h" + +#include + +#define CX23885_VERSION "0.0.4" + +#define UNSET (-1U) + +#define CX23885_MAXBOARDS 8 + +/* Max number of inputs by card */ +#define MAX_CX23885_INPUT 8 +#define INPUT(nr) (&cx23885_boards[dev->board].input[nr]) + +#define BUFFER_TIMEOUT (HZ) /* 0.5 seconds */ + +#define CX23885_BOARD_NOAUTO UNSET +#define CX23885_BOARD_UNKNOWN 0 +#define CX23885_BOARD_HAUPPAUGE_HVR1800lp 1 +#define CX23885_BOARD_HAUPPAUGE_HVR1800 2 +#define CX23885_BOARD_HAUPPAUGE_HVR1250 3 +#define CX23885_BOARD_DVICO_FUSIONHDTV_5_EXP 4 +#define CX23885_BOARD_HAUPPAUGE_HVR1500Q 5 +#define CX23885_BOARD_HAUPPAUGE_HVR1500 6 +#define CX23885_BOARD_HAUPPAUGE_HVR1200 7 +#define CX23885_BOARD_HAUPPAUGE_HVR1700 8 +#define CX23885_BOARD_HAUPPAUGE_HVR1400 9 +#define CX23885_BOARD_DVICO_FUSIONHDTV_7_DUAL_EXP 10 +#define CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP 11 +#define CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H 12 +#define CX23885_BOARD_COMPRO_VIDEOMATE_E650F 13 +#define CX23885_BOARD_TBS_6920 14 +#define CX23885_BOARD_TEVII_S470 15 +#define CX23885_BOARD_DVBWORLD_2005 16 +#define CX23885_BOARD_NETUP_DUAL_DVBS2_CI 17 +#define CX23885_BOARD_HAUPPAUGE_HVR1270 18 +#define CX23885_BOARD_HAUPPAUGE_HVR1275 19 +#define CX23885_BOARD_HAUPPAUGE_HVR1255 20 +#define CX23885_BOARD_HAUPPAUGE_HVR1210 21 +#define CX23885_BOARD_MYGICA_X8506 22 +#define CX23885_BOARD_MAGICPRO_PROHDTVE2 23 +#define CX23885_BOARD_HAUPPAUGE_HVR1850 24 +#define CX23885_BOARD_COMPRO_VIDEOMATE_E800 25 +#define CX23885_BOARD_HAUPPAUGE_HVR1290 26 +#define CX23885_BOARD_MYGICA_X8558PRO 27 +#define CX23885_BOARD_LEADTEK_WINFAST_PXTV1200 28 +#define CX23885_BOARD_GOTVIEW_X5_3D_HYBRID 29 +#define CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF 30 +#define CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000 31 +#define CX23885_BOARD_MPX885 32 +#define CX23885_BOARD_MYGICA_X8507 33 +#define CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL 34 +#define CX23885_BOARD_TEVII_S471 35 +#define CX23885_BOARD_HAUPPAUGE_HVR1255_22111 36 +#define CX23885_BOARD_PROF_8000 37 +#define CX23885_BOARD_HAUPPAUGE_HVR4400 38 +#define CX23885_BOARD_AVERMEDIA_HC81R 39 +#define CX23885_BOARD_TBS_6981 40 +#define CX23885_BOARD_TBS_6980 41 +#define CX23885_BOARD_LEADTEK_WINFAST_PXPVR2200 42 +#define CX23885_BOARD_HAUPPAUGE_IMPACTVCBE 43 +#define CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP2 44 +#define CX23885_BOARD_DVBSKY_T9580 45 +#define CX23885_BOARD_DVBSKY_T980C 46 +#define CX23885_BOARD_DVBSKY_S950C 47 +#define CX23885_BOARD_TT_CT2_4500_CI 48 +#define CX23885_BOARD_DVBSKY_S950 49 +#define CX23885_BOARD_DVBSKY_S952 50 +#define CX23885_BOARD_DVBSKY_T982 51 +#define CX23885_BOARD_HAUPPAUGE_HVR5525 52 +#define CX23885_BOARD_HAUPPAUGE_STARBURST 53 +#define CX23885_BOARD_VIEWCAST_260E 54 +#define CX23885_BOARD_VIEWCAST_460E 55 +#define CX23885_BOARD_HAUPPAUGE_QUADHD_DVB 56 +#define CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC 57 +#define CX23885_BOARD_HAUPPAUGE_HVR1265_K4 58 +#define CX23885_BOARD_HAUPPAUGE_STARBURST2 59 +#define CX23885_BOARD_HAUPPAUGE_QUADHD_DVB_885 60 +#define CX23885_BOARD_HAUPPAUGE_QUADHD_ATSC_885 61 +#define CX23885_BOARD_AVERMEDIA_CE310B 62 +#define CX23885_BOARD_AVERMEDIA_H789C 63 + +#define GPIO_0 0x00000001 +#define GPIO_1 0x00000002 +#define GPIO_2 0x00000004 +#define GPIO_3 0x00000008 +#define GPIO_4 0x00000010 +#define GPIO_5 0x00000020 +#define GPIO_6 0x00000040 +#define GPIO_7 0x00000080 +#define GPIO_8 0x00000100 +#define GPIO_9 0x00000200 +#define GPIO_10 0x00000400 +#define GPIO_11 0x00000800 +#define GPIO_12 0x00001000 +#define GPIO_13 0x00002000 +#define GPIO_14 0x00004000 +#define GPIO_15 0x00008000 + +/* Currently unsupported by the driver: PAL/H, NTSC/Kr, SECAM B/G/H/LC */ +#define CX23885_NORMS (\ + V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_JP | V4L2_STD_NTSC_443 | \ + V4L2_STD_PAL_BG | V4L2_STD_PAL_DK | V4L2_STD_PAL_I | \ + V4L2_STD_PAL_M | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | \ + V4L2_STD_PAL_60 | V4L2_STD_SECAM_L | V4L2_STD_SECAM_DK) + +struct cx23885_fmt { + u32 fourcc; /* v4l2 format id */ + int depth; + int flags; + u32 cxformat; +}; + +struct cx23885_tvnorm { + char *name; + v4l2_std_id id; + u32 cxiformat; + u32 cxoformat; +}; + +enum cx23885_itype { + CX23885_VMUX_COMPOSITE1 = 1, + CX23885_VMUX_COMPOSITE2, + CX23885_VMUX_COMPOSITE3, + CX23885_VMUX_COMPOSITE4, + CX23885_VMUX_SVIDEO, + CX23885_VMUX_COMPONENT, + CX23885_VMUX_TELEVISION, + CX23885_VMUX_CABLE, + CX23885_VMUX_DVB, + CX23885_VMUX_DEBUG, + CX23885_RADIO, +}; + +enum cx23885_src_sel_type { + CX23885_SRC_SEL_EXT_656_VIDEO = 0, + CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO +}; + +struct cx23885_riscmem { + unsigned int size; + __le32 *cpu; + __le32 *jmp; + dma_addr_t dma; +}; + +/* buffer for one video frame */ +struct cx23885_buffer { + /* common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer vb; + struct list_head queue; + + /* cx23885 specific */ + unsigned int bpl; + struct cx23885_riscmem risc; + struct cx23885_fmt *fmt; + u32 count; +}; + +struct cx23885_input { + enum cx23885_itype type; + unsigned int vmux; + unsigned int amux; + u32 gpio0, gpio1, gpio2, gpio3; +}; + +typedef enum { + CX23885_MPEG_UNDEFINED = 0, + CX23885_MPEG_DVB, + CX23885_ANALOG_VIDEO, + CX23885_MPEG_ENCODER, +} port_t; + +struct cx23885_board { + char *name; + port_t porta, portb, portc; + int num_fds_portb, num_fds_portc; + unsigned int tuner_type; + unsigned int radio_type; + unsigned char tuner_addr; + unsigned char radio_addr; + unsigned int tuner_bus; + + /* Vendors can and do run the PCIe bridge at different + * clock rates, driven physically by crystals on the PCBs. + * The core has to accommodate this. This allows the user + * to add new boards with new frequencys. The value is + * expressed in Hz. + * + * The core framework will default this value based on + * current designs, but it can vary. + */ + u32 clk_freq; + struct cx23885_input input[MAX_CX23885_INPUT]; + int ci_type; /* for NetUP */ + /* Force bottom field first during DMA (888 workaround) */ + u32 force_bff; +}; + +struct cx23885_subid { + u16 subvendor; + u16 subdevice; + u32 card; +}; + +struct cx23885_i2c { + struct cx23885_dev *dev; + + int nr; + + /* i2c i/o */ + struct i2c_adapter i2c_adap; + struct i2c_client i2c_client; + u32 i2c_rc; + + /* 885 registers used for raw address */ + u32 i2c_period; + u32 reg_ctrl; + u32 reg_stat; + u32 reg_addr; + u32 reg_rdata; + u32 reg_wdata; +}; + +struct cx23885_dmaqueue { + struct list_head active; + u32 count; +}; + +struct cx23885_tsport { + struct cx23885_dev *dev; + + unsigned nr; + int sram_chno; + + struct vb2_dvb_frontends frontends; + + /* dma queues */ + struct cx23885_dmaqueue mpegq; + u32 ts_packet_size; + u32 ts_packet_count; + + int width; + int height; + + spinlock_t slock; + + /* registers */ + u32 reg_gpcnt; + u32 reg_gpcnt_ctl; + u32 reg_dma_ctl; + u32 reg_lngth; + u32 reg_hw_sop_ctrl; + u32 reg_gen_ctrl; + u32 reg_bd_pkt_status; + u32 reg_sop_status; + u32 reg_fifo_ovfl_stat; + u32 reg_vld_misc; + u32 reg_ts_clk_en; + u32 reg_ts_int_msk; + u32 reg_ts_int_stat; + u32 reg_src_sel; + + /* Default register vals */ + int pci_irqmask; + u32 dma_ctl_val; + u32 ts_int_msk_val; + u32 gen_ctrl_val; + u32 ts_clk_en_val; + u32 src_sel_val; + u32 vld_misc_val; + u32 hw_sop_ctrl_val; + + /* Allow a single tsport to have multiple frontends */ + u32 num_frontends; + void (*gate_ctrl)(struct cx23885_tsport *port, int open); + void *port_priv; + + /* Workaround for a temp dvb_frontend that the tuner can attached to */ + struct dvb_frontend analog_fe; + + struct i2c_client *i2c_client_demod; + struct i2c_client *i2c_client_tuner; + struct i2c_client *i2c_client_sec; + struct i2c_client *i2c_client_ci; + + int (*set_frontend)(struct dvb_frontend *fe); + int (*fe_set_voltage)(struct dvb_frontend *fe, + enum fe_sec_voltage voltage); +}; + +struct cx23885_kernel_ir { + struct cx23885_dev *cx; + char *name; + char *phys; + + struct rc_dev *rc; +}; + +struct cx23885_audio_buffer { + unsigned int bpl; + struct cx23885_riscmem risc; + void *vaddr; + struct scatterlist *sglist; + int sglen; + unsigned long nr_pages; +}; + +struct cx23885_audio_dev { + struct cx23885_dev *dev; + + struct pci_dev *pci; + + struct snd_card *card; + + spinlock_t lock; + + atomic_t count; + + unsigned int dma_size; + unsigned int period_size; + unsigned int num_periods; + + struct cx23885_audio_buffer *buf; + + struct snd_pcm_substream *substream; +}; + +struct cx23885_dev { + atomic_t refcount; + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler ctrl_handler; + + /* pci stuff */ + struct pci_dev *pci; + unsigned char pci_rev, pci_lat; + int pci_bus, pci_slot; + u32 __iomem *lmmio; + u8 __iomem *bmmio; + int pci_irqmask; + spinlock_t pci_irqmask_lock; /* protects mask reg too */ + int hwrevision; + + /* This valud is board specific and is used to configure the + * AV core so we see nice clean and stable video and audio. */ + u32 clk_freq; + + /* I2C adapters: Master 1 & 2 (External) & Master 3 (Internal only) */ + struct cx23885_i2c i2c_bus[3]; + + int nr; + struct mutex lock; + struct mutex gpio_lock; + + /* board details */ + unsigned int board; + char name[32]; + + struct cx23885_tsport ts1, ts2; + + /* sram configuration */ + struct sram_channel *sram_channels; + + enum { + CX23885_BRIDGE_UNDEFINED = 0, + CX23885_BRIDGE_885 = 885, + CX23885_BRIDGE_887 = 887, + CX23885_BRIDGE_888 = 888, + } bridge; + + /* Analog video */ + unsigned int input; + unsigned int audinput; /* Selectable audio input */ + u32 tvaudio; + v4l2_std_id tvnorm; + unsigned int tuner_type; + unsigned char tuner_addr; + unsigned int tuner_bus; + unsigned int radio_type; + unsigned char radio_addr; + struct v4l2_subdev *sd_cx25840; + struct work_struct cx25840_work; + unsigned int disable_analog; + + /* Infrared */ + struct v4l2_subdev *sd_ir; + struct work_struct ir_rx_work; + unsigned long ir_rx_notifications; + struct work_struct ir_tx_work; + unsigned long ir_tx_notifications; + + struct cx23885_kernel_ir *kernel_ir; + atomic_t ir_input_stopping; + + /* V4l */ + u32 freq; + struct video_device *video_dev; + struct video_device *vbi_dev; + + /* video capture */ + struct cx23885_fmt *fmt; + unsigned int width, height; + unsigned field; + + struct cx23885_dmaqueue vidq; + struct vb2_queue vb2_vidq; + struct cx23885_dmaqueue vbiq; + struct vb2_queue vb2_vbiq; + + spinlock_t slock; + + /* MPEG Encoder ONLY settings */ + u32 cx23417_mailbox; + struct cx2341x_handler cxhdl; + struct video_device *v4l_device; + struct vb2_queue vb2_mpegq; + struct cx23885_tvnorm encodernorm; + + /* Analog raw audio */ + struct cx23885_audio_dev *audio_dev; + + /* Does the system require periodic DMA resets? */ + unsigned int need_dma_reset:1; +}; + +static inline struct cx23885_dev *to_cx23885(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct cx23885_dev, v4l2_dev); +} + +#define call_all(dev, o, f, args...) \ + v4l2_device_call_all(&dev->v4l2_dev, 0, o, f, ##args) + +#define CX23885_HW_888_IR (1 << 0) +#define CX23885_HW_AV_CORE (1 << 1) + +#define call_hw(dev, grpid, o, f, args...) \ + v4l2_device_call_all(&dev->v4l2_dev, grpid, o, f, ##args) + +extern struct v4l2_subdev *cx23885_find_hw(struct cx23885_dev *dev, u32 hw); + +#define SRAM_CH01 0 /* Video A */ +#define SRAM_CH02 1 /* VBI A */ +#define SRAM_CH03 2 /* Video B */ +#define SRAM_CH04 3 /* Transport via B */ +#define SRAM_CH05 4 /* VBI B */ +#define SRAM_CH06 5 /* Video C */ +#define SRAM_CH07 6 /* Transport via C */ +#define SRAM_CH08 7 /* Audio Internal A */ +#define SRAM_CH09 8 /* Audio Internal B */ +#define SRAM_CH10 9 /* Audio External */ +#define SRAM_CH11 10 /* COMB_3D_N */ +#define SRAM_CH12 11 /* Comb 3D N1 */ +#define SRAM_CH13 12 /* Comb 3D N2 */ +#define SRAM_CH14 13 /* MOE Vid */ +#define SRAM_CH15 14 /* MOE RSLT */ + +struct sram_channel { + char *name; + u32 cmds_start; + u32 ctrl_start; + u32 cdt; + u32 fifo_start; + u32 fifo_size; + u32 ptr1_reg; + u32 ptr2_reg; + u32 cnt1_reg; + u32 cnt2_reg; + u32 jumponly; +}; + +/* ----------------------------------------------------------- */ + +#define cx_read(reg) readl(dev->lmmio + ((reg)>>2)) +#define cx_write(reg, value) writel((value), dev->lmmio + ((reg)>>2)) + +#define cx_andor(reg, mask, value) \ + writel((readl(dev->lmmio+((reg)>>2)) & ~(mask)) |\ + ((value) & (mask)), dev->lmmio+((reg)>>2)) + +#define cx_set(reg, bit) cx_andor((reg), (bit), (bit)) +#define cx_clear(reg, bit) cx_andor((reg), (bit), 0) + +/* ----------------------------------------------------------- */ +/* cx23885-core.c */ + +extern int cx23885_sram_channel_setup(struct cx23885_dev *dev, + struct sram_channel *ch, + unsigned int bpl, u32 risc); + +extern void cx23885_sram_channel_dump(struct cx23885_dev *dev, + struct sram_channel *ch); + +extern int cx23885_risc_buffer(struct pci_dev *pci, struct cx23885_riscmem *risc, + struct scatterlist *sglist, + unsigned int top_offset, unsigned int bottom_offset, + unsigned int bpl, unsigned int padding, unsigned int lines); + +extern int cx23885_risc_vbibuffer(struct pci_dev *pci, + struct cx23885_riscmem *risc, struct scatterlist *sglist, + unsigned int top_offset, unsigned int bottom_offset, + unsigned int bpl, unsigned int padding, unsigned int lines); + +int cx23885_start_dma(struct cx23885_tsport *port, + struct cx23885_dmaqueue *q, + struct cx23885_buffer *buf); +void cx23885_cancel_buffers(struct cx23885_tsport *port); + + +extern void cx23885_gpio_set(struct cx23885_dev *dev, u32 mask); +extern void cx23885_gpio_clear(struct cx23885_dev *dev, u32 mask); +extern u32 cx23885_gpio_get(struct cx23885_dev *dev, u32 mask); +extern void cx23885_gpio_enable(struct cx23885_dev *dev, u32 mask, + int asoutput); + +extern void cx23885_irq_add_enable(struct cx23885_dev *dev, u32 mask); +extern void cx23885_irq_enable(struct cx23885_dev *dev, u32 mask); +extern void cx23885_irq_disable(struct cx23885_dev *dev, u32 mask); +extern void cx23885_irq_remove(struct cx23885_dev *dev, u32 mask); + +/* ----------------------------------------------------------- */ +/* cx23885-cards.c */ +extern struct cx23885_board cx23885_boards[]; +extern const unsigned int cx23885_bcount; + +extern struct cx23885_subid cx23885_subids[]; +extern const unsigned int cx23885_idcount; + +extern int cx23885_tuner_callback(void *priv, int component, + int command, int arg); +extern void cx23885_card_list(struct cx23885_dev *dev); +extern int cx23885_ir_init(struct cx23885_dev *dev); +extern void cx23885_ir_pci_int_enable(struct cx23885_dev *dev); +extern void cx23885_ir_fini(struct cx23885_dev *dev); +extern void cx23885_gpio_setup(struct cx23885_dev *dev); +extern void cx23885_card_setup(struct cx23885_dev *dev); +extern void cx23885_card_setup_pre_i2c(struct cx23885_dev *dev); + +extern int cx23885_dvb_register(struct cx23885_tsport *port); +extern int cx23885_dvb_unregister(struct cx23885_tsport *port); + +extern int cx23885_buf_prepare(struct cx23885_buffer *buf, + struct cx23885_tsport *port); +extern void cx23885_buf_queue(struct cx23885_tsport *port, + struct cx23885_buffer *buf); +extern void cx23885_free_buffer(struct cx23885_dev *dev, + struct cx23885_buffer *buf); + +/* ----------------------------------------------------------- */ +/* cx23885-video.c */ +/* Video */ +extern int cx23885_video_register(struct cx23885_dev *dev); +extern void cx23885_video_unregister(struct cx23885_dev *dev); +extern int cx23885_video_irq(struct cx23885_dev *dev, u32 status); +extern void cx23885_video_wakeup(struct cx23885_dev *dev, + struct cx23885_dmaqueue *q, u32 count); +int cx23885_enum_input(struct cx23885_dev *dev, struct v4l2_input *i); +int cx23885_set_input(struct file *file, void *priv, unsigned int i); +int cx23885_get_input(struct file *file, void *priv, unsigned int *i); +int cx23885_set_frequency(struct file *file, void *priv, const struct v4l2_frequency *f); +int cx23885_set_tvnorm(struct cx23885_dev *dev, v4l2_std_id norm); + +/* ----------------------------------------------------------- */ +/* cx23885-vbi.c */ +extern int cx23885_vbi_fmt(struct file *file, void *priv, + struct v4l2_format *f); +extern void cx23885_vbi_timeout(unsigned long data); +extern const struct vb2_ops cx23885_vbi_qops; +extern int cx23885_vbi_irq(struct cx23885_dev *dev, u32 status); + +/* cx23885-i2c.c */ +extern int cx23885_i2c_register(struct cx23885_i2c *bus); +extern int cx23885_i2c_unregister(struct cx23885_i2c *bus); +extern void cx23885_av_clk(struct cx23885_dev *dev, int enable); + +/* ----------------------------------------------------------- */ +/* cx23885-417.c */ +extern int cx23885_417_register(struct cx23885_dev *dev); +extern void cx23885_417_unregister(struct cx23885_dev *dev); +extern int cx23885_irq_417(struct cx23885_dev *dev, u32 status); +extern void cx23885_417_check_encoder(struct cx23885_dev *dev); +extern void cx23885_mc417_init(struct cx23885_dev *dev); +extern int mc417_memory_read(struct cx23885_dev *dev, u32 address, u32 *value); +extern int mc417_memory_write(struct cx23885_dev *dev, u32 address, u32 value); +extern int mc417_register_read(struct cx23885_dev *dev, + u16 address, u32 *value); +extern int mc417_register_write(struct cx23885_dev *dev, + u16 address, u32 value); +extern void mc417_gpio_set(struct cx23885_dev *dev, u32 mask); +extern void mc417_gpio_clear(struct cx23885_dev *dev, u32 mask); +extern void mc417_gpio_enable(struct cx23885_dev *dev, u32 mask, int asoutput); + +/* ----------------------------------------------------------- */ +/* cx23885-alsa.c */ +extern struct cx23885_audio_dev *cx23885_audio_register( + struct cx23885_dev *dev); +extern void cx23885_audio_unregister(struct cx23885_dev *dev); +extern int cx23885_audio_irq(struct cx23885_dev *dev, u32 status, u32 mask); +extern int cx23885_risc_databuffer(struct pci_dev *pci, + struct cx23885_riscmem *risc, + struct scatterlist *sglist, + unsigned int bpl, + unsigned int lines, + unsigned int lpi); + +/* ----------------------------------------------------------- */ +/* tv norms */ + +static inline unsigned int norm_maxh(v4l2_std_id norm) +{ + return (norm & V4L2_STD_525_60) ? 480 : 576; +} diff --git a/drivers/media/pci/cx23885/cx23888-ir.c b/drivers/media/pci/cx23885/cx23888-ir.c new file mode 100644 index 000000000..087beb82a --- /dev/null +++ b/drivers/media/pci/cx23885/cx23888-ir.c @@ -0,0 +1,1205 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX23885/7/8 PCIe bridge + * + * CX23888 Integrated Consumer Infrared Controller + * + * Copyright (C) 2009 Andy Walls + */ + +#include "cx23885.h" +#include "cx23888-ir.h" + +#include +#include + +#include +#include + +static unsigned int ir_888_debug; +module_param(ir_888_debug, int, 0644); +MODULE_PARM_DESC(ir_888_debug, "enable debug messages [CX23888 IR controller]"); + +#define CX23888_IR_REG_BASE 0x170000 +/* + * These CX23888 register offsets have a straightforward one to one mapping + * to the CX23885 register offsets of 0x200 through 0x218 + */ +#define CX23888_IR_CNTRL_REG 0x170000 +#define CNTRL_WIN_3_3 0x00000000 +#define CNTRL_WIN_4_3 0x00000001 +#define CNTRL_WIN_3_4 0x00000002 +#define CNTRL_WIN_4_4 0x00000003 +#define CNTRL_WIN 0x00000003 +#define CNTRL_EDG_NONE 0x00000000 +#define CNTRL_EDG_FALL 0x00000004 +#define CNTRL_EDG_RISE 0x00000008 +#define CNTRL_EDG_BOTH 0x0000000C +#define CNTRL_EDG 0x0000000C +#define CNTRL_DMD 0x00000010 +#define CNTRL_MOD 0x00000020 +#define CNTRL_RFE 0x00000040 +#define CNTRL_TFE 0x00000080 +#define CNTRL_RXE 0x00000100 +#define CNTRL_TXE 0x00000200 +#define CNTRL_RIC 0x00000400 +#define CNTRL_TIC 0x00000800 +#define CNTRL_CPL 0x00001000 +#define CNTRL_LBM 0x00002000 +#define CNTRL_R 0x00004000 +/* CX23888 specific control flag */ +#define CNTRL_IVO 0x00008000 + +#define CX23888_IR_TXCLK_REG 0x170004 +#define TXCLK_TCD 0x0000FFFF + +#define CX23888_IR_RXCLK_REG 0x170008 +#define RXCLK_RCD 0x0000FFFF + +#define CX23888_IR_CDUTY_REG 0x17000C +#define CDUTY_CDC 0x0000000F + +#define CX23888_IR_STATS_REG 0x170010 +#define STATS_RTO 0x00000001 +#define STATS_ROR 0x00000002 +#define STATS_RBY 0x00000004 +#define STATS_TBY 0x00000008 +#define STATS_RSR 0x00000010 +#define STATS_TSR 0x00000020 + +#define CX23888_IR_IRQEN_REG 0x170014 +#define IRQEN_RTE 0x00000001 +#define IRQEN_ROE 0x00000002 +#define IRQEN_RSE 0x00000010 +#define IRQEN_TSE 0x00000020 + +#define CX23888_IR_FILTR_REG 0x170018 +#define FILTR_LPF 0x0000FFFF + +/* This register doesn't follow the pattern; it's 0x23C on a CX23885 */ +#define CX23888_IR_FIFO_REG 0x170040 +#define FIFO_RXTX 0x0000FFFF +#define FIFO_RXTX_LVL 0x00010000 +#define FIFO_RXTX_RTO 0x0001FFFF +#define FIFO_RX_NDV 0x00020000 +#define FIFO_RX_DEPTH 8 +#define FIFO_TX_DEPTH 8 + +/* CX23888 unique registers */ +#define CX23888_IR_SEEDP_REG 0x17001C +#define CX23888_IR_TIMOL_REG 0x170020 +#define CX23888_IR_WAKE0_REG 0x170024 +#define CX23888_IR_WAKE1_REG 0x170028 +#define CX23888_IR_WAKE2_REG 0x17002C +#define CX23888_IR_MASK0_REG 0x170030 +#define CX23888_IR_MASK1_REG 0x170034 +#define CX23888_IR_MAKS2_REG 0x170038 +#define CX23888_IR_DPIPG_REG 0x17003C +#define CX23888_IR_LEARN_REG 0x170044 + +#define CX23888_VIDCLK_FREQ 108000000 /* 108 MHz, BT.656 */ +#define CX23888_IR_REFCLK_FREQ (CX23888_VIDCLK_FREQ / 2) + +/* + * We use this union internally for convenience, but callers to tx_write + * and rx_read will be expecting records of type struct ir_raw_event. + * Always ensure the size of this union is dictated by struct ir_raw_event. + */ +union cx23888_ir_fifo_rec { + u32 hw_fifo_data; + struct ir_raw_event ir_core_data; +}; + +#define CX23888_IR_RX_KFIFO_SIZE (256 * sizeof(union cx23888_ir_fifo_rec)) +#define CX23888_IR_TX_KFIFO_SIZE (256 * sizeof(union cx23888_ir_fifo_rec)) + +struct cx23888_ir_state { + struct v4l2_subdev sd; + struct cx23885_dev *dev; + + struct v4l2_subdev_ir_parameters rx_params; + struct mutex rx_params_lock; + atomic_t rxclk_divider; + atomic_t rx_invert; + + struct kfifo rx_kfifo; + spinlock_t rx_kfifo_lock; + + struct v4l2_subdev_ir_parameters tx_params; + struct mutex tx_params_lock; + atomic_t txclk_divider; +}; + +static inline struct cx23888_ir_state *to_state(struct v4l2_subdev *sd) +{ + return v4l2_get_subdevdata(sd); +} + +/* + * IR register block read and write functions + */ +static +inline int cx23888_ir_write4(struct cx23885_dev *dev, u32 addr, u32 value) +{ + cx_write(addr, value); + return 0; +} + +static inline u32 cx23888_ir_read4(struct cx23885_dev *dev, u32 addr) +{ + return cx_read(addr); +} + +static inline int cx23888_ir_and_or4(struct cx23885_dev *dev, u32 addr, + u32 and_mask, u32 or_value) +{ + cx_andor(addr, ~and_mask, or_value); + return 0; +} + +/* + * Rx and Tx Clock Divider register computations + * + * Note the largest clock divider value of 0xffff corresponds to: + * (0xffff + 1) * 1000 / 108/2 MHz = 1,213,629.629... ns + * which fits in 21 bits, so we'll use unsigned int for time arguments. + */ +static inline u16 count_to_clock_divider(unsigned int d) +{ + if (d > RXCLK_RCD + 1) + d = RXCLK_RCD; + else if (d < 2) + d = 1; + else + d--; + return (u16) d; +} + +static inline u16 carrier_freq_to_clock_divider(unsigned int freq) +{ + return count_to_clock_divider( + DIV_ROUND_CLOSEST(CX23888_IR_REFCLK_FREQ, freq * 16)); +} + +static inline unsigned int clock_divider_to_carrier_freq(unsigned int divider) +{ + return DIV_ROUND_CLOSEST(CX23888_IR_REFCLK_FREQ, (divider + 1) * 16); +} + +static inline unsigned int clock_divider_to_freq(unsigned int divider, + unsigned int rollovers) +{ + return DIV_ROUND_CLOSEST(CX23888_IR_REFCLK_FREQ, + (divider + 1) * rollovers); +} + +/* + * Low Pass Filter register calculations + * + * Note the largest count value of 0xffff corresponds to: + * 0xffff * 1000 / 108/2 MHz = 1,213,611.11... ns + * which fits in 21 bits, so we'll use unsigned int for time arguments. + */ +static inline u16 count_to_lpf_count(unsigned int d) +{ + if (d > FILTR_LPF) + d = FILTR_LPF; + else if (d < 4) + d = 0; + return (u16) d; +} + +static inline u16 ns_to_lpf_count(unsigned int ns) +{ + return count_to_lpf_count( + DIV_ROUND_CLOSEST(CX23888_IR_REFCLK_FREQ / 1000000 * ns, 1000)); +} + +static inline unsigned int lpf_count_to_ns(unsigned int count) +{ + /* Duration of the Low Pass Filter rejection window in ns */ + return DIV_ROUND_CLOSEST(count * 1000, + CX23888_IR_REFCLK_FREQ / 1000000); +} + +static inline unsigned int lpf_count_to_us(unsigned int count) +{ + /* Duration of the Low Pass Filter rejection window in us */ + return DIV_ROUND_CLOSEST(count, CX23888_IR_REFCLK_FREQ / 1000000); +} + +/* + * FIFO register pulse width count computations + */ +static u32 clock_divider_to_resolution(u16 divider) +{ + /* + * Resolution is the duration of 1 tick of the readable portion of + * the pulse width counter as read from the FIFO. The two lsb's are + * not readable, hence the << 2. This function returns ns. + */ + return DIV_ROUND_CLOSEST((1 << 2) * ((u32) divider + 1) * 1000, + CX23888_IR_REFCLK_FREQ / 1000000); +} + +static u64 pulse_width_count_to_ns(u16 count, u16 divider) +{ + u64 n; + u32 rem; + + /* + * The 2 lsb's of the pulse width timer count are not readable, hence + * the (count << 2) | 0x3 + */ + n = (((u64) count << 2) | 0x3) * (divider + 1) * 1000; /* millicycles */ + rem = do_div(n, CX23888_IR_REFCLK_FREQ / 1000000); /* / MHz => ns */ + if (rem >= CX23888_IR_REFCLK_FREQ / 1000000 / 2) + n++; + return n; +} + +static unsigned int pulse_width_count_to_us(u16 count, u16 divider) +{ + u64 n; + u32 rem; + + /* + * The 2 lsb's of the pulse width timer count are not readable, hence + * the (count << 2) | 0x3 + */ + n = (((u64) count << 2) | 0x3) * (divider + 1); /* cycles */ + rem = do_div(n, CX23888_IR_REFCLK_FREQ / 1000000); /* / MHz => us */ + if (rem >= CX23888_IR_REFCLK_FREQ / 1000000 / 2) + n++; + return (unsigned int) n; +} + +/* + * Pulse Clocks computations: Combined Pulse Width Count & Rx Clock Counts + * + * The total pulse clock count is an 18 bit pulse width timer count as the most + * significant part and (up to) 16 bit clock divider count as a modulus. + * When the Rx clock divider ticks down to 0, it increments the 18 bit pulse + * width timer count's least significant bit. + */ +static u64 ns_to_pulse_clocks(u32 ns) +{ + u64 clocks; + u32 rem; + clocks = CX23888_IR_REFCLK_FREQ / 1000000 * (u64) ns; /* millicycles */ + rem = do_div(clocks, 1000); /* /1000 = cycles */ + if (rem >= 1000 / 2) + clocks++; + return clocks; +} + +static u16 pulse_clocks_to_clock_divider(u64 count) +{ + do_div(count, (FIFO_RXTX << 2) | 0x3); + + /* net result needs to be rounded down and decremented by 1 */ + if (count > RXCLK_RCD + 1) + count = RXCLK_RCD; + else if (count < 2) + count = 1; + else + count--; + return (u16) count; +} + +/* + * IR Control Register helpers + */ +enum tx_fifo_watermark { + TX_FIFO_HALF_EMPTY = 0, + TX_FIFO_EMPTY = CNTRL_TIC, +}; + +enum rx_fifo_watermark { + RX_FIFO_HALF_FULL = 0, + RX_FIFO_NOT_EMPTY = CNTRL_RIC, +}; + +static inline void control_tx_irq_watermark(struct cx23885_dev *dev, + enum tx_fifo_watermark level) +{ + cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~CNTRL_TIC, level); +} + +static inline void control_rx_irq_watermark(struct cx23885_dev *dev, + enum rx_fifo_watermark level) +{ + cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~CNTRL_RIC, level); +} + +static inline void control_tx_enable(struct cx23885_dev *dev, bool enable) +{ + cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~(CNTRL_TXE | CNTRL_TFE), + enable ? (CNTRL_TXE | CNTRL_TFE) : 0); +} + +static inline void control_rx_enable(struct cx23885_dev *dev, bool enable) +{ + cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~(CNTRL_RXE | CNTRL_RFE), + enable ? (CNTRL_RXE | CNTRL_RFE) : 0); +} + +static inline void control_tx_modulation_enable(struct cx23885_dev *dev, + bool enable) +{ + cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~CNTRL_MOD, + enable ? CNTRL_MOD : 0); +} + +static inline void control_rx_demodulation_enable(struct cx23885_dev *dev, + bool enable) +{ + cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~CNTRL_DMD, + enable ? CNTRL_DMD : 0); +} + +static inline void control_rx_s_edge_detection(struct cx23885_dev *dev, + u32 edge_types) +{ + cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~CNTRL_EDG_BOTH, + edge_types & CNTRL_EDG_BOTH); +} + +static void control_rx_s_carrier_window(struct cx23885_dev *dev, + unsigned int carrier, + unsigned int *carrier_range_low, + unsigned int *carrier_range_high) +{ + u32 v; + unsigned int c16 = carrier * 16; + + if (*carrier_range_low < DIV_ROUND_CLOSEST(c16, 16 + 3)) { + v = CNTRL_WIN_3_4; + *carrier_range_low = DIV_ROUND_CLOSEST(c16, 16 + 4); + } else { + v = CNTRL_WIN_3_3; + *carrier_range_low = DIV_ROUND_CLOSEST(c16, 16 + 3); + } + + if (*carrier_range_high > DIV_ROUND_CLOSEST(c16, 16 - 3)) { + v |= CNTRL_WIN_4_3; + *carrier_range_high = DIV_ROUND_CLOSEST(c16, 16 - 4); + } else { + v |= CNTRL_WIN_3_3; + *carrier_range_high = DIV_ROUND_CLOSEST(c16, 16 - 3); + } + cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~CNTRL_WIN, v); +} + +static inline void control_tx_polarity_invert(struct cx23885_dev *dev, + bool invert) +{ + cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~CNTRL_CPL, + invert ? CNTRL_CPL : 0); +} + +static inline void control_tx_level_invert(struct cx23885_dev *dev, + bool invert) +{ + cx23888_ir_and_or4(dev, CX23888_IR_CNTRL_REG, ~CNTRL_IVO, + invert ? CNTRL_IVO : 0); +} + +/* + * IR Rx & Tx Clock Register helpers + */ +static unsigned int txclk_tx_s_carrier(struct cx23885_dev *dev, + unsigned int freq, + u16 *divider) +{ + *divider = carrier_freq_to_clock_divider(freq); + cx23888_ir_write4(dev, CX23888_IR_TXCLK_REG, *divider); + return clock_divider_to_carrier_freq(*divider); +} + +static unsigned int rxclk_rx_s_carrier(struct cx23885_dev *dev, + unsigned int freq, + u16 *divider) +{ + *divider = carrier_freq_to_clock_divider(freq); + cx23888_ir_write4(dev, CX23888_IR_RXCLK_REG, *divider); + return clock_divider_to_carrier_freq(*divider); +} + +static u32 txclk_tx_s_max_pulse_width(struct cx23885_dev *dev, u32 ns, + u16 *divider) +{ + u64 pulse_clocks; + + if (ns > IR_MAX_DURATION) + ns = IR_MAX_DURATION; + pulse_clocks = ns_to_pulse_clocks(ns); + *divider = pulse_clocks_to_clock_divider(pulse_clocks); + cx23888_ir_write4(dev, CX23888_IR_TXCLK_REG, *divider); + return (u32) pulse_width_count_to_ns(FIFO_RXTX, *divider); +} + +static u32 rxclk_rx_s_max_pulse_width(struct cx23885_dev *dev, u32 ns, + u16 *divider) +{ + u64 pulse_clocks; + + if (ns > IR_MAX_DURATION) + ns = IR_MAX_DURATION; + pulse_clocks = ns_to_pulse_clocks(ns); + *divider = pulse_clocks_to_clock_divider(pulse_clocks); + cx23888_ir_write4(dev, CX23888_IR_RXCLK_REG, *divider); + return (u32) pulse_width_count_to_ns(FIFO_RXTX, *divider); +} + +/* + * IR Tx Carrier Duty Cycle register helpers + */ +static unsigned int cduty_tx_s_duty_cycle(struct cx23885_dev *dev, + unsigned int duty_cycle) +{ + u32 n; + n = DIV_ROUND_CLOSEST(duty_cycle * 100, 625); /* 16ths of 100% */ + if (n != 0) + n--; + if (n > 15) + n = 15; + cx23888_ir_write4(dev, CX23888_IR_CDUTY_REG, n); + return DIV_ROUND_CLOSEST((n + 1) * 100, 16); +} + +/* + * IR Filter Register helpers + */ +static u32 filter_rx_s_min_width(struct cx23885_dev *dev, u32 min_width_ns) +{ + u32 count = ns_to_lpf_count(min_width_ns); + cx23888_ir_write4(dev, CX23888_IR_FILTR_REG, count); + return lpf_count_to_ns(count); +} + +/* + * IR IRQ Enable Register helpers + */ +static inline void irqenable_rx(struct cx23885_dev *dev, u32 mask) +{ + mask &= (IRQEN_RTE | IRQEN_ROE | IRQEN_RSE); + cx23888_ir_and_or4(dev, CX23888_IR_IRQEN_REG, + ~(IRQEN_RTE | IRQEN_ROE | IRQEN_RSE), mask); +} + +static inline void irqenable_tx(struct cx23885_dev *dev, u32 mask) +{ + mask &= IRQEN_TSE; + cx23888_ir_and_or4(dev, CX23888_IR_IRQEN_REG, ~IRQEN_TSE, mask); +} + +/* + * V4L2 Subdevice IR Ops + */ +static int cx23888_ir_irq_handler(struct v4l2_subdev *sd, u32 status, + bool *handled) +{ + struct cx23888_ir_state *state = to_state(sd); + struct cx23885_dev *dev = state->dev; + unsigned long flags; + + u32 cntrl = cx23888_ir_read4(dev, CX23888_IR_CNTRL_REG); + u32 irqen = cx23888_ir_read4(dev, CX23888_IR_IRQEN_REG); + u32 stats = cx23888_ir_read4(dev, CX23888_IR_STATS_REG); + + union cx23888_ir_fifo_rec rx_data[FIFO_RX_DEPTH]; + unsigned int i, j, k; + u32 events, v; + int tsr, rsr, rto, ror, tse, rse, rte, roe, kror; + + tsr = stats & STATS_TSR; /* Tx FIFO Service Request */ + rsr = stats & STATS_RSR; /* Rx FIFO Service Request */ + rto = stats & STATS_RTO; /* Rx Pulse Width Timer Time Out */ + ror = stats & STATS_ROR; /* Rx FIFO Over Run */ + + tse = irqen & IRQEN_TSE; /* Tx FIFO Service Request IRQ Enable */ + rse = irqen & IRQEN_RSE; /* Rx FIFO Service Request IRQ Enable */ + rte = irqen & IRQEN_RTE; /* Rx Pulse Width Timer Time Out IRQ Enable */ + roe = irqen & IRQEN_ROE; /* Rx FIFO Over Run IRQ Enable */ + + *handled = false; + v4l2_dbg(2, ir_888_debug, sd, "IRQ Status: %s %s %s %s %s %s\n", + tsr ? "tsr" : " ", rsr ? "rsr" : " ", + rto ? "rto" : " ", ror ? "ror" : " ", + stats & STATS_TBY ? "tby" : " ", + stats & STATS_RBY ? "rby" : " "); + + v4l2_dbg(2, ir_888_debug, sd, "IRQ Enables: %s %s %s %s\n", + tse ? "tse" : " ", rse ? "rse" : " ", + rte ? "rte" : " ", roe ? "roe" : " "); + + /* + * Transmitter interrupt service + */ + if (tse && tsr) { + /* + * TODO: + * Check the watermark threshold setting + * Pull FIFO_TX_DEPTH or FIFO_TX_DEPTH/2 entries from tx_kfifo + * Push the data to the hardware FIFO. + * If there was nothing more to send in the tx_kfifo, disable + * the TSR IRQ and notify the v4l2_device. + * If there was something in the tx_kfifo, check the tx_kfifo + * level and notify the v4l2_device, if it is low. + */ + /* For now, inhibit TSR interrupt until Tx is implemented */ + irqenable_tx(dev, 0); + events = V4L2_SUBDEV_IR_TX_FIFO_SERVICE_REQ; + v4l2_subdev_notify(sd, V4L2_SUBDEV_IR_TX_NOTIFY, &events); + *handled = true; + } + + /* + * Receiver interrupt service + */ + kror = 0; + if ((rse && rsr) || (rte && rto)) { + /* + * Receive data on RSR to clear the STATS_RSR. + * Receive data on RTO, since we may not have yet hit the RSR + * watermark when we receive the RTO. + */ + for (i = 0, v = FIFO_RX_NDV; + (v & FIFO_RX_NDV) && !kror; i = 0) { + for (j = 0; + (v & FIFO_RX_NDV) && j < FIFO_RX_DEPTH; j++) { + v = cx23888_ir_read4(dev, CX23888_IR_FIFO_REG); + rx_data[i].hw_fifo_data = v & ~FIFO_RX_NDV; + i++; + } + if (i == 0) + break; + j = i * sizeof(union cx23888_ir_fifo_rec); + k = kfifo_in_locked(&state->rx_kfifo, + (unsigned char *) rx_data, j, + &state->rx_kfifo_lock); + if (k != j) + kror++; /* rx_kfifo over run */ + } + *handled = true; + } + + events = 0; + v = 0; + if (kror) { + events |= V4L2_SUBDEV_IR_RX_SW_FIFO_OVERRUN; + v4l2_err(sd, "IR receiver software FIFO overrun\n"); + } + if (roe && ror) { + /* + * The RX FIFO Enable (CNTRL_RFE) must be toggled to clear + * the Rx FIFO Over Run status (STATS_ROR) + */ + v |= CNTRL_RFE; + events |= V4L2_SUBDEV_IR_RX_HW_FIFO_OVERRUN; + v4l2_err(sd, "IR receiver hardware FIFO overrun\n"); + } + if (rte && rto) { + /* + * The IR Receiver Enable (CNTRL_RXE) must be toggled to clear + * the Rx Pulse Width Timer Time Out (STATS_RTO) + */ + v |= CNTRL_RXE; + events |= V4L2_SUBDEV_IR_RX_END_OF_RX_DETECTED; + } + if (v) { + /* Clear STATS_ROR & STATS_RTO as needed by resetting hardware */ + cx23888_ir_write4(dev, CX23888_IR_CNTRL_REG, cntrl & ~v); + cx23888_ir_write4(dev, CX23888_IR_CNTRL_REG, cntrl); + *handled = true; + } + + spin_lock_irqsave(&state->rx_kfifo_lock, flags); + if (kfifo_len(&state->rx_kfifo) >= CX23888_IR_RX_KFIFO_SIZE / 2) + events |= V4L2_SUBDEV_IR_RX_FIFO_SERVICE_REQ; + spin_unlock_irqrestore(&state->rx_kfifo_lock, flags); + + if (events) + v4l2_subdev_notify(sd, V4L2_SUBDEV_IR_RX_NOTIFY, &events); + return 0; +} + +/* Receiver */ +static int cx23888_ir_rx_read(struct v4l2_subdev *sd, u8 *buf, size_t count, + ssize_t *num) +{ + struct cx23888_ir_state *state = to_state(sd); + bool invert = (bool) atomic_read(&state->rx_invert); + u16 divider = (u16) atomic_read(&state->rxclk_divider); + + unsigned int i, n; + union cx23888_ir_fifo_rec *p; + unsigned u, v, w; + + n = count / sizeof(union cx23888_ir_fifo_rec) + * sizeof(union cx23888_ir_fifo_rec); + if (n == 0) { + *num = 0; + return 0; + } + + n = kfifo_out_locked(&state->rx_kfifo, buf, n, &state->rx_kfifo_lock); + + n /= sizeof(union cx23888_ir_fifo_rec); + *num = n * sizeof(union cx23888_ir_fifo_rec); + + for (p = (union cx23888_ir_fifo_rec *) buf, i = 0; i < n; p++, i++) { + + if ((p->hw_fifo_data & FIFO_RXTX_RTO) == FIFO_RXTX_RTO) { + /* Assume RTO was because of no IR light input */ + u = 0; + w = 1; + } else { + u = (p->hw_fifo_data & FIFO_RXTX_LVL) ? 1 : 0; + if (invert) + u = u ? 0 : 1; + w = 0; + } + + v = (unsigned) pulse_width_count_to_ns( + (u16)(p->hw_fifo_data & FIFO_RXTX), divider) / 1000; + if (v > IR_MAX_DURATION) + v = IR_MAX_DURATION; + + p->ir_core_data = (struct ir_raw_event) + { .pulse = u, .duration = v, .timeout = w }; + + v4l2_dbg(2, ir_888_debug, sd, "rx read: %10u ns %s %s\n", + v, u ? "mark" : "space", w ? "(timed out)" : ""); + if (w) + v4l2_dbg(2, ir_888_debug, sd, "rx read: end of rx\n"); + } + return 0; +} + +static int cx23888_ir_rx_g_parameters(struct v4l2_subdev *sd, + struct v4l2_subdev_ir_parameters *p) +{ + struct cx23888_ir_state *state = to_state(sd); + mutex_lock(&state->rx_params_lock); + memcpy(p, &state->rx_params, sizeof(struct v4l2_subdev_ir_parameters)); + mutex_unlock(&state->rx_params_lock); + return 0; +} + +static int cx23888_ir_rx_shutdown(struct v4l2_subdev *sd) +{ + struct cx23888_ir_state *state = to_state(sd); + struct cx23885_dev *dev = state->dev; + + mutex_lock(&state->rx_params_lock); + + /* Disable or slow down all IR Rx circuits and counters */ + irqenable_rx(dev, 0); + control_rx_enable(dev, false); + control_rx_demodulation_enable(dev, false); + control_rx_s_edge_detection(dev, CNTRL_EDG_NONE); + filter_rx_s_min_width(dev, 0); + cx23888_ir_write4(dev, CX23888_IR_RXCLK_REG, RXCLK_RCD); + + state->rx_params.shutdown = true; + + mutex_unlock(&state->rx_params_lock); + return 0; +} + +static int cx23888_ir_rx_s_parameters(struct v4l2_subdev *sd, + struct v4l2_subdev_ir_parameters *p) +{ + struct cx23888_ir_state *state = to_state(sd); + struct cx23885_dev *dev = state->dev; + struct v4l2_subdev_ir_parameters *o = &state->rx_params; + u16 rxclk_divider; + + if (p->shutdown) + return cx23888_ir_rx_shutdown(sd); + + if (p->mode != V4L2_SUBDEV_IR_MODE_PULSE_WIDTH) + return -ENOSYS; + + mutex_lock(&state->rx_params_lock); + + o->shutdown = p->shutdown; + + o->mode = p->mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH; + + o->bytes_per_data_element = p->bytes_per_data_element + = sizeof(union cx23888_ir_fifo_rec); + + /* Before we tweak the hardware, we have to disable the receiver */ + irqenable_rx(dev, 0); + control_rx_enable(dev, false); + + control_rx_demodulation_enable(dev, p->modulation); + o->modulation = p->modulation; + + if (p->modulation) { + p->carrier_freq = rxclk_rx_s_carrier(dev, p->carrier_freq, + &rxclk_divider); + + o->carrier_freq = p->carrier_freq; + + o->duty_cycle = p->duty_cycle = 50; + + control_rx_s_carrier_window(dev, p->carrier_freq, + &p->carrier_range_lower, + &p->carrier_range_upper); + o->carrier_range_lower = p->carrier_range_lower; + o->carrier_range_upper = p->carrier_range_upper; + + p->max_pulse_width = + (u32) pulse_width_count_to_ns(FIFO_RXTX, rxclk_divider); + } else { + p->max_pulse_width = + rxclk_rx_s_max_pulse_width(dev, p->max_pulse_width, + &rxclk_divider); + } + o->max_pulse_width = p->max_pulse_width; + atomic_set(&state->rxclk_divider, rxclk_divider); + + p->noise_filter_min_width = + filter_rx_s_min_width(dev, p->noise_filter_min_width); + o->noise_filter_min_width = p->noise_filter_min_width; + + p->resolution = clock_divider_to_resolution(rxclk_divider); + o->resolution = p->resolution; + + /* FIXME - make this dependent on resolution for better performance */ + control_rx_irq_watermark(dev, RX_FIFO_HALF_FULL); + + control_rx_s_edge_detection(dev, CNTRL_EDG_BOTH); + + o->invert_level = p->invert_level; + atomic_set(&state->rx_invert, p->invert_level); + + o->interrupt_enable = p->interrupt_enable; + o->enable = p->enable; + if (p->enable) { + unsigned long flags; + + spin_lock_irqsave(&state->rx_kfifo_lock, flags); + kfifo_reset(&state->rx_kfifo); + /* reset tx_fifo too if there is one... */ + spin_unlock_irqrestore(&state->rx_kfifo_lock, flags); + if (p->interrupt_enable) + irqenable_rx(dev, IRQEN_RSE | IRQEN_RTE | IRQEN_ROE); + control_rx_enable(dev, p->enable); + } + + mutex_unlock(&state->rx_params_lock); + return 0; +} + +/* Transmitter */ +static int cx23888_ir_tx_write(struct v4l2_subdev *sd, u8 *buf, size_t count, + ssize_t *num) +{ + struct cx23888_ir_state *state = to_state(sd); + struct cx23885_dev *dev = state->dev; + /* For now enable the Tx FIFO Service interrupt & pretend we did work */ + irqenable_tx(dev, IRQEN_TSE); + *num = count; + return 0; +} + +static int cx23888_ir_tx_g_parameters(struct v4l2_subdev *sd, + struct v4l2_subdev_ir_parameters *p) +{ + struct cx23888_ir_state *state = to_state(sd); + mutex_lock(&state->tx_params_lock); + memcpy(p, &state->tx_params, sizeof(struct v4l2_subdev_ir_parameters)); + mutex_unlock(&state->tx_params_lock); + return 0; +} + +static int cx23888_ir_tx_shutdown(struct v4l2_subdev *sd) +{ + struct cx23888_ir_state *state = to_state(sd); + struct cx23885_dev *dev = state->dev; + + mutex_lock(&state->tx_params_lock); + + /* Disable or slow down all IR Tx circuits and counters */ + irqenable_tx(dev, 0); + control_tx_enable(dev, false); + control_tx_modulation_enable(dev, false); + cx23888_ir_write4(dev, CX23888_IR_TXCLK_REG, TXCLK_TCD); + + state->tx_params.shutdown = true; + + mutex_unlock(&state->tx_params_lock); + return 0; +} + +static int cx23888_ir_tx_s_parameters(struct v4l2_subdev *sd, + struct v4l2_subdev_ir_parameters *p) +{ + struct cx23888_ir_state *state = to_state(sd); + struct cx23885_dev *dev = state->dev; + struct v4l2_subdev_ir_parameters *o = &state->tx_params; + u16 txclk_divider; + + if (p->shutdown) + return cx23888_ir_tx_shutdown(sd); + + if (p->mode != V4L2_SUBDEV_IR_MODE_PULSE_WIDTH) + return -ENOSYS; + + mutex_lock(&state->tx_params_lock); + + o->shutdown = p->shutdown; + + o->mode = p->mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH; + + o->bytes_per_data_element = p->bytes_per_data_element + = sizeof(union cx23888_ir_fifo_rec); + + /* Before we tweak the hardware, we have to disable the transmitter */ + irqenable_tx(dev, 0); + control_tx_enable(dev, false); + + control_tx_modulation_enable(dev, p->modulation); + o->modulation = p->modulation; + + if (p->modulation) { + p->carrier_freq = txclk_tx_s_carrier(dev, p->carrier_freq, + &txclk_divider); + o->carrier_freq = p->carrier_freq; + + p->duty_cycle = cduty_tx_s_duty_cycle(dev, p->duty_cycle); + o->duty_cycle = p->duty_cycle; + + p->max_pulse_width = + (u32) pulse_width_count_to_ns(FIFO_RXTX, txclk_divider); + } else { + p->max_pulse_width = + txclk_tx_s_max_pulse_width(dev, p->max_pulse_width, + &txclk_divider); + } + o->max_pulse_width = p->max_pulse_width; + atomic_set(&state->txclk_divider, txclk_divider); + + p->resolution = clock_divider_to_resolution(txclk_divider); + o->resolution = p->resolution; + + /* FIXME - make this dependent on resolution for better performance */ + control_tx_irq_watermark(dev, TX_FIFO_HALF_EMPTY); + + control_tx_polarity_invert(dev, p->invert_carrier_sense); + o->invert_carrier_sense = p->invert_carrier_sense; + + control_tx_level_invert(dev, p->invert_level); + o->invert_level = p->invert_level; + + o->interrupt_enable = p->interrupt_enable; + o->enable = p->enable; + if (p->enable) { + if (p->interrupt_enable) + irqenable_tx(dev, IRQEN_TSE); + control_tx_enable(dev, p->enable); + } + + mutex_unlock(&state->tx_params_lock); + return 0; +} + + +/* + * V4L2 Subdevice Core Ops + */ +static int cx23888_ir_log_status(struct v4l2_subdev *sd) +{ + struct cx23888_ir_state *state = to_state(sd); + struct cx23885_dev *dev = state->dev; + char *s; + int i, j; + + u32 cntrl = cx23888_ir_read4(dev, CX23888_IR_CNTRL_REG); + u32 txclk = cx23888_ir_read4(dev, CX23888_IR_TXCLK_REG) & TXCLK_TCD; + u32 rxclk = cx23888_ir_read4(dev, CX23888_IR_RXCLK_REG) & RXCLK_RCD; + u32 cduty = cx23888_ir_read4(dev, CX23888_IR_CDUTY_REG) & CDUTY_CDC; + u32 stats = cx23888_ir_read4(dev, CX23888_IR_STATS_REG); + u32 irqen = cx23888_ir_read4(dev, CX23888_IR_IRQEN_REG); + u32 filtr = cx23888_ir_read4(dev, CX23888_IR_FILTR_REG) & FILTR_LPF; + + v4l2_info(sd, "IR Receiver:\n"); + v4l2_info(sd, "\tEnabled: %s\n", + cntrl & CNTRL_RXE ? "yes" : "no"); + v4l2_info(sd, "\tDemodulation from a carrier: %s\n", + cntrl & CNTRL_DMD ? "enabled" : "disabled"); + v4l2_info(sd, "\tFIFO: %s\n", + cntrl & CNTRL_RFE ? "enabled" : "disabled"); + switch (cntrl & CNTRL_EDG) { + case CNTRL_EDG_NONE: + s = "disabled"; + break; + case CNTRL_EDG_FALL: + s = "falling edge"; + break; + case CNTRL_EDG_RISE: + s = "rising edge"; + break; + case CNTRL_EDG_BOTH: + s = "rising & falling edges"; + break; + default: + s = "??? edge"; + break; + } + v4l2_info(sd, "\tPulse timers' start/stop trigger: %s\n", s); + v4l2_info(sd, "\tFIFO data on pulse timer overflow: %s\n", + cntrl & CNTRL_R ? "not loaded" : "overflow marker"); + v4l2_info(sd, "\tFIFO interrupt watermark: %s\n", + cntrl & CNTRL_RIC ? "not empty" : "half full or greater"); + v4l2_info(sd, "\tLoopback mode: %s\n", + cntrl & CNTRL_LBM ? "loopback active" : "normal receive"); + if (cntrl & CNTRL_DMD) { + v4l2_info(sd, "\tExpected carrier (16 clocks): %u Hz\n", + clock_divider_to_carrier_freq(rxclk)); + switch (cntrl & CNTRL_WIN) { + case CNTRL_WIN_3_3: + i = 3; + j = 3; + break; + case CNTRL_WIN_4_3: + i = 4; + j = 3; + break; + case CNTRL_WIN_3_4: + i = 3; + j = 4; + break; + case CNTRL_WIN_4_4: + i = 4; + j = 4; + break; + default: + i = 0; + j = 0; + break; + } + v4l2_info(sd, "\tNext carrier edge window: 16 clocks -%1d/+%1d, %u to %u Hz\n", + i, j, + clock_divider_to_freq(rxclk, 16 + j), + clock_divider_to_freq(rxclk, 16 - i)); + } + v4l2_info(sd, "\tMax measurable pulse width: %u us, %llu ns\n", + pulse_width_count_to_us(FIFO_RXTX, rxclk), + pulse_width_count_to_ns(FIFO_RXTX, rxclk)); + v4l2_info(sd, "\tLow pass filter: %s\n", + filtr ? "enabled" : "disabled"); + if (filtr) + v4l2_info(sd, "\tMin acceptable pulse width (LPF): %u us, %u ns\n", + lpf_count_to_us(filtr), + lpf_count_to_ns(filtr)); + v4l2_info(sd, "\tPulse width timer timed-out: %s\n", + stats & STATS_RTO ? "yes" : "no"); + v4l2_info(sd, "\tPulse width timer time-out intr: %s\n", + irqen & IRQEN_RTE ? "enabled" : "disabled"); + v4l2_info(sd, "\tFIFO overrun: %s\n", + stats & STATS_ROR ? "yes" : "no"); + v4l2_info(sd, "\tFIFO overrun interrupt: %s\n", + irqen & IRQEN_ROE ? "enabled" : "disabled"); + v4l2_info(sd, "\tBusy: %s\n", + stats & STATS_RBY ? "yes" : "no"); + v4l2_info(sd, "\tFIFO service requested: %s\n", + stats & STATS_RSR ? "yes" : "no"); + v4l2_info(sd, "\tFIFO service request interrupt: %s\n", + irqen & IRQEN_RSE ? "enabled" : "disabled"); + + v4l2_info(sd, "IR Transmitter:\n"); + v4l2_info(sd, "\tEnabled: %s\n", + cntrl & CNTRL_TXE ? "yes" : "no"); + v4l2_info(sd, "\tModulation onto a carrier: %s\n", + cntrl & CNTRL_MOD ? "enabled" : "disabled"); + v4l2_info(sd, "\tFIFO: %s\n", + cntrl & CNTRL_TFE ? "enabled" : "disabled"); + v4l2_info(sd, "\tFIFO interrupt watermark: %s\n", + cntrl & CNTRL_TIC ? "not empty" : "half full or less"); + v4l2_info(sd, "\tOutput pin level inversion %s\n", + cntrl & CNTRL_IVO ? "yes" : "no"); + v4l2_info(sd, "\tCarrier polarity: %s\n", + cntrl & CNTRL_CPL ? "space:burst mark:noburst" + : "space:noburst mark:burst"); + if (cntrl & CNTRL_MOD) { + v4l2_info(sd, "\tCarrier (16 clocks): %u Hz\n", + clock_divider_to_carrier_freq(txclk)); + v4l2_info(sd, "\tCarrier duty cycle: %2u/16\n", + cduty + 1); + } + v4l2_info(sd, "\tMax pulse width: %u us, %llu ns\n", + pulse_width_count_to_us(FIFO_RXTX, txclk), + pulse_width_count_to_ns(FIFO_RXTX, txclk)); + v4l2_info(sd, "\tBusy: %s\n", + stats & STATS_TBY ? "yes" : "no"); + v4l2_info(sd, "\tFIFO service requested: %s\n", + stats & STATS_TSR ? "yes" : "no"); + v4l2_info(sd, "\tFIFO service request interrupt: %s\n", + irqen & IRQEN_TSE ? "enabled" : "disabled"); + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int cx23888_ir_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct cx23888_ir_state *state = to_state(sd); + u32 addr = CX23888_IR_REG_BASE + (u32) reg->reg; + + if ((addr & 0x3) != 0) + return -EINVAL; + if (addr < CX23888_IR_CNTRL_REG || addr > CX23888_IR_LEARN_REG) + return -EINVAL; + reg->size = 4; + reg->val = cx23888_ir_read4(state->dev, addr); + return 0; +} + +static int cx23888_ir_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct cx23888_ir_state *state = to_state(sd); + u32 addr = CX23888_IR_REG_BASE + (u32) reg->reg; + + if ((addr & 0x3) != 0) + return -EINVAL; + if (addr < CX23888_IR_CNTRL_REG || addr > CX23888_IR_LEARN_REG) + return -EINVAL; + cx23888_ir_write4(state->dev, addr, reg->val); + return 0; +} +#endif + +static const struct v4l2_subdev_core_ops cx23888_ir_core_ops = { + .log_status = cx23888_ir_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = cx23888_ir_g_register, + .s_register = cx23888_ir_s_register, +#endif + .interrupt_service_routine = cx23888_ir_irq_handler, +}; + +static const struct v4l2_subdev_ir_ops cx23888_ir_ir_ops = { + .rx_read = cx23888_ir_rx_read, + .rx_g_parameters = cx23888_ir_rx_g_parameters, + .rx_s_parameters = cx23888_ir_rx_s_parameters, + + .tx_write = cx23888_ir_tx_write, + .tx_g_parameters = cx23888_ir_tx_g_parameters, + .tx_s_parameters = cx23888_ir_tx_s_parameters, +}; + +static const struct v4l2_subdev_ops cx23888_ir_controller_ops = { + .core = &cx23888_ir_core_ops, + .ir = &cx23888_ir_ir_ops, +}; + +static const struct v4l2_subdev_ir_parameters default_rx_params = { + .bytes_per_data_element = sizeof(union cx23888_ir_fifo_rec), + .mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH, + + .enable = false, + .interrupt_enable = false, + .shutdown = true, + + .modulation = true, + .carrier_freq = 36000, /* 36 kHz - RC-5, RC-6, and RC-6A carrier */ + + /* RC-5: 666,667 ns = 1/36 kHz * 32 cycles * 1 mark * 0.75 */ + /* RC-6A: 333,333 ns = 1/36 kHz * 16 cycles * 1 mark * 0.75 */ + .noise_filter_min_width = 333333, /* ns */ + .carrier_range_lower = 35000, + .carrier_range_upper = 37000, + .invert_level = false, +}; + +static const struct v4l2_subdev_ir_parameters default_tx_params = { + .bytes_per_data_element = sizeof(union cx23888_ir_fifo_rec), + .mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH, + + .enable = false, + .interrupt_enable = false, + .shutdown = true, + + .modulation = true, + .carrier_freq = 36000, /* 36 kHz - RC-5 carrier */ + .duty_cycle = 25, /* 25 % - RC-5 carrier */ + .invert_level = false, + .invert_carrier_sense = false, +}; + +int cx23888_ir_probe(struct cx23885_dev *dev) +{ + struct cx23888_ir_state *state; + struct v4l2_subdev *sd; + struct v4l2_subdev_ir_parameters default_params; + int ret; + + state = kzalloc_obj(struct cx23888_ir_state); + if (state == NULL) + return -ENOMEM; + + spin_lock_init(&state->rx_kfifo_lock); + if (kfifo_alloc(&state->rx_kfifo, CX23888_IR_RX_KFIFO_SIZE, + GFP_KERNEL)) { + kfree(state); + return -ENOMEM; + } + + state->dev = dev; + sd = &state->sd; + + v4l2_subdev_init(sd, &cx23888_ir_controller_ops); + v4l2_set_subdevdata(sd, state); + /* FIXME - fix the formatting of dev->v4l2_dev.name and use it */ + snprintf(sd->name, sizeof(sd->name), "%s/888-ir", dev->name); + sd->grp_id = CX23885_HW_888_IR; + + ret = v4l2_device_register_subdev(&dev->v4l2_dev, sd); + if (ret == 0) { + /* + * Ensure no interrupts arrive from '888 specific conditions, + * since we ignore them in this driver to have commonality with + * similar IR controller cores. + */ + cx23888_ir_write4(dev, CX23888_IR_IRQEN_REG, 0); + + mutex_init(&state->rx_params_lock); + default_params = default_rx_params; + v4l2_subdev_call(sd, ir, rx_s_parameters, &default_params); + + mutex_init(&state->tx_params_lock); + default_params = default_tx_params; + v4l2_subdev_call(sd, ir, tx_s_parameters, &default_params); + } else { + kfifo_free(&state->rx_kfifo); + } + return ret; +} + +int cx23888_ir_remove(struct cx23885_dev *dev) +{ + struct v4l2_subdev *sd; + struct cx23888_ir_state *state; + + sd = cx23885_find_hw(dev, CX23885_HW_888_IR); + if (sd == NULL) + return -ENODEV; + + cx23888_ir_rx_shutdown(sd); + cx23888_ir_tx_shutdown(sd); + + state = to_state(sd); + v4l2_device_unregister_subdev(sd); + kfifo_free(&state->rx_kfifo); + kfree(state); + /* Nothing more to free() as state held the actual v4l2_subdev object */ + return 0; +} diff --git a/drivers/media/pci/cx23885/cx23888-ir.h b/drivers/media/pci/cx23885/cx23888-ir.h new file mode 100644 index 000000000..da532c50f --- /dev/null +++ b/drivers/media/pci/cx23885/cx23888-ir.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the Conexant CX23885/7/8 PCIe bridge + * + * CX23888 Integrated Consumer Infrared Controller + * + * Copyright (C) 2009 Andy Walls + */ + +#ifndef _CX23888_IR_H_ +#define _CX23888_IR_H_ +int cx23888_ir_probe(struct cx23885_dev *dev); +int cx23888_ir_remove(struct cx23885_dev *dev); +#endif diff --git a/drivers/media/pci/cx23885/netup-eeprom.c b/drivers/media/pci/cx23885/netup-eeprom.c new file mode 100644 index 000000000..335d150d8 --- /dev/null +++ b/drivers/media/pci/cx23885/netup-eeprom.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* + * netup-eeprom.c + * + * 24LC02 EEPROM driver in conjunction with NetUP Dual DVB-S2 CI card + * + * Copyright (C) 2009 NetUP Inc. + * Copyright (C) 2009 Abylay Ospan + */ + +# +#include "cx23885.h" +#include "netup-eeprom.h" + +#define EEPROM_I2C_ADDR 0x50 + +int netup_eeprom_read(struct i2c_adapter *i2c_adap, u8 addr) +{ + int ret; + unsigned char buf[2]; + + /* Read from EEPROM */ + struct i2c_msg msg[] = { + { + .addr = EEPROM_I2C_ADDR, + .flags = 0, + .buf = &buf[0], + .len = 1 + }, { + .addr = EEPROM_I2C_ADDR, + .flags = I2C_M_RD, + .buf = &buf[1], + .len = 1 + } + + }; + + buf[0] = addr; + buf[1] = 0x0; + + ret = i2c_transfer(i2c_adap, msg, 2); + + if (ret != 2) { + pr_err("eeprom i2c read error, status=%d\n", ret); + return -1; + } + + return buf[1]; +}; + +void netup_get_card_info(struct i2c_adapter *i2c_adap, + struct netup_card_info *cinfo) +{ + int i, j; + + cinfo->rev = netup_eeprom_read(i2c_adap, 63); + + for (i = 64, j = 0; i < 70; i++, j++) + cinfo->port[0].mac[j] = netup_eeprom_read(i2c_adap, i); + + for (i = 70, j = 0; i < 76; i++, j++) + cinfo->port[1].mac[j] = netup_eeprom_read(i2c_adap, i); +}; diff --git a/drivers/media/pci/cx23885/netup-eeprom.h b/drivers/media/pci/cx23885/netup-eeprom.h new file mode 100644 index 000000000..fb8217eb4 --- /dev/null +++ b/drivers/media/pci/cx23885/netup-eeprom.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * netup-eeprom.h + * + * 24LC02 EEPROM driver in conjunction with NetUP Dual DVB-S2 CI card + * + * Copyright (C) 2009 NetUP Inc. + * Copyright (C) 2009 Abylay Ospan + */ + +#ifndef NETUP_EEPROM_H +#define NETUP_EEPROM_H + +struct netup_port_info { + u8 mac[6];/* card MAC address */ +}; + +struct netup_card_info { + struct netup_port_info port[2];/* ports - 1,2 */ + u8 rev;/* card revision */ +}; + +extern int netup_eeprom_read(struct i2c_adapter *i2c_adap, u8 addr); +extern void netup_get_card_info(struct i2c_adapter *i2c_adap, + struct netup_card_info *cinfo); + +#endif diff --git a/drivers/media/pci/cx23885/netup-init.c b/drivers/media/pci/cx23885/netup-init.c new file mode 100644 index 000000000..c653b0766 --- /dev/null +++ b/drivers/media/pci/cx23885/netup-init.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * netup-init.c + * + * NetUP Dual DVB-S2 CI driver + * + * Copyright (C) 2009 NetUP Inc. + * Copyright (C) 2009 Igor M. Liplianin + * Copyright (C) 2009 Abylay Ospan + */ + +#include "cx23885.h" +#include "netup-init.h" + +static void i2c_av_write(struct i2c_adapter *i2c, u16 reg, u8 val) +{ + int ret; + u8 buf[3]; + struct i2c_msg msg = { + .addr = 0x88 >> 1, + .flags = 0, + .buf = buf, + .len = 3 + }; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + buf[2] = val; + + ret = i2c_transfer(i2c, &msg, 1); + + if (ret != 1) + pr_err("%s: i2c write error!\n", __func__); +} + +static void i2c_av_write4(struct i2c_adapter *i2c, u16 reg, u32 val) +{ + int ret; + u8 buf[6]; + struct i2c_msg msg = { + .addr = 0x88 >> 1, + .flags = 0, + .buf = buf, + .len = 6 + }; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + buf[2] = val & 0xff; + buf[3] = (val >> 8) & 0xff; + buf[4] = (val >> 16) & 0xff; + buf[5] = val >> 24; + + ret = i2c_transfer(i2c, &msg, 1); + + if (ret != 1) + pr_err("%s: i2c write error!\n", __func__); +} + +static u8 i2c_av_read(struct i2c_adapter *i2c, u16 reg) +{ + int ret; + u8 buf[2]; + struct i2c_msg msg = { + .addr = 0x88 >> 1, + .flags = 0, + .buf = buf, + .len = 2 + }; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + ret = i2c_transfer(i2c, &msg, 1); + + if (ret != 1) + pr_err("%s: i2c write error!\n", __func__); + + msg.flags = I2C_M_RD; + msg.len = 1; + + ret = i2c_transfer(i2c, &msg, 1); + + if (ret != 1) + pr_err("%s: i2c read error!\n", __func__); + + return buf[0]; +} + +static void i2c_av_and_or(struct i2c_adapter *i2c, u16 reg, unsigned and_mask, + u8 or_value) +{ + i2c_av_write(i2c, reg, (i2c_av_read(i2c, reg) & and_mask) | or_value); +} +/* set 27MHz on AUX_CLK */ +void netup_initialize(struct cx23885_dev *dev) +{ + struct cx23885_i2c *i2c_bus = &dev->i2c_bus[2]; + struct i2c_adapter *i2c = &i2c_bus->i2c_adap; + + /* Stop microcontroller */ + i2c_av_and_or(i2c, 0x803, ~0x10, 0x00); + + /* Aux PLL frac for 27 MHz */ + i2c_av_write4(i2c, 0x114, 0xea0eb3); + + /* Aux PLL int for 27 MHz */ + i2c_av_write4(i2c, 0x110, 0x090319); + + /* start microcontroller */ + i2c_av_and_or(i2c, 0x803, ~0x10, 0x10); +} diff --git a/drivers/media/pci/cx23885/netup-init.h b/drivers/media/pci/cx23885/netup-init.h new file mode 100644 index 000000000..a009f73f8 --- /dev/null +++ b/drivers/media/pci/cx23885/netup-init.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * netup-init.h + * + * NetUP Dual DVB-S2 CI driver + * + * Copyright (C) 2009 NetUP Inc. + * Copyright (C) 2009 Igor M. Liplianin + * Copyright (C) 2009 Abylay Ospan + */ +extern void netup_initialize(struct cx23885_dev *dev); diff --git a/drivers/media/pci/cx25821/Kconfig b/drivers/media/pci/cx25821/Kconfig new file mode 100644 index 000000000..b26615fdc --- /dev/null +++ b/drivers/media/pci/cx25821/Kconfig @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_CX25821 + tristate "Conexant cx25821 support" + depends on VIDEO_DEV && PCI && I2C + select I2C_ALGOBIT + select VIDEOBUF2_DMA_SG + help + This is a video4linux driver for Conexant 25821 based + TV cards. + + To compile this driver as a module, choose M here: the + module will be called cx25821 + +config VIDEO_CX25821_ALSA + tristate "Conexant 25821 DMA audio support" + depends on VIDEO_CX25821 && SND + select SND_PCM + help + This is a video4linux driver for direct (DMA) audio on + Conexant 25821 based capture cards using ALSA. + + It only works with boards with function 01 enabled. + To check if your board supports, use lspci -n. + If supported, you should see 14f1:8801 or 14f1:8811 + PCI device. + + To compile this driver as a module, choose M here: the + module will be called cx25821-alsa. + diff --git a/drivers/media/pci/cx25821/Makefile b/drivers/media/pci/cx25821/Makefile new file mode 100644 index 000000000..94633d02a --- /dev/null +++ b/drivers/media/pci/cx25821/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +cx25821-y := cx25821-core.o cx25821-cards.o cx25821-i2c.o \ + cx25821-gpio.o cx25821-medusa-video.o \ + cx25821-video.o + +obj-$(CONFIG_VIDEO_CX25821) += cx25821.o +obj-$(CONFIG_VIDEO_CX25821_ALSA) += cx25821-alsa.o diff --git a/drivers/media/pci/cx25821/cx25821-alsa.c b/drivers/media/pci/cx25821/cx25821-alsa.c new file mode 100644 index 000000000..f963ae79f --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821-alsa.c @@ -0,0 +1,820 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + * Based on SAA713x ALSA driver and CX88 driver + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "cx25821.h" +#include "cx25821-reg.h" + +#define AUDIO_SRAM_CHANNEL SRAM_CH08 + +#define dprintk(level, fmt, arg...) \ +do { \ + if (debug >= level) \ + pr_info("%s/1: " fmt, chip->dev->name, ##arg); \ +} while (0) +#define dprintk_core(level, fmt, arg...) \ +do { \ + if (debug >= level) \ + printk(KERN_DEBUG "%s/1: " fmt, chip->dev->name, ##arg); \ +} while (0) + +/**************************************************************************** + Data type declarations - Can be moded to a header file later + ****************************************************************************/ + +static int devno; + +struct cx25821_audio_buffer { + unsigned int bpl; + struct cx25821_riscmem risc; + void *vaddr; + struct scatterlist *sglist; + int sglen; + unsigned long nr_pages; +}; + +struct cx25821_audio_dev { + struct cx25821_dev *dev; + struct cx25821_dmaqueue q; + + /* pci i/o */ + struct pci_dev *pci; + + /* audio controls */ + int irq; + + struct snd_card *card; + + unsigned long iobase; + spinlock_t reg_lock; + atomic_t count; + + unsigned int dma_size; + unsigned int period_size; + unsigned int num_periods; + + struct cx25821_audio_buffer *buf; + + struct snd_pcm_substream *substream; +}; + + +/**************************************************************************** + Module global static vars + ****************************************************************************/ + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable cx25821 soundcard. default enabled."); + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for cx25821 capture interface(s)."); + +/**************************************************************************** + Module macros + ****************************************************************************/ + +MODULE_DESCRIPTION("ALSA driver module for cx25821 based capture cards"); +MODULE_AUTHOR("Hiep Huynh"); +MODULE_LICENSE("GPL"); + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "enable debug messages"); + +/**************************************************************************** + Module specific functions + ****************************************************************************/ +/* Constants taken from cx88-reg.h */ +#define AUD_INT_DN_RISCI1 (1 << 0) +#define AUD_INT_UP_RISCI1 (1 << 1) +#define AUD_INT_RDS_DN_RISCI1 (1 << 2) +#define AUD_INT_DN_RISCI2 (1 << 4) /* yes, 3 is skipped */ +#define AUD_INT_UP_RISCI2 (1 << 5) +#define AUD_INT_RDS_DN_RISCI2 (1 << 6) +#define AUD_INT_DN_SYNC (1 << 12) +#define AUD_INT_UP_SYNC (1 << 13) +#define AUD_INT_RDS_DN_SYNC (1 << 14) +#define AUD_INT_OPC_ERR (1 << 16) +#define AUD_INT_BER_IRQ (1 << 20) +#define AUD_INT_MCHG_IRQ (1 << 21) +#define GP_COUNT_CONTROL_RESET 0x3 + +#define PCI_MSK_AUD_EXT (1 << 4) +#define PCI_MSK_AUD_INT (1 << 3) + +static int cx25821_alsa_dma_init(struct cx25821_audio_dev *chip, + unsigned long nr_pages) +{ + struct cx25821_audio_buffer *buf = chip->buf; + struct page *pg; + int i; + + buf->vaddr = vmalloc_32(nr_pages << PAGE_SHIFT); + if (NULL == buf->vaddr) { + dprintk(1, "vmalloc_32(%lu pages) failed\n", nr_pages); + return -ENOMEM; + } + + dprintk(1, "vmalloc is at addr 0x%p, size=%lu\n", + buf->vaddr, + nr_pages << PAGE_SHIFT); + + memset(buf->vaddr, 0, nr_pages << PAGE_SHIFT); + buf->nr_pages = nr_pages; + + buf->sglist = vzalloc(array_size(sizeof(*buf->sglist), buf->nr_pages)); + if (NULL == buf->sglist) + goto vzalloc_err; + + sg_init_table(buf->sglist, buf->nr_pages); + for (i = 0; i < buf->nr_pages; i++) { + pg = vmalloc_to_page(buf->vaddr + i * PAGE_SIZE); + if (NULL == pg) + goto vmalloc_to_page_err; + sg_set_page(&buf->sglist[i], pg, PAGE_SIZE, 0); + } + return 0; + +vmalloc_to_page_err: + vfree(buf->sglist); + buf->sglist = NULL; +vzalloc_err: + vfree(buf->vaddr); + buf->vaddr = NULL; + return -ENOMEM; +} + +static int cx25821_alsa_dma_map(struct cx25821_audio_dev *dev) +{ + struct cx25821_audio_buffer *buf = dev->buf; + + buf->sglen = dma_map_sg(&dev->pci->dev, buf->sglist, + buf->nr_pages, DMA_FROM_DEVICE); + + if (0 == buf->sglen) { + pr_warn("%s: cx25821_alsa_map_sg failed\n", __func__); + return -ENOMEM; + } + return 0; +} + +static int cx25821_alsa_dma_unmap(struct cx25821_audio_dev *dev) +{ + struct cx25821_audio_buffer *buf = dev->buf; + + if (!buf->sglen) + return 0; + + dma_unmap_sg(&dev->pci->dev, buf->sglist, buf->nr_pages, DMA_FROM_DEVICE); + buf->sglen = 0; + return 0; +} + +static int cx25821_alsa_dma_free(struct cx25821_audio_buffer *buf) +{ + vfree(buf->sglist); + buf->sglist = NULL; + vfree(buf->vaddr); + buf->vaddr = NULL; + return 0; +} + +/* + * BOARD Specific: Sets audio DMA + */ + +static int _cx25821_start_audio_dma(struct cx25821_audio_dev *chip) +{ + struct cx25821_audio_buffer *buf = chip->buf; + struct cx25821_dev *dev = chip->dev; + const struct sram_channel *audio_ch = + &cx25821_sram_channels[AUDIO_SRAM_CHANNEL]; + u32 tmp = 0; + + /* enable output on the GPIO 0 for the MCLK ADC (Audio) */ + cx25821_set_gpiopin_direction(chip->dev, 0, 0); + + /* Make sure RISC/FIFO are off before changing FIFO/RISC settings */ + cx_clear(AUD_INT_DMA_CTL, + FLD_AUD_DST_A_RISC_EN | FLD_AUD_DST_A_FIFO_EN); + + /* setup fifo + format - out channel */ + cx25821_sram_channel_setup_audio(chip->dev, audio_ch, buf->bpl, + buf->risc.dma); + + /* sets bpl size */ + cx_write(AUD_A_LNGTH, buf->bpl); + + /* reset counter */ + /* GP_COUNT_CONTROL_RESET = 0x3 */ + cx_write(AUD_A_GPCNT_CTL, GP_COUNT_CONTROL_RESET); + atomic_set(&chip->count, 0); + + /* Set the input mode to 16-bit */ + tmp = cx_read(AUD_A_CFG); + cx_write(AUD_A_CFG, tmp | FLD_AUD_DST_PK_MODE | FLD_AUD_DST_ENABLE | + FLD_AUD_CLK_ENABLE); + + /* + pr_info("DEBUG: Start audio DMA, %d B/line, cmds_start(0x%x)= %d lines/FIFO, %d periods, %d byte buffer\n", + buf->bpl, audio_ch->cmds_start, + cx_read(audio_ch->cmds_start + 12)>>1, + chip->num_periods, buf->bpl * chip->num_periods); + */ + + /* Enables corresponding bits at AUD_INT_STAT */ + cx_write(AUD_A_INT_MSK, FLD_AUD_DST_RISCI1 | FLD_AUD_DST_OF | + FLD_AUD_DST_SYNC | FLD_AUD_DST_OPC_ERR); + + /* Clean any pending interrupt bits already set */ + cx_write(AUD_A_INT_STAT, ~0); + + /* enable audio irqs */ + cx_set(PCI_INT_MSK, chip->dev->pci_irqmask | PCI_MSK_AUD_INT); + + /* Turn on audio downstream fifo and risc enable 0x101 */ + tmp = cx_read(AUD_INT_DMA_CTL); + cx_set(AUD_INT_DMA_CTL, tmp | + (FLD_AUD_DST_A_RISC_EN | FLD_AUD_DST_A_FIFO_EN)); + + mdelay(100); + return 0; +} + +/* + * BOARD Specific: Resets audio DMA + */ +static int _cx25821_stop_audio_dma(struct cx25821_audio_dev *chip) +{ + struct cx25821_dev *dev = chip->dev; + + /* stop dma */ + cx_clear(AUD_INT_DMA_CTL, + FLD_AUD_DST_A_RISC_EN | FLD_AUD_DST_A_FIFO_EN); + + /* disable irqs */ + cx_clear(PCI_INT_MSK, PCI_MSK_AUD_INT); + cx_clear(AUD_A_INT_MSK, AUD_INT_OPC_ERR | AUD_INT_DN_SYNC | + AUD_INT_DN_RISCI2 | AUD_INT_DN_RISCI1); + + return 0; +} + +#define MAX_IRQ_LOOP 50 + +/* + * BOARD Specific: IRQ dma bits + */ +static char *cx25821_aud_irqs[32] = { + "dn_risci1", "up_risci1", "rds_dn_risc1", /* 0-2 */ + NULL, /* reserved */ + "dn_risci2", "up_risci2", "rds_dn_risc2", /* 4-6 */ + NULL, /* reserved */ + "dnf_of", "upf_uf", "rds_dnf_uf", /* 8-10 */ + NULL, /* reserved */ + "dn_sync", "up_sync", "rds_dn_sync", /* 12-14 */ + NULL, /* reserved */ + "opc_err", "par_err", "rip_err", /* 16-18 */ + "pci_abort", "ber_irq", "mchg_irq" /* 19-21 */ +}; + +/* + * BOARD Specific: Threats IRQ audio specific calls + */ +static void cx25821_aud_irq(struct cx25821_audio_dev *chip, u32 status, + u32 mask) +{ + struct cx25821_dev *dev = chip->dev; + + if (0 == (status & mask)) + return; + + cx_write(AUD_A_INT_STAT, status); + if (debug > 1 || (status & mask & ~0xff)) + cx25821_print_irqbits(dev->name, "irq aud", cx25821_aud_irqs, + ARRAY_SIZE(cx25821_aud_irqs), status, mask); + + /* risc op code error */ + if (status & AUD_INT_OPC_ERR) { + pr_warn("WARNING %s/1: Audio risc op code error\n", dev->name); + + cx_clear(AUD_INT_DMA_CTL, + FLD_AUD_DST_A_RISC_EN | FLD_AUD_DST_A_FIFO_EN); + cx25821_sram_channel_dump_audio(dev, + &cx25821_sram_channels[AUDIO_SRAM_CHANNEL]); + } + if (status & AUD_INT_DN_SYNC) { + pr_warn("WARNING %s: Downstream sync error!\n", dev->name); + cx_write(AUD_A_GPCNT_CTL, GP_COUNT_CONTROL_RESET); + return; + } + + /* risc1 downstream */ + if (status & AUD_INT_DN_RISCI1) { + atomic_set(&chip->count, cx_read(AUD_A_GPCNT)); + snd_pcm_period_elapsed(chip->substream); + } +} + +/* + * BOARD Specific: Handles IRQ calls + */ +static irqreturn_t cx25821_irq(int irq, void *dev_id) +{ + struct cx25821_audio_dev *chip = dev_id; + struct cx25821_dev *dev = chip->dev; + u32 status, pci_status; + u32 audint_status, audint_mask; + int loop, handled = 0; + + audint_status = cx_read(AUD_A_INT_STAT); + audint_mask = cx_read(AUD_A_INT_MSK); + status = cx_read(PCI_INT_STAT); + + for (loop = 0; loop < 1; loop++) { + status = cx_read(PCI_INT_STAT); + if (0 == status) { + status = cx_read(PCI_INT_STAT); + audint_status = cx_read(AUD_A_INT_STAT); + audint_mask = cx_read(AUD_A_INT_MSK); + + if (status) { + handled = 1; + cx_write(PCI_INT_STAT, status); + + cx25821_aud_irq(chip, audint_status, + audint_mask); + break; + } else { + goto out; + } + } + + handled = 1; + cx_write(PCI_INT_STAT, status); + + cx25821_aud_irq(chip, audint_status, audint_mask); + } + + pci_status = cx_read(PCI_INT_STAT); + + if (handled) + cx_write(PCI_INT_STAT, pci_status); + +out: + return IRQ_RETVAL(handled); +} + +static int dsp_buffer_free(struct cx25821_audio_dev *chip) +{ + struct cx25821_riscmem *risc = &chip->buf->risc; + + WARN_ON(!chip->dma_size); + + dprintk(2, "Freeing buffer\n"); + cx25821_alsa_dma_unmap(chip); + cx25821_alsa_dma_free(chip->buf); + dma_free_coherent(&chip->pci->dev, risc->size, risc->cpu, risc->dma); + kfree(chip->buf); + + chip->buf = NULL; + chip->dma_size = 0; + + return 0; +} + +/**************************************************************************** + ALSA PCM Interface + ****************************************************************************/ + +/* + * Digital hardware definition + */ +#define DEFAULT_FIFO_SIZE 384 +static const struct snd_pcm_hardware snd_cx25821_digital_hw = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + /* Analog audio output will be full of clicks and pops if there + are not exactly four lines in the SRAM FIFO buffer. */ + .period_bytes_min = DEFAULT_FIFO_SIZE / 3, + .period_bytes_max = DEFAULT_FIFO_SIZE / 3, + .periods_min = 1, + .periods_max = AUDIO_LINE_SIZE, + /* 128 * 128 = 16384 = 1024 * 16 */ + .buffer_bytes_max = (AUDIO_LINE_SIZE * AUDIO_LINE_SIZE), +}; + +/* + * audio pcm capture open callback + */ +static int snd_cx25821_pcm_open(struct snd_pcm_substream *substream) +{ + struct cx25821_audio_dev *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + unsigned int bpl = 0; + + if (!chip) { + pr_err("DEBUG: cx25821 can't find device struct. Can't proceed with open\n"); + return -ENODEV; + } + + err = snd_pcm_hw_constraint_pow2(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + goto _error; + + chip->substream = substream; + + runtime->hw = snd_cx25821_digital_hw; + + if (cx25821_sram_channels[AUDIO_SRAM_CHANNEL].fifo_size != + DEFAULT_FIFO_SIZE) { + /* since there are 3 audio Clusters */ + bpl = cx25821_sram_channels[AUDIO_SRAM_CHANNEL].fifo_size / 3; + bpl &= ~7; /* must be multiple of 8 */ + + if (bpl > AUDIO_LINE_SIZE) + bpl = AUDIO_LINE_SIZE; + + runtime->hw.period_bytes_min = bpl; + runtime->hw.period_bytes_max = bpl; + } + + return 0; +_error: + dprintk(1, "Error opening PCM!\n"); + return err; +} + +/* + * audio close callback + */ +static int snd_cx25821_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* + * hw_params callback + */ +static int snd_cx25821_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct cx25821_audio_dev *chip = snd_pcm_substream_chip(substream); + struct cx25821_audio_buffer *buf; + int ret; + + if (substream->runtime->dma_area) { + dsp_buffer_free(chip); + substream->runtime->dma_area = NULL; + } + + chip->period_size = params_period_bytes(hw_params); + chip->num_periods = params_periods(hw_params); + chip->dma_size = chip->period_size * params_periods(hw_params); + + WARN_ON(!chip->dma_size); + WARN_ON(chip->num_periods & (chip->num_periods - 1)); + + buf = kzalloc_obj(*buf); + if (NULL == buf) + return -ENOMEM; + + if (chip->period_size > AUDIO_LINE_SIZE) + chip->period_size = AUDIO_LINE_SIZE; + + buf->bpl = chip->period_size; + chip->buf = buf; + + ret = cx25821_alsa_dma_init(chip, + (PAGE_ALIGN(chip->dma_size) >> PAGE_SHIFT)); + if (ret < 0) + goto error; + + ret = cx25821_alsa_dma_map(chip); + if (ret < 0) + goto error; + + ret = cx25821_risc_databuffer_audio(chip->pci, &buf->risc, buf->sglist, + chip->period_size, chip->num_periods, 1); + if (ret < 0) { + pr_info("DEBUG: ERROR after cx25821_risc_databuffer_audio()\n"); + cx25821_alsa_dma_unmap(chip); + goto error; + } + + /* Loop back to start of program */ + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + buf->risc.jmp[2] = cpu_to_le32(0); /* bits 63-32 */ + + substream->runtime->dma_area = chip->buf->vaddr; + substream->runtime->dma_bytes = chip->dma_size; + substream->runtime->dma_addr = 0; + + return 0; + +error: + chip->buf = NULL; + kfree(buf); + return ret; +} + +/* + * hw free callback + */ +static int snd_cx25821_hw_free(struct snd_pcm_substream *substream) +{ + struct cx25821_audio_dev *chip = snd_pcm_substream_chip(substream); + + if (substream->runtime->dma_area) { + dsp_buffer_free(chip); + substream->runtime->dma_area = NULL; + } + + return 0; +} + +/* + * prepare callback + */ +static int snd_cx25821_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* + * trigger callback + */ +static int snd_cx25821_card_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct cx25821_audio_dev *chip = snd_pcm_substream_chip(substream); + int err = 0; + + /* Local interrupts are already disabled by ALSA */ + spin_lock(&chip->reg_lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + err = _cx25821_start_audio_dma(chip); + break; + case SNDRV_PCM_TRIGGER_STOP: + err = _cx25821_stop_audio_dma(chip); + break; + default: + err = -EINVAL; + break; + } + + spin_unlock(&chip->reg_lock); + + return err; +} + +/* + * pointer callback + */ +static snd_pcm_uframes_t snd_cx25821_pointer(struct snd_pcm_substream + *substream) +{ + struct cx25821_audio_dev *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + u16 count; + + count = atomic_read(&chip->count); + + return runtime->period_size * (count & (runtime->periods - 1)); +} + +/* + * page callback (needed for mmap) + */ +static struct page *snd_cx25821_page(struct snd_pcm_substream *substream, + unsigned long offset) +{ + void *pageptr = substream->runtime->dma_area + offset; + + return vmalloc_to_page(pageptr); +} + +/* + * operators + */ +static const struct snd_pcm_ops snd_cx25821_pcm_ops = { + .open = snd_cx25821_pcm_open, + .close = snd_cx25821_close, + .hw_params = snd_cx25821_hw_params, + .hw_free = snd_cx25821_hw_free, + .prepare = snd_cx25821_prepare, + .trigger = snd_cx25821_card_trigger, + .pointer = snd_cx25821_pointer, + .page = snd_cx25821_page, +}; + +/* + * ALSA create a PCM device: Called when initializing the board. + * Sets up the name and hooks up the callbacks + */ +static int snd_cx25821_pcm(struct cx25821_audio_dev *chip, int device, + char *name) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, name, device, 0, 1, &pcm); + if (err < 0) { + pr_info("ERROR: FAILED snd_pcm_new() in %s\n", __func__); + return err; + } + pcm->private_data = chip; + pcm->info_flags = 0; + strscpy(pcm->name, name, sizeof(pcm->name)); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cx25821_pcm_ops); + + return 0; +} + +/**************************************************************************** + Basic Flow for Sound Devices + ****************************************************************************/ + +/* + * PCI ID Table - 14f1:8801 and 14f1:8811 means function 1: Audio + * Only boards with eeprom and byte 1 at eeprom=1 have it + */ + +static const struct pci_device_id __maybe_unused cx25821_audio_pci_tbl[] = { + { PCI_DEVICE(0x14f1, 0x0920) }, + { } +}; + +MODULE_DEVICE_TABLE(pci, cx25821_audio_pci_tbl); + +/* + * Alsa Constructor - Component probe + */ +static int cx25821_audio_initdev(struct cx25821_dev *dev) +{ + struct snd_card *card; + struct cx25821_audio_dev *chip; + int err; + + if (devno >= SNDRV_CARDS) { + pr_info("DEBUG ERROR: devno >= SNDRV_CARDS %s\n", __func__); + return -ENODEV; + } + + if (!enable[devno]) { + ++devno; + pr_info("DEBUG ERROR: !enable[devno] %s\n", __func__); + return -ENOENT; + } + + err = snd_card_new(&dev->pci->dev, index[devno], id[devno], + THIS_MODULE, + sizeof(struct cx25821_audio_dev), &card); + if (err < 0) { + pr_info("DEBUG ERROR: cannot create snd_card_new in %s\n", + __func__); + return err; + } + + strscpy(card->driver, "cx25821", sizeof(card->driver)); + + /* Card "creation" */ + chip = card->private_data; + spin_lock_init(&chip->reg_lock); + + chip->dev = dev; + chip->card = card; + chip->pci = dev->pci; + chip->iobase = pci_resource_start(dev->pci, 0); + + chip->irq = dev->pci->irq; + + err = devm_request_irq(&dev->pci->dev, dev->pci->irq, cx25821_irq, + IRQF_SHARED, chip->dev->name, chip); + + if (err < 0) { + pr_err("ERROR %s: can't get IRQ %d for ALSA\n", chip->dev->name, + dev->pci->irq); + goto error; + } + + err = snd_cx25821_pcm(chip, 0, "cx25821 Digital"); + if (err < 0) { + pr_info("DEBUG ERROR: cannot create snd_cx25821_pcm %s\n", + __func__); + goto error; + } + + strscpy(card->shortname, "cx25821", sizeof(card->shortname)); + sprintf(card->longname, "%s at 0x%lx irq %d", chip->dev->name, + chip->iobase, chip->irq); + strscpy(card->mixername, "CX25821", sizeof(card->mixername)); + + pr_info("%s/%i: ALSA support for cx25821 boards\n", card->driver, + devno); + + err = snd_card_register(card); + if (err < 0) { + pr_info("DEBUG ERROR: cannot register sound card %s\n", + __func__); + goto error; + } + + dev->card = card; + devno++; + return 0; + +error: + snd_card_free(card); + return err; +} + +/**************************************************************************** + LINUX MODULE INIT + ****************************************************************************/ + +static int cx25821_alsa_exit_callback(struct device *dev, void *data) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); + struct cx25821_dev *cxdev = get_cx25821(v4l2_dev); + + snd_card_free(cxdev->card); + return 0; +} + +static void cx25821_audio_fini(void) +{ + struct device_driver *drv = driver_find("cx25821", &pci_bus_type); + int ret; + + ret = driver_for_each_device(drv, NULL, NULL, cx25821_alsa_exit_callback); + if (ret) + pr_err("%s failed to find a cx25821 driver.\n", __func__); +} + +static int cx25821_alsa_init_callback(struct device *dev, void *data) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); + struct cx25821_dev *cxdev = get_cx25821(v4l2_dev); + + cx25821_audio_initdev(cxdev); + return 0; +} + +/* + * Module initializer + * + * Loops through present saa7134 cards, and assigns an ALSA device + * to each one + * + */ +static int cx25821_alsa_init(void) +{ + struct device_driver *drv = driver_find("cx25821", &pci_bus_type); + + return driver_for_each_device(drv, NULL, NULL, cx25821_alsa_init_callback); + +} + +late_initcall(cx25821_alsa_init); +module_exit(cx25821_audio_fini); diff --git a/drivers/media/pci/cx25821/cx25821-audio.h b/drivers/media/pci/cx25821/cx25821-audio.h new file mode 100644 index 000000000..96f26e4b2 --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821-audio.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + */ + +#ifndef __CX25821_AUDIO_H__ +#define __CX25821_AUDIO_H__ + +#define USE_RISC_NOOP 1 +#define LINES_PER_BUFFER 15 +#define AUDIO_LINE_SIZE 128 + +/* Number of buffer programs to use at once. */ +#define NUMBER_OF_PROGRAMS 8 + +/* + * Max size of the RISC program for a buffer. - worst case is 2 writes per line + * Space is also added for the 4 no-op instructions added on the end. + */ +#ifndef USE_RISC_NOOP +#define MAX_BUFFER_PROGRAM_SIZE \ + (2 * LINES_PER_BUFFER * RISC_WRITE_INSTRUCTION_SIZE + \ + RISC_WRITECR_INSTRUCTION_SIZE * 4) +#endif + +/* MAE 12 July 2005 Try to use NOOP RISC instruction instead */ +#ifdef USE_RISC_NOOP +#define MAX_BUFFER_PROGRAM_SIZE \ + (2 * LINES_PER_BUFFER * RISC_WRITE_INSTRUCTION_SIZE + \ + RISC_NOOP_INSTRUCTION_SIZE * 4) +#endif + +/* Sizes of various instructions in bytes. Used when adding instructions. */ +#define RISC_WRITE_INSTRUCTION_SIZE 12 +#define RISC_JUMP_INSTRUCTION_SIZE 12 +#define RISC_SKIP_INSTRUCTION_SIZE 4 +#define RISC_SYNC_INSTRUCTION_SIZE 4 +#define RISC_WRITECR_INSTRUCTION_SIZE 16 +#define RISC_NOOP_INSTRUCTION_SIZE 4 + +#define MAX_AUDIO_DMA_BUFFER_SIZE \ + (MAX_BUFFER_PROGRAM_SIZE * NUMBER_OF_PROGRAMS + \ + RISC_SYNC_INSTRUCTION_SIZE) + +#endif diff --git a/drivers/media/pci/cx25821/cx25821-biffuncs.h b/drivers/media/pci/cx25821/cx25821-biffuncs.h new file mode 100644 index 000000000..6c1aa132e --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821-biffuncs.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + */ + +#ifndef _BITFUNCS_H +#define _BITFUNCS_H + +#define SetBit(Bit) (1 << Bit) + +static inline u8 getBit(u32 sample, u8 index) +{ + return (u8) ((sample >> index) & 1); +} + +static inline u32 clearBitAtPos(u32 value, u8 bit) +{ + return value & ~(1 << bit); +} + +static inline u32 setBitAtPos(u32 sample, u8 bit) +{ + sample |= (1 << bit); + return sample; + +} + +#endif diff --git a/drivers/media/pci/cx25821/cx25821-cards.c b/drivers/media/pci/cx25821/cx25821-cards.c new file mode 100644 index 000000000..5aa67fa51 --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821-cards.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + * Based on Steven Toth cx23885 driver + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include + +#include "cx25821.h" + +/* board config info */ + +struct cx25821_board cx25821_boards[] = { + [UNKNOWN_BOARD] = { + .name = "UNKNOWN/GENERIC", + /* Ensure safe default for unknown boards */ + .clk_freq = 0, + }, + + [CX25821_BOARD] = { + .name = "CX25821", + .portb = CX25821_RAW, + .portc = CX25821_264, + }, + +}; diff --git a/drivers/media/pci/cx25821/cx25821-core.c b/drivers/media/pci/cx25821/cx25821-core.c new file mode 100644 index 000000000..f883a779b --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821-core.c @@ -0,0 +1,1380 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + * Based on Steven Toth cx23885 driver + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include "cx25821.h" +#include "cx25821-sram.h" +#include "cx25821-video.h" + +MODULE_DESCRIPTION("Driver for Athena cards"); +MODULE_AUTHOR("Shu Lin - Hiep Huynh"); +MODULE_LICENSE("GPL"); + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "enable debug messages"); + +static unsigned int card[] = {[0 ... (CX25821_MAXBOARDS - 1)] = UNSET }; +module_param_array(card, int, NULL, 0444); +MODULE_PARM_DESC(card, "card type"); + +const struct sram_channel cx25821_sram_channels[] = { + [SRAM_CH00] = { + .i = SRAM_CH00, + .name = "VID A", + .cmds_start = VID_A_DOWN_CMDS, + .ctrl_start = VID_A_IQ, + .cdt = VID_A_CDT, + .fifo_start = VID_A_DOWN_CLUSTER_1, + .fifo_size = (VID_CLUSTER_SIZE << 2), + .ptr1_reg = DMA1_PTR1, + .ptr2_reg = DMA1_PTR2, + .cnt1_reg = DMA1_CNT1, + .cnt2_reg = DMA1_CNT2, + .int_msk = VID_A_INT_MSK, + .int_stat = VID_A_INT_STAT, + .int_mstat = VID_A_INT_MSTAT, + .dma_ctl = VID_DST_A_DMA_CTL, + .gpcnt_ctl = VID_DST_A_GPCNT_CTL, + .gpcnt = VID_DST_A_GPCNT, + .vip_ctl = VID_DST_A_VIP_CTL, + .pix_frmt = VID_DST_A_PIX_FRMT, + }, + + [SRAM_CH01] = { + .i = SRAM_CH01, + .name = "VID B", + .cmds_start = VID_B_DOWN_CMDS, + .ctrl_start = VID_B_IQ, + .cdt = VID_B_CDT, + .fifo_start = VID_B_DOWN_CLUSTER_1, + .fifo_size = (VID_CLUSTER_SIZE << 2), + .ptr1_reg = DMA2_PTR1, + .ptr2_reg = DMA2_PTR2, + .cnt1_reg = DMA2_CNT1, + .cnt2_reg = DMA2_CNT2, + .int_msk = VID_B_INT_MSK, + .int_stat = VID_B_INT_STAT, + .int_mstat = VID_B_INT_MSTAT, + .dma_ctl = VID_DST_B_DMA_CTL, + .gpcnt_ctl = VID_DST_B_GPCNT_CTL, + .gpcnt = VID_DST_B_GPCNT, + .vip_ctl = VID_DST_B_VIP_CTL, + .pix_frmt = VID_DST_B_PIX_FRMT, + }, + + [SRAM_CH02] = { + .i = SRAM_CH02, + .name = "VID C", + .cmds_start = VID_C_DOWN_CMDS, + .ctrl_start = VID_C_IQ, + .cdt = VID_C_CDT, + .fifo_start = VID_C_DOWN_CLUSTER_1, + .fifo_size = (VID_CLUSTER_SIZE << 2), + .ptr1_reg = DMA3_PTR1, + .ptr2_reg = DMA3_PTR2, + .cnt1_reg = DMA3_CNT1, + .cnt2_reg = DMA3_CNT2, + .int_msk = VID_C_INT_MSK, + .int_stat = VID_C_INT_STAT, + .int_mstat = VID_C_INT_MSTAT, + .dma_ctl = VID_DST_C_DMA_CTL, + .gpcnt_ctl = VID_DST_C_GPCNT_CTL, + .gpcnt = VID_DST_C_GPCNT, + .vip_ctl = VID_DST_C_VIP_CTL, + .pix_frmt = VID_DST_C_PIX_FRMT, + }, + + [SRAM_CH03] = { + .i = SRAM_CH03, + .name = "VID D", + .cmds_start = VID_D_DOWN_CMDS, + .ctrl_start = VID_D_IQ, + .cdt = VID_D_CDT, + .fifo_start = VID_D_DOWN_CLUSTER_1, + .fifo_size = (VID_CLUSTER_SIZE << 2), + .ptr1_reg = DMA4_PTR1, + .ptr2_reg = DMA4_PTR2, + .cnt1_reg = DMA4_CNT1, + .cnt2_reg = DMA4_CNT2, + .int_msk = VID_D_INT_MSK, + .int_stat = VID_D_INT_STAT, + .int_mstat = VID_D_INT_MSTAT, + .dma_ctl = VID_DST_D_DMA_CTL, + .gpcnt_ctl = VID_DST_D_GPCNT_CTL, + .gpcnt = VID_DST_D_GPCNT, + .vip_ctl = VID_DST_D_VIP_CTL, + .pix_frmt = VID_DST_D_PIX_FRMT, + }, + + [SRAM_CH04] = { + .i = SRAM_CH04, + .name = "VID E", + .cmds_start = VID_E_DOWN_CMDS, + .ctrl_start = VID_E_IQ, + .cdt = VID_E_CDT, + .fifo_start = VID_E_DOWN_CLUSTER_1, + .fifo_size = (VID_CLUSTER_SIZE << 2), + .ptr1_reg = DMA5_PTR1, + .ptr2_reg = DMA5_PTR2, + .cnt1_reg = DMA5_CNT1, + .cnt2_reg = DMA5_CNT2, + .int_msk = VID_E_INT_MSK, + .int_stat = VID_E_INT_STAT, + .int_mstat = VID_E_INT_MSTAT, + .dma_ctl = VID_DST_E_DMA_CTL, + .gpcnt_ctl = VID_DST_E_GPCNT_CTL, + .gpcnt = VID_DST_E_GPCNT, + .vip_ctl = VID_DST_E_VIP_CTL, + .pix_frmt = VID_DST_E_PIX_FRMT, + }, + + [SRAM_CH05] = { + .i = SRAM_CH05, + .name = "VID F", + .cmds_start = VID_F_DOWN_CMDS, + .ctrl_start = VID_F_IQ, + .cdt = VID_F_CDT, + .fifo_start = VID_F_DOWN_CLUSTER_1, + .fifo_size = (VID_CLUSTER_SIZE << 2), + .ptr1_reg = DMA6_PTR1, + .ptr2_reg = DMA6_PTR2, + .cnt1_reg = DMA6_CNT1, + .cnt2_reg = DMA6_CNT2, + .int_msk = VID_F_INT_MSK, + .int_stat = VID_F_INT_STAT, + .int_mstat = VID_F_INT_MSTAT, + .dma_ctl = VID_DST_F_DMA_CTL, + .gpcnt_ctl = VID_DST_F_GPCNT_CTL, + .gpcnt = VID_DST_F_GPCNT, + .vip_ctl = VID_DST_F_VIP_CTL, + .pix_frmt = VID_DST_F_PIX_FRMT, + }, + + [SRAM_CH06] = { + .i = SRAM_CH06, + .name = "VID G", + .cmds_start = VID_G_DOWN_CMDS, + .ctrl_start = VID_G_IQ, + .cdt = VID_G_CDT, + .fifo_start = VID_G_DOWN_CLUSTER_1, + .fifo_size = (VID_CLUSTER_SIZE << 2), + .ptr1_reg = DMA7_PTR1, + .ptr2_reg = DMA7_PTR2, + .cnt1_reg = DMA7_CNT1, + .cnt2_reg = DMA7_CNT2, + .int_msk = VID_G_INT_MSK, + .int_stat = VID_G_INT_STAT, + .int_mstat = VID_G_INT_MSTAT, + .dma_ctl = VID_DST_G_DMA_CTL, + .gpcnt_ctl = VID_DST_G_GPCNT_CTL, + .gpcnt = VID_DST_G_GPCNT, + .vip_ctl = VID_DST_G_VIP_CTL, + .pix_frmt = VID_DST_G_PIX_FRMT, + }, + + [SRAM_CH07] = { + .i = SRAM_CH07, + .name = "VID H", + .cmds_start = VID_H_DOWN_CMDS, + .ctrl_start = VID_H_IQ, + .cdt = VID_H_CDT, + .fifo_start = VID_H_DOWN_CLUSTER_1, + .fifo_size = (VID_CLUSTER_SIZE << 2), + .ptr1_reg = DMA8_PTR1, + .ptr2_reg = DMA8_PTR2, + .cnt1_reg = DMA8_CNT1, + .cnt2_reg = DMA8_CNT2, + .int_msk = VID_H_INT_MSK, + .int_stat = VID_H_INT_STAT, + .int_mstat = VID_H_INT_MSTAT, + .dma_ctl = VID_DST_H_DMA_CTL, + .gpcnt_ctl = VID_DST_H_GPCNT_CTL, + .gpcnt = VID_DST_H_GPCNT, + .vip_ctl = VID_DST_H_VIP_CTL, + .pix_frmt = VID_DST_H_PIX_FRMT, + }, + + [SRAM_CH08] = { + .name = "audio from", + .cmds_start = AUD_A_DOWN_CMDS, + .ctrl_start = AUD_A_IQ, + .cdt = AUD_A_CDT, + .fifo_start = AUD_A_DOWN_CLUSTER_1, + .fifo_size = AUDIO_CLUSTER_SIZE * 3, + .ptr1_reg = DMA17_PTR1, + .ptr2_reg = DMA17_PTR2, + .cnt1_reg = DMA17_CNT1, + .cnt2_reg = DMA17_CNT2, + }, + + [SRAM_CH09] = { + .i = SRAM_CH09, + .name = "VID Upstream I", + .cmds_start = VID_I_UP_CMDS, + .ctrl_start = VID_I_IQ, + .cdt = VID_I_CDT, + .fifo_start = VID_I_UP_CLUSTER_1, + .fifo_size = (VID_CLUSTER_SIZE << 2), + .ptr1_reg = DMA15_PTR1, + .ptr2_reg = DMA15_PTR2, + .cnt1_reg = DMA15_CNT1, + .cnt2_reg = DMA15_CNT2, + .int_msk = VID_I_INT_MSK, + .int_stat = VID_I_INT_STAT, + .int_mstat = VID_I_INT_MSTAT, + .dma_ctl = VID_SRC_I_DMA_CTL, + .gpcnt_ctl = VID_SRC_I_GPCNT_CTL, + .gpcnt = VID_SRC_I_GPCNT, + + .vid_fmt_ctl = VID_SRC_I_FMT_CTL, + .vid_active_ctl1 = VID_SRC_I_ACTIVE_CTL1, + .vid_active_ctl2 = VID_SRC_I_ACTIVE_CTL2, + .vid_cdt_size = VID_SRC_I_CDT_SZ, + .irq_bit = 8, + }, + + [SRAM_CH10] = { + .i = SRAM_CH10, + .name = "VID Upstream J", + .cmds_start = VID_J_UP_CMDS, + .ctrl_start = VID_J_IQ, + .cdt = VID_J_CDT, + .fifo_start = VID_J_UP_CLUSTER_1, + .fifo_size = (VID_CLUSTER_SIZE << 2), + .ptr1_reg = DMA16_PTR1, + .ptr2_reg = DMA16_PTR2, + .cnt1_reg = DMA16_CNT1, + .cnt2_reg = DMA16_CNT2, + .int_msk = VID_J_INT_MSK, + .int_stat = VID_J_INT_STAT, + .int_mstat = VID_J_INT_MSTAT, + .dma_ctl = VID_SRC_J_DMA_CTL, + .gpcnt_ctl = VID_SRC_J_GPCNT_CTL, + .gpcnt = VID_SRC_J_GPCNT, + + .vid_fmt_ctl = VID_SRC_J_FMT_CTL, + .vid_active_ctl1 = VID_SRC_J_ACTIVE_CTL1, + .vid_active_ctl2 = VID_SRC_J_ACTIVE_CTL2, + .vid_cdt_size = VID_SRC_J_CDT_SZ, + .irq_bit = 9, + }, + + [SRAM_CH11] = { + .i = SRAM_CH11, + .name = "Audio Upstream Channel B", + .cmds_start = AUD_B_UP_CMDS, + .ctrl_start = AUD_B_IQ, + .cdt = AUD_B_CDT, + .fifo_start = AUD_B_UP_CLUSTER_1, + .fifo_size = (AUDIO_CLUSTER_SIZE * 3), + .ptr1_reg = DMA22_PTR1, + .ptr2_reg = DMA22_PTR2, + .cnt1_reg = DMA22_CNT1, + .cnt2_reg = DMA22_CNT2, + .int_msk = AUD_B_INT_MSK, + .int_stat = AUD_B_INT_STAT, + .int_mstat = AUD_B_INT_MSTAT, + .dma_ctl = AUD_INT_DMA_CTL, + .gpcnt_ctl = AUD_B_GPCNT_CTL, + .gpcnt = AUD_B_GPCNT, + .aud_length = AUD_B_LNGTH, + .aud_cfg = AUD_B_CFG, + .fld_aud_fifo_en = FLD_AUD_SRC_B_FIFO_EN, + .fld_aud_risc_en = FLD_AUD_SRC_B_RISC_EN, + .irq_bit = 11, + }, +}; +EXPORT_SYMBOL(cx25821_sram_channels); + +static int cx25821_risc_decode(u32 risc) +{ + static const char * const instr[16] = { + [RISC_SYNC >> 28] = "sync", + [RISC_WRITE >> 28] = "write", + [RISC_WRITEC >> 28] = "writec", + [RISC_READ >> 28] = "read", + [RISC_READC >> 28] = "readc", + [RISC_JUMP >> 28] = "jump", + [RISC_SKIP >> 28] = "skip", + [RISC_WRITERM >> 28] = "writerm", + [RISC_WRITECM >> 28] = "writecm", + [RISC_WRITECR >> 28] = "writecr", + }; + static const int incr[16] = { + [RISC_WRITE >> 28] = 3, + [RISC_JUMP >> 28] = 3, + [RISC_SKIP >> 28] = 1, + [RISC_SYNC >> 28] = 1, + [RISC_WRITERM >> 28] = 3, + [RISC_WRITECM >> 28] = 3, + [RISC_WRITECR >> 28] = 4, + }; + static const char * const bits[] = { + "12", "13", "14", "resync", + "cnt0", "cnt1", "18", "19", + "20", "21", "22", "23", + "irq1", "irq2", "eol", "sol", + }; + int i; + + pr_cont("0x%08x [ %s", + risc, instr[risc >> 28] ? instr[risc >> 28] : "INVALID"); + for (i = ARRAY_SIZE(bits) - 1; i >= 0; i--) { + if (risc & (1 << (i + 12))) + pr_cont(" %s", bits[i]); + } + pr_cont(" count=%d ]\n", risc & 0xfff); + return incr[risc >> 28] ? incr[risc >> 28] : 1; +} + +static void cx25821_registers_init(struct cx25821_dev *dev) +{ + u32 tmp; + + /* enable RUN_RISC in Pecos */ + cx_write(DEV_CNTRL2, 0x20); + + /* Set the master PCI interrupt masks to enable video, audio, MBIF, + * and GPIO interrupts + * I2C interrupt masking is handled by the I2C objects themselves. */ + cx_write(PCI_INT_MSK, 0x2001FFFF); + + tmp = cx_read(RDR_TLCTL0); + tmp &= ~FLD_CFG_RCB_CK_EN; /* Clear the RCB_CK_EN bit */ + cx_write(RDR_TLCTL0, tmp); + + /* PLL-A setting for the Audio Master Clock */ + cx_write(PLL_A_INT_FRAC, 0x9807A58B); + + /* PLL_A_POST = 0x1C, PLL_A_OUT_TO_PIN = 0x1 */ + cx_write(PLL_A_POST_STAT_BIST, 0x8000019C); + + /* clear reset bit [31] */ + tmp = cx_read(PLL_A_INT_FRAC); + cx_write(PLL_A_INT_FRAC, tmp & 0x7FFFFFFF); + + /* PLL-B setting for Mobilygen Host Bus Interface */ + cx_write(PLL_B_INT_FRAC, 0x9883A86F); + + /* PLL_B_POST = 0xD, PLL_B_OUT_TO_PIN = 0x0 */ + cx_write(PLL_B_POST_STAT_BIST, 0x8000018D); + + /* clear reset bit [31] */ + tmp = cx_read(PLL_B_INT_FRAC); + cx_write(PLL_B_INT_FRAC, tmp & 0x7FFFFFFF); + + /* PLL-C setting for video upstream channel */ + cx_write(PLL_C_INT_FRAC, 0x96A0EA3F); + + /* PLL_C_POST = 0x3, PLL_C_OUT_TO_PIN = 0x0 */ + cx_write(PLL_C_POST_STAT_BIST, 0x80000103); + + /* clear reset bit [31] */ + tmp = cx_read(PLL_C_INT_FRAC); + cx_write(PLL_C_INT_FRAC, tmp & 0x7FFFFFFF); + + /* PLL-D setting for audio upstream channel */ + cx_write(PLL_D_INT_FRAC, 0x98757F5B); + + /* PLL_D_POST = 0x13, PLL_D_OUT_TO_PIN = 0x0 */ + cx_write(PLL_D_POST_STAT_BIST, 0x80000113); + + /* clear reset bit [31] */ + tmp = cx_read(PLL_D_INT_FRAC); + cx_write(PLL_D_INT_FRAC, tmp & 0x7FFFFFFF); + + /* This selects the PLL C clock source for the video upstream channel + * I and J */ + tmp = cx_read(VID_CH_CLK_SEL); + cx_write(VID_CH_CLK_SEL, (tmp & 0x00FFFFFF) | 0x24000000); + + /* 656/VIP SRC Upstream Channel I & J and 7 - Host Bus Interface for + * channel A-C + * select 656/VIP DST for downstream Channel A - C */ + tmp = cx_read(VID_CH_MODE_SEL); + /* cx_write( VID_CH_MODE_SEL, tmp | 0x1B0001FF); */ + cx_write(VID_CH_MODE_SEL, tmp & 0xFFFFFE00); + + /* enables 656 port I and J as output */ + tmp = cx_read(CLK_RST); + /* use external ALT_PLL_REF pin as its reference clock instead */ + tmp |= FLD_USE_ALT_PLL_REF; + cx_write(CLK_RST, tmp & ~(FLD_VID_I_CLK_NOE | FLD_VID_J_CLK_NOE)); + + msleep(100); +} + +int cx25821_sram_channel_setup(struct cx25821_dev *dev, + const struct sram_channel *ch, + unsigned int bpl, u32 risc) +{ + unsigned int i, lines; + u32 cdt; + + if (ch->cmds_start == 0) { + cx_write(ch->ptr1_reg, 0); + cx_write(ch->ptr2_reg, 0); + cx_write(ch->cnt2_reg, 0); + cx_write(ch->cnt1_reg, 0); + return 0; + } + + bpl = (bpl + 7) & ~7; /* alignment */ + cdt = ch->cdt; + lines = ch->fifo_size / bpl; + + if (lines > 4) + lines = 4; + + BUG_ON(lines < 2); + + cx_write(8 + 0, RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); + cx_write(8 + 4, 8); + cx_write(8 + 8, 0); + + /* write CDT */ + for (i = 0; i < lines; i++) { + cx_write(cdt + 16 * i, ch->fifo_start + bpl * i); + cx_write(cdt + 16 * i + 4, 0); + cx_write(cdt + 16 * i + 8, 0); + cx_write(cdt + 16 * i + 12, 0); + } + + /* init the first cdt buffer */ + for (i = 0; i < 128; i++) + cx_write(ch->fifo_start + 4 * i, i); + + /* write CMDS */ + if (ch->jumponly) + cx_write(ch->cmds_start + 0, 8); + else + cx_write(ch->cmds_start + 0, risc); + + cx_write(ch->cmds_start + 4, 0); /* 64 bits 63-32 */ + cx_write(ch->cmds_start + 8, cdt); + cx_write(ch->cmds_start + 12, (lines * 16) >> 3); + cx_write(ch->cmds_start + 16, ch->ctrl_start); + + if (ch->jumponly) + cx_write(ch->cmds_start + 20, 0x80000000 | (64 >> 2)); + else + cx_write(ch->cmds_start + 20, 64 >> 2); + + for (i = 24; i < 80; i += 4) + cx_write(ch->cmds_start + i, 0); + + /* fill registers */ + cx_write(ch->ptr1_reg, ch->fifo_start); + cx_write(ch->ptr2_reg, cdt); + cx_write(ch->cnt2_reg, (lines * 16) >> 3); + cx_write(ch->cnt1_reg, (bpl >> 3) - 1); + + return 0; +} + +int cx25821_sram_channel_setup_audio(struct cx25821_dev *dev, + const struct sram_channel *ch, + unsigned int bpl, u32 risc) +{ + unsigned int i, lines; + u32 cdt; + + if (ch->cmds_start == 0) { + cx_write(ch->ptr1_reg, 0); + cx_write(ch->ptr2_reg, 0); + cx_write(ch->cnt2_reg, 0); + cx_write(ch->cnt1_reg, 0); + return 0; + } + + bpl = (bpl + 7) & ~7; /* alignment */ + cdt = ch->cdt; + lines = ch->fifo_size / bpl; + + if (lines > 3) + lines = 3; /* for AUDIO */ + + BUG_ON(lines < 2); + + cx_write(8 + 0, RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); + cx_write(8 + 4, 8); + cx_write(8 + 8, 0); + + /* write CDT */ + for (i = 0; i < lines; i++) { + cx_write(cdt + 16 * i, ch->fifo_start + bpl * i); + cx_write(cdt + 16 * i + 4, 0); + cx_write(cdt + 16 * i + 8, 0); + cx_write(cdt + 16 * i + 12, 0); + } + + /* write CMDS */ + if (ch->jumponly) + cx_write(ch->cmds_start + 0, 8); + else + cx_write(ch->cmds_start + 0, risc); + + cx_write(ch->cmds_start + 4, 0); /* 64 bits 63-32 */ + cx_write(ch->cmds_start + 8, cdt); + cx_write(ch->cmds_start + 12, (lines * 16) >> 3); + cx_write(ch->cmds_start + 16, ch->ctrl_start); + + /* IQ size */ + if (ch->jumponly) + cx_write(ch->cmds_start + 20, 0x80000000 | (64 >> 2)); + else + cx_write(ch->cmds_start + 20, 64 >> 2); + + /* zero out */ + for (i = 24; i < 80; i += 4) + cx_write(ch->cmds_start + i, 0); + + /* fill registers */ + cx_write(ch->ptr1_reg, ch->fifo_start); + cx_write(ch->ptr2_reg, cdt); + cx_write(ch->cnt2_reg, (lines * 16) >> 3); + cx_write(ch->cnt1_reg, (bpl >> 3) - 1); + + return 0; +} +EXPORT_SYMBOL(cx25821_sram_channel_setup_audio); + +void cx25821_sram_channel_dump(struct cx25821_dev *dev, const struct sram_channel *ch) +{ + static char *name[] = { + "init risc lo", + "init risc hi", + "cdt base", + "cdt size", + "iq base", + "iq size", + "risc pc lo", + "risc pc hi", + "iq wr ptr", + "iq rd ptr", + "cdt current", + "pci target lo", + "pci target hi", + "line / byte", + }; + u32 risc; + unsigned int i, j, n; + + pr_warn("%s: %s - dma channel status dump\n", dev->name, ch->name); + for (i = 0; i < ARRAY_SIZE(name); i++) + pr_warn("cmds + 0x%2x: %-15s: 0x%08x\n", + i * 4, name[i], cx_read(ch->cmds_start + 4 * i)); + + j = i * 4; + for (i = 0; i < 4;) { + risc = cx_read(ch->cmds_start + 4 * (i + 14)); + pr_warn("cmds + 0x%2x: risc%d: ", j + i * 4, i); + i += cx25821_risc_decode(risc); + } + + for (i = 0; i < (64 >> 2); i += n) { + risc = cx_read(ch->ctrl_start + 4 * i); + /* No consideration for bits 63-32 */ + + pr_warn("ctrl + 0x%2x (0x%08x): iq %x: ", + i * 4, ch->ctrl_start + 4 * i, i); + n = cx25821_risc_decode(risc); + for (j = 1; j < n; j++) { + risc = cx_read(ch->ctrl_start + 4 * (i + j)); + pr_warn("ctrl + 0x%2x : iq %x: 0x%08x [ arg #%d ]\n", + 4 * (i + j), i + j, risc, j); + } + } + + pr_warn(" : fifo: 0x%08x -> 0x%x\n", + ch->fifo_start, ch->fifo_start + ch->fifo_size); + pr_warn(" : ctrl: 0x%08x -> 0x%x\n", + ch->ctrl_start, ch->ctrl_start + 6 * 16); + pr_warn(" : ptr1_reg: 0x%08x\n", + cx_read(ch->ptr1_reg)); + pr_warn(" : ptr2_reg: 0x%08x\n", + cx_read(ch->ptr2_reg)); + pr_warn(" : cnt1_reg: 0x%08x\n", + cx_read(ch->cnt1_reg)); + pr_warn(" : cnt2_reg: 0x%08x\n", + cx_read(ch->cnt2_reg)); +} + +void cx25821_sram_channel_dump_audio(struct cx25821_dev *dev, + const struct sram_channel *ch) +{ + static const char * const name[] = { + "init risc lo", + "init risc hi", + "cdt base", + "cdt size", + "iq base", + "iq size", + "risc pc lo", + "risc pc hi", + "iq wr ptr", + "iq rd ptr", + "cdt current", + "pci target lo", + "pci target hi", + "line / byte", + }; + + u32 risc, value, tmp; + unsigned int i, j, n; + + pr_info("\n%s: %s - dma Audio channel status dump\n", + dev->name, ch->name); + + for (i = 0; i < ARRAY_SIZE(name); i++) + pr_info("%s: cmds + 0x%2x: %-15s: 0x%08x\n", + dev->name, i * 4, name[i], + cx_read(ch->cmds_start + 4 * i)); + + j = i * 4; + for (i = 0; i < 4;) { + risc = cx_read(ch->cmds_start + 4 * (i + 14)); + pr_warn("cmds + 0x%2x: risc%d: ", j + i * 4, i); + i += cx25821_risc_decode(risc); + } + + for (i = 0; i < (64 >> 2); i += n) { + risc = cx_read(ch->ctrl_start + 4 * i); + /* No consideration for bits 63-32 */ + + pr_warn("ctrl + 0x%2x (0x%08x): iq %x: ", + i * 4, ch->ctrl_start + 4 * i, i); + n = cx25821_risc_decode(risc); + + for (j = 1; j < n; j++) { + risc = cx_read(ch->ctrl_start + 4 * (i + j)); + pr_warn("ctrl + 0x%2x : iq %x: 0x%08x [ arg #%d ]\n", + 4 * (i + j), i + j, risc, j); + } + } + + pr_warn(" : fifo: 0x%08x -> 0x%x\n", + ch->fifo_start, ch->fifo_start + ch->fifo_size); + pr_warn(" : ctrl: 0x%08x -> 0x%x\n", + ch->ctrl_start, ch->ctrl_start + 6 * 16); + pr_warn(" : ptr1_reg: 0x%08x\n", + cx_read(ch->ptr1_reg)); + pr_warn(" : ptr2_reg: 0x%08x\n", + cx_read(ch->ptr2_reg)); + pr_warn(" : cnt1_reg: 0x%08x\n", + cx_read(ch->cnt1_reg)); + pr_warn(" : cnt2_reg: 0x%08x\n", + cx_read(ch->cnt2_reg)); + + for (i = 0; i < 4; i++) { + risc = cx_read(ch->cmds_start + 56 + (i * 4)); + pr_warn("instruction %d = 0x%x\n", i, risc); + } + + /* read data from the first cdt buffer */ + risc = cx_read(AUD_A_CDT); + pr_warn("\nread cdt loc=0x%x\n", risc); + for (i = 0; i < 8; i++) { + n = cx_read(risc + i * 4); + pr_cont("0x%x ", n); + } + pr_cont("\n\n"); + + value = cx_read(CLK_RST); + CX25821_INFO(" CLK_RST = 0x%x\n\n", value); + + value = cx_read(PLL_A_POST_STAT_BIST); + CX25821_INFO(" PLL_A_POST_STAT_BIST = 0x%x\n\n", value); + value = cx_read(PLL_A_INT_FRAC); + CX25821_INFO(" PLL_A_INT_FRAC = 0x%x\n\n", value); + + value = cx_read(PLL_B_POST_STAT_BIST); + CX25821_INFO(" PLL_B_POST_STAT_BIST = 0x%x\n\n", value); + value = cx_read(PLL_B_INT_FRAC); + CX25821_INFO(" PLL_B_INT_FRAC = 0x%x\n\n", value); + + value = cx_read(PLL_C_POST_STAT_BIST); + CX25821_INFO(" PLL_C_POST_STAT_BIST = 0x%x\n\n", value); + value = cx_read(PLL_C_INT_FRAC); + CX25821_INFO(" PLL_C_INT_FRAC = 0x%x\n\n", value); + + value = cx_read(PLL_D_POST_STAT_BIST); + CX25821_INFO(" PLL_D_POST_STAT_BIST = 0x%x\n\n", value); + value = cx_read(PLL_D_INT_FRAC); + CX25821_INFO(" PLL_D_INT_FRAC = 0x%x\n\n", value); + + value = cx25821_i2c_read(&dev->i2c_bus[0], AFE_AB_DIAG_CTRL, &tmp); + CX25821_INFO(" AFE_AB_DIAG_CTRL (0x10900090) = 0x%x\n\n", value); +} +EXPORT_SYMBOL(cx25821_sram_channel_dump_audio); + +static void cx25821_shutdown(struct cx25821_dev *dev) +{ + int i; + + /* disable RISC controller */ + cx_write(DEV_CNTRL2, 0); + + /* Disable Video A/B activity */ + for (i = 0; i < VID_CHANNEL_NUM; i++) { + cx_write(dev->channels[i].sram_channels->dma_ctl, 0); + cx_write(dev->channels[i].sram_channels->int_msk, 0); + } + + for (i = VID_UPSTREAM_SRAM_CHANNEL_I; + i <= VID_UPSTREAM_SRAM_CHANNEL_J; i++) { + cx_write(dev->channels[i].sram_channels->dma_ctl, 0); + cx_write(dev->channels[i].sram_channels->int_msk, 0); + } + + /* Disable Audio activity */ + cx_write(AUD_INT_DMA_CTL, 0); + + /* Disable Serial port */ + cx_write(UART_CTL, 0); + + /* Disable Interrupts */ + cx_write(PCI_INT_MSK, 0); + cx_write(AUD_A_INT_MSK, 0); +} + +void cx25821_set_pixel_format(struct cx25821_dev *dev, int channel_select, + u32 format) +{ + if (channel_select <= 7 && channel_select >= 0) { + cx_write(dev->channels[channel_select].sram_channels->pix_frmt, + format); + } + dev->channels[channel_select].pixel_formats = format; +} + +static void cx25821_set_vip_mode(struct cx25821_dev *dev, + const struct sram_channel *ch) +{ + cx_write(ch->pix_frmt, PIXEL_FRMT_422); + cx_write(ch->vip_ctl, PIXEL_ENGINE_VIP1); +} + +static void cx25821_initialize(struct cx25821_dev *dev) +{ + int i; + + dprintk(1, "%s()\n", __func__); + + cx25821_shutdown(dev); + cx_write(PCI_INT_STAT, 0xffffffff); + + for (i = 0; i < VID_CHANNEL_NUM; i++) + cx_write(dev->channels[i].sram_channels->int_stat, 0xffffffff); + + cx_write(AUD_A_INT_STAT, 0xffffffff); + cx_write(AUD_B_INT_STAT, 0xffffffff); + cx_write(AUD_C_INT_STAT, 0xffffffff); + cx_write(AUD_D_INT_STAT, 0xffffffff); + cx_write(AUD_E_INT_STAT, 0xffffffff); + + cx_write(CLK_DELAY, cx_read(CLK_DELAY) & 0x80000000); + cx_write(PAD_CTRL, 0x12); /* for I2C */ + cx25821_registers_init(dev); /* init Pecos registers */ + msleep(100); + + for (i = 0; i < VID_CHANNEL_NUM; i++) { + cx25821_set_vip_mode(dev, dev->channels[i].sram_channels); + cx25821_sram_channel_setup(dev, dev->channels[i].sram_channels, + 1440, 0); + dev->channels[i].pixel_formats = PIXEL_FRMT_422; + dev->channels[i].use_cif_resolution = 0; + } + + /* Probably only affect Downstream */ + for (i = VID_UPSTREAM_SRAM_CHANNEL_I; + i <= VID_UPSTREAM_SRAM_CHANNEL_J; i++) { + dev->channels[i].pixel_formats = PIXEL_FRMT_422; + cx25821_set_vip_mode(dev, dev->channels[i].sram_channels); + } + + cx25821_sram_channel_setup_audio(dev, + dev->channels[SRAM_CH08].sram_channels, 128, 0); + + cx25821_gpio_init(dev); +} + +static int cx25821_get_resources(struct cx25821_dev *dev) +{ + if (request_mem_region(pci_resource_start(dev->pci, 0), + pci_resource_len(dev->pci, 0), dev->name)) + return 0; + + pr_err("%s: can't get MMIO memory @ 0x%llx\n", + dev->name, (unsigned long long)pci_resource_start(dev->pci, 0)); + + return -EBUSY; +} + +static void cx25821_dev_checkrevision(struct cx25821_dev *dev) +{ + dev->hwrevision = cx_read(RDR_CFG2) & 0xff; + + pr_info("Hardware revision = 0x%02x\n", dev->hwrevision); +} + +static void cx25821_iounmap(struct cx25821_dev *dev) +{ + if (dev == NULL) + return; + + /* Releasing IO memory */ + if (dev->lmmio != NULL) { + iounmap(dev->lmmio); + dev->lmmio = NULL; + } +} + +static int cx25821_dev_setup(struct cx25821_dev *dev) +{ + static unsigned int cx25821_devcount; + int i; + + mutex_init(&dev->lock); + + dev->nr = ++cx25821_devcount; + sprintf(dev->name, "cx25821[%d]", dev->nr); + + if (dev->nr >= ARRAY_SIZE(card)) { + CX25821_INFO("dev->nr >= %zd", ARRAY_SIZE(card)); + return -ENODEV; + } + if (dev->pci->device != 0x8210) { + pr_info("%s(): Exiting. Incorrect Hardware device = 0x%02x\n", + __func__, dev->pci->device); + return -ENODEV; + } + pr_info("Athena Hardware device = 0x%02x\n", dev->pci->device); + + /* Apply a sensible clock frequency for the PCIe bridge */ + dev->clk_freq = 28000000; + for (i = 0; i < MAX_VID_CHANNEL_NUM; i++) { + dev->channels[i].dev = dev; + dev->channels[i].id = i; + dev->channels[i].sram_channels = &cx25821_sram_channels[i]; + } + + /* board config */ + dev->board = 1; /* card[dev->nr]; */ + dev->_max_num_decoders = MAX_DECODERS; + + dev->pci_bus = dev->pci->bus->number; + dev->pci_slot = PCI_SLOT(dev->pci->devfn); + dev->pci_irqmask = 0x001f00; + + /* External Master 1 Bus */ + dev->i2c_bus[0].nr = 0; + dev->i2c_bus[0].dev = dev; + dev->i2c_bus[0].reg_stat = I2C1_STAT; + dev->i2c_bus[0].reg_ctrl = I2C1_CTRL; + dev->i2c_bus[0].reg_addr = I2C1_ADDR; + dev->i2c_bus[0].reg_rdata = I2C1_RDATA; + dev->i2c_bus[0].reg_wdata = I2C1_WDATA; + dev->i2c_bus[0].i2c_period = (0x07 << 24); /* 1.95MHz */ + + if (cx25821_get_resources(dev) < 0) { + pr_err("%s: No more PCIe resources for subsystem: %04x:%04x\n", + dev->name, dev->pci->subsystem_vendor, + dev->pci->subsystem_device); + + cx25821_devcount--; + return -EBUSY; + } + + /* PCIe stuff */ + dev->base_io_addr = pci_resource_start(dev->pci, 0); + + if (!dev->base_io_addr) { + CX25821_ERR("No PCI Memory resources, exiting!\n"); + return -ENODEV; + } + + dev->lmmio = ioremap(dev->base_io_addr, pci_resource_len(dev->pci, 0)); + + if (!dev->lmmio) { + CX25821_ERR("ioremap failed, maybe increasing __VMALLOC_RESERVE in page.h\n"); + release_mem_region(dev->base_io_addr, pci_resource_len(dev->pci, 0)); + cx25821_iounmap(dev); + return -ENOMEM; + } + + dev->bmmio = (u8 __iomem *) dev->lmmio; + + pr_info("%s: subsystem: %04x:%04x, board: %s [card=%d,%s]\n", + dev->name, dev->pci->subsystem_vendor, + dev->pci->subsystem_device, cx25821_boards[dev->board].name, + dev->board, card[dev->nr] == dev->board ? + "insmod option" : "autodetected"); + + /* init hardware */ + cx25821_initialize(dev); + + cx25821_i2c_register(&dev->i2c_bus[0]); +/* cx25821_i2c_register(&dev->i2c_bus[1]); + * cx25821_i2c_register(&dev->i2c_bus[2]); */ + + if (medusa_video_init(dev) < 0) + CX25821_ERR("%s(): Failed to initialize medusa!\n", __func__); + + cx25821_video_register(dev); + + cx25821_dev_checkrevision(dev); + return 0; +} + +void cx25821_dev_unregister(struct cx25821_dev *dev) +{ + int i; + + if (!dev->base_io_addr) + return; + + release_mem_region(dev->base_io_addr, pci_resource_len(dev->pci, 0)); + + for (i = 0; i < MAX_VID_CAP_CHANNEL_NUM - 1; i++) { + if (i == SRAM_CH08) /* audio channel */ + continue; + /* + * TODO: enable when video output is properly + * supported. + if (i == SRAM_CH09 || i == SRAM_CH10) + cx25821_free_mem_upstream(&dev->channels[i]); + */ + cx25821_video_unregister(dev, i); + } + + cx25821_i2c_unregister(&dev->i2c_bus[0]); + cx25821_iounmap(dev); +} +EXPORT_SYMBOL(cx25821_dev_unregister); + +int cx25821_riscmem_alloc(struct pci_dev *pci, + struct cx25821_riscmem *risc, + unsigned int size) +{ + __le32 *cpu; + dma_addr_t dma = 0; + + if (risc->cpu && risc->size < size) { + dma_free_coherent(&pci->dev, risc->size, risc->cpu, risc->dma); + risc->cpu = NULL; + } + if (NULL == risc->cpu) { + cpu = dma_alloc_coherent(&pci->dev, size, &dma, GFP_KERNEL); + if (NULL == cpu) + return -ENOMEM; + risc->cpu = cpu; + risc->dma = dma; + risc->size = size; + } + return 0; +} +EXPORT_SYMBOL(cx25821_riscmem_alloc); + +static __le32 *cx25821_risc_field(__le32 * rp, struct scatterlist *sglist, + unsigned int offset, u32 sync_line, + unsigned int bpl, unsigned int padding, + unsigned int lines, bool jump) +{ + struct scatterlist *sg; + unsigned int line, todo; + + if (jump) { + *(rp++) = cpu_to_le32(RISC_JUMP); + *(rp++) = cpu_to_le32(0); + *(rp++) = cpu_to_le32(0); /* bits 63-32 */ + } + + /* sync instruction */ + if (sync_line != NO_SYNC_LINE) + *(rp++) = cpu_to_le32(RISC_RESYNC | sync_line); + + /* scan lines */ + sg = sglist; + for (line = 0; line < lines; line++) { + while (offset && offset >= sg_dma_len(sg)) { + offset -= sg_dma_len(sg); + sg = sg_next(sg); + } + if (bpl <= sg_dma_len(sg) - offset) { + /* fits into current chunk */ + *(rp++) = cpu_to_le32(RISC_WRITE | RISC_SOL | RISC_EOL | + bpl); + *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset); + *(rp++) = cpu_to_le32(0); /* bits 63-32 */ + offset += bpl; + } else { + /* scanline needs to be split */ + todo = bpl; + *(rp++) = cpu_to_le32(RISC_WRITE | RISC_SOL | + (sg_dma_len(sg) - offset)); + *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset); + *(rp++) = cpu_to_le32(0); /* bits 63-32 */ + todo -= (sg_dma_len(sg) - offset); + offset = 0; + sg = sg_next(sg); + while (todo > sg_dma_len(sg)) { + *(rp++) = cpu_to_le32(RISC_WRITE | + sg_dma_len(sg)); + *(rp++) = cpu_to_le32(sg_dma_address(sg)); + *(rp++) = cpu_to_le32(0); /* bits 63-32 */ + todo -= sg_dma_len(sg); + sg = sg_next(sg); + } + *(rp++) = cpu_to_le32(RISC_WRITE | RISC_EOL | todo); + *(rp++) = cpu_to_le32(sg_dma_address(sg)); + *(rp++) = cpu_to_le32(0); /* bits 63-32 */ + offset += todo; + } + + offset += padding; + } + + return rp; +} + +int cx25821_risc_buffer(struct pci_dev *pci, struct cx25821_riscmem *risc, + struct scatterlist *sglist, unsigned int top_offset, + unsigned int bottom_offset, unsigned int bpl, + unsigned int padding, unsigned int lines) +{ + u32 instructions; + u32 fields; + __le32 *rp; + int rc; + + fields = 0; + if (UNSET != top_offset) + fields++; + if (UNSET != bottom_offset) + fields++; + + /* estimate risc mem: worst case is one write per page border + + one write per scan line + syncs + jump (all 3 dwords). Padding + can cause next bpl to start close to a page border. First DMA + region may be smaller than PAGE_SIZE */ + /* write and jump need and extra dword */ + instructions = fields * (1 + ((bpl + padding) * lines) / PAGE_SIZE + + lines); + instructions += 5; + rc = cx25821_riscmem_alloc(pci, risc, instructions * 12); + + if (rc < 0) + return rc; + + /* write risc instructions */ + rp = risc->cpu; + + if (UNSET != top_offset) { + rp = cx25821_risc_field(rp, sglist, top_offset, 0, bpl, padding, + lines, true); + } + + if (UNSET != bottom_offset) { + rp = cx25821_risc_field(rp, sglist, bottom_offset, 0x200, bpl, + padding, lines, UNSET == top_offset); + } + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + BUG_ON((risc->jmp - risc->cpu + 3) * sizeof(*risc->cpu) > risc->size); + + return 0; +} + +static __le32 *cx25821_risc_field_audio(__le32 * rp, struct scatterlist *sglist, + unsigned int offset, u32 sync_line, + unsigned int bpl, unsigned int padding, + unsigned int lines, unsigned int lpi) +{ + struct scatterlist *sg; + unsigned int line, todo, sol; + + /* sync instruction */ + if (sync_line != NO_SYNC_LINE) + *(rp++) = cpu_to_le32(RISC_RESYNC | sync_line); + + /* scan lines */ + sg = sglist; + for (line = 0; line < lines; line++) { + while (offset && offset >= sg_dma_len(sg)) { + offset -= sg_dma_len(sg); + sg = sg_next(sg); + } + + if (lpi && line > 0 && !(line % lpi)) + sol = RISC_SOL | RISC_IRQ1 | RISC_CNT_INC; + else + sol = RISC_SOL; + + if (bpl <= sg_dma_len(sg) - offset) { + /* fits into current chunk */ + *(rp++) = cpu_to_le32(RISC_WRITE | sol | RISC_EOL | + bpl); + *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset); + *(rp++) = cpu_to_le32(0); /* bits 63-32 */ + offset += bpl; + } else { + /* scanline needs to be split */ + todo = bpl; + *(rp++) = cpu_to_le32(RISC_WRITE | sol | + (sg_dma_len(sg) - offset)); + *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset); + *(rp++) = cpu_to_le32(0); /* bits 63-32 */ + todo -= (sg_dma_len(sg) - offset); + offset = 0; + sg = sg_next(sg); + while (todo > sg_dma_len(sg)) { + *(rp++) = cpu_to_le32(RISC_WRITE | + sg_dma_len(sg)); + *(rp++) = cpu_to_le32(sg_dma_address(sg)); + *(rp++) = cpu_to_le32(0); /* bits 63-32 */ + todo -= sg_dma_len(sg); + sg = sg_next(sg); + } + *(rp++) = cpu_to_le32(RISC_WRITE | RISC_EOL | todo); + *(rp++) = cpu_to_le32(sg_dma_address(sg)); + *(rp++) = cpu_to_le32(0); /* bits 63-32 */ + offset += todo; + } + offset += padding; + } + + return rp; +} + +int cx25821_risc_databuffer_audio(struct pci_dev *pci, + struct cx25821_riscmem *risc, + struct scatterlist *sglist, + unsigned int bpl, + unsigned int lines, unsigned int lpi) +{ + u32 instructions; + __le32 *rp; + int rc; + + /* estimate risc mem: worst case is one write per page border + + one write per scan line + syncs + jump (all 2 dwords). Here + there is no padding and no sync. First DMA region may be smaller + than PAGE_SIZE */ + /* Jump and write need an extra dword */ + instructions = 1 + (bpl * lines) / PAGE_SIZE + lines; + instructions += 1; + + rc = cx25821_riscmem_alloc(pci, risc, instructions * 12); + if (rc < 0) + return rc; + + /* write risc instructions */ + rp = risc->cpu; + rp = cx25821_risc_field_audio(rp, sglist, 0, NO_SYNC_LINE, bpl, 0, + lines, lpi); + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + BUG_ON((risc->jmp - risc->cpu + 2) * sizeof(*risc->cpu) > risc->size); + return 0; +} +EXPORT_SYMBOL(cx25821_risc_databuffer_audio); + +void cx25821_free_buffer(struct cx25821_dev *dev, struct cx25821_buffer *buf) +{ + if (WARN_ON(buf->risc.size == 0)) + return; + dma_free_coherent(&dev->pci->dev, buf->risc.size, buf->risc.cpu, + buf->risc.dma); + memset(&buf->risc, 0, sizeof(buf->risc)); +} + +static irqreturn_t cx25821_irq(int irq, void *dev_id) +{ + struct cx25821_dev *dev = dev_id; + u32 pci_status; + u32 vid_status; + int i, handled = 0; + u32 mask[8] = { 1, 2, 4, 8, 16, 32, 64, 128 }; + + pci_status = cx_read(PCI_INT_STAT); + + if (pci_status == 0) + goto out; + + for (i = 0; i < VID_CHANNEL_NUM; i++) { + if (pci_status & mask[i]) { + vid_status = cx_read(dev->channels[i]. + sram_channels->int_stat); + + if (vid_status) + handled += cx25821_video_irq(dev, i, + vid_status); + + cx_write(PCI_INT_STAT, mask[i]); + } + } + +out: + return IRQ_RETVAL(handled); +} + +void cx25821_print_irqbits(char *name, char *tag, char **strings, + int len, u32 bits, u32 mask) +{ + unsigned int i; + + printk(KERN_DEBUG pr_fmt("%s: %s [0x%x]"), name, tag, bits); + + for (i = 0; i < len; i++) { + if (!(bits & (1 << i))) + continue; + if (strings[i]) + pr_cont(" %s", strings[i]); + else + pr_cont(" %d", i); + if (!(mask & (1 << i))) + continue; + pr_cont("*"); + } + pr_cont("\n"); +} +EXPORT_SYMBOL(cx25821_print_irqbits); + +struct cx25821_dev *cx25821_dev_get(struct pci_dev *pci) +{ + struct cx25821_dev *dev = pci_get_drvdata(pci); + return dev; +} +EXPORT_SYMBOL(cx25821_dev_get); + +static int cx25821_initdev(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct cx25821_dev *dev; + int err = 0; + + dev = kzalloc_obj(*dev); + if (NULL == dev) + return -ENOMEM; + + err = v4l2_device_register(&pci_dev->dev, &dev->v4l2_dev); + if (err < 0) + goto fail_free; + + /* pci init */ + dev->pci = pci_dev; + if (pci_enable_device(pci_dev)) { + err = -EIO; + + pr_info("pci enable failed!\n"); + + goto fail_unregister_device; + } + + err = cx25821_dev_setup(dev); + if (err) + goto fail_unregister_pci; + + /* print pci info */ + pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &dev->pci_rev); + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat); + pr_info("%s/0: found at %s, rev: %d, irq: %d, latency: %d, mmio: 0x%llx\n", + dev->name, pci_name(pci_dev), dev->pci_rev, pci_dev->irq, + dev->pci_lat, (unsigned long long)dev->base_io_addr); + + pci_set_master(pci_dev); + err = dma_set_mask(&pci_dev->dev, 0xffffffff); + if (err) { + pr_err("%s/0: Oops: no 32bit PCI DMA ???\n", dev->name); + err = -EIO; + goto fail_irq; + } + + err = request_irq(pci_dev->irq, cx25821_irq, + IRQF_SHARED, dev->name, dev); + + if (err < 0) { + pr_err("%s: can't get IRQ %d\n", dev->name, pci_dev->irq); + goto fail_irq; + } + + return 0; + +fail_irq: + pr_info("cx25821_initdev() can't get IRQ !\n"); + cx25821_dev_unregister(dev); + +fail_unregister_pci: + pci_disable_device(pci_dev); +fail_unregister_device: + v4l2_device_unregister(&dev->v4l2_dev); + +fail_free: + kfree(dev); + return err; +} + +static void cx25821_finidev(struct pci_dev *pci_dev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct cx25821_dev *dev = get_cx25821(v4l2_dev); + + cx25821_shutdown(dev); + + /* unregister stuff */ + if (pci_dev->irq) + free_irq(pci_dev->irq, dev); + pci_disable_device(pci_dev); + + cx25821_dev_unregister(dev); + v4l2_device_unregister(v4l2_dev); + kfree(dev); +} + +static const struct pci_device_id cx25821_pci_tbl[] = { + { + /* CX25821 Athena */ + PCI_DEVICE_SUB(0x14f1, 0x8210, 0x14f1, 0x0920), + }, { + /* CX25821 No Brand */ + PCI_DEVICE_SUB(0x14f1, 0x8210, 0x0000, 0x0000), + }, { + /* --- end of list --- */ + } +}; + +MODULE_DEVICE_TABLE(pci, cx25821_pci_tbl); + +static struct pci_driver cx25821_pci_driver = { + .name = "cx25821", + .id_table = cx25821_pci_tbl, + .probe = cx25821_initdev, + .remove = cx25821_finidev, +}; + +static int __init cx25821_init(void) +{ + pr_info("driver loaded\n"); + return pci_register_driver(&cx25821_pci_driver); +} + +static void __exit cx25821_fini(void) +{ + pci_unregister_driver(&cx25821_pci_driver); +} + +module_init(cx25821_init); +module_exit(cx25821_fini); diff --git a/drivers/media/pci/cx25821/cx25821-gpio.c b/drivers/media/pci/cx25821/cx25821-gpio.c new file mode 100644 index 000000000..dbe3ca61c --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821-gpio.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + */ + +#include +#include "cx25821.h" + +/********************* GPIO stuffs *********************/ +void cx25821_set_gpiopin_direction(struct cx25821_dev *dev, + int pin_number, int pin_logic_value) +{ + int bit = pin_number; + u32 gpio_oe_reg = GPIO_LO_OE; + u32 gpio_register = 0; + u32 value = 0; + + /* Check for valid pinNumber */ + if (pin_number >= 47) + return; + + if (pin_number > 31) { + bit = pin_number - 31; + gpio_oe_reg = GPIO_HI_OE; + } + /* Here we will make sure that the GPIOs 0 and 1 are output. keep the + * rest as is */ + gpio_register = cx_read(gpio_oe_reg); + + if (pin_logic_value == 1) + value = gpio_register | Set_GPIO_Bit(bit); + else + value = gpio_register & Clear_GPIO_Bit(bit); + + cx_write(gpio_oe_reg, value); +} +EXPORT_SYMBOL(cx25821_set_gpiopin_direction); + +static void cx25821_set_gpiopin_logicvalue(struct cx25821_dev *dev, + int pin_number, int pin_logic_value) +{ + int bit = pin_number; + u32 gpio_reg = GPIO_LO; + u32 value = 0; + + /* Check for valid pinNumber */ + if (pin_number >= 47) + return; + + /* change to output direction */ + cx25821_set_gpiopin_direction(dev, pin_number, 0); + + if (pin_number > 31) { + bit = pin_number - 31; + gpio_reg = GPIO_HI; + } + + value = cx_read(gpio_reg); + + if (pin_logic_value == 0) + value &= Clear_GPIO_Bit(bit); + else + value |= Set_GPIO_Bit(bit); + + cx_write(gpio_reg, value); +} + +void cx25821_gpio_init(struct cx25821_dev *dev) +{ + if (dev == NULL) + return; + + switch (dev->board) { + case CX25821_BOARD_CONEXANT_ATHENA10: + default: + /* set GPIO 5 to select the path for Medusa/Athena */ + cx25821_set_gpiopin_logicvalue(dev, 5, 1); + msleep(20); + break; + } + +} diff --git a/drivers/media/pci/cx25821/cx25821-i2c.c b/drivers/media/pci/cx25821/cx25821-i2c.c new file mode 100644 index 000000000..0ef4cd652 --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821-i2c.c @@ -0,0 +1,405 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + * Based on Steven Toth cx23885 driver + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include "cx25821.h" + +static unsigned int i2c_debug; +module_param(i2c_debug, int, 0644); +MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]"); + +static unsigned int i2c_scan; +module_param(i2c_scan, int, 0444); +MODULE_PARM_DESC(i2c_scan, "scan i2c bus at insmod time"); + +#define dprintk(level, fmt, arg...) \ +do { \ + if (i2c_debug >= level) \ + printk(KERN_DEBUG "%s/0: " fmt, dev->name, ##arg); \ +} while (0) + +#define I2C_WAIT_DELAY 32 +#define I2C_WAIT_RETRY 64 + +#define I2C_EXTEND (1 << 3) +#define I2C_NOSTOP (1 << 4) + +static inline int i2c_slave_did_ack(struct i2c_adapter *i2c_adap) +{ + struct cx25821_i2c *bus = i2c_adap->algo_data; + struct cx25821_dev *dev = bus->dev; + return cx_read(bus->reg_stat) & 0x01; +} + +static inline int i2c_is_busy(struct i2c_adapter *i2c_adap) +{ + struct cx25821_i2c *bus = i2c_adap->algo_data; + struct cx25821_dev *dev = bus->dev; + return cx_read(bus->reg_stat) & 0x02 ? 1 : 0; +} + +static int i2c_wait_done(struct i2c_adapter *i2c_adap) +{ + int count; + + for (count = 0; count < I2C_WAIT_RETRY; count++) { + if (!i2c_is_busy(i2c_adap)) + break; + udelay(I2C_WAIT_DELAY); + } + + if (I2C_WAIT_RETRY == count) + return 0; + + return 1; +} + +static int i2c_sendbytes(struct i2c_adapter *i2c_adap, + const struct i2c_msg *msg, int joined_rlen) +{ + struct cx25821_i2c *bus = i2c_adap->algo_data; + struct cx25821_dev *dev = bus->dev; + u32 wdata, addr, ctrl; + int retval, cnt; + + if (joined_rlen) + dprintk(1, "%s(msg->wlen=%d, nextmsg->rlen=%d)\n", __func__, + msg->len, joined_rlen); + else + dprintk(1, "%s(msg->len=%d)\n", __func__, msg->len); + + /* Deal with i2c probe functions with zero payload */ + if (msg->len == 0) { + cx_write(bus->reg_addr, msg->addr << 25); + cx_write(bus->reg_ctrl, bus->i2c_period | (1 << 2)); + + if (!i2c_wait_done(i2c_adap)) + return -EIO; + + if (!i2c_slave_did_ack(i2c_adap)) + return -EIO; + + dprintk(1, "%s(): returns 0\n", __func__); + return 0; + } + + /* dev, reg + first byte */ + addr = (msg->addr << 25) | msg->buf[0]; + wdata = msg->buf[0]; + + ctrl = bus->i2c_period | (1 << 12) | (1 << 2); + + if (msg->len > 1) + ctrl |= I2C_NOSTOP | I2C_EXTEND; + else if (joined_rlen) + ctrl |= I2C_NOSTOP; + + cx_write(bus->reg_addr, addr); + cx_write(bus->reg_wdata, wdata); + cx_write(bus->reg_ctrl, ctrl); + + retval = i2c_wait_done(i2c_adap); + if (retval < 0) + goto err; + + if (retval == 0) + goto eio; + + if (i2c_debug) { + if (!(ctrl & I2C_NOSTOP)) + printk(" >\n"); + } + + for (cnt = 1; cnt < msg->len; cnt++) { + /* following bytes */ + wdata = msg->buf[cnt]; + ctrl = bus->i2c_period | (1 << 12) | (1 << 2); + + if (cnt < msg->len - 1) + ctrl |= I2C_NOSTOP | I2C_EXTEND; + else if (joined_rlen) + ctrl |= I2C_NOSTOP; + + cx_write(bus->reg_addr, addr); + cx_write(bus->reg_wdata, wdata); + cx_write(bus->reg_ctrl, ctrl); + + retval = i2c_wait_done(i2c_adap); + if (retval < 0) + goto err; + + if (retval == 0) + goto eio; + + if (i2c_debug) { + dprintk(1, " %02x", msg->buf[cnt]); + if (!(ctrl & I2C_NOSTOP)) + dprintk(1, " >\n"); + } + } + + return msg->len; + +eio: + retval = -EIO; +err: + if (i2c_debug) + pr_err(" ERR: %d\n", retval); + return retval; +} + +static int i2c_readbytes(struct i2c_adapter *i2c_adap, + const struct i2c_msg *msg, int joined) +{ + struct cx25821_i2c *bus = i2c_adap->algo_data; + struct cx25821_dev *dev = bus->dev; + u32 ctrl, cnt; + int retval; + + if (i2c_debug && !joined) + dprintk(1, "6-%s(msg->len=%d)\n", __func__, msg->len); + + /* Deal with i2c probe functions with zero payload */ + if (msg->len == 0) { + cx_write(bus->reg_addr, msg->addr << 25); + cx_write(bus->reg_ctrl, bus->i2c_period | (1 << 2) | 1); + if (!i2c_wait_done(i2c_adap)) + return -EIO; + if (!i2c_slave_did_ack(i2c_adap)) + return -EIO; + + dprintk(1, "%s(): returns 0\n", __func__); + return 0; + } + + if (i2c_debug) { + if (joined) + dprintk(1, " R"); + else + dprintk(1, " addr << 1) + 1); + } + + for (cnt = 0; cnt < msg->len; cnt++) { + + ctrl = bus->i2c_period | (1 << 12) | (1 << 2) | 1; + + if (cnt < msg->len - 1) + ctrl |= I2C_NOSTOP | I2C_EXTEND; + + cx_write(bus->reg_addr, msg->addr << 25); + cx_write(bus->reg_ctrl, ctrl); + + retval = i2c_wait_done(i2c_adap); + if (retval < 0) + goto err; + if (retval == 0) + goto eio; + msg->buf[cnt] = cx_read(bus->reg_rdata) & 0xff; + + if (i2c_debug) { + dprintk(1, " %02x", msg->buf[cnt]); + if (!(ctrl & I2C_NOSTOP)) + dprintk(1, " >\n"); + } + } + + return msg->len; +eio: + retval = -EIO; +err: + if (i2c_debug) + pr_err(" ERR: %d\n", retval); + return retval; +} + +static int i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, int num) +{ + struct cx25821_i2c *bus = i2c_adap->algo_data; + struct cx25821_dev *dev = bus->dev; + int i, retval = 0; + + dprintk(1, "%s(num = %d)\n", __func__, num); + + for (i = 0; i < num; i++) { + dprintk(1, "%s(num = %d) addr = 0x%02x len = 0x%x\n", + __func__, num, msgs[i].addr, msgs[i].len); + + if (msgs[i].flags & I2C_M_RD) { + /* read */ + retval = i2c_readbytes(i2c_adap, &msgs[i], 0); + } else if (i + 1 < num && (msgs[i + 1].flags & I2C_M_RD) && + msgs[i].addr == msgs[i + 1].addr) { + /* write then read from same address */ + retval = i2c_sendbytes(i2c_adap, &msgs[i], + msgs[i + 1].len); + + if (retval < 0) + goto err; + i++; + retval = i2c_readbytes(i2c_adap, &msgs[i], 1); + } else { + /* write */ + retval = i2c_sendbytes(i2c_adap, &msgs[i], 0); + } + + if (retval < 0) + goto err; + } + return num; + +err: + return retval; +} + + +static u32 cx25821_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_READ_WORD_DATA | I2C_FUNC_SMBUS_WRITE_WORD_DATA; +} + +static const struct i2c_algorithm cx25821_i2c_algo_template = { + .master_xfer = i2c_xfer, + .functionality = cx25821_functionality, +#ifdef NEED_ALGO_CONTROL + .algo_control = dummy_algo_control, +#endif +}; + +static const struct i2c_adapter cx25821_i2c_adap_template = { + .name = "cx25821", + .owner = THIS_MODULE, + .algo = &cx25821_i2c_algo_template, +}; + +static const struct i2c_client cx25821_i2c_client_template = { + .name = "cx25821 internal", +}; + +/* init + register i2c adapter */ +int cx25821_i2c_register(struct cx25821_i2c *bus) +{ + struct cx25821_dev *dev = bus->dev; + + dprintk(1, "%s(bus = %d)\n", __func__, bus->nr); + + bus->i2c_adap = cx25821_i2c_adap_template; + bus->i2c_client = cx25821_i2c_client_template; + bus->i2c_adap.dev.parent = &dev->pci->dev; + + strscpy(bus->i2c_adap.name, bus->dev->name, sizeof(bus->i2c_adap.name)); + + bus->i2c_adap.algo_data = bus; + i2c_set_adapdata(&bus->i2c_adap, &dev->v4l2_dev); + i2c_add_adapter(&bus->i2c_adap); + + bus->i2c_client.adapter = &bus->i2c_adap; + + /* set up the I2c */ + bus->i2c_client.addr = (0x88 >> 1); + + return bus->i2c_rc; +} + +int cx25821_i2c_unregister(struct cx25821_i2c *bus) +{ + i2c_del_adapter(&bus->i2c_adap); + return 0; +} + +#if 0 /* Currently unused */ +static void cx25821_av_clk(struct cx25821_dev *dev, int enable) +{ + /* write 0 to bus 2 addr 0x144 via i2x_xfer() */ + char buffer[3]; + struct i2c_msg msg; + dprintk(1, "%s(enabled = %d)\n", __func__, enable); + + /* Register 0x144 */ + buffer[0] = 0x01; + buffer[1] = 0x44; + if (enable == 1) + buffer[2] = 0x05; + else + buffer[2] = 0x00; + + msg.addr = 0x44; + msg.flags = I2C_M_TEN; + msg.len = 3; + msg.buf = buffer; + + i2c_xfer(&dev->i2c_bus[0].i2c_adap, &msg, 1); +} +#endif + +int cx25821_i2c_read(struct cx25821_i2c *bus, u16 reg_addr, int *value) +{ + struct i2c_client *client = &bus->i2c_client; + int v = 0; + u8 addr[2] = { 0, 0 }; + u8 buf[4] = { 0, 0, 0, 0 }; + + struct i2c_msg msgs[2] = { + { + .addr = client->addr, + .flags = 0, + .len = 2, + .buf = addr, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = 4, + .buf = buf, + } + }; + + addr[0] = (reg_addr >> 8); + addr[1] = (reg_addr & 0xff); + msgs[0].addr = 0x44; + msgs[1].addr = 0x44; + + i2c_xfer(client->adapter, msgs, 2); + + v = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; + *value = v; + + return v; +} + +int cx25821_i2c_write(struct cx25821_i2c *bus, u16 reg_addr, int value) +{ + struct i2c_client *client = &bus->i2c_client; + int retval = 0; + u8 buf[6] = { 0, 0, 0, 0, 0, 0 }; + + struct i2c_msg msgs[1] = { + { + .addr = client->addr, + .flags = 0, + .len = 6, + .buf = buf, + } + }; + + buf[0] = reg_addr >> 8; + buf[1] = reg_addr & 0xff; + buf[5] = (value >> 24) & 0xff; + buf[4] = (value >> 16) & 0xff; + buf[3] = (value >> 8) & 0xff; + buf[2] = value & 0xff; + client->flags = 0; + msgs[0].addr = 0x44; + + retval = i2c_xfer(client->adapter, msgs, 1); + + return retval; +} diff --git a/drivers/media/pci/cx25821/cx25821-medusa-defines.h b/drivers/media/pci/cx25821/cx25821-medusa-defines.h new file mode 100644 index 000000000..20c93855f --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821-medusa-defines.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + */ + +#ifndef _MEDUSA_DEF_H_ +#define _MEDUSA_DEF_H_ + +/* Video decoder that we supported */ +#define VDEC_A 0 +#define VDEC_B 1 +#define VDEC_C 2 +#define VDEC_D 3 +#define VDEC_E 4 +#define VDEC_F 5 +#define VDEC_G 6 +#define VDEC_H 7 + +/* end of display sequence */ +#define END_OF_SEQ 0xF; + +/* registry string size */ +#define MAX_REGISTRY_SZ 40; + +#endif diff --git a/drivers/media/pci/cx25821/cx25821-medusa-reg.h b/drivers/media/pci/cx25821/cx25821-medusa-reg.h new file mode 100644 index 000000000..117507e13 --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821-medusa-reg.h @@ -0,0 +1,441 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + */ + +#ifndef __MEDUSA_REGISTERS__ +#define __MEDUSA_REGISTERS__ + +/* Serial Slave Registers */ +#define HOST_REGISTER1 0x0000 +#define HOST_REGISTER2 0x0001 + +/* Chip Configuration Registers */ +#define CHIP_CTRL 0x0100 +#define AFE_AB_CTRL 0x0104 +#define AFE_CD_CTRL 0x0108 +#define AFE_EF_CTRL 0x010C +#define AFE_GH_CTRL 0x0110 +#define DENC_AB_CTRL 0x0114 +#define BYP_AB_CTRL 0x0118 +#define MON_A_CTRL 0x011C +#define DISP_SEQ_A 0x0120 +#define DISP_SEQ_B 0x0124 +#define DISP_AB_CNT 0x0128 +#define DISP_CD_CNT 0x012C +#define DISP_EF_CNT 0x0130 +#define DISP_GH_CNT 0x0134 +#define DISP_IJ_CNT 0x0138 +#define PIN_OE_CTRL 0x013C +#define PIN_SPD_CTRL 0x0140 +#define PIN_SPD_CTRL2 0x0144 +#define IRQ_STAT_CTRL 0x0148 +#define POWER_CTRL_AB 0x014C +#define POWER_CTRL_CD 0x0150 +#define POWER_CTRL_EF 0x0154 +#define POWER_CTRL_GH 0x0158 +#define TUNE_CTRL 0x015C +#define BIAS_CTRL 0x0160 +#define AFE_AB_DIAG_CTRL 0x0164 +#define AFE_CD_DIAG_CTRL 0x0168 +#define AFE_EF_DIAG_CTRL 0x016C +#define AFE_GH_DIAG_CTRL 0x0170 +#define PLL_AB_DIAG_CTRL 0x0174 +#define PLL_CD_DIAG_CTRL 0x0178 +#define PLL_EF_DIAG_CTRL 0x017C +#define PLL_GH_DIAG_CTRL 0x0180 +#define TEST_CTRL 0x0184 +#define BIST_STAT 0x0188 +#define BIST_STAT2 0x018C +#define BIST_VID_PLL_AB_STAT 0x0190 +#define BIST_VID_PLL_CD_STAT 0x0194 +#define BIST_VID_PLL_EF_STAT 0x0198 +#define BIST_VID_PLL_GH_STAT 0x019C +#define DLL_DIAG_CTRL 0x01A0 +#define DEV_CH_ID_CTRL 0x01A4 +#define ABIST_CTRL_STATUS 0x01A8 +#define ABIST_FREQ 0x01AC +#define ABIST_GOERT_SHIFT 0x01B0 +#define ABIST_COEF12 0x01B4 +#define ABIST_COEF34 0x01B8 +#define ABIST_COEF56 0x01BC +#define ABIST_COEF7_SNR 0x01C0 +#define ABIST_ADC_CAL 0x01C4 +#define ABIST_BIN1_VGA0 0x01C8 +#define ABIST_BIN2_VGA1 0x01CC +#define ABIST_BIN3_VGA2 0x01D0 +#define ABIST_BIN4_VGA3 0x01D4 +#define ABIST_BIN5_VGA4 0x01D8 +#define ABIST_BIN6_VGA5 0x01DC +#define ABIST_BIN7_VGA6 0x01E0 +#define ABIST_CLAMP_A 0x01E4 +#define ABIST_CLAMP_B 0x01E8 +#define ABIST_CLAMP_C 0x01EC +#define ABIST_CLAMP_D 0x01F0 +#define ABIST_CLAMP_E 0x01F4 +#define ABIST_CLAMP_F 0x01F8 + +/* Digital Video Encoder A Registers */ +#define DENC_A_REG_1 0x0200 +#define DENC_A_REG_2 0x0204 +#define DENC_A_REG_3 0x0208 +#define DENC_A_REG_4 0x020C +#define DENC_A_REG_5 0x0210 +#define DENC_A_REG_6 0x0214 +#define DENC_A_REG_7 0x0218 +#define DENC_A_REG_8 0x021C + +/* Digital Video Encoder B Registers */ +#define DENC_B_REG_1 0x0300 +#define DENC_B_REG_2 0x0304 +#define DENC_B_REG_3 0x0308 +#define DENC_B_REG_4 0x030C +#define DENC_B_REG_5 0x0310 +#define DENC_B_REG_6 0x0314 +#define DENC_B_REG_7 0x0318 +#define DENC_B_REG_8 0x031C + +/* Video Decoder A Registers */ +#define MODE_CTRL 0x1000 +#define OUT_CTRL1 0x1004 +#define OUT_CTRL_NS 0x1008 +#define GEN_STAT 0x100C +#define INT_STAT_MASK 0x1010 +#define LUMA_CTRL 0x1014 +#define CHROMA_CTRL 0x1018 +#define CRUSH_CTRL 0x101C +#define HORIZ_TIM_CTRL 0x1020 +#define VERT_TIM_CTRL 0x1024 +#define MISC_TIM_CTRL 0x1028 +#define FIELD_COUNT 0x102C +#define HSCALE_CTRL 0x1030 +#define VSCALE_CTRL 0x1034 +#define MAN_VGA_CTRL 0x1038 +#define MAN_AGC_CTRL 0x103C +#define DFE_CTRL1 0x1040 +#define DFE_CTRL2 0x1044 +#define DFE_CTRL3 0x1048 +#define PLL_CTRL 0x104C +#define PLL_CTRL_FAST 0x1050 +#define HTL_CTRL 0x1054 +#define SRC_CFG 0x1058 +#define SC_STEP_SIZE 0x105C +#define SC_CONVERGE_CTRL 0x1060 +#define SC_LOOP_CTRL 0x1064 +#define COMB_2D_HFS_CFG 0x1068 +#define COMB_2D_HFD_CFG 0x106C +#define COMB_2D_LF_CFG 0x1070 +#define COMB_2D_BLEND 0x1074 +#define COMB_MISC_CTRL 0x1078 +#define COMB_FLAT_THRESH_CTRL 0x107C +#define COMB_TEST 0x1080 +#define BP_MISC_CTRL 0x1084 +#define VCR_DET_CTRL 0x1088 +#define NOISE_DET_CTRL 0x108C +#define COMB_FLAT_NOISE_CTRL 0x1090 +#define VERSION 0x11F8 +#define SOFT_RST_CTRL 0x11FC + +/* Video Decoder B Registers */ +#define VDEC_B_MODE_CTRL 0x1200 +#define VDEC_B_OUT_CTRL1 0x1204 +#define VDEC_B_OUT_CTRL_NS 0x1208 +#define VDEC_B_GEN_STAT 0x120C +#define VDEC_B_INT_STAT_MASK 0x1210 +#define VDEC_B_LUMA_CTRL 0x1214 +#define VDEC_B_CHROMA_CTRL 0x1218 +#define VDEC_B_CRUSH_CTRL 0x121C +#define VDEC_B_HORIZ_TIM_CTRL 0x1220 +#define VDEC_B_VERT_TIM_CTRL 0x1224 +#define VDEC_B_MISC_TIM_CTRL 0x1228 +#define VDEC_B_FIELD_COUNT 0x122C +#define VDEC_B_HSCALE_CTRL 0x1230 +#define VDEC_B_VSCALE_CTRL 0x1234 +#define VDEC_B_MAN_VGA_CTRL 0x1238 +#define VDEC_B_MAN_AGC_CTRL 0x123C +#define VDEC_B_DFE_CTRL1 0x1240 +#define VDEC_B_DFE_CTRL2 0x1244 +#define VDEC_B_DFE_CTRL3 0x1248 +#define VDEC_B_PLL_CTRL 0x124C +#define VDEC_B_PLL_CTRL_FAST 0x1250 +#define VDEC_B_HTL_CTRL 0x1254 +#define VDEC_B_SRC_CFG 0x1258 +#define VDEC_B_SC_STEP_SIZE 0x125C +#define VDEC_B_SC_CONVERGE_CTRL 0x1260 +#define VDEC_B_SC_LOOP_CTRL 0x1264 +#define VDEC_B_COMB_2D_HFS_CFG 0x1268 +#define VDEC_B_COMB_2D_HFD_CFG 0x126C +#define VDEC_B_COMB_2D_LF_CFG 0x1270 +#define VDEC_B_COMB_2D_BLEND 0x1274 +#define VDEC_B_COMB_MISC_CTRL 0x1278 +#define VDEC_B_COMB_FLAT_THRESH_CTRL 0x127C +#define VDEC_B_COMB_TEST 0x1280 +#define VDEC_B_BP_MISC_CTRL 0x1284 +#define VDEC_B_VCR_DET_CTRL 0x1288 +#define VDEC_B_NOISE_DET_CTRL 0x128C +#define VDEC_B_COMB_FLAT_NOISE_CTRL 0x1290 +#define VDEC_B_VERSION 0x13F8 +#define VDEC_B_SOFT_RST_CTRL 0x13FC + +/* Video Decoder C Registers */ +#define VDEC_C_MODE_CTRL 0x1400 +#define VDEC_C_OUT_CTRL1 0x1404 +#define VDEC_C_OUT_CTRL_NS 0x1408 +#define VDEC_C_GEN_STAT 0x140C +#define VDEC_C_INT_STAT_MASK 0x1410 +#define VDEC_C_LUMA_CTRL 0x1414 +#define VDEC_C_CHROMA_CTRL 0x1418 +#define VDEC_C_CRUSH_CTRL 0x141C +#define VDEC_C_HORIZ_TIM_CTRL 0x1420 +#define VDEC_C_VERT_TIM_CTRL 0x1424 +#define VDEC_C_MISC_TIM_CTRL 0x1428 +#define VDEC_C_FIELD_COUNT 0x142C +#define VDEC_C_HSCALE_CTRL 0x1430 +#define VDEC_C_VSCALE_CTRL 0x1434 +#define VDEC_C_MAN_VGA_CTRL 0x1438 +#define VDEC_C_MAN_AGC_CTRL 0x143C +#define VDEC_C_DFE_CTRL1 0x1440 +#define VDEC_C_DFE_CTRL2 0x1444 +#define VDEC_C_DFE_CTRL3 0x1448 +#define VDEC_C_PLL_CTRL 0x144C +#define VDEC_C_PLL_CTRL_FAST 0x1450 +#define VDEC_C_HTL_CTRL 0x1454 +#define VDEC_C_SRC_CFG 0x1458 +#define VDEC_C_SC_STEP_SIZE 0x145C +#define VDEC_C_SC_CONVERGE_CTRL 0x1460 +#define VDEC_C_SC_LOOP_CTRL 0x1464 +#define VDEC_C_COMB_2D_HFS_CFG 0x1468 +#define VDEC_C_COMB_2D_HFD_CFG 0x146C +#define VDEC_C_COMB_2D_LF_CFG 0x1470 +#define VDEC_C_COMB_2D_BLEND 0x1474 +#define VDEC_C_COMB_MISC_CTRL 0x1478 +#define VDEC_C_COMB_FLAT_THRESH_CTRL 0x147C +#define VDEC_C_COMB_TEST 0x1480 +#define VDEC_C_BP_MISC_CTRL 0x1484 +#define VDEC_C_VCR_DET_CTRL 0x1488 +#define VDEC_C_NOISE_DET_CTRL 0x148C +#define VDEC_C_COMB_FLAT_NOISE_CTRL 0x1490 +#define VDEC_C_VERSION 0x15F8 +#define VDEC_C_SOFT_RST_CTRL 0x15FC + +/* Video Decoder D Registers */ +#define VDEC_D_MODE_CTRL 0x1600 +#define VDEC_D_OUT_CTRL1 0x1604 +#define VDEC_D_OUT_CTRL_NS 0x1608 +#define VDEC_D_GEN_STAT 0x160C +#define VDEC_D_INT_STAT_MASK 0x1610 +#define VDEC_D_LUMA_CTRL 0x1614 +#define VDEC_D_CHROMA_CTRL 0x1618 +#define VDEC_D_CRUSH_CTRL 0x161C +#define VDEC_D_HORIZ_TIM_CTRL 0x1620 +#define VDEC_D_VERT_TIM_CTRL 0x1624 +#define VDEC_D_MISC_TIM_CTRL 0x1628 +#define VDEC_D_FIELD_COUNT 0x162C +#define VDEC_D_HSCALE_CTRL 0x1630 +#define VDEC_D_VSCALE_CTRL 0x1634 +#define VDEC_D_MAN_VGA_CTRL 0x1638 +#define VDEC_D_MAN_AGC_CTRL 0x163C +#define VDEC_D_DFE_CTRL1 0x1640 +#define VDEC_D_DFE_CTRL2 0x1644 +#define VDEC_D_DFE_CTRL3 0x1648 +#define VDEC_D_PLL_CTRL 0x164C +#define VDEC_D_PLL_CTRL_FAST 0x1650 +#define VDEC_D_HTL_CTRL 0x1654 +#define VDEC_D_SRC_CFG 0x1658 +#define VDEC_D_SC_STEP_SIZE 0x165C +#define VDEC_D_SC_CONVERGE_CTRL 0x1660 +#define VDEC_D_SC_LOOP_CTRL 0x1664 +#define VDEC_D_COMB_2D_HFS_CFG 0x1668 +#define VDEC_D_COMB_2D_HFD_CFG 0x166C +#define VDEC_D_COMB_2D_LF_CFG 0x1670 +#define VDEC_D_COMB_2D_BLEND 0x1674 +#define VDEC_D_COMB_MISC_CTRL 0x1678 +#define VDEC_D_COMB_FLAT_THRESH_CTRL 0x167C +#define VDEC_D_COMB_TEST 0x1680 +#define VDEC_D_BP_MISC_CTRL 0x1684 +#define VDEC_D_VCR_DET_CTRL 0x1688 +#define VDEC_D_NOISE_DET_CTRL 0x168C +#define VDEC_D_COMB_FLAT_NOISE_CTRL 0x1690 +#define VDEC_D_VERSION 0x17F8 +#define VDEC_D_SOFT_RST_CTRL 0x17FC + +/* Video Decoder E Registers */ +#define VDEC_E_MODE_CTRL 0x1800 +#define VDEC_E_OUT_CTRL1 0x1804 +#define VDEC_E_OUT_CTRL_NS 0x1808 +#define VDEC_E_GEN_STAT 0x180C +#define VDEC_E_INT_STAT_MASK 0x1810 +#define VDEC_E_LUMA_CTRL 0x1814 +#define VDEC_E_CHROMA_CTRL 0x1818 +#define VDEC_E_CRUSH_CTRL 0x181C +#define VDEC_E_HORIZ_TIM_CTRL 0x1820 +#define VDEC_E_VERT_TIM_CTRL 0x1824 +#define VDEC_E_MISC_TIM_CTRL 0x1828 +#define VDEC_E_FIELD_COUNT 0x182C +#define VDEC_E_HSCALE_CTRL 0x1830 +#define VDEC_E_VSCALE_CTRL 0x1834 +#define VDEC_E_MAN_VGA_CTRL 0x1838 +#define VDEC_E_MAN_AGC_CTRL 0x183C +#define VDEC_E_DFE_CTRL1 0x1840 +#define VDEC_E_DFE_CTRL2 0x1844 +#define VDEC_E_DFE_CTRL3 0x1848 +#define VDEC_E_PLL_CTRL 0x184C +#define VDEC_E_PLL_CTRL_FAST 0x1850 +#define VDEC_E_HTL_CTRL 0x1854 +#define VDEC_E_SRC_CFG 0x1858 +#define VDEC_E_SC_STEP_SIZE 0x185C +#define VDEC_E_SC_CONVERGE_CTRL 0x1860 +#define VDEC_E_SC_LOOP_CTRL 0x1864 +#define VDEC_E_COMB_2D_HFS_CFG 0x1868 +#define VDEC_E_COMB_2D_HFD_CFG 0x186C +#define VDEC_E_COMB_2D_LF_CFG 0x1870 +#define VDEC_E_COMB_2D_BLEND 0x1874 +#define VDEC_E_COMB_MISC_CTRL 0x1878 +#define VDEC_E_COMB_FLAT_THRESH_CTRL 0x187C +#define VDEC_E_COMB_TEST 0x1880 +#define VDEC_E_BP_MISC_CTRL 0x1884 +#define VDEC_E_VCR_DET_CTRL 0x1888 +#define VDEC_E_NOISE_DET_CTRL 0x188C +#define VDEC_E_COMB_FLAT_NOISE_CTRL 0x1890 +#define VDEC_E_VERSION 0x19F8 +#define VDEC_E_SOFT_RST_CTRL 0x19FC + +/* Video Decoder F Registers */ +#define VDEC_F_MODE_CTRL 0x1A00 +#define VDEC_F_OUT_CTRL1 0x1A04 +#define VDEC_F_OUT_CTRL_NS 0x1A08 +#define VDEC_F_GEN_STAT 0x1A0C +#define VDEC_F_INT_STAT_MASK 0x1A10 +#define VDEC_F_LUMA_CTRL 0x1A14 +#define VDEC_F_CHROMA_CTRL 0x1A18 +#define VDEC_F_CRUSH_CTRL 0x1A1C +#define VDEC_F_HORIZ_TIM_CTRL 0x1A20 +#define VDEC_F_VERT_TIM_CTRL 0x1A24 +#define VDEC_F_MISC_TIM_CTRL 0x1A28 +#define VDEC_F_FIELD_COUNT 0x1A2C +#define VDEC_F_HSCALE_CTRL 0x1A30 +#define VDEC_F_VSCALE_CTRL 0x1A34 +#define VDEC_F_MAN_VGA_CTRL 0x1A38 +#define VDEC_F_MAN_AGC_CTRL 0x1A3C +#define VDEC_F_DFE_CTRL1 0x1A40 +#define VDEC_F_DFE_CTRL2 0x1A44 +#define VDEC_F_DFE_CTRL3 0x1A48 +#define VDEC_F_PLL_CTRL 0x1A4C +#define VDEC_F_PLL_CTRL_FAST 0x1A50 +#define VDEC_F_HTL_CTRL 0x1A54 +#define VDEC_F_SRC_CFG 0x1A58 +#define VDEC_F_SC_STEP_SIZE 0x1A5C +#define VDEC_F_SC_CONVERGE_CTRL 0x1A60 +#define VDEC_F_SC_LOOP_CTRL 0x1A64 +#define VDEC_F_COMB_2D_HFS_CFG 0x1A68 +#define VDEC_F_COMB_2D_HFD_CFG 0x1A6C +#define VDEC_F_COMB_2D_LF_CFG 0x1A70 +#define VDEC_F_COMB_2D_BLEND 0x1A74 +#define VDEC_F_COMB_MISC_CTRL 0x1A78 +#define VDEC_F_COMB_FLAT_THRESH_CTRL 0x1A7C +#define VDEC_F_COMB_TEST 0x1A80 +#define VDEC_F_BP_MISC_CTRL 0x1A84 +#define VDEC_F_VCR_DET_CTRL 0x1A88 +#define VDEC_F_NOISE_DET_CTRL 0x1A8C +#define VDEC_F_COMB_FLAT_NOISE_CTRL 0x1A90 +#define VDEC_F_VERSION 0x1BF8 +#define VDEC_F_SOFT_RST_CTRL 0x1BFC + +/* Video Decoder G Registers */ +#define VDEC_G_MODE_CTRL 0x1C00 +#define VDEC_G_OUT_CTRL1 0x1C04 +#define VDEC_G_OUT_CTRL_NS 0x1C08 +#define VDEC_G_GEN_STAT 0x1C0C +#define VDEC_G_INT_STAT_MASK 0x1C10 +#define VDEC_G_LUMA_CTRL 0x1C14 +#define VDEC_G_CHROMA_CTRL 0x1C18 +#define VDEC_G_CRUSH_CTRL 0x1C1C +#define VDEC_G_HORIZ_TIM_CTRL 0x1C20 +#define VDEC_G_VERT_TIM_CTRL 0x1C24 +#define VDEC_G_MISC_TIM_CTRL 0x1C28 +#define VDEC_G_FIELD_COUNT 0x1C2C +#define VDEC_G_HSCALE_CTRL 0x1C30 +#define VDEC_G_VSCALE_CTRL 0x1C34 +#define VDEC_G_MAN_VGA_CTRL 0x1C38 +#define VDEC_G_MAN_AGC_CTRL 0x1C3C +#define VDEC_G_DFE_CTRL1 0x1C40 +#define VDEC_G_DFE_CTRL2 0x1C44 +#define VDEC_G_DFE_CTRL3 0x1C48 +#define VDEC_G_PLL_CTRL 0x1C4C +#define VDEC_G_PLL_CTRL_FAST 0x1C50 +#define VDEC_G_HTL_CTRL 0x1C54 +#define VDEC_G_SRC_CFG 0x1C58 +#define VDEC_G_SC_STEP_SIZE 0x1C5C +#define VDEC_G_SC_CONVERGE_CTRL 0x1C60 +#define VDEC_G_SC_LOOP_CTRL 0x1C64 +#define VDEC_G_COMB_2D_HFS_CFG 0x1C68 +#define VDEC_G_COMB_2D_HFD_CFG 0x1C6C +#define VDEC_G_COMB_2D_LF_CFG 0x1C70 +#define VDEC_G_COMB_2D_BLEND 0x1C74 +#define VDEC_G_COMB_MISC_CTRL 0x1C78 +#define VDEC_G_COMB_FLAT_THRESH_CTRL 0x1C7C +#define VDEC_G_COMB_TEST 0x1C80 +#define VDEC_G_BP_MISC_CTRL 0x1C84 +#define VDEC_G_VCR_DET_CTRL 0x1C88 +#define VDEC_G_NOISE_DET_CTRL 0x1C8C +#define VDEC_G_COMB_FLAT_NOISE_CTRL 0x1C90 +#define VDEC_G_VERSION 0x1DF8 +#define VDEC_G_SOFT_RST_CTRL 0x1DFC + +/* Video Decoder H Registers */ +#define VDEC_H_MODE_CTRL 0x1E00 +#define VDEC_H_OUT_CTRL1 0x1E04 +#define VDEC_H_OUT_CTRL_NS 0x1E08 +#define VDEC_H_GEN_STAT 0x1E0C +#define VDEC_H_INT_STAT_MASK 0x1E1E +#define VDEC_H_LUMA_CTRL 0x1E14 +#define VDEC_H_CHROMA_CTRL 0x1E18 +#define VDEC_H_CRUSH_CTRL 0x1E1C +#define VDEC_H_HORIZ_TIM_CTRL 0x1E20 +#define VDEC_H_VERT_TIM_CTRL 0x1E24 +#define VDEC_H_MISC_TIM_CTRL 0x1E28 +#define VDEC_H_FIELD_COUNT 0x1E2C +#define VDEC_H_HSCALE_CTRL 0x1E30 +#define VDEC_H_VSCALE_CTRL 0x1E34 +#define VDEC_H_MAN_VGA_CTRL 0x1E38 +#define VDEC_H_MAN_AGC_CTRL 0x1E3C +#define VDEC_H_DFE_CTRL1 0x1E40 +#define VDEC_H_DFE_CTRL2 0x1E44 +#define VDEC_H_DFE_CTRL3 0x1E48 +#define VDEC_H_PLL_CTRL 0x1E4C +#define VDEC_H_PLL_CTRL_FAST 0x1E50 +#define VDEC_H_HTL_CTRL 0x1E54 +#define VDEC_H_SRC_CFG 0x1E58 +#define VDEC_H_SC_STEP_SIZE 0x1E5C +#define VDEC_H_SC_CONVERGE_CTRL 0x1E60 +#define VDEC_H_SC_LOOP_CTRL 0x1E64 +#define VDEC_H_COMB_2D_HFS_CFG 0x1E68 +#define VDEC_H_COMB_2D_HFD_CFG 0x1E6C +#define VDEC_H_COMB_2D_LF_CFG 0x1E70 +#define VDEC_H_COMB_2D_BLEND 0x1E74 +#define VDEC_H_COMB_MISC_CTRL 0x1E78 +#define VDEC_H_COMB_FLAT_THRESH_CTRL 0x1E7C +#define VDEC_H_COMB_TEST 0x1E80 +#define VDEC_H_BP_MISC_CTRL 0x1E84 +#define VDEC_H_VCR_DET_CTRL 0x1E88 +#define VDEC_H_NOISE_DET_CTRL 0x1E8C +#define VDEC_H_COMB_FLAT_NOISE_CTRL 0x1E90 +#define VDEC_H_VERSION 0x1FF8 +#define VDEC_H_SOFT_RST_CTRL 0x1FFC + +/*****************************************************************************/ +/* LUMA_CTRL register fields */ +#define VDEC_A_BRITE_CTRL 0x1014 +#define VDEC_A_CNTRST_CTRL 0x1015 +#define VDEC_A_PEAK_SEL 0x1016 + +/*****************************************************************************/ +/* CHROMA_CTRL register fields */ +#define VDEC_A_USAT_CTRL 0x1018 +#define VDEC_A_VSAT_CTRL 0x1019 +#define VDEC_A_HUE_CTRL 0x101A + +#endif diff --git a/drivers/media/pci/cx25821/cx25821-medusa-video.c b/drivers/media/pci/cx25821/cx25821-medusa-video.c new file mode 100644 index 000000000..f0a1ac77f --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821-medusa-video.c @@ -0,0 +1,731 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "cx25821.h" +#include "cx25821-medusa-video.h" +#include "cx25821-biffuncs.h" + +/* + * medusa_enable_bluefield_output() + * + * Enable the generation of blue filed output if no video + * + */ +static void medusa_enable_bluefield_output(struct cx25821_dev *dev, int channel, + int enable) +{ + u32 value = 0; + u32 tmp = 0; + int out_ctrl = OUT_CTRL1; + int out_ctrl_ns = OUT_CTRL_NS; + + switch (channel) { + default: + case VDEC_A: + break; + case VDEC_B: + out_ctrl = VDEC_B_OUT_CTRL1; + out_ctrl_ns = VDEC_B_OUT_CTRL_NS; + break; + case VDEC_C: + out_ctrl = VDEC_C_OUT_CTRL1; + out_ctrl_ns = VDEC_C_OUT_CTRL_NS; + break; + case VDEC_D: + out_ctrl = VDEC_D_OUT_CTRL1; + out_ctrl_ns = VDEC_D_OUT_CTRL_NS; + break; + case VDEC_E: + out_ctrl = VDEC_E_OUT_CTRL1; + out_ctrl_ns = VDEC_E_OUT_CTRL_NS; + return; + case VDEC_F: + out_ctrl = VDEC_F_OUT_CTRL1; + out_ctrl_ns = VDEC_F_OUT_CTRL_NS; + return; + case VDEC_G: + out_ctrl = VDEC_G_OUT_CTRL1; + out_ctrl_ns = VDEC_G_OUT_CTRL_NS; + return; + case VDEC_H: + out_ctrl = VDEC_H_OUT_CTRL1; + out_ctrl_ns = VDEC_H_OUT_CTRL_NS; + return; + } + + value = cx25821_i2c_read(&dev->i2c_bus[0], out_ctrl, &tmp); + value &= 0xFFFFFF7F; /* clear BLUE_FIELD_EN */ + if (enable) + value |= 0x00000080; /* set BLUE_FIELD_EN */ + cx25821_i2c_write(&dev->i2c_bus[0], out_ctrl, value); + + value = cx25821_i2c_read(&dev->i2c_bus[0], out_ctrl_ns, &tmp); + value &= 0xFFFFFF7F; + if (enable) + value |= 0x00000080; /* set BLUE_FIELD_EN */ + cx25821_i2c_write(&dev->i2c_bus[0], out_ctrl_ns, value); +} + +static int medusa_initialize_ntsc(struct cx25821_dev *dev) +{ + int ret_val = 0; + int i = 0; + u32 value = 0; + u32 tmp = 0; + + for (i = 0; i < MAX_DECODERS; i++) { + /* set video format NTSC-M */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + MODE_CTRL + (0x200 * i), &tmp); + value &= 0xFFFFFFF0; + /* enable the fast locking mode bit[16] */ + value |= 0x10001; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + MODE_CTRL + (0x200 * i), value); + + /* resolution NTSC 720x480 */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + HORIZ_TIM_CTRL + (0x200 * i), &tmp); + value &= 0x00C00C00; + value |= 0x612D0074; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + HORIZ_TIM_CTRL + (0x200 * i), value); + + value = cx25821_i2c_read(&dev->i2c_bus[0], + VERT_TIM_CTRL + (0x200 * i), &tmp); + value &= 0x00C00C00; + value |= 0x1C1E001A; /* vblank_cnt + 2 to get camera ID */ + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + VERT_TIM_CTRL + (0x200 * i), value); + + /* chroma subcarrier step size */ + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + SC_STEP_SIZE + (0x200 * i), 0x43E00000); + + /* enable VIP optional active */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + OUT_CTRL_NS + (0x200 * i), &tmp); + value &= 0xFFFBFFFF; + value |= 0x00040000; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + OUT_CTRL_NS + (0x200 * i), value); + + /* enable VIP optional active (VIP_OPT_AL) for direct output. */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + OUT_CTRL1 + (0x200 * i), &tmp); + value &= 0xFFFBFFFF; + value |= 0x00040000; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + OUT_CTRL1 + (0x200 * i), value); + + /* + * clear VPRES_VERT_EN bit, fixes the chroma run away problem + * when the input switching rate < 16 fields + */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + MISC_TIM_CTRL + (0x200 * i), &tmp); + /* disable special play detection */ + value = setBitAtPos(value, 14); + value = clearBitAtPos(value, 15); + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + MISC_TIM_CTRL + (0x200 * i), value); + + /* set vbi_gate_en to 0 */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + DFE_CTRL1 + (0x200 * i), &tmp); + value = clearBitAtPos(value, 29); + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + DFE_CTRL1 + (0x200 * i), value); + + /* Enable the generation of blue field output if no video */ + medusa_enable_bluefield_output(dev, i, 1); + } + + for (i = 0; i < MAX_ENCODERS; i++) { + /* NTSC hclock */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + DENC_A_REG_1 + (0x100 * i), &tmp); + value &= 0xF000FC00; + value |= 0x06B402D0; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + DENC_A_REG_1 + (0x100 * i), value); + + /* burst begin and burst end */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + DENC_A_REG_2 + (0x100 * i), &tmp); + value &= 0xFF000000; + value |= 0x007E9054; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + DENC_A_REG_2 + (0x100 * i), value); + + value = cx25821_i2c_read(&dev->i2c_bus[0], + DENC_A_REG_3 + (0x100 * i), &tmp); + value &= 0xFC00FE00; + value |= 0x00EC00F0; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + DENC_A_REG_3 + (0x100 * i), value); + + /* set NTSC vblank, no phase alternation, 7.5 IRE pedestal */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + DENC_A_REG_4 + (0x100 * i), &tmp); + value &= 0x00FCFFFF; + value |= 0x13020000; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + DENC_A_REG_4 + (0x100 * i), value); + + value = cx25821_i2c_read(&dev->i2c_bus[0], + DENC_A_REG_5 + (0x100 * i), &tmp); + value &= 0xFFFF0000; + value |= 0x0000E575; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + DENC_A_REG_5 + (0x100 * i), value); + + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + DENC_A_REG_6 + (0x100 * i), 0x009A89C1); + + /* Subcarrier Increment */ + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + DENC_A_REG_7 + (0x100 * i), 0x21F07C1F); + } + + /* set picture resolutions */ + /* 0 - 720 */ + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], HSCALE_CTRL, 0x0); + /* 0 - 480 */ + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], VSCALE_CTRL, 0x0); + + /* set Bypass input format to NTSC 525 lines */ + value = cx25821_i2c_read(&dev->i2c_bus[0], BYP_AB_CTRL, &tmp); + value |= 0x00080200; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], BYP_AB_CTRL, value); + + return ret_val; +} + +static int medusa_PALCombInit(struct cx25821_dev *dev, int dec) +{ + int ret_val = -1; + u32 value = 0, tmp = 0; + + /* Setup for 2D threshold */ + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + COMB_2D_HFS_CFG + (0x200 * dec), 0x20002861); + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + COMB_2D_HFD_CFG + (0x200 * dec), 0x20002861); + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + COMB_2D_LF_CFG + (0x200 * dec), 0x200A1023); + + /* Setup flat chroma and luma thresholds */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + COMB_FLAT_THRESH_CTRL + (0x200 * dec), &tmp); + value &= 0x06230000; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + COMB_FLAT_THRESH_CTRL + (0x200 * dec), value); + + /* set comb 2D blend */ + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + COMB_2D_BLEND + (0x200 * dec), 0x210F0F0F); + + /* COMB MISC CONTROL */ + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + COMB_MISC_CTRL + (0x200 * dec), 0x41120A7F); + + return ret_val; +} + +static int medusa_initialize_pal(struct cx25821_dev *dev) +{ + int ret_val = 0; + int i = 0; + u32 value = 0; + u32 tmp = 0; + + for (i = 0; i < MAX_DECODERS; i++) { + /* set video format PAL-BDGHI */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + MODE_CTRL + (0x200 * i), &tmp); + value &= 0xFFFFFFF0; + /* enable the fast locking mode bit[16] */ + value |= 0x10004; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + MODE_CTRL + (0x200 * i), value); + + /* resolution PAL 720x576 */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + HORIZ_TIM_CTRL + (0x200 * i), &tmp); + value &= 0x00C00C00; + value |= 0x632D007D; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + HORIZ_TIM_CTRL + (0x200 * i), value); + + /* vblank656_cnt=x26, vactive_cnt=240h, vblank_cnt=x24 */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + VERT_TIM_CTRL + (0x200 * i), &tmp); + value &= 0x00C00C00; + value |= 0x28240026; /* vblank_cnt + 2 to get camera ID */ + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + VERT_TIM_CTRL + (0x200 * i), value); + + /* chroma subcarrier step size */ + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + SC_STEP_SIZE + (0x200 * i), 0x5411E2D0); + + /* enable VIP optional active */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + OUT_CTRL_NS + (0x200 * i), &tmp); + value &= 0xFFFBFFFF; + value |= 0x00040000; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + OUT_CTRL_NS + (0x200 * i), value); + + /* enable VIP optional active (VIP_OPT_AL) for direct output. */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + OUT_CTRL1 + (0x200 * i), &tmp); + value &= 0xFFFBFFFF; + value |= 0x00040000; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + OUT_CTRL1 + (0x200 * i), value); + + /* + * clear VPRES_VERT_EN bit, fixes the chroma run away problem + * when the input switching rate < 16 fields + */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + MISC_TIM_CTRL + (0x200 * i), &tmp); + /* disable special play detection */ + value = setBitAtPos(value, 14); + value = clearBitAtPos(value, 15); + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + MISC_TIM_CTRL + (0x200 * i), value); + + /* set vbi_gate_en to 0 */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + DFE_CTRL1 + (0x200 * i), &tmp); + value = clearBitAtPos(value, 29); + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + DFE_CTRL1 + (0x200 * i), value); + + medusa_PALCombInit(dev, i); + + /* Enable the generation of blue field output if no video */ + medusa_enable_bluefield_output(dev, i, 1); + } + + for (i = 0; i < MAX_ENCODERS; i++) { + /* PAL hclock */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + DENC_A_REG_1 + (0x100 * i), &tmp); + value &= 0xF000FC00; + value |= 0x06C002D0; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + DENC_A_REG_1 + (0x100 * i), value); + + /* burst begin and burst end */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + DENC_A_REG_2 + (0x100 * i), &tmp); + value &= 0xFF000000; + value |= 0x007E9754; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + DENC_A_REG_2 + (0x100 * i), value); + + /* hblank and vactive */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + DENC_A_REG_3 + (0x100 * i), &tmp); + value &= 0xFC00FE00; + value |= 0x00FC0120; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + DENC_A_REG_3 + (0x100 * i), value); + + /* set PAL vblank, phase alternation, 0 IRE pedestal */ + value = cx25821_i2c_read(&dev->i2c_bus[0], + DENC_A_REG_4 + (0x100 * i), &tmp); + value &= 0x00FCFFFF; + value |= 0x14010000; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + DENC_A_REG_4 + (0x100 * i), value); + + value = cx25821_i2c_read(&dev->i2c_bus[0], + DENC_A_REG_5 + (0x100 * i), &tmp); + value &= 0xFFFF0000; + value |= 0x0000F078; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + DENC_A_REG_5 + (0x100 * i), value); + + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + DENC_A_REG_6 + (0x100 * i), 0x00A493CF); + + /* Subcarrier Increment */ + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], + DENC_A_REG_7 + (0x100 * i), 0x2A098ACB); + } + + /* set picture resolutions */ + /* 0 - 720 */ + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], HSCALE_CTRL, 0x0); + /* 0 - 576 */ + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], VSCALE_CTRL, 0x0); + + /* set Bypass input format to PAL 625 lines */ + value = cx25821_i2c_read(&dev->i2c_bus[0], BYP_AB_CTRL, &tmp); + value &= 0xFFF7FDFF; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], BYP_AB_CTRL, value); + + return ret_val; +} + +int medusa_set_videostandard(struct cx25821_dev *dev) +{ + int status = 0; + u32 value = 0, tmp = 0; + + if (dev->tvnorm & V4L2_STD_PAL_BG || dev->tvnorm & V4L2_STD_PAL_DK) + status = medusa_initialize_pal(dev); + else + status = medusa_initialize_ntsc(dev); + + /* Enable DENC_A output */ + value = cx25821_i2c_read(&dev->i2c_bus[0], DENC_A_REG_4, &tmp); + value = setBitAtPos(value, 4); + status = cx25821_i2c_write(&dev->i2c_bus[0], DENC_A_REG_4, value); + + /* Enable DENC_B output */ + value = cx25821_i2c_read(&dev->i2c_bus[0], DENC_B_REG_4, &tmp); + value = setBitAtPos(value, 4); + status = cx25821_i2c_write(&dev->i2c_bus[0], DENC_B_REG_4, value); + + return status; +} + +void medusa_set_resolution(struct cx25821_dev *dev, int width, + int decoder_select) +{ + int decoder = 0; + int decoder_count = 0; + u32 hscale = 0x0; + u32 vscale = 0x0; + const int MAX_WIDTH = 720; + + /* validate the width */ + if (width > MAX_WIDTH) { + pr_info("%s(): width %d > MAX_WIDTH %d ! resetting to MAX_WIDTH\n", + __func__, width, MAX_WIDTH); + width = MAX_WIDTH; + } + + if (decoder_select <= 7 && decoder_select >= 0) { + decoder = decoder_select; + decoder_count = decoder_select + 1; + } else { + decoder = 0; + decoder_count = dev->_max_num_decoders; + } + + switch (width) { + case 320: + hscale = 0x13E34B; + vscale = 0x0; + break; + + case 352: + hscale = 0x10A273; + vscale = 0x0; + break; + + case 176: + hscale = 0x3115B2; + vscale = 0x1E00; + break; + + case 160: + hscale = 0x378D84; + vscale = 0x1E00; + break; + + default: /* 720 */ + hscale = 0x0; + vscale = 0x0; + break; + } + + for (; decoder < decoder_count; decoder++) { + /* write scaling values for each decoder */ + cx25821_i2c_write(&dev->i2c_bus[0], + HSCALE_CTRL + (0x200 * decoder), hscale); + cx25821_i2c_write(&dev->i2c_bus[0], + VSCALE_CTRL + (0x200 * decoder), vscale); + } +} + +static void medusa_set_decoderduration(struct cx25821_dev *dev, int decoder, + int duration) +{ + u32 fld_cnt = 0; + u32 tmp = 0; + u32 disp_cnt_reg = DISP_AB_CNT; + + /* no support */ + if (decoder < VDEC_A || decoder > VDEC_H) { + return; + } + + switch (decoder) { + default: + break; + case VDEC_C: + case VDEC_D: + disp_cnt_reg = DISP_CD_CNT; + break; + case VDEC_E: + case VDEC_F: + disp_cnt_reg = DISP_EF_CNT; + break; + case VDEC_G: + case VDEC_H: + disp_cnt_reg = DISP_GH_CNT; + break; + } + + /* update hardware */ + fld_cnt = cx25821_i2c_read(&dev->i2c_bus[0], disp_cnt_reg, &tmp); + + if (!(decoder % 2)) { /* EVEN decoder */ + fld_cnt &= 0xFFFF0000; + fld_cnt |= duration; + } else { + fld_cnt &= 0x0000FFFF; + fld_cnt |= ((u32) duration) << 16; + } + + cx25821_i2c_write(&dev->i2c_bus[0], disp_cnt_reg, fld_cnt); +} + +/* Map to Medusa register setting */ +static int mapM(int srcMin, int srcMax, int srcVal, int dstMin, int dstMax, + int *dstVal) +{ + int numerator; + int denominator; + int quotient; + + if ((srcMin == srcMax) || (srcVal < srcMin) || (srcVal > srcMax)) + return -1; + /* + * This is the overall expression used: + * *dstVal = + * (srcVal - srcMin)*(dstMax - dstMin) / (srcMax - srcMin) + dstMin; + * but we need to account for rounding so below we use the modulus + * operator to find the remainder and increment if necessary. + */ + numerator = (srcVal - srcMin) * (dstMax - dstMin); + denominator = srcMax - srcMin; + quotient = numerator / denominator; + + if (2 * (numerator % denominator) >= denominator) + quotient++; + + *dstVal = quotient + dstMin; + + return 0; +} + +static unsigned long convert_to_twos(long numeric, unsigned long bits_len) +{ + unsigned char temp; + + if (numeric >= 0) + return numeric; + else { + temp = ~(abs(numeric) & 0xFF); + temp += 1; + return temp; + } +} + +int medusa_set_brightness(struct cx25821_dev *dev, int brightness, int decoder) +{ + int ret_val = 0; + int value = 0; + u32 val = 0, tmp = 0; + + if ((brightness > VIDEO_PROCAMP_MAX) || + (brightness < VIDEO_PROCAMP_MIN)) { + return -1; + } + ret_val = mapM(VIDEO_PROCAMP_MIN, VIDEO_PROCAMP_MAX, brightness, + SIGNED_BYTE_MIN, SIGNED_BYTE_MAX, &value); + value = convert_to_twos(value, 8); + val = cx25821_i2c_read(&dev->i2c_bus[0], + VDEC_A_BRITE_CTRL + (0x200 * decoder), &tmp); + val &= 0xFFFFFF00; + ret_val |= cx25821_i2c_write(&dev->i2c_bus[0], + VDEC_A_BRITE_CTRL + (0x200 * decoder), val | value); + return ret_val; +} + +int medusa_set_contrast(struct cx25821_dev *dev, int contrast, int decoder) +{ + int ret_val = 0; + int value = 0; + u32 val = 0, tmp = 0; + + if ((contrast > VIDEO_PROCAMP_MAX) || (contrast < VIDEO_PROCAMP_MIN)) { + return -1; + } + + ret_val = mapM(VIDEO_PROCAMP_MIN, VIDEO_PROCAMP_MAX, contrast, + UNSIGNED_BYTE_MIN, UNSIGNED_BYTE_MAX, &value); + val = cx25821_i2c_read(&dev->i2c_bus[0], + VDEC_A_CNTRST_CTRL + (0x200 * decoder), &tmp); + val &= 0xFFFFFF00; + ret_val |= cx25821_i2c_write(&dev->i2c_bus[0], + VDEC_A_CNTRST_CTRL + (0x200 * decoder), val | value); + + return ret_val; +} + +int medusa_set_hue(struct cx25821_dev *dev, int hue, int decoder) +{ + int ret_val = 0; + int value = 0; + u32 val = 0, tmp = 0; + + if ((hue > VIDEO_PROCAMP_MAX) || (hue < VIDEO_PROCAMP_MIN)) { + return -1; + } + + ret_val = mapM(VIDEO_PROCAMP_MIN, VIDEO_PROCAMP_MAX, hue, + SIGNED_BYTE_MIN, SIGNED_BYTE_MAX, &value); + + value = convert_to_twos(value, 8); + val = cx25821_i2c_read(&dev->i2c_bus[0], + VDEC_A_HUE_CTRL + (0x200 * decoder), &tmp); + val &= 0xFFFFFF00; + + ret_val |= cx25821_i2c_write(&dev->i2c_bus[0], + VDEC_A_HUE_CTRL + (0x200 * decoder), val | value); + + return ret_val; +} + +int medusa_set_saturation(struct cx25821_dev *dev, int saturation, int decoder) +{ + int ret_val = 0; + int value = 0; + u32 val = 0, tmp = 0; + + if ((saturation > VIDEO_PROCAMP_MAX) || + (saturation < VIDEO_PROCAMP_MIN)) { + return -1; + } + + ret_val = mapM(VIDEO_PROCAMP_MIN, VIDEO_PROCAMP_MAX, saturation, + UNSIGNED_BYTE_MIN, UNSIGNED_BYTE_MAX, &value); + + val = cx25821_i2c_read(&dev->i2c_bus[0], + VDEC_A_USAT_CTRL + (0x200 * decoder), &tmp); + val &= 0xFFFFFF00; + ret_val |= cx25821_i2c_write(&dev->i2c_bus[0], + VDEC_A_USAT_CTRL + (0x200 * decoder), val | value); + + val = cx25821_i2c_read(&dev->i2c_bus[0], + VDEC_A_VSAT_CTRL + (0x200 * decoder), &tmp); + val &= 0xFFFFFF00; + ret_val |= cx25821_i2c_write(&dev->i2c_bus[0], + VDEC_A_VSAT_CTRL + (0x200 * decoder), val | value); + + return ret_val; +} + +/* Program the display sequence and monitor output. */ + +int medusa_video_init(struct cx25821_dev *dev) +{ + u32 value = 0, tmp = 0; + int ret_val = 0; + int i = 0; + + /* disable Auto source selection on all video decoders */ + value = cx25821_i2c_read(&dev->i2c_bus[0], MON_A_CTRL, &tmp); + value &= 0xFFFFF0FF; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], MON_A_CTRL, value); + + if (ret_val < 0) + goto error; + + /* Turn off Master source switch enable */ + value = cx25821_i2c_read(&dev->i2c_bus[0], MON_A_CTRL, &tmp); + value &= 0xFFFFFFDF; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], MON_A_CTRL, value); + + if (ret_val < 0) + goto error; + + /* + * FIXME: due to a coding bug the duration was always 0. It's + * likely that it really should be something else, but due to the + * lack of documentation I have no idea what it should be. For + * now just fill in 0 as the duration. + */ + for (i = 0; i < dev->_max_num_decoders; i++) + medusa_set_decoderduration(dev, i, 0); + + /* Select monitor as DENC A input, power up the DAC */ + value = cx25821_i2c_read(&dev->i2c_bus[0], DENC_AB_CTRL, &tmp); + value &= 0xFF70FF70; + value |= 0x00090008; /* set en_active */ + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], DENC_AB_CTRL, value); + + if (ret_val < 0) + goto error; + + /* enable input is VIP/656 */ + value = cx25821_i2c_read(&dev->i2c_bus[0], BYP_AB_CTRL, &tmp); + value |= 0x00040100; /* enable VIP */ + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], BYP_AB_CTRL, value); + + if (ret_val < 0) + goto error; + + /* select AFE clock to output mode */ + value = cx25821_i2c_read(&dev->i2c_bus[0], AFE_AB_DIAG_CTRL, &tmp); + value &= 0x83FFFFFF; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], AFE_AB_DIAG_CTRL, + value | 0x10000000); + + if (ret_val < 0) + goto error; + + /* Turn on all of the data out and control output pins. */ + value = cx25821_i2c_read(&dev->i2c_bus[0], PIN_OE_CTRL, &tmp); + value &= 0xFEF0FE00; + if (dev->_max_num_decoders == MAX_DECODERS) { + /* + * Note: The octal board does not support control pins(bit16-19) + * These bits are ignored in the octal board. + * + * disable VDEC A-C port, default to Mobilygen Interface + */ + value |= 0x010001F8; + } else { + /* disable VDEC A-C port, default to Mobilygen Interface */ + value |= 0x010F0108; + } + + value |= 7; + ret_val = cx25821_i2c_write(&dev->i2c_bus[0], PIN_OE_CTRL, value); + + if (ret_val < 0) + goto error; + + ret_val = medusa_set_videostandard(dev); + +error: + return ret_val; +} diff --git a/drivers/media/pci/cx25821/cx25821-medusa-video.h b/drivers/media/pci/cx25821/cx25821-medusa-video.h new file mode 100644 index 000000000..37f6f49cd --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821-medusa-video.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + */ + +#ifndef _MEDUSA_VIDEO_H +#define _MEDUSA_VIDEO_H + +#include "cx25821-medusa-defines.h" + +/* Color control constants */ +#define VIDEO_PROCAMP_MIN 0 +#define VIDEO_PROCAMP_MAX 10000 +#define UNSIGNED_BYTE_MIN 0 +#define UNSIGNED_BYTE_MAX 0xFF +#define SIGNED_BYTE_MIN -128 +#define SIGNED_BYTE_MAX 127 + +/* Default video color settings */ +#define SHARPNESS_DEFAULT 50 +#define SATURATION_DEFAULT 5000 +#define BRIGHTNESS_DEFAULT 6200 +#define CONTRAST_DEFAULT 5000 +#define HUE_DEFAULT 5000 + +#endif diff --git a/drivers/media/pci/cx25821/cx25821-reg.h b/drivers/media/pci/cx25821/cx25821-reg.h new file mode 100644 index 000000000..83ee798ab --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821-reg.h @@ -0,0 +1,1578 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + */ + +#ifndef __CX25821_REGISTERS__ +#define __CX25821_REGISTERS__ + +/* Risc Instructions */ +#define RISC_CNT_INC 0x00010000 +#define RISC_CNT_RESET 0x00030000 +#define RISC_IRQ1 0x01000000 +#define RISC_IRQ2 0x02000000 +#define RISC_EOL 0x04000000 +#define RISC_SOL 0x08000000 +#define RISC_WRITE 0x10000000 +#define RISC_SKIP 0x20000000 +#define RISC_JUMP 0x70000000 +#define RISC_SYNC 0x80000000 +#define RISC_RESYNC 0x80008000 +#define RISC_READ 0x90000000 +#define RISC_WRITERM 0xB0000000 +#define RISC_WRITECM 0xC0000000 +#define RISC_WRITECR 0xD0000000 +#define RISC_WRITEC 0x50000000 +#define RISC_READC 0xA0000000 + +#define RISC_SYNC_ODD 0x00000000 +#define RISC_SYNC_EVEN 0x00000200 +#define RISC_SYNC_ODD_VBI 0x00000006 +#define RISC_SYNC_EVEN_VBI 0x00000207 +#define RISC_NOOP 0xF0000000 + +/***************************************************************************** +* ASB SRAM + *****************************************************************************/ +#define TX_SRAM 0x000000 /* Transmit SRAM */ + +/*****************************************************************************/ +#define RX_RAM 0x010000 /* Receive SRAM */ + +/***************************************************************************** +* Application Layer (AL) + *****************************************************************************/ +#define DEV_CNTRL2 0x040000 /* Device control */ +#define FLD_RUN_RISC 0x00000020 + +/* ***************************************************************************** */ +#define PCI_INT_MSK 0x040010 /* PCI interrupt mask */ +#define PCI_INT_STAT 0x040014 /* PCI interrupt status */ +#define PCI_INT_MSTAT 0x040018 /* PCI interrupt masked status */ +#define FLD_HAMMERHEAD_INT (1 << 27) +#define FLD_UART_INT (1 << 26) +#define FLD_IRQN_INT (1 << 25) +#define FLD_TM_INT (1 << 28) +#define FLD_I2C_3_RACK (1 << 27) +#define FLD_I2C_3_INT (1 << 26) +#define FLD_I2C_2_RACK (1 << 25) +#define FLD_I2C_2_INT (1 << 24) +#define FLD_I2C_1_RACK (1 << 23) +#define FLD_I2C_1_INT (1 << 22) + +#define FLD_APB_DMA_BERR_INT (1 << 21) +#define FLD_AL_WR_BERR_INT (1 << 20) +#define FLD_AL_RD_BERR_INT (1 << 19) +#define FLD_RISC_WR_BERR_INT (1 << 18) +#define FLD_RISC_RD_BERR_INT (1 << 17) + +#define FLD_VID_I_INT (1 << 8) +#define FLD_VID_H_INT (1 << 7) +#define FLD_VID_G_INT (1 << 6) +#define FLD_VID_F_INT (1 << 5) +#define FLD_VID_E_INT (1 << 4) +#define FLD_VID_D_INT (1 << 3) +#define FLD_VID_C_INT (1 << 2) +#define FLD_VID_B_INT (1 << 1) +#define FLD_VID_A_INT (1 << 0) + +/* ***************************************************************************** */ +#define VID_A_INT_MSK 0x040020 /* Video A interrupt mask */ +#define VID_A_INT_STAT 0x040024 /* Video A interrupt status */ +#define VID_A_INT_MSTAT 0x040028 /* Video A interrupt masked status */ +#define VID_A_INT_SSTAT 0x04002C /* Video A interrupt set status */ + +/* ***************************************************************************** */ +#define VID_B_INT_MSK 0x040030 /* Video B interrupt mask */ +#define VID_B_INT_STAT 0x040034 /* Video B interrupt status */ +#define VID_B_INT_MSTAT 0x040038 /* Video B interrupt masked status */ +#define VID_B_INT_SSTAT 0x04003C /* Video B interrupt set status */ + +/* ***************************************************************************** */ +#define VID_C_INT_MSK 0x040040 /* Video C interrupt mask */ +#define VID_C_INT_STAT 0x040044 /* Video C interrupt status */ +#define VID_C_INT_MSTAT 0x040048 /* Video C interrupt masked status */ +#define VID_C_INT_SSTAT 0x04004C /* Video C interrupt set status */ + +/* ***************************************************************************** */ +#define VID_D_INT_MSK 0x040050 /* Video D interrupt mask */ +#define VID_D_INT_STAT 0x040054 /* Video D interrupt status */ +#define VID_D_INT_MSTAT 0x040058 /* Video D interrupt masked status */ +#define VID_D_INT_SSTAT 0x04005C /* Video D interrupt set status */ + +/* ***************************************************************************** */ +#define VID_E_INT_MSK 0x040060 /* Video E interrupt mask */ +#define VID_E_INT_STAT 0x040064 /* Video E interrupt status */ +#define VID_E_INT_MSTAT 0x040068 /* Video E interrupt masked status */ +#define VID_E_INT_SSTAT 0x04006C /* Video E interrupt set status */ + +/* ***************************************************************************** */ +#define VID_F_INT_MSK 0x040070 /* Video F interrupt mask */ +#define VID_F_INT_STAT 0x040074 /* Video F interrupt status */ +#define VID_F_INT_MSTAT 0x040078 /* Video F interrupt masked status */ +#define VID_F_INT_SSTAT 0x04007C /* Video F interrupt set status */ + +/* ***************************************************************************** */ +#define VID_G_INT_MSK 0x040080 /* Video G interrupt mask */ +#define VID_G_INT_STAT 0x040084 /* Video G interrupt status */ +#define VID_G_INT_MSTAT 0x040088 /* Video G interrupt masked status */ +#define VID_G_INT_SSTAT 0x04008C /* Video G interrupt set status */ + +/* ***************************************************************************** */ +#define VID_H_INT_MSK 0x040090 /* Video H interrupt mask */ +#define VID_H_INT_STAT 0x040094 /* Video H interrupt status */ +#define VID_H_INT_MSTAT 0x040098 /* Video H interrupt masked status */ +#define VID_H_INT_SSTAT 0x04009C /* Video H interrupt set status */ + +/* ***************************************************************************** */ +#define VID_I_INT_MSK 0x0400A0 /* Video I interrupt mask */ +#define VID_I_INT_STAT 0x0400A4 /* Video I interrupt status */ +#define VID_I_INT_MSTAT 0x0400A8 /* Video I interrupt masked status */ +#define VID_I_INT_SSTAT 0x0400AC /* Video I interrupt set status */ + +/* ***************************************************************************** */ +#define VID_J_INT_MSK 0x0400B0 /* Video J interrupt mask */ +#define VID_J_INT_STAT 0x0400B4 /* Video J interrupt status */ +#define VID_J_INT_MSTAT 0x0400B8 /* Video J interrupt masked status */ +#define VID_J_INT_SSTAT 0x0400BC /* Video J interrupt set status */ + +#define FLD_VID_SRC_OPC_ERR 0x00020000 +#define FLD_VID_DST_OPC_ERR 0x00010000 +#define FLD_VID_SRC_SYNC 0x00002000 +#define FLD_VID_DST_SYNC 0x00001000 +#define FLD_VID_SRC_UF 0x00000200 +#define FLD_VID_DST_OF 0x00000100 +#define FLD_VID_SRC_RISC2 0x00000020 +#define FLD_VID_DST_RISC2 0x00000010 +#define FLD_VID_SRC_RISC1 0x00000002 +#define FLD_VID_DST_RISC1 0x00000001 +#define FLD_VID_SRC_ERRORS (FLD_VID_SRC_OPC_ERR | FLD_VID_SRC_SYNC | FLD_VID_SRC_UF) +#define FLD_VID_DST_ERRORS (FLD_VID_DST_OPC_ERR | FLD_VID_DST_SYNC | FLD_VID_DST_OF) + +/* ***************************************************************************** */ +#define AUD_A_INT_MSK 0x0400C0 /* Audio Int interrupt mask */ +#define AUD_A_INT_STAT 0x0400C4 /* Audio Int interrupt status */ +#define AUD_A_INT_MSTAT 0x0400C8 /* Audio Int interrupt masked status */ +#define AUD_A_INT_SSTAT 0x0400CC /* Audio Int interrupt set status */ + +/* ***************************************************************************** */ +#define AUD_B_INT_MSK 0x0400D0 /* Audio Int interrupt mask */ +#define AUD_B_INT_STAT 0x0400D4 /* Audio Int interrupt status */ +#define AUD_B_INT_MSTAT 0x0400D8 /* Audio Int interrupt masked status */ +#define AUD_B_INT_SSTAT 0x0400DC /* Audio Int interrupt set status */ + +/* ***************************************************************************** */ +#define AUD_C_INT_MSK 0x0400E0 /* Audio Int interrupt mask */ +#define AUD_C_INT_STAT 0x0400E4 /* Audio Int interrupt status */ +#define AUD_C_INT_MSTAT 0x0400E8 /* Audio Int interrupt masked status */ +#define AUD_C_INT_SSTAT 0x0400EC /* Audio Int interrupt set status */ + +/* ***************************************************************************** */ +#define AUD_D_INT_MSK 0x0400F0 /* Audio Int interrupt mask */ +#define AUD_D_INT_STAT 0x0400F4 /* Audio Int interrupt status */ +#define AUD_D_INT_MSTAT 0x0400F8 /* Audio Int interrupt masked status */ +#define AUD_D_INT_SSTAT 0x0400FC /* Audio Int interrupt set status */ + +/* ***************************************************************************** */ +#define AUD_E_INT_MSK 0x040100 /* Audio Int interrupt mask */ +#define AUD_E_INT_STAT 0x040104 /* Audio Int interrupt status */ +#define AUD_E_INT_MSTAT 0x040108 /* Audio Int interrupt masked status */ +#define AUD_E_INT_SSTAT 0x04010C /* Audio Int interrupt set status */ + +#define FLD_AUD_SRC_OPC_ERR 0x00020000 +#define FLD_AUD_DST_OPC_ERR 0x00010000 +#define FLD_AUD_SRC_SYNC 0x00002000 +#define FLD_AUD_DST_SYNC 0x00001000 +#define FLD_AUD_SRC_OF 0x00000200 +#define FLD_AUD_DST_OF 0x00000100 +#define FLD_AUD_SRC_RISCI2 0x00000020 +#define FLD_AUD_DST_RISCI2 0x00000010 +#define FLD_AUD_SRC_RISCI1 0x00000002 +#define FLD_AUD_DST_RISCI1 0x00000001 + +/* ***************************************************************************** */ +#define MBIF_A_INT_MSK 0x040110 /* MBIF Int interrupt mask */ +#define MBIF_A_INT_STAT 0x040114 /* MBIF Int interrupt status */ +#define MBIF_A_INT_MSTAT 0x040118 /* MBIF Int interrupt masked status */ +#define MBIF_A_INT_SSTAT 0x04011C /* MBIF Int interrupt set status */ + +/* ***************************************************************************** */ +#define MBIF_B_INT_MSK 0x040120 /* MBIF Int interrupt mask */ +#define MBIF_B_INT_STAT 0x040124 /* MBIF Int interrupt status */ +#define MBIF_B_INT_MSTAT 0x040128 /* MBIF Int interrupt masked status */ +#define MBIF_B_INT_SSTAT 0x04012C /* MBIF Int interrupt set status */ + +#define FLD_MBIF_DST_OPC_ERR 0x00010000 +#define FLD_MBIF_DST_SYNC 0x00001000 +#define FLD_MBIF_DST_OF 0x00000100 +#define FLD_MBIF_DST_RISCI2 0x00000010 +#define FLD_MBIF_DST_RISCI1 0x00000001 + +/* ***************************************************************************** */ +#define AUD_EXT_INT_MSK 0x040060 /* Audio Ext interrupt mask */ +#define AUD_EXT_INT_STAT 0x040064 /* Audio Ext interrupt status */ +#define AUD_EXT_INT_MSTAT 0x040068 /* Audio Ext interrupt masked status */ +#define AUD_EXT_INT_SSTAT 0x04006C /* Audio Ext interrupt set status */ +#define FLD_AUD_EXT_OPC_ERR 0x00010000 +#define FLD_AUD_EXT_SYNC 0x00001000 +#define FLD_AUD_EXT_OF 0x00000100 +#define FLD_AUD_EXT_RISCI2 0x00000010 +#define FLD_AUD_EXT_RISCI1 0x00000001 + +/* ***************************************************************************** */ +#define GPIO_LO 0x110010 /* Lower of GPIO pins [31:0] */ +#define GPIO_HI 0x110014 /* Upper WORD of GPIO pins [47:31] */ + +#define GPIO_LO_OE 0x110018 /* Lower of GPIO output enable [31:0] */ +#define GPIO_HI_OE 0x11001C /* Upper word of GPIO output enable [47:32] */ + +#define GPIO_LO_INT_MSK 0x11003C /* GPIO interrupt mask */ +#define GPIO_LO_INT_STAT 0x110044 /* GPIO interrupt status */ +#define GPIO_LO_INT_MSTAT 0x11004C /* GPIO interrupt masked status */ +#define GPIO_LO_ISM_SNS 0x110054 /* GPIO interrupt sensitivity */ +#define GPIO_LO_ISM_POL 0x11005C /* GPIO interrupt polarity */ + +#define GPIO_HI_INT_MSK 0x110040 /* GPIO interrupt mask */ +#define GPIO_HI_INT_STAT 0x110048 /* GPIO interrupt status */ +#define GPIO_HI_INT_MSTAT 0x110050 /* GPIO interrupt masked status */ +#define GPIO_HI_ISM_SNS 0x110058 /* GPIO interrupt sensitivity */ +#define GPIO_HI_ISM_POL 0x110060 /* GPIO interrupt polarity */ + +#define FLD_GPIO43_INT (1 << 11) +#define FLD_GPIO42_INT (1 << 10) +#define FLD_GPIO41_INT (1 << 9) +#define FLD_GPIO40_INT (1 << 8) + +#define FLD_GPIO9_INT (1 << 9) +#define FLD_GPIO8_INT (1 << 8) +#define FLD_GPIO7_INT (1 << 7) +#define FLD_GPIO6_INT (1 << 6) +#define FLD_GPIO5_INT (1 << 5) +#define FLD_GPIO4_INT (1 << 4) +#define FLD_GPIO3_INT (1 << 3) +#define FLD_GPIO2_INT (1 << 2) +#define FLD_GPIO1_INT (1 << 1) +#define FLD_GPIO0_INT (1 << 0) + +/* ***************************************************************************** */ +#define TC_REQ 0x040090 /* Rider PCI Express traFFic class request */ + +/* ***************************************************************************** */ +#define TC_REQ_SET 0x040094 /* Rider PCI Express traFFic class request set */ + +/* ***************************************************************************** */ +/* Rider */ +/* ***************************************************************************** */ + +/* PCI Compatible Header */ +/* ***************************************************************************** */ +#define RDR_CFG0 0x050000 +#define RDR_VENDOR_DEVICE_ID_CFG 0x050000 + +/* ***************************************************************************** */ +#define RDR_CFG1 0x050004 + +/* ***************************************************************************** */ +#define RDR_CFG2 0x050008 + +/* ***************************************************************************** */ +#define RDR_CFG3 0x05000C + +/* ***************************************************************************** */ +#define RDR_CFG4 0x050010 + +/* ***************************************************************************** */ +#define RDR_CFG5 0x050014 + +/* ***************************************************************************** */ +#define RDR_CFG6 0x050018 + +/* ***************************************************************************** */ +#define RDR_CFG7 0x05001C + +/* ***************************************************************************** */ +#define RDR_CFG8 0x050020 + +/* ***************************************************************************** */ +#define RDR_CFG9 0x050024 + +/* ***************************************************************************** */ +#define RDR_CFGA 0x050028 + +/* ***************************************************************************** */ +#define RDR_CFGB 0x05002C +#define RDR_SUSSYSTEM_ID_CFG 0x05002C + +/* ***************************************************************************** */ +#define RDR_CFGC 0x050030 + +/* ***************************************************************************** */ +#define RDR_CFGD 0x050034 + +/* ***************************************************************************** */ +#define RDR_CFGE 0x050038 + +/* ***************************************************************************** */ +#define RDR_CFGF 0x05003C + +/* ***************************************************************************** */ +/* PCI-Express Capabilities */ +/* ***************************************************************************** */ +#define RDR_PECAP 0x050040 + +/* ***************************************************************************** */ +#define RDR_PEDEVCAP 0x050044 + +/* ***************************************************************************** */ +#define RDR_PEDEVSC 0x050048 + +/* ***************************************************************************** */ +#define RDR_PELINKCAP 0x05004C + +/* ***************************************************************************** */ +#define RDR_PELINKSC 0x050050 + +/* ***************************************************************************** */ +#define RDR_PMICAP 0x050080 + +/* ***************************************************************************** */ +#define RDR_PMCSR 0x050084 + +/* ***************************************************************************** */ +#define RDR_VPDCAP 0x050090 + +/* ***************************************************************************** */ +#define RDR_VPDDATA 0x050094 + +/* ***************************************************************************** */ +#define RDR_MSICAP 0x0500A0 + +/* ***************************************************************************** */ +#define RDR_MSIARL 0x0500A4 + +/* ***************************************************************************** */ +#define RDR_MSIARU 0x0500A8 + +/* ***************************************************************************** */ +#define RDR_MSIDATA 0x0500AC + +/* ***************************************************************************** */ +/* PCI Express Extended Capabilities */ +/* ***************************************************************************** */ +#define RDR_AERXCAP 0x050100 + +/* ***************************************************************************** */ +#define RDR_AERUESTA 0x050104 + +/* ***************************************************************************** */ +#define RDR_AERUEMSK 0x050108 + +/* ***************************************************************************** */ +#define RDR_AERUESEV 0x05010C + +/* ***************************************************************************** */ +#define RDR_AERCESTA 0x050110 + +/* ***************************************************************************** */ +#define RDR_AERCEMSK 0x050114 + +/* ***************************************************************************** */ +#define RDR_AERCC 0x050118 + +/* ***************************************************************************** */ +#define RDR_AERHL0 0x05011C + +/* ***************************************************************************** */ +#define RDR_AERHL1 0x050120 + +/* ***************************************************************************** */ +#define RDR_AERHL2 0x050124 + +/* ***************************************************************************** */ +#define RDR_AERHL3 0x050128 + +/* ***************************************************************************** */ +#define RDR_VCXCAP 0x050200 + +/* ***************************************************************************** */ +#define RDR_VCCAP1 0x050204 + +/* ***************************************************************************** */ +#define RDR_VCCAP2 0x050208 + +/* ***************************************************************************** */ +#define RDR_VCSC 0x05020C + +/* ***************************************************************************** */ +#define RDR_VCR0_CAP 0x050210 + +/* ***************************************************************************** */ +#define RDR_VCR0_CTRL 0x050214 + +/* ***************************************************************************** */ +#define RDR_VCR0_STAT 0x050218 + +/* ***************************************************************************** */ +#define RDR_VCR1_CAP 0x05021C + +/* ***************************************************************************** */ +#define RDR_VCR1_CTRL 0x050220 + +/* ***************************************************************************** */ +#define RDR_VCR1_STAT 0x050224 + +/* ***************************************************************************** */ +#define RDR_VCR2_CAP 0x050228 + +/* ***************************************************************************** */ +#define RDR_VCR2_CTRL 0x05022C + +/* ***************************************************************************** */ +#define RDR_VCR2_STAT 0x050230 + +/* ***************************************************************************** */ +#define RDR_VCR3_CAP 0x050234 + +/* ***************************************************************************** */ +#define RDR_VCR3_CTRL 0x050238 + +/* ***************************************************************************** */ +#define RDR_VCR3_STAT 0x05023C + +/* ***************************************************************************** */ +#define RDR_VCARB0 0x050240 + +/* ***************************************************************************** */ +#define RDR_VCARB1 0x050244 + +/* ***************************************************************************** */ +#define RDR_VCARB2 0x050248 + +/* ***************************************************************************** */ +#define RDR_VCARB3 0x05024C + +/* ***************************************************************************** */ +#define RDR_VCARB4 0x050250 + +/* ***************************************************************************** */ +#define RDR_VCARB5 0x050254 + +/* ***************************************************************************** */ +#define RDR_VCARB6 0x050258 + +/* ***************************************************************************** */ +#define RDR_VCARB7 0x05025C + +/* ***************************************************************************** */ +#define RDR_RDRSTAT0 0x050300 + +/* ***************************************************************************** */ +#define RDR_RDRSTAT1 0x050304 + +/* ***************************************************************************** */ +#define RDR_RDRCTL0 0x050308 + +/* ***************************************************************************** */ +#define RDR_RDRCTL1 0x05030C + +/* ***************************************************************************** */ +/* Transaction Layer Registers */ +/* ***************************************************************************** */ +#define RDR_TLSTAT0 0x050310 + +/* ***************************************************************************** */ +#define RDR_TLSTAT1 0x050314 + +/* ***************************************************************************** */ +#define RDR_TLCTL0 0x050318 +#define FLD_CFG_UR_CPL_MODE 0x00000040 +#define FLD_CFG_CORR_ERR_QUITE 0x00000020 +#define FLD_CFG_RCB_CK_EN 0x00000010 +#define FLD_CFG_BNDRY_CK_EN 0x00000008 +#define FLD_CFG_BYTE_EN_CK_EN 0x00000004 +#define FLD_CFG_RELAX_ORDER_MSK 0x00000002 +#define FLD_CFG_TAG_ORDER_EN 0x00000001 + +/* ***************************************************************************** */ +#define RDR_TLCTL1 0x05031C + +/* ***************************************************************************** */ +#define RDR_REQRCAL 0x050320 + +/* ***************************************************************************** */ +#define RDR_REQRCAU 0x050324 + +/* ***************************************************************************** */ +#define RDR_REQEPA 0x050328 + +/* ***************************************************************************** */ +#define RDR_REQCTRL 0x05032C + +/* ***************************************************************************** */ +#define RDR_REQSTAT 0x050330 + +/* ***************************************************************************** */ +#define RDR_TL_TEST 0x050334 + +/* ***************************************************************************** */ +#define RDR_VCR01_CTL 0x050348 + +/* ***************************************************************************** */ +#define RDR_VCR23_CTL 0x05034C + +/* ***************************************************************************** */ +#define RDR_RX_VCR0_FC 0x050350 + +/* ***************************************************************************** */ +#define RDR_RX_VCR1_FC 0x050354 + +/* ***************************************************************************** */ +#define RDR_RX_VCR2_FC 0x050358 + +/* ***************************************************************************** */ +#define RDR_RX_VCR3_FC 0x05035C + +/* ***************************************************************************** */ +/* Data Link Layer Registers */ +/* ***************************************************************************** */ +#define RDR_DLLSTAT 0x050360 + +/* ***************************************************************************** */ +#define RDR_DLLCTRL 0x050364 + +/* ***************************************************************************** */ +#define RDR_REPLAYTO 0x050368 + +/* ***************************************************************************** */ +#define RDR_ACKLATTO 0x05036C + +/* ***************************************************************************** */ +/* MAC Layer Registers */ +/* ***************************************************************************** */ +#define RDR_MACSTAT0 0x050380 + +/* ***************************************************************************** */ +#define RDR_MACSTAT1 0x050384 + +/* ***************************************************************************** */ +#define RDR_MACCTRL0 0x050388 + +/* ***************************************************************************** */ +#define RDR_MACCTRL1 0x05038C + +/* ***************************************************************************** */ +#define RDR_MACCTRL2 0x050390 + +/* ***************************************************************************** */ +#define RDR_MAC_LB_DATA 0x050394 + +/* ***************************************************************************** */ +#define RDR_L0S_EXIT_LAT 0x050398 + +/* ***************************************************************************** */ +/* DMAC */ +/* ***************************************************************************** */ +#define DMA1_PTR1 0x100000 /* DMA Current Ptr : Ch#1 */ + +/* ***************************************************************************** */ +#define DMA2_PTR1 0x100004 /* DMA Current Ptr : Ch#2 */ + +/* ***************************************************************************** */ +#define DMA3_PTR1 0x100008 /* DMA Current Ptr : Ch#3 */ + +/* ***************************************************************************** */ +#define DMA4_PTR1 0x10000C /* DMA Current Ptr : Ch#4 */ + +/* ***************************************************************************** */ +#define DMA5_PTR1 0x100010 /* DMA Current Ptr : Ch#5 */ + +/* ***************************************************************************** */ +#define DMA6_PTR1 0x100014 /* DMA Current Ptr : Ch#6 */ + +/* ***************************************************************************** */ +#define DMA7_PTR1 0x100018 /* DMA Current Ptr : Ch#7 */ + +/* ***************************************************************************** */ +#define DMA8_PTR1 0x10001C /* DMA Current Ptr : Ch#8 */ + +/* ***************************************************************************** */ +#define DMA9_PTR1 0x100020 /* DMA Current Ptr : Ch#9 */ + +/* ***************************************************************************** */ +#define DMA10_PTR1 0x100024 /* DMA Current Ptr : Ch#10 */ + +/* ***************************************************************************** */ +#define DMA11_PTR1 0x100028 /* DMA Current Ptr : Ch#11 */ + +/* ***************************************************************************** */ +#define DMA12_PTR1 0x10002C /* DMA Current Ptr : Ch#12 */ + +/* ***************************************************************************** */ +#define DMA13_PTR1 0x100030 /* DMA Current Ptr : Ch#13 */ + +/* ***************************************************************************** */ +#define DMA14_PTR1 0x100034 /* DMA Current Ptr : Ch#14 */ + +/* ***************************************************************************** */ +#define DMA15_PTR1 0x100038 /* DMA Current Ptr : Ch#15 */ + +/* ***************************************************************************** */ +#define DMA16_PTR1 0x10003C /* DMA Current Ptr : Ch#16 */ + +/* ***************************************************************************** */ +#define DMA17_PTR1 0x100040 /* DMA Current Ptr : Ch#17 */ + +/* ***************************************************************************** */ +#define DMA18_PTR1 0x100044 /* DMA Current Ptr : Ch#18 */ + +/* ***************************************************************************** */ +#define DMA19_PTR1 0x100048 /* DMA Current Ptr : Ch#19 */ + +/* ***************************************************************************** */ +#define DMA20_PTR1 0x10004C /* DMA Current Ptr : Ch#20 */ + +/* ***************************************************************************** */ +#define DMA21_PTR1 0x100050 /* DMA Current Ptr : Ch#21 */ + +/* ***************************************************************************** */ +#define DMA22_PTR1 0x100054 /* DMA Current Ptr : Ch#22 */ + +/* ***************************************************************************** */ +#define DMA23_PTR1 0x100058 /* DMA Current Ptr : Ch#23 */ + +/* ***************************************************************************** */ +#define DMA24_PTR1 0x10005C /* DMA Current Ptr : Ch#24 */ + +/* ***************************************************************************** */ +#define DMA25_PTR1 0x100060 /* DMA Current Ptr : Ch#25 */ + +/* ***************************************************************************** */ +#define DMA26_PTR1 0x100064 /* DMA Current Ptr : Ch#26 */ + +/* ***************************************************************************** */ +#define DMA1_PTR2 0x100080 /* DMA Tab Ptr : Ch#1 */ + +/* ***************************************************************************** */ +#define DMA2_PTR2 0x100084 /* DMA Tab Ptr : Ch#2 */ + +/* ***************************************************************************** */ +#define DMA3_PTR2 0x100088 /* DMA Tab Ptr : Ch#3 */ + +/* ***************************************************************************** */ +#define DMA4_PTR2 0x10008C /* DMA Tab Ptr : Ch#4 */ + +/* ***************************************************************************** */ +#define DMA5_PTR2 0x100090 /* DMA Tab Ptr : Ch#5 */ + +/* ***************************************************************************** */ +#define DMA6_PTR2 0x100094 /* DMA Tab Ptr : Ch#6 */ + +/* ***************************************************************************** */ +#define DMA7_PTR2 0x100098 /* DMA Tab Ptr : Ch#7 */ + +/* ***************************************************************************** */ +#define DMA8_PTR2 0x10009C /* DMA Tab Ptr : Ch#8 */ + +/* ***************************************************************************** */ +#define DMA9_PTR2 0x1000A0 /* DMA Tab Ptr : Ch#9 */ + +/* ***************************************************************************** */ +#define DMA10_PTR2 0x1000A4 /* DMA Tab Ptr : Ch#10 */ + +/* ***************************************************************************** */ +#define DMA11_PTR2 0x1000A8 /* DMA Tab Ptr : Ch#11 */ + +/* ***************************************************************************** */ +#define DMA12_PTR2 0x1000AC /* DMA Tab Ptr : Ch#12 */ + +/* ***************************************************************************** */ +#define DMA13_PTR2 0x1000B0 /* DMA Tab Ptr : Ch#13 */ + +/* ***************************************************************************** */ +#define DMA14_PTR2 0x1000B4 /* DMA Tab Ptr : Ch#14 */ + +/* ***************************************************************************** */ +#define DMA15_PTR2 0x1000B8 /* DMA Tab Ptr : Ch#15 */ + +/* ***************************************************************************** */ +#define DMA16_PTR2 0x1000BC /* DMA Tab Ptr : Ch#16 */ + +/* ***************************************************************************** */ +#define DMA17_PTR2 0x1000C0 /* DMA Tab Ptr : Ch#17 */ + +/* ***************************************************************************** */ +#define DMA18_PTR2 0x1000C4 /* DMA Tab Ptr : Ch#18 */ + +/* ***************************************************************************** */ +#define DMA19_PTR2 0x1000C8 /* DMA Tab Ptr : Ch#19 */ + +/* ***************************************************************************** */ +#define DMA20_PTR2 0x1000CC /* DMA Tab Ptr : Ch#20 */ + +/* ***************************************************************************** */ +#define DMA21_PTR2 0x1000D0 /* DMA Tab Ptr : Ch#21 */ + +/* ***************************************************************************** */ +#define DMA22_PTR2 0x1000D4 /* DMA Tab Ptr : Ch#22 */ + +/* ***************************************************************************** */ +#define DMA23_PTR2 0x1000D8 /* DMA Tab Ptr : Ch#23 */ + +/* ***************************************************************************** */ +#define DMA24_PTR2 0x1000DC /* DMA Tab Ptr : Ch#24 */ + +/* ***************************************************************************** */ +#define DMA25_PTR2 0x1000E0 /* DMA Tab Ptr : Ch#25 */ + +/* ***************************************************************************** */ +#define DMA26_PTR2 0x1000E4 /* DMA Tab Ptr : Ch#26 */ + +/* ***************************************************************************** */ +#define DMA1_CNT1 0x100100 /* DMA BuFFer Size : Ch#1 */ + +/* ***************************************************************************** */ +#define DMA2_CNT1 0x100104 /* DMA BuFFer Size : Ch#2 */ + +/* ***************************************************************************** */ +#define DMA3_CNT1 0x100108 /* DMA BuFFer Size : Ch#3 */ + +/* ***************************************************************************** */ +#define DMA4_CNT1 0x10010C /* DMA BuFFer Size : Ch#4 */ + +/* ***************************************************************************** */ +#define DMA5_CNT1 0x100110 /* DMA BuFFer Size : Ch#5 */ + +/* ***************************************************************************** */ +#define DMA6_CNT1 0x100114 /* DMA BuFFer Size : Ch#6 */ + +/* ***************************************************************************** */ +#define DMA7_CNT1 0x100118 /* DMA BuFFer Size : Ch#7 */ + +/* ***************************************************************************** */ +#define DMA8_CNT1 0x10011C /* DMA BuFFer Size : Ch#8 */ + +/* ***************************************************************************** */ +#define DMA9_CNT1 0x100120 /* DMA BuFFer Size : Ch#9 */ + +/* ***************************************************************************** */ +#define DMA10_CNT1 0x100124 /* DMA BuFFer Size : Ch#10 */ + +/* ***************************************************************************** */ +#define DMA11_CNT1 0x100128 /* DMA BuFFer Size : Ch#11 */ + +/* ***************************************************************************** */ +#define DMA12_CNT1 0x10012C /* DMA BuFFer Size : Ch#12 */ + +/* ***************************************************************************** */ +#define DMA13_CNT1 0x100130 /* DMA BuFFer Size : Ch#13 */ + +/* ***************************************************************************** */ +#define DMA14_CNT1 0x100134 /* DMA BuFFer Size : Ch#14 */ + +/* ***************************************************************************** */ +#define DMA15_CNT1 0x100138 /* DMA BuFFer Size : Ch#15 */ + +/* ***************************************************************************** */ +#define DMA16_CNT1 0x10013C /* DMA BuFFer Size : Ch#16 */ + +/* ***************************************************************************** */ +#define DMA17_CNT1 0x100140 /* DMA BuFFer Size : Ch#17 */ + +/* ***************************************************************************** */ +#define DMA18_CNT1 0x100144 /* DMA BuFFer Size : Ch#18 */ + +/* ***************************************************************************** */ +#define DMA19_CNT1 0x100148 /* DMA BuFFer Size : Ch#19 */ + +/* ***************************************************************************** */ +#define DMA20_CNT1 0x10014C /* DMA BuFFer Size : Ch#20 */ + +/* ***************************************************************************** */ +#define DMA21_CNT1 0x100150 /* DMA BuFFer Size : Ch#21 */ + +/* ***************************************************************************** */ +#define DMA22_CNT1 0x100154 /* DMA BuFFer Size : Ch#22 */ + +/* ***************************************************************************** */ +#define DMA23_CNT1 0x100158 /* DMA BuFFer Size : Ch#23 */ + +/* ***************************************************************************** */ +#define DMA24_CNT1 0x10015C /* DMA BuFFer Size : Ch#24 */ + +/* ***************************************************************************** */ +#define DMA25_CNT1 0x100160 /* DMA BuFFer Size : Ch#25 */ + +/* ***************************************************************************** */ +#define DMA26_CNT1 0x100164 /* DMA BuFFer Size : Ch#26 */ + +/* ***************************************************************************** */ +#define DMA1_CNT2 0x100180 /* DMA Table Size : Ch#1 */ + +/* ***************************************************************************** */ +#define DMA2_CNT2 0x100184 /* DMA Table Size : Ch#2 */ + +/* ***************************************************************************** */ +#define DMA3_CNT2 0x100188 /* DMA Table Size : Ch#3 */ + +/* ***************************************************************************** */ +#define DMA4_CNT2 0x10018C /* DMA Table Size : Ch#4 */ + +/* ***************************************************************************** */ +#define DMA5_CNT2 0x100190 /* DMA Table Size : Ch#5 */ + +/* ***************************************************************************** */ +#define DMA6_CNT2 0x100194 /* DMA Table Size : Ch#6 */ + +/* ***************************************************************************** */ +#define DMA7_CNT2 0x100198 /* DMA Table Size : Ch#7 */ + +/* ***************************************************************************** */ +#define DMA8_CNT2 0x10019C /* DMA Table Size : Ch#8 */ + +/* ***************************************************************************** */ +#define DMA9_CNT2 0x1001A0 /* DMA Table Size : Ch#9 */ + +/* ***************************************************************************** */ +#define DMA10_CNT2 0x1001A4 /* DMA Table Size : Ch#10 */ + +/* ***************************************************************************** */ +#define DMA11_CNT2 0x1001A8 /* DMA Table Size : Ch#11 */ + +/* ***************************************************************************** */ +#define DMA12_CNT2 0x1001AC /* DMA Table Size : Ch#12 */ + +/* ***************************************************************************** */ +#define DMA13_CNT2 0x1001B0 /* DMA Table Size : Ch#13 */ + +/* ***************************************************************************** */ +#define DMA14_CNT2 0x1001B4 /* DMA Table Size : Ch#14 */ + +/* ***************************************************************************** */ +#define DMA15_CNT2 0x1001B8 /* DMA Table Size : Ch#15 */ + +/* ***************************************************************************** */ +#define DMA16_CNT2 0x1001BC /* DMA Table Size : Ch#16 */ + +/* ***************************************************************************** */ +#define DMA17_CNT2 0x1001C0 /* DMA Table Size : Ch#17 */ + +/* ***************************************************************************** */ +#define DMA18_CNT2 0x1001C4 /* DMA Table Size : Ch#18 */ + +/* ***************************************************************************** */ +#define DMA19_CNT2 0x1001C8 /* DMA Table Size : Ch#19 */ + +/* ***************************************************************************** */ +#define DMA20_CNT2 0x1001CC /* DMA Table Size : Ch#20 */ + +/* ***************************************************************************** */ +#define DMA21_CNT2 0x1001D0 /* DMA Table Size : Ch#21 */ + +/* ***************************************************************************** */ +#define DMA22_CNT2 0x1001D4 /* DMA Table Size : Ch#22 */ + +/* ***************************************************************************** */ +#define DMA23_CNT2 0x1001D8 /* DMA Table Size : Ch#23 */ + +/* ***************************************************************************** */ +#define DMA24_CNT2 0x1001DC /* DMA Table Size : Ch#24 */ + +/* ***************************************************************************** */ +#define DMA25_CNT2 0x1001E0 /* DMA Table Size : Ch#25 */ + +/* ***************************************************************************** */ +#define DMA26_CNT2 0x1001E4 /* DMA Table Size : Ch#26 */ + +/* ***************************************************************************** */ + /* ITG */ +/* ***************************************************************************** */ +#define TM_CNT_LDW 0x110000 /* Timer : Counter low */ + +/* ***************************************************************************** */ +#define TM_CNT_UW 0x110004 /* Timer : Counter high word */ + +/* ***************************************************************************** */ +#define TM_LMT_LDW 0x110008 /* Timer : Limit low */ + +/* ***************************************************************************** */ +#define TM_LMT_UW 0x11000C /* Timer : Limit high word */ + +/* ***************************************************************************** */ +#define GP0_IO 0x110010 /* GPIO output enables data I/O */ +#define FLD_GP_OE 0x00FF0000 /* GPIO: GP_OE output enable */ +#define FLD_GP_IN 0x0000FF00 /* GPIO: GP_IN status */ +#define FLD_GP_OUT 0x000000FF /* GPIO: GP_OUT control */ + +/* ***************************************************************************** */ +#define GPIO_ISM 0x110014 /* GPIO interrupt sensitivity mode */ +#define FLD_GP_ISM_SNS 0x00000070 +#define FLD_GP_ISM_POL 0x00000007 + +/* ***************************************************************************** */ +#define SOFT_RESET 0x11001C /* Output system reset reg */ +#define FLD_PECOS_SOFT_RESET 0x00000001 + +/* ***************************************************************************** */ +#define MC416_RWD 0x110020 /* MC416 GPIO[18:3] pin */ +#define MC416_OEN 0x110024 /* Output enable of GPIO[18:3] */ +#define MC416_CTL 0x110028 + +/* ***************************************************************************** */ +#define ALT_PIN_OUT_SEL 0x11002C /* Alternate GPIO output select */ + +#define FLD_ALT_GPIO_OUT_SEL 0xF0000000 +/* 0 Disabled <-- default */ +/* 1 GPIO[0] */ +/* 2 GPIO[10] */ +/* 3 VIP_656_DATA_VAL */ +/* 4 VIP_656_DATA[0] */ +/* 5 VIP_656_CLK */ +/* 6 VIP_656_DATA_EXT[1] */ +/* 7 VIP_656_DATA_EXT[0] */ +/* 8 ATT_IF */ + +#define FLD_AUX_PLL_CLK_ALT_SEL 0x0F000000 +/* 0 AUX_PLL_CLK<-- default */ +/* 1 GPIO[2] */ +/* 2 GPIO[10] */ +/* 3 VIP_656_DATA_VAL */ +/* 4 VIP_656_DATA[0] */ +/* 5 VIP_656_CLK */ +/* 6 VIP_656_DATA_EXT[1] */ +/* 7 VIP_656_DATA_EXT[0] */ + +#define FLD_IR_TX_ALT_SEL 0x00F00000 +/* 0 IR_TX <-- default */ +/* 1 GPIO[1] */ +/* 2 GPIO[10] */ +/* 3 VIP_656_DATA_VAL */ +/* 4 VIP_656_DATA[0] */ +/* 5 VIP_656_CLK */ +/* 6 VIP_656_DATA_EXT[1] */ +/* 7 VIP_656_DATA_EXT[0] */ + +#define FLD_IR_RX_ALT_SEL 0x000F0000 +/* 0 IR_RX <-- default */ +/* 1 GPIO[0] */ +/* 2 GPIO[10] */ +/* 3 VIP_656_DATA_VAL */ +/* 4 VIP_656_DATA[0] */ +/* 5 VIP_656_CLK */ +/* 6 VIP_656_DATA_EXT[1] */ +/* 7 VIP_656_DATA_EXT[0] */ + +#define FLD_GPIO10_ALT_SEL 0x0000F000 +/* 0 GPIO[10] <-- default */ +/* 1 GPIO[0] */ +/* 2 GPIO[10] */ +/* 3 VIP_656_DATA_VAL */ +/* 4 VIP_656_DATA[0] */ +/* 5 VIP_656_CLK */ +/* 6 VIP_656_DATA_EXT[1] */ +/* 7 VIP_656_DATA_EXT[0] */ + +#define FLD_GPIO2_ALT_SEL 0x00000F00 +/* 0 GPIO[2] <-- default */ +/* 1 GPIO[1] */ +/* 2 GPIO[10] */ +/* 3 VIP_656_DATA_VAL */ +/* 4 VIP_656_DATA[0] */ +/* 5 VIP_656_CLK */ +/* 6 VIP_656_DATA_EXT[1] */ +/* 7 VIP_656_DATA_EXT[0] */ + +#define FLD_GPIO1_ALT_SEL 0x000000F0 +/* 0 GPIO[1] <-- default */ +/* 1 GPIO[0] */ +/* 2 GPIO[10] */ +/* 3 VIP_656_DATA_VAL */ +/* 4 VIP_656_DATA[0] */ +/* 5 VIP_656_CLK */ +/* 6 VIP_656_DATA_EXT[1] */ +/* 7 VIP_656_DATA_EXT[0] */ + +#define FLD_GPIO0_ALT_SEL 0x0000000F +/* 0 GPIO[0] <-- default */ +/* 1 GPIO[1] */ +/* 2 GPIO[10] */ +/* 3 VIP_656_DATA_VAL */ +/* 4 VIP_656_DATA[0] */ +/* 5 VIP_656_CLK */ +/* 6 VIP_656_DATA_EXT[1] */ +/* 7 VIP_656_DATA_EXT[0] */ + +#define ALT_PIN_IN_SEL 0x110030 /* Alternate GPIO input select */ + +#define FLD_GPIO10_ALT_IN_SEL 0x0000F000 +/* 0 GPIO[10] <-- default */ +/* 1 IR_RX */ +/* 2 IR_TX */ +/* 3 AUX_PLL_CLK */ +/* 4 IF_ATT_SEL */ +/* 5 GPIO[0] */ +/* 6 GPIO[1] */ +/* 7 GPIO[2] */ + +#define FLD_GPIO2_ALT_IN_SEL 0x00000F00 +/* 0 GPIO[2] <-- default */ +/* 1 IR_RX */ +/* 2 IR_TX */ +/* 3 AUX_PLL_CLK */ +/* 4 IF_ATT_SEL */ + +#define FLD_GPIO1_ALT_IN_SEL 0x000000F0 +/* 0 GPIO[1] <-- default */ +/* 1 IR_RX */ +/* 2 IR_TX */ +/* 3 AUX_PLL_CLK */ +/* 4 IF_ATT_SEL */ + +#define FLD_GPIO0_ALT_IN_SEL 0x0000000F +/* 0 GPIO[0] <-- default */ +/* 1 IR_RX */ +/* 2 IR_TX */ +/* 3 AUX_PLL_CLK */ +/* 4 IF_ATT_SEL */ + +/* ***************************************************************************** */ +#define TEST_BUS_CTL1 0x110040 /* Test bus control register #1 */ + +/* ***************************************************************************** */ +#define TEST_BUS_CTL2 0x110044 /* Test bus control register #2 */ + +/* ***************************************************************************** */ +#define CLK_DELAY 0x110048 /* Clock delay */ +#define FLD_MOE_CLK_DIS 0x80000000 /* Disable MoE clock */ + +/* ***************************************************************************** */ +#define PAD_CTRL 0x110068 /* Pad drive strength control */ + +/* ***************************************************************************** */ +#define MBIST_CTRL 0x110050 /* SRAM memory built-in self test control */ + +/* ***************************************************************************** */ +#define MBIST_STAT 0x110054 /* SRAM memory built-in self test status */ + +/* ***************************************************************************** */ +/* PLL registers */ +/* ***************************************************************************** */ +#define PLL_A_INT_FRAC 0x110088 +#define PLL_A_POST_STAT_BIST 0x11008C +#define PLL_B_INT_FRAC 0x110090 +#define PLL_B_POST_STAT_BIST 0x110094 +#define PLL_C_INT_FRAC 0x110098 +#define PLL_C_POST_STAT_BIST 0x11009C +#define PLL_D_INT_FRAC 0x1100A0 +#define PLL_D_POST_STAT_BIST 0x1100A4 + +#define CLK_RST 0x11002C +#define FLD_VID_I_CLK_NOE 0x00001000 +#define FLD_VID_J_CLK_NOE 0x00002000 +#define FLD_USE_ALT_PLL_REF 0x00004000 + +#define VID_CH_MODE_SEL 0x110078 +#define VID_CH_CLK_SEL 0x11007C + +/* ***************************************************************************** */ +#define VBI_A_DMA 0x130008 /* VBI A DMA data port */ + +/* ***************************************************************************** */ +#define VID_A_VIP_CTL 0x130080 /* Video A VIP format control */ +#define FLD_VIP_MODE 0x00000001 + +/* ***************************************************************************** */ +#define VID_A_PIXEL_FRMT 0x130084 /* Video A pixel format */ +#define FLD_VID_A_GAMMA_DIS 0x00000008 +#define FLD_VID_A_FORMAT 0x00000007 +#define FLD_VID_A_GAMMA_FACTOR 0x00000010 + +/* ***************************************************************************** */ +#define VID_A_VBI_CTL 0x130088 /* Video A VBI miscellaneous control */ +#define FLD_VID_A_VIP_EXT 0x00000003 + +/* ***************************************************************************** */ +#define VID_B_DMA 0x130100 /* Video B DMA data port */ + +/* ***************************************************************************** */ +#define VBI_B_DMA 0x130108 /* VBI B DMA data port */ + +/* ***************************************************************************** */ +#define VID_B_SRC_SEL 0x130144 /* Video B source select */ +#define FLD_VID_B_SRC_SEL 0x00000000 + +/* ***************************************************************************** */ +#define VID_B_LNGTH 0x130150 /* Video B line length */ +#define FLD_VID_B_LN_LNGTH 0x00000FFF + +/* ***************************************************************************** */ +#define VID_B_VIP_CTL 0x130180 /* Video B VIP format control */ + +/* ***************************************************************************** */ +#define VID_B_PIXEL_FRMT 0x130184 /* Video B pixel format */ +#define FLD_VID_B_GAMMA_DIS 0x00000008 +#define FLD_VID_B_FORMAT 0x00000007 +#define FLD_VID_B_GAMMA_FACTOR 0x00000010 + +/* ***************************************************************************** */ +#define VID_C_DMA 0x130200 /* Video C DMA data port */ + +/* ***************************************************************************** */ +#define VID_C_LNGTH 0x130250 /* Video C line length */ +#define FLD_VID_C_LN_LNGTH 0x00000FFF + +/* ***************************************************************************** */ +/* Video Destination Channels */ +/* ***************************************************************************** */ + +#define VID_DST_A_GPCNT 0x130020 /* Video A general purpose counter */ +#define VID_DST_B_GPCNT 0x130120 /* Video B general purpose counter */ +#define VID_DST_C_GPCNT 0x130220 /* Video C general purpose counter */ +#define VID_DST_D_GPCNT 0x130320 /* Video D general purpose counter */ +#define VID_DST_E_GPCNT 0x130420 /* Video E general purpose counter */ +#define VID_DST_F_GPCNT 0x130520 /* Video F general purpose counter */ +#define VID_DST_G_GPCNT 0x130620 /* Video G general purpose counter */ +#define VID_DST_H_GPCNT 0x130720 /* Video H general purpose counter */ + +/* ***************************************************************************** */ + +#define VID_DST_A_GPCNT_CTL 0x130030 /* Video A general purpose control */ +#define VID_DST_B_GPCNT_CTL 0x130130 /* Video B general purpose control */ +#define VID_DST_C_GPCNT_CTL 0x130230 /* Video C general purpose control */ +#define VID_DST_D_GPCNT_CTL 0x130330 /* Video D general purpose control */ +#define VID_DST_E_GPCNT_CTL 0x130430 /* Video E general purpose control */ +#define VID_DST_F_GPCNT_CTL 0x130530 /* Video F general purpose control */ +#define VID_DST_G_GPCNT_CTL 0x130630 /* Video G general purpose control */ +#define VID_DST_H_GPCNT_CTL 0x130730 /* Video H general purpose control */ + +/* ***************************************************************************** */ + +#define VID_DST_A_DMA_CTL 0x130040 /* Video A DMA control */ +#define VID_DST_B_DMA_CTL 0x130140 /* Video B DMA control */ +#define VID_DST_C_DMA_CTL 0x130240 /* Video C DMA control */ +#define VID_DST_D_DMA_CTL 0x130340 /* Video D DMA control */ +#define VID_DST_E_DMA_CTL 0x130440 /* Video E DMA control */ +#define VID_DST_F_DMA_CTL 0x130540 /* Video F DMA control */ +#define VID_DST_G_DMA_CTL 0x130640 /* Video G DMA control */ +#define VID_DST_H_DMA_CTL 0x130740 /* Video H DMA control */ + +#define FLD_VID_RISC_EN 0x00000010 +#define FLD_VID_FIFO_EN 0x00000001 + +/* ***************************************************************************** */ + +#define VID_DST_A_VIP_CTL 0x130080 /* Video A VIP control */ +#define VID_DST_B_VIP_CTL 0x130180 /* Video B VIP control */ +#define VID_DST_C_VIP_CTL 0x130280 /* Video C VIP control */ +#define VID_DST_D_VIP_CTL 0x130380 /* Video D VIP control */ +#define VID_DST_E_VIP_CTL 0x130480 /* Video E VIP control */ +#define VID_DST_F_VIP_CTL 0x130580 /* Video F VIP control */ +#define VID_DST_G_VIP_CTL 0x130680 /* Video G VIP control */ +#define VID_DST_H_VIP_CTL 0x130780 /* Video H VIP control */ + +/* ***************************************************************************** */ + +#define VID_DST_A_PIX_FRMT 0x130084 /* Video A Pixel format */ +#define VID_DST_B_PIX_FRMT 0x130184 /* Video B Pixel format */ +#define VID_DST_C_PIX_FRMT 0x130284 /* Video C Pixel format */ +#define VID_DST_D_PIX_FRMT 0x130384 /* Video D Pixel format */ +#define VID_DST_E_PIX_FRMT 0x130484 /* Video E Pixel format */ +#define VID_DST_F_PIX_FRMT 0x130584 /* Video F Pixel format */ +#define VID_DST_G_PIX_FRMT 0x130684 /* Video G Pixel format */ +#define VID_DST_H_PIX_FRMT 0x130784 /* Video H Pixel format */ + +/* ***************************************************************************** */ +/* Video Source Channels */ +/* ***************************************************************************** */ + +#define VID_SRC_A_GPCNT_CTL 0x130804 /* Video A general purpose control */ +#define VID_SRC_B_GPCNT_CTL 0x130904 /* Video B general purpose control */ +#define VID_SRC_C_GPCNT_CTL 0x130A04 /* Video C general purpose control */ +#define VID_SRC_D_GPCNT_CTL 0x130B04 /* Video D general purpose control */ +#define VID_SRC_E_GPCNT_CTL 0x130C04 /* Video E general purpose control */ +#define VID_SRC_F_GPCNT_CTL 0x130D04 /* Video F general purpose control */ +#define VID_SRC_I_GPCNT_CTL 0x130E04 /* Video I general purpose control */ +#define VID_SRC_J_GPCNT_CTL 0x130F04 /* Video J general purpose control */ + +/* ***************************************************************************** */ + +#define VID_SRC_A_GPCNT 0x130808 /* Video A general purpose counter */ +#define VID_SRC_B_GPCNT 0x130908 /* Video B general purpose counter */ +#define VID_SRC_C_GPCNT 0x130A08 /* Video C general purpose counter */ +#define VID_SRC_D_GPCNT 0x130B08 /* Video D general purpose counter */ +#define VID_SRC_E_GPCNT 0x130C08 /* Video E general purpose counter */ +#define VID_SRC_F_GPCNT 0x130D08 /* Video F general purpose counter */ +#define VID_SRC_I_GPCNT 0x130E08 /* Video I general purpose counter */ +#define VID_SRC_J_GPCNT 0x130F08 /* Video J general purpose counter */ + +/* ***************************************************************************** */ + +#define VID_SRC_A_DMA_CTL 0x13080C /* Video A DMA control */ +#define VID_SRC_B_DMA_CTL 0x13090C /* Video B DMA control */ +#define VID_SRC_C_DMA_CTL 0x130A0C /* Video C DMA control */ +#define VID_SRC_D_DMA_CTL 0x130B0C /* Video D DMA control */ +#define VID_SRC_E_DMA_CTL 0x130C0C /* Video E DMA control */ +#define VID_SRC_F_DMA_CTL 0x130D0C /* Video F DMA control */ +#define VID_SRC_I_DMA_CTL 0x130E0C /* Video I DMA control */ +#define VID_SRC_J_DMA_CTL 0x130F0C /* Video J DMA control */ + +#define FLD_APB_RISC_EN 0x00000010 +#define FLD_APB_FIFO_EN 0x00000001 + +/* ***************************************************************************** */ + +#define VID_SRC_A_FMT_CTL 0x130810 /* Video A format control */ +#define VID_SRC_B_FMT_CTL 0x130910 /* Video B format control */ +#define VID_SRC_C_FMT_CTL 0x130A10 /* Video C format control */ +#define VID_SRC_D_FMT_CTL 0x130B10 /* Video D format control */ +#define VID_SRC_E_FMT_CTL 0x130C10 /* Video E format control */ +#define VID_SRC_F_FMT_CTL 0x130D10 /* Video F format control */ +#define VID_SRC_I_FMT_CTL 0x130E10 /* Video I format control */ +#define VID_SRC_J_FMT_CTL 0x130F10 /* Video J format control */ + +/* ***************************************************************************** */ + +#define VID_SRC_A_ACTIVE_CTL1 0x130814 /* Video A active control 1 */ +#define VID_SRC_B_ACTIVE_CTL1 0x130914 /* Video B active control 1 */ +#define VID_SRC_C_ACTIVE_CTL1 0x130A14 /* Video C active control 1 */ +#define VID_SRC_D_ACTIVE_CTL1 0x130B14 /* Video D active control 1 */ +#define VID_SRC_E_ACTIVE_CTL1 0x130C14 /* Video E active control 1 */ +#define VID_SRC_F_ACTIVE_CTL1 0x130D14 /* Video F active control 1 */ +#define VID_SRC_I_ACTIVE_CTL1 0x130E14 /* Video I active control 1 */ +#define VID_SRC_J_ACTIVE_CTL1 0x130F14 /* Video J active control 1 */ + +/* ***************************************************************************** */ + +#define VID_SRC_A_ACTIVE_CTL2 0x130818 /* Video A active control 2 */ +#define VID_SRC_B_ACTIVE_CTL2 0x130918 /* Video B active control 2 */ +#define VID_SRC_C_ACTIVE_CTL2 0x130A18 /* Video C active control 2 */ +#define VID_SRC_D_ACTIVE_CTL2 0x130B18 /* Video D active control 2 */ +#define VID_SRC_E_ACTIVE_CTL2 0x130C18 /* Video E active control 2 */ +#define VID_SRC_F_ACTIVE_CTL2 0x130D18 /* Video F active control 2 */ +#define VID_SRC_I_ACTIVE_CTL2 0x130E18 /* Video I active control 2 */ +#define VID_SRC_J_ACTIVE_CTL2 0x130F18 /* Video J active control 2 */ + +/* ***************************************************************************** */ + +#define VID_SRC_A_CDT_SZ 0x13081C /* Video A CDT size */ +#define VID_SRC_B_CDT_SZ 0x13091C /* Video B CDT size */ +#define VID_SRC_C_CDT_SZ 0x130A1C /* Video C CDT size */ +#define VID_SRC_D_CDT_SZ 0x130B1C /* Video D CDT size */ +#define VID_SRC_E_CDT_SZ 0x130C1C /* Video E CDT size */ +#define VID_SRC_F_CDT_SZ 0x130D1C /* Video F CDT size */ +#define VID_SRC_I_CDT_SZ 0x130E1C /* Video I CDT size */ +#define VID_SRC_J_CDT_SZ 0x130F1C /* Video J CDT size */ + +/* ***************************************************************************** */ +/* Audio I/F */ +/* ***************************************************************************** */ +#define AUD_DST_A_DMA 0x140000 /* Audio Int A DMA data port */ +#define AUD_SRC_A_DMA 0x140008 /* Audio Int A DMA data port */ + +#define AUD_A_GPCNT 0x140010 /* Audio Int A gp counter */ +#define FLD_AUD_A_GP_CNT 0x0000FFFF + +#define AUD_A_GPCNT_CTL 0x140014 /* Audio Int A gp control */ + +#define AUD_A_LNGTH 0x140018 /* Audio Int A line length */ + +#define AUD_A_CFG 0x14001C /* Audio Int A configuration */ + +/* ***************************************************************************** */ +#define AUD_DST_B_DMA 0x140100 /* Audio Int B DMA data port */ +#define AUD_SRC_B_DMA 0x140108 /* Audio Int B DMA data port */ + +#define AUD_B_GPCNT 0x140110 /* Audio Int B gp counter */ +#define FLD_AUD_B_GP_CNT 0x0000FFFF + +#define AUD_B_GPCNT_CTL 0x140114 /* Audio Int B gp control */ + +#define AUD_B_LNGTH 0x140118 /* Audio Int B line length */ + +#define AUD_B_CFG 0x14011C /* Audio Int B configuration */ + +/* ***************************************************************************** */ +#define AUD_DST_C_DMA 0x140200 /* Audio Int C DMA data port */ +#define AUD_SRC_C_DMA 0x140208 /* Audio Int C DMA data port */ + +#define AUD_C_GPCNT 0x140210 /* Audio Int C gp counter */ +#define FLD_AUD_C_GP_CNT 0x0000FFFF + +#define AUD_C_GPCNT_CTL 0x140214 /* Audio Int C gp control */ + +#define AUD_C_LNGTH 0x140218 /* Audio Int C line length */ + +#define AUD_C_CFG 0x14021C /* Audio Int C configuration */ + +/* ***************************************************************************** */ +#define AUD_DST_D_DMA 0x140300 /* Audio Int D DMA data port */ +#define AUD_SRC_D_DMA 0x140308 /* Audio Int D DMA data port */ + +#define AUD_D_GPCNT 0x140310 /* Audio Int D gp counter */ +#define FLD_AUD_D_GP_CNT 0x0000FFFF + +#define AUD_D_GPCNT_CTL 0x140314 /* Audio Int D gp control */ + +#define AUD_D_LNGTH 0x140318 /* Audio Int D line length */ + +#define AUD_D_CFG 0x14031C /* Audio Int D configuration */ + +/* ***************************************************************************** */ +#define AUD_SRC_E_DMA 0x140400 /* Audio Int E DMA data port */ + +#define AUD_E_GPCNT 0x140410 /* Audio Int E gp counter */ +#define FLD_AUD_E_GP_CNT 0x0000FFFF + +#define AUD_E_GPCNT_CTL 0x140414 /* Audio Int E gp control */ + +#define AUD_E_CFG 0x14041C /* Audio Int E configuration */ + +/* ***************************************************************************** */ + +#define FLD_AUD_DST_LN_LNGTH 0x00000FFF + +#define FLD_AUD_DST_PK_MODE 0x00004000 + +#define FLD_AUD_CLK_ENABLE 0x00000200 + +#define FLD_AUD_MASTER_MODE 0x00000002 + +#define FLD_AUD_SONY_MODE 0x00000001 + +#define FLD_AUD_CLK_SELECT_PLL_D 0x00001800 + +#define FLD_AUD_DST_ENABLE 0x00020000 + +#define FLD_AUD_SRC_ENABLE 0x00010000 + +/* ***************************************************************************** */ +#define AUD_INT_DMA_CTL 0x140500 /* Audio Int DMA control */ + +#define FLD_AUD_SRC_E_RISC_EN 0x00008000 +#define FLD_AUD_SRC_C_RISC_EN 0x00004000 +#define FLD_AUD_SRC_B_RISC_EN 0x00002000 +#define FLD_AUD_SRC_A_RISC_EN 0x00001000 + +#define FLD_AUD_DST_D_RISC_EN 0x00000800 +#define FLD_AUD_DST_C_RISC_EN 0x00000400 +#define FLD_AUD_DST_B_RISC_EN 0x00000200 +#define FLD_AUD_DST_A_RISC_EN 0x00000100 + +#define FLD_AUD_SRC_E_FIFO_EN 0x00000080 +#define FLD_AUD_SRC_C_FIFO_EN 0x00000040 +#define FLD_AUD_SRC_B_FIFO_EN 0x00000020 +#define FLD_AUD_SRC_A_FIFO_EN 0x00000010 + +#define FLD_AUD_DST_D_FIFO_EN 0x00000008 +#define FLD_AUD_DST_C_FIFO_EN 0x00000004 +#define FLD_AUD_DST_B_FIFO_EN 0x00000002 +#define FLD_AUD_DST_A_FIFO_EN 0x00000001 + +/* ***************************************************************************** */ +/* */ +/* Mobilygen Interface Registers */ +/* */ +/* ***************************************************************************** */ +/* Mobilygen Interface A */ +/* ***************************************************************************** */ +#define MB_IF_A_DMA 0x150000 /* MBIF A DMA data port */ +#define MB_IF_A_GPCN 0x150008 /* MBIF A GP counter */ +#define MB_IF_A_GPCN_CTRL 0x15000C +#define MB_IF_A_DMA_CTRL 0x150010 +#define MB_IF_A_LENGTH 0x150014 +#define MB_IF_A_HDMA_XFER_SZ 0x150018 +#define MB_IF_A_HCMD 0x15001C +#define MB_IF_A_HCONFIG 0x150020 +#define MB_IF_A_DATA_STRUCT_0 0x150024 +#define MB_IF_A_DATA_STRUCT_1 0x150028 +#define MB_IF_A_DATA_STRUCT_2 0x15002C +#define MB_IF_A_DATA_STRUCT_3 0x150030 +#define MB_IF_A_DATA_STRUCT_4 0x150034 +#define MB_IF_A_DATA_STRUCT_5 0x150038 +#define MB_IF_A_DATA_STRUCT_6 0x15003C +#define MB_IF_A_DATA_STRUCT_7 0x150040 +#define MB_IF_A_DATA_STRUCT_8 0x150044 +#define MB_IF_A_DATA_STRUCT_9 0x150048 +#define MB_IF_A_DATA_STRUCT_A 0x15004C +#define MB_IF_A_DATA_STRUCT_B 0x150050 +#define MB_IF_A_DATA_STRUCT_C 0x150054 +#define MB_IF_A_DATA_STRUCT_D 0x150058 +#define MB_IF_A_DATA_STRUCT_E 0x15005C +#define MB_IF_A_DATA_STRUCT_F 0x150060 +/* ***************************************************************************** */ +/* Mobilygen Interface B */ +/* ***************************************************************************** */ +#define MB_IF_B_DMA 0x160000 /* MBIF A DMA data port */ +#define MB_IF_B_GPCN 0x160008 /* MBIF A GP counter */ +#define MB_IF_B_GPCN_CTRL 0x16000C +#define MB_IF_B_DMA_CTRL 0x160010 +#define MB_IF_B_LENGTH 0x160014 +#define MB_IF_B_HDMA_XFER_SZ 0x160018 +#define MB_IF_B_HCMD 0x16001C +#define MB_IF_B_HCONFIG 0x160020 +#define MB_IF_B_DATA_STRUCT_0 0x160024 +#define MB_IF_B_DATA_STRUCT_1 0x160028 +#define MB_IF_B_DATA_STRUCT_2 0x16002C +#define MB_IF_B_DATA_STRUCT_3 0x160030 +#define MB_IF_B_DATA_STRUCT_4 0x160034 +#define MB_IF_B_DATA_STRUCT_5 0x160038 +#define MB_IF_B_DATA_STRUCT_6 0x16003C +#define MB_IF_B_DATA_STRUCT_7 0x160040 +#define MB_IF_B_DATA_STRUCT_8 0x160044 +#define MB_IF_B_DATA_STRUCT_9 0x160048 +#define MB_IF_B_DATA_STRUCT_A 0x16004C +#define MB_IF_B_DATA_STRUCT_B 0x160050 +#define MB_IF_B_DATA_STRUCT_C 0x160054 +#define MB_IF_B_DATA_STRUCT_D 0x160058 +#define MB_IF_B_DATA_STRUCT_E 0x16005C +#define MB_IF_B_DATA_STRUCT_F 0x160060 + +/* MB_DMA_CTRL */ +#define FLD_MB_IF_RISC_EN 0x00000010 +#define FLD_MB_IF_FIFO_EN 0x00000001 + +/* MB_LENGTH */ +#define FLD_MB_IF_LN_LNGTH 0x00000FFF + +/* MB_HCMD register */ +#define FLD_MB_HCMD_H_GO 0x80000000 +#define FLD_MB_HCMD_H_BUSY 0x40000000 +#define FLD_MB_HCMD_H_DMA_HOLD 0x10000000 +#define FLD_MB_HCMD_H_DMA_BUSY 0x08000000 +#define FLD_MB_HCMD_H_DMA_TYPE 0x04000000 +#define FLD_MB_HCMD_H_DMA_XACT 0x02000000 +#define FLD_MB_HCMD_H_RW_N 0x01000000 +#define FLD_MB_HCMD_H_ADDR 0x00FF0000 +#define FLD_MB_HCMD_H_DATA 0x0000FFFF + +/* ***************************************************************************** */ +/* I2C #1 */ +/* ***************************************************************************** */ +#define I2C1_ADDR 0x180000 /* I2C #1 address */ +#define FLD_I2C_DADDR 0xfe000000 /* RW [31:25] I2C Device Address */ + /* RO [24] reserved */ +/* ***************************************************************************** */ +#define FLD_I2C_SADDR 0x00FFFFFF /* RW [23:0] I2C Sub-address */ + +/* ***************************************************************************** */ +#define I2C1_WDATA 0x180004 /* I2C #1 write data */ +#define FLD_I2C_WDATA 0xFFFFFFFF /* RW [31:0] */ + +/* ***************************************************************************** */ +#define I2C1_CTRL 0x180008 /* I2C #1 control */ +#define FLD_I2C_PERIOD 0xFF000000 /* RW [31:24] */ +#define FLD_I2C_SCL_IN 0x00200000 /* RW [21] */ +#define FLD_I2C_SDA_IN 0x00100000 /* RW [20] */ + /* RO [19:18] reserved */ +#define FLD_I2C_SCL_OUT 0x00020000 /* RW [17] */ +#define FLD_I2C_SDA_OUT 0x00010000 /* RW [16] */ + /* RO [15] reserved */ +#define FLD_I2C_DATA_LEN 0x00007000 /* RW [14:12] */ +#define FLD_I2C_SADDR_INC 0x00000800 /* RW [11] */ + /* RO [10:9] reserved */ +#define FLD_I2C_SADDR_LEN 0x00000300 /* RW [9:8] */ + /* RO [7:6] reserved */ +#define FLD_I2C_SOFT 0x00000020 /* RW [5] */ +#define FLD_I2C_NOSTOP 0x00000010 /* RW [4] */ +#define FLD_I2C_EXTEND 0x00000008 /* RW [3] */ +#define FLD_I2C_SYNC 0x00000004 /* RW [2] */ +#define FLD_I2C_READ_SA 0x00000002 /* RW [1] */ +#define FLD_I2C_READ_WRN 0x00000001 /* RW [0] */ + +/* ***************************************************************************** */ +#define I2C1_RDATA 0x18000C /* I2C #1 read data */ +#define FLD_I2C_RDATA 0xFFFFFFFF /* RO [31:0] */ + +/* ***************************************************************************** */ +#define I2C1_STAT 0x180010 /* I2C #1 status */ +#define FLD_I2C_XFER_IN_PROG 0x00000002 /* RO [1] */ +#define FLD_I2C_RACK 0x00000001 /* RO [0] */ + +/* ***************************************************************************** */ +/* I2C #2 */ +/* ***************************************************************************** */ +#define I2C2_ADDR 0x190000 /* I2C #2 address */ + +/* ***************************************************************************** */ +#define I2C2_WDATA 0x190004 /* I2C #2 write data */ + +/* ***************************************************************************** */ +#define I2C2_CTRL 0x190008 /* I2C #2 control */ + +/* ***************************************************************************** */ +#define I2C2_RDATA 0x19000C /* I2C #2 read data */ + +/* ***************************************************************************** */ +#define I2C2_STAT 0x190010 /* I2C #2 status */ + +/* ***************************************************************************** */ +/* I2C #3 */ +/* ***************************************************************************** */ +#define I2C3_ADDR 0x1A0000 /* I2C #3 address */ + +/* ***************************************************************************** */ +#define I2C3_WDATA 0x1A0004 /* I2C #3 write data */ + +/* ***************************************************************************** */ +#define I2C3_CTRL 0x1A0008 /* I2C #3 control */ + +/* ***************************************************************************** */ +#define I2C3_RDATA 0x1A000C /* I2C #3 read data */ + +/* ***************************************************************************** */ +#define I2C3_STAT 0x1A0010 /* I2C #3 status */ + +/* ***************************************************************************** */ +/* UART */ +/* ***************************************************************************** */ +#define UART_CTL 0x1B0000 /* UART Control Register */ +#define FLD_LOOP_BACK_EN (1 << 7) /* RW field - default 0 */ +#define FLD_RX_TRG_SZ (3 << 2) /* RW field - default 0 */ +#define FLD_RX_EN (1 << 1) /* RW field - default 0 */ +#define FLD_TX_EN (1 << 0) /* RW field - default 0 */ + +/* ***************************************************************************** */ +#define UART_BRD 0x1B0004 /* UART Baud Rate Divisor */ +#define FLD_BRD 0x0000FFFF /* RW field - default 0x197 */ + +/* ***************************************************************************** */ +#define UART_DBUF 0x1B0008 /* UART Tx/Rx Data BuFFer */ +#define FLD_DB 0xFFFFFFFF /* RW field - default 0 */ + +/* ***************************************************************************** */ +#define UART_ISR 0x1B000C /* UART Interrupt Status */ +#define FLD_RXD_TIMEOUT_EN (1 << 7) /* RW field - default 0 */ +#define FLD_FRM_ERR_EN (1 << 6) /* RW field - default 0 */ +#define FLD_RXD_RDY_EN (1 << 5) /* RW field - default 0 */ +#define FLD_TXD_EMPTY_EN (1 << 4) /* RW field - default 0 */ +#define FLD_RXD_OVERFLOW (1 << 3) /* RW field - default 0 */ +#define FLD_FRM_ERR (1 << 2) /* RW field - default 0 */ +#define FLD_RXD_RDY (1 << 1) /* RW field - default 0 */ +#define FLD_TXD_EMPTY (1 << 0) /* RW field - default 0 */ + +/* ***************************************************************************** */ +#define UART_CNT 0x1B0010 /* UART Tx/Rx FIFO Byte Count */ +#define FLD_TXD_CNT (0x1F << 8) /* RW field - default 0 */ +#define FLD_RXD_CNT (0x1F << 0) /* RW field - default 0 */ + +/* ***************************************************************************** */ +/* Motion Detection */ +#define MD_CH0_GRID_BLOCK_YCNT 0x170014 +#define MD_CH1_GRID_BLOCK_YCNT 0x170094 +#define MD_CH2_GRID_BLOCK_YCNT 0x170114 +#define MD_CH3_GRID_BLOCK_YCNT 0x170194 +#define MD_CH4_GRID_BLOCK_YCNT 0x170214 +#define MD_CH5_GRID_BLOCK_YCNT 0x170294 +#define MD_CH6_GRID_BLOCK_YCNT 0x170314 +#define MD_CH7_GRID_BLOCK_YCNT 0x170394 + +#define PIXEL_FRMT_422 4 +#define PIXEL_FRMT_411 5 +#define PIXEL_FRMT_Y8 6 + +#define PIXEL_ENGINE_VIP1 0 +#define PIXEL_ENGINE_VIP2 1 + +#endif /* Athena_REGISTERS */ diff --git a/drivers/media/pci/cx25821/cx25821-sram.h b/drivers/media/pci/cx25821/cx25821-sram.h new file mode 100644 index 000000000..99bbb7252 --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821-sram.h @@ -0,0 +1,247 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + */ + +#ifndef __ATHENA_SRAM_H__ +#define __ATHENA_SRAM_H__ + +/* #define RX_SRAM_START_SIZE = 0; // Start of reserved SRAM */ +#define VID_CMDS_SIZE 80 /* Video CMDS size in bytes */ +#define AUDIO_CMDS_SIZE 80 /* AUDIO CMDS size in bytes */ +#define MBIF_CMDS_SIZE 80 /* MBIF CMDS size in bytes */ + +/* #define RX_SRAM_POOL_START_SIZE = 0; // Start of usable RX SRAM for buffers */ +#define VID_IQ_SIZE 64 /* VID instruction queue size in bytes */ +#define MBIF_IQ_SIZE 64 +#define AUDIO_IQ_SIZE 64 /* AUD instruction queue size in bytes */ + +#define VID_CDT_SIZE 64 /* VID cluster descriptor table size in bytes */ +#define MBIF_CDT_SIZE 64 /* MBIF/HBI cluster descriptor table size in bytes */ +#define AUDIO_CDT_SIZE 48 /* AUD cluster descriptor table size in bytes */ + +/* #define RX_SRAM_POOL_FREE_SIZE = 16; // Start of available RX SRAM */ +/* #define RX_SRAM_END_SIZE = 0; // End of RX SRAM */ + +/* #define TX_SRAM_POOL_START_SIZE = 0; // Start of transmit pool SRAM */ +/* #define MSI_DATA_SIZE = 64; // Reserved (MSI Data, RISC working stora */ + +#define VID_CLUSTER_SIZE 1440 /* VID cluster data line */ +#define AUDIO_CLUSTER_SIZE 128 /* AUDIO cluster data line */ +#define MBIF_CLUSTER_SIZE 1440 /* MBIF/HBI cluster data line */ + +/* #define TX_SRAM_POOL_FREE_SIZE = 704; // Start of available TX SRAM */ +/* #define TX_SRAM_END_SIZE = 0; // End of TX SRAM */ + +/* Receive SRAM */ +#define RX_SRAM_START 0x10000 +#define VID_A_DOWN_CMDS 0x10000 +#define VID_B_DOWN_CMDS 0x10050 +#define VID_C_DOWN_CMDS 0x100A0 +#define VID_D_DOWN_CMDS 0x100F0 +#define VID_E_DOWN_CMDS 0x10140 +#define VID_F_DOWN_CMDS 0x10190 +#define VID_G_DOWN_CMDS 0x101E0 +#define VID_H_DOWN_CMDS 0x10230 +#define VID_A_UP_CMDS 0x10280 +#define VID_B_UP_CMDS 0x102D0 +#define VID_C_UP_CMDS 0x10320 +#define VID_D_UP_CMDS 0x10370 +#define VID_E_UP_CMDS 0x103C0 +#define VID_F_UP_CMDS 0x10410 +#define VID_I_UP_CMDS 0x10460 +#define VID_J_UP_CMDS 0x104B0 +#define AUD_A_DOWN_CMDS 0x10500 +#define AUD_B_DOWN_CMDS 0x10550 +#define AUD_C_DOWN_CMDS 0x105A0 +#define AUD_D_DOWN_CMDS 0x105F0 +#define AUD_A_UP_CMDS 0x10640 +#define AUD_B_UP_CMDS 0x10690 +#define AUD_C_UP_CMDS 0x106E0 +#define AUD_E_UP_CMDS 0x10730 +#define MBIF_A_DOWN_CMDS 0x10780 +#define MBIF_B_DOWN_CMDS 0x107D0 +#define DMA_SCRATCH_PAD 0x10820 /* Scratch pad area from 0x10820 to 0x10B40 */ + +/* #define RX_SRAM_POOL_START = 0x105B0; */ + +#define VID_A_IQ 0x11000 +#define VID_B_IQ 0x11040 +#define VID_C_IQ 0x11080 +#define VID_D_IQ 0x110C0 +#define VID_E_IQ 0x11100 +#define VID_F_IQ 0x11140 +#define VID_G_IQ 0x11180 +#define VID_H_IQ 0x111C0 +#define VID_I_IQ 0x11200 +#define VID_J_IQ 0x11240 +#define AUD_A_IQ 0x11280 +#define AUD_B_IQ 0x112C0 +#define AUD_C_IQ 0x11300 +#define AUD_D_IQ 0x11340 +#define AUD_E_IQ 0x11380 +#define MBIF_A_IQ 0x11000 +#define MBIF_B_IQ 0x110C0 + +#define VID_A_CDT 0x10C00 +#define VID_B_CDT 0x10C40 +#define VID_C_CDT 0x10C80 +#define VID_D_CDT 0x10CC0 +#define VID_E_CDT 0x10D00 +#define VID_F_CDT 0x10D40 +#define VID_G_CDT 0x10D80 +#define VID_H_CDT 0x10DC0 +#define VID_I_CDT 0x10E00 +#define VID_J_CDT 0x10E40 +#define AUD_A_CDT 0x10E80 +#define AUD_B_CDT 0x10EB0 +#define AUD_C_CDT 0x10EE0 +#define AUD_D_CDT 0x10F10 +#define AUD_E_CDT 0x10F40 +#define MBIF_A_CDT 0x10C00 +#define MBIF_B_CDT 0x10CC0 + +/* Cluster Buffer for RX */ +#define VID_A_UP_CLUSTER_1 0x11400 +#define VID_A_UP_CLUSTER_2 0x119A0 +#define VID_A_UP_CLUSTER_3 0x11F40 +#define VID_A_UP_CLUSTER_4 0x124E0 + +#define VID_B_UP_CLUSTER_1 0x12A80 +#define VID_B_UP_CLUSTER_2 0x13020 +#define VID_B_UP_CLUSTER_3 0x135C0 +#define VID_B_UP_CLUSTER_4 0x13B60 + +#define VID_C_UP_CLUSTER_1 0x14100 +#define VID_C_UP_CLUSTER_2 0x146A0 +#define VID_C_UP_CLUSTER_3 0x14C40 +#define VID_C_UP_CLUSTER_4 0x151E0 + +#define VID_D_UP_CLUSTER_1 0x15780 +#define VID_D_UP_CLUSTER_2 0x15D20 +#define VID_D_UP_CLUSTER_3 0x162C0 +#define VID_D_UP_CLUSTER_4 0x16860 + +#define VID_E_UP_CLUSTER_1 0x16E00 +#define VID_E_UP_CLUSTER_2 0x173A0 +#define VID_E_UP_CLUSTER_3 0x17940 +#define VID_E_UP_CLUSTER_4 0x17EE0 + +#define VID_F_UP_CLUSTER_1 0x18480 +#define VID_F_UP_CLUSTER_2 0x18A20 +#define VID_F_UP_CLUSTER_3 0x18FC0 +#define VID_F_UP_CLUSTER_4 0x19560 + +#define VID_I_UP_CLUSTER_1 0x19B00 +#define VID_I_UP_CLUSTER_2 0x1A0A0 +#define VID_I_UP_CLUSTER_3 0x1A640 +#define VID_I_UP_CLUSTER_4 0x1ABE0 + +#define VID_J_UP_CLUSTER_1 0x1B180 +#define VID_J_UP_CLUSTER_2 0x1B720 +#define VID_J_UP_CLUSTER_3 0x1BCC0 +#define VID_J_UP_CLUSTER_4 0x1C260 + +#define AUD_A_UP_CLUSTER_1 0x1C800 +#define AUD_A_UP_CLUSTER_2 0x1C880 +#define AUD_A_UP_CLUSTER_3 0x1C900 + +#define AUD_B_UP_CLUSTER_1 0x1C980 +#define AUD_B_UP_CLUSTER_2 0x1CA00 +#define AUD_B_UP_CLUSTER_3 0x1CA80 + +#define AUD_C_UP_CLUSTER_1 0x1CB00 +#define AUD_C_UP_CLUSTER_2 0x1CB80 +#define AUD_C_UP_CLUSTER_3 0x1CC00 + +#define AUD_E_UP_CLUSTER_1 0x1CC80 +#define AUD_E_UP_CLUSTER_2 0x1CD00 +#define AUD_E_UP_CLUSTER_3 0x1CD80 + +#define RX_SRAM_POOL_FREE 0x1CE00 +#define RX_SRAM_END 0x1D000 + +/* Free Receive SRAM 144 Bytes */ + +/* Transmit SRAM */ +#define TX_SRAM_POOL_START 0x00000 + +#define VID_A_DOWN_CLUSTER_1 0x00040 +#define VID_A_DOWN_CLUSTER_2 0x005E0 +#define VID_A_DOWN_CLUSTER_3 0x00B80 +#define VID_A_DOWN_CLUSTER_4 0x01120 + +#define VID_B_DOWN_CLUSTER_1 0x016C0 +#define VID_B_DOWN_CLUSTER_2 0x01C60 +#define VID_B_DOWN_CLUSTER_3 0x02200 +#define VID_B_DOWN_CLUSTER_4 0x027A0 + +#define VID_C_DOWN_CLUSTER_1 0x02D40 +#define VID_C_DOWN_CLUSTER_2 0x032E0 +#define VID_C_DOWN_CLUSTER_3 0x03880 +#define VID_C_DOWN_CLUSTER_4 0x03E20 + +#define VID_D_DOWN_CLUSTER_1 0x043C0 +#define VID_D_DOWN_CLUSTER_2 0x04960 +#define VID_D_DOWN_CLUSTER_3 0x04F00 +#define VID_D_DOWN_CLUSTER_4 0x054A0 + +#define VID_E_DOWN_CLUSTER_1 0x05a40 +#define VID_E_DOWN_CLUSTER_2 0x05FE0 +#define VID_E_DOWN_CLUSTER_3 0x06580 +#define VID_E_DOWN_CLUSTER_4 0x06B20 + +#define VID_F_DOWN_CLUSTER_1 0x070C0 +#define VID_F_DOWN_CLUSTER_2 0x07660 +#define VID_F_DOWN_CLUSTER_3 0x07C00 +#define VID_F_DOWN_CLUSTER_4 0x081A0 + +#define VID_G_DOWN_CLUSTER_1 0x08740 +#define VID_G_DOWN_CLUSTER_2 0x08CE0 +#define VID_G_DOWN_CLUSTER_3 0x09280 +#define VID_G_DOWN_CLUSTER_4 0x09820 + +#define VID_H_DOWN_CLUSTER_1 0x09DC0 +#define VID_H_DOWN_CLUSTER_2 0x0A360 +#define VID_H_DOWN_CLUSTER_3 0x0A900 +#define VID_H_DOWN_CLUSTER_4 0x0AEA0 + +#define AUD_A_DOWN_CLUSTER_1 0x0B500 +#define AUD_A_DOWN_CLUSTER_2 0x0B580 +#define AUD_A_DOWN_CLUSTER_3 0x0B600 + +#define AUD_B_DOWN_CLUSTER_1 0x0B680 +#define AUD_B_DOWN_CLUSTER_2 0x0B700 +#define AUD_B_DOWN_CLUSTER_3 0x0B780 + +#define AUD_C_DOWN_CLUSTER_1 0x0B800 +#define AUD_C_DOWN_CLUSTER_2 0x0B880 +#define AUD_C_DOWN_CLUSTER_3 0x0B900 + +#define AUD_D_DOWN_CLUSTER_1 0x0B980 +#define AUD_D_DOWN_CLUSTER_2 0x0BA00 +#define AUD_D_DOWN_CLUSTER_3 0x0BA80 + +#define TX_SRAM_POOL_FREE 0x0BB00 +#define TX_SRAM_END 0x0C000 + +#define BYTES_TO_DWORDS(bcount) ((bcount) >> 2) +#define BYTES_TO_QWORDS(bcount) ((bcount) >> 3) +#define BYTES_TO_OWORDS(bcount) ((bcount) >> 4) + +#define VID_IQ_SIZE_DW BYTES_TO_DWORDS(VID_IQ_SIZE) +#define VID_CDT_SIZE_QW BYTES_TO_QWORDS(VID_CDT_SIZE) +#define VID_CLUSTER_SIZE_OW BYTES_TO_OWORDS(VID_CLUSTER_SIZE) + +#define AUDIO_IQ_SIZE_DW BYTES_TO_DWORDS(AUDIO_IQ_SIZE) +#define AUDIO_CDT_SIZE_QW BYTES_TO_QWORDS(AUDIO_CDT_SIZE) +#define AUDIO_CLUSTER_SIZE_QW BYTES_TO_QWORDS(AUDIO_CLUSTER_SIZE) + +#define MBIF_IQ_SIZE_DW BYTES_TO_DWORDS(MBIF_IQ_SIZE) +#define MBIF_CDT_SIZE_QW BYTES_TO_QWORDS(MBIF_CDT_SIZE) +#define MBIF_CLUSTER_SIZE_OW BYTES_TO_OWORDS(MBIF_CLUSTER_SIZE) + +#endif diff --git a/drivers/media/pci/cx25821/cx25821-video.c b/drivers/media/pci/cx25821/cx25821-video.c new file mode 100644 index 000000000..84aa1209e --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821-video.c @@ -0,0 +1,774 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + * Based on Steven Toth cx25821 driver + * Parts adapted/taken from Eduardo Moscoso Rubino + * Copyright (C) 2009 Eduardo Moscoso Rubino + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "cx25821-video.h" + +MODULE_DESCRIPTION("v4l2 driver module for cx25821 based TV cards"); +MODULE_AUTHOR("Hiep Huynh "); +MODULE_LICENSE("GPL"); + +static unsigned int video_nr[] = {[0 ... (CX25821_MAXBOARDS - 1)] = UNSET }; + +module_param_array(video_nr, int, NULL, 0444); + +MODULE_PARM_DESC(video_nr, "video device numbers"); + +static unsigned int video_debug = VIDEO_DEBUG; +module_param(video_debug, int, 0644); +MODULE_PARM_DESC(video_debug, "enable debug messages [video]"); + +static unsigned int irq_debug; +module_param(irq_debug, int, 0644); +MODULE_PARM_DESC(irq_debug, "enable debug messages [IRQ handler]"); + +#define FORMAT_FLAGS_PACKED 0x01 + +static const struct cx25821_fmt formats[] = { + { + .fourcc = V4L2_PIX_FMT_Y41P, + .depth = 12, + .flags = FORMAT_FLAGS_PACKED, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, +}; + +static const struct cx25821_fmt *cx25821_format_by_fourcc(unsigned int fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) + if (formats[i].fourcc == fourcc) + return formats + i; + return NULL; +} + +int cx25821_start_video_dma(struct cx25821_dev *dev, + struct cx25821_dmaqueue *q, + struct cx25821_buffer *buf, + const struct sram_channel *channel) +{ + int tmp = 0; + + /* setup fifo + format */ + cx25821_sram_channel_setup(dev, channel, buf->bpl, buf->risc.dma); + + /* reset counter */ + cx_write(channel->gpcnt_ctl, 3); + + /* enable irq */ + cx_set(PCI_INT_MSK, cx_read(PCI_INT_MSK) | (1 << channel->i)); + cx_set(channel->int_msk, 0x11); + + /* start dma */ + cx_write(channel->dma_ctl, 0x11); /* FIFO and RISC enable */ + + /* make sure upstream setting if any is reversed */ + tmp = cx_read(VID_CH_MODE_SEL); + cx_write(VID_CH_MODE_SEL, tmp & 0xFFFFFE00); + + return 0; +} + +int cx25821_video_irq(struct cx25821_dev *dev, int chan_num, u32 status) +{ + int handled = 0; + u32 mask; + const struct sram_channel *channel = dev->channels[chan_num].sram_channels; + + mask = cx_read(channel->int_msk); + if (0 == (status & mask)) + return handled; + + cx_write(channel->int_stat, status); + + /* risc op code error */ + if (status & (1 << 16)) { + pr_warn("%s, %s: video risc op code error\n", + dev->name, channel->name); + cx_clear(channel->dma_ctl, 0x11); + cx25821_sram_channel_dump(dev, channel); + } + + /* risc1 y */ + if (status & FLD_VID_DST_RISC1) { + struct cx25821_dmaqueue *dmaq = + &dev->channels[channel->i].dma_vidq; + struct cx25821_buffer *buf; + + spin_lock(&dev->slock); + if (!list_empty(&dmaq->active)) { + buf = list_entry(dmaq->active.next, + struct cx25821_buffer, queue); + + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + buf->vb.sequence = dmaq->count++; + list_del(&buf->queue); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + } + spin_unlock(&dev->slock); + handled++; + } + return handled; +} + +static int cx25821_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct cx25821_channel *chan = q->drv_priv; + unsigned size = (chan->fmt->depth * chan->width * chan->height) >> 3; + + if (*num_planes) + return sizes[0] < size ? -EINVAL : 0; + + *num_planes = 1; + sizes[0] = size; + return 0; +} + +static int cx25821_buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx25821_channel *chan = vb->vb2_queue->drv_priv; + struct cx25821_dev *dev = chan->dev; + struct cx25821_buffer *buf = + container_of(vbuf, struct cx25821_buffer, vb); + struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, 0); + u32 line0_offset; + int bpl_local = LINE_SIZE_D1; + int ret; + + if (chan->pixel_formats == PIXEL_FRMT_411) + buf->bpl = (chan->fmt->depth * chan->width) >> 3; + else + buf->bpl = (chan->fmt->depth >> 3) * chan->width; + + if (vb2_plane_size(vb, 0) < chan->height * buf->bpl) + return -EINVAL; + vb2_set_plane_payload(vb, 0, chan->height * buf->bpl); + buf->vb.field = chan->field; + + if (chan->pixel_formats == PIXEL_FRMT_411) { + bpl_local = buf->bpl; + } else { + bpl_local = buf->bpl; /* Default */ + + if (chan->use_cif_resolution) { + if (dev->tvnorm & V4L2_STD_625_50) + bpl_local = 352 << 1; + else + bpl_local = chan->cif_width << 1; + } + } + + switch (chan->field) { + case V4L2_FIELD_TOP: + ret = cx25821_risc_buffer(dev->pci, &buf->risc, + sgt->sgl, 0, UNSET, + buf->bpl, 0, chan->height); + break; + case V4L2_FIELD_BOTTOM: + ret = cx25821_risc_buffer(dev->pci, &buf->risc, + sgt->sgl, UNSET, 0, + buf->bpl, 0, chan->height); + break; + case V4L2_FIELD_INTERLACED: + /* All other formats are top field first */ + line0_offset = 0; + dprintk(1, "top field first\n"); + + ret = cx25821_risc_buffer(dev->pci, &buf->risc, + sgt->sgl, line0_offset, + bpl_local, bpl_local, bpl_local, + chan->height >> 1); + break; + case V4L2_FIELD_SEQ_TB: + ret = cx25821_risc_buffer(dev->pci, &buf->risc, + sgt->sgl, + 0, buf->bpl * (chan->height >> 1), + buf->bpl, 0, chan->height >> 1); + break; + case V4L2_FIELD_SEQ_BT: + ret = cx25821_risc_buffer(dev->pci, &buf->risc, + sgt->sgl, + buf->bpl * (chan->height >> 1), 0, + buf->bpl, 0, chan->height >> 1); + break; + default: + WARN_ON(1); + ret = -EINVAL; + break; + } + + dprintk(2, "[%p/%d] buffer_prep - %dx%d %dbpp 0x%08x - dma=0x%08lx\n", + buf, buf->vb.vb2_buf.index, chan->width, chan->height, + chan->fmt->depth, chan->fmt->fourcc, + (unsigned long)buf->risc.dma); + + return ret; +} + +static void cx25821_buffer_finish(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx25821_buffer *buf = + container_of(vbuf, struct cx25821_buffer, vb); + struct cx25821_channel *chan = vb->vb2_queue->drv_priv; + struct cx25821_dev *dev = chan->dev; + + cx25821_free_buffer(dev, buf); +} + +static void cx25821_buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx25821_buffer *buf = + container_of(vbuf, struct cx25821_buffer, vb); + struct cx25821_channel *chan = vb->vb2_queue->drv_priv; + struct cx25821_dev *dev = chan->dev; + struct cx25821_buffer *prev; + struct cx25821_dmaqueue *q = &dev->channels[chan->id].dma_vidq; + + buf->risc.cpu[1] = cpu_to_le32(buf->risc.dma + 12); + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma + 12); + buf->risc.jmp[2] = cpu_to_le32(0); /* bits 63-32 */ + + if (list_empty(&q->active)) { + list_add_tail(&buf->queue, &q->active); + } else { + buf->risc.cpu[0] |= cpu_to_le32(RISC_IRQ1); + prev = list_entry(q->active.prev, struct cx25821_buffer, + queue); + list_add_tail(&buf->queue, &q->active); + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + } +} + +static int cx25821_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct cx25821_channel *chan = q->drv_priv; + struct cx25821_dev *dev = chan->dev; + struct cx25821_dmaqueue *dmaq = &dev->channels[chan->id].dma_vidq; + struct cx25821_buffer *buf = list_entry(dmaq->active.next, + struct cx25821_buffer, queue); + + dmaq->count = 0; + cx25821_start_video_dma(dev, dmaq, buf, chan->sram_channels); + return 0; +} + +static void cx25821_stop_streaming(struct vb2_queue *q) +{ + struct cx25821_channel *chan = q->drv_priv; + struct cx25821_dev *dev = chan->dev; + struct cx25821_dmaqueue *dmaq = &dev->channels[chan->id].dma_vidq; + unsigned long flags; + + cx_write(chan->sram_channels->dma_ctl, 0); /* FIFO and RISC disable */ + spin_lock_irqsave(&dev->slock, flags); + while (!list_empty(&dmaq->active)) { + struct cx25821_buffer *buf = list_entry(dmaq->active.next, + struct cx25821_buffer, queue); + + list_del(&buf->queue); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&dev->slock, flags); +} + +static const struct vb2_ops cx25821_video_qops = { + .queue_setup = cx25821_queue_setup, + .buf_prepare = cx25821_buffer_prepare, + .buf_finish = cx25821_buffer_finish, + .buf_queue = cx25821_buffer_queue, + .start_streaming = cx25821_start_streaming, + .stop_streaming = cx25821_stop_streaming, +}; + +/* VIDEO IOCTLS */ + +static int cx25821_vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (unlikely(f->index >= ARRAY_SIZE(formats))) + return -EINVAL; + + f->pixelformat = formats[f->index].fourcc; + + return 0; +} + +static int cx25821_vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx25821_channel *chan = video_drvdata(file); + + f->fmt.pix.width = chan->width; + f->fmt.pix.height = chan->height; + f->fmt.pix.field = chan->field; + f->fmt.pix.pixelformat = chan->fmt->fourcc; + f->fmt.pix.bytesperline = (chan->width * chan->fmt->depth) >> 3; + f->fmt.pix.sizeimage = chan->height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int cx25821_vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx25821_channel *chan = video_drvdata(file); + struct cx25821_dev *dev = chan->dev; + const struct cx25821_fmt *fmt; + enum v4l2_field field = f->fmt.pix.field; + unsigned int maxh; + unsigned w; + + fmt = cx25821_format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + maxh = (dev->tvnorm & V4L2_STD_625_50) ? 576 : 480; + + w = f->fmt.pix.width; + if (field != V4L2_FIELD_BOTTOM) + field = V4L2_FIELD_TOP; + if (w < 352) { + w = 176; + f->fmt.pix.height = maxh / 4; + } else if (w < 720) { + w = 352; + f->fmt.pix.height = maxh / 2; + } else { + w = 720; + f->fmt.pix.height = maxh; + field = V4L2_FIELD_INTERLACED; + } + f->fmt.pix.field = field; + f->fmt.pix.width = w; + f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx25821_channel *chan = video_drvdata(file); + struct cx25821_dev *dev = chan->dev; + int pix_format = PIXEL_FRMT_422; + int err; + + err = cx25821_vidioc_try_fmt_vid_cap(file, priv, f); + + if (0 != err) + return err; + + chan->fmt = cx25821_format_by_fourcc(f->fmt.pix.pixelformat); + chan->field = f->fmt.pix.field; + chan->width = f->fmt.pix.width; + chan->height = f->fmt.pix.height; + + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_Y41P) + pix_format = PIXEL_FRMT_411; + else + pix_format = PIXEL_FRMT_422; + + cx25821_set_pixel_format(dev, SRAM_CH00, pix_format); + + /* check if cif resolution */ + if (chan->width == 320 || chan->width == 352) + chan->use_cif_resolution = 1; + else + chan->use_cif_resolution = 0; + + chan->cif_width = chan->width; + medusa_set_resolution(dev, chan->width, SRAM_CH00); + return 0; +} + +static int vidioc_log_status(struct file *file, void *priv) +{ + struct cx25821_channel *chan = video_drvdata(file); + struct cx25821_dev *dev = chan->dev; + const struct sram_channel *sram_ch = chan->sram_channels; + u32 tmp = 0; + + tmp = cx_read(sram_ch->dma_ctl); + pr_info("Video input 0 is %s\n", + (tmp & 0x11) ? "streaming" : "stopped"); + return 0; +} + + +static int cx25821_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct cx25821_channel *chan = video_drvdata(file); + struct cx25821_dev *dev = chan->dev; + + strscpy(cap->driver, "cx25821", sizeof(cap->driver)); + strscpy(cap->card, cx25821_boards[dev->board].name, sizeof(cap->card)); + sprintf(cap->bus_info, "PCIe:%s", pci_name(dev->pci)); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT | + V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | + V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int cx25821_vidioc_g_std(struct file *file, void *priv, v4l2_std_id *tvnorms) +{ + struct cx25821_channel *chan = video_drvdata(file); + + *tvnorms = chan->dev->tvnorm; + return 0; +} + +static int cx25821_vidioc_s_std(struct file *file, void *priv, + v4l2_std_id tvnorms) +{ + struct cx25821_channel *chan = video_drvdata(file); + struct cx25821_dev *dev = chan->dev; + + if (dev->tvnorm == tvnorms) + return 0; + + dev->tvnorm = tvnorms; + chan->width = 720; + chan->height = (dev->tvnorm & V4L2_STD_625_50) ? 576 : 480; + + medusa_set_videostandard(dev); + + return 0; +} + +static int cx25821_vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + if (i->index) + return -EINVAL; + + i->type = V4L2_INPUT_TYPE_CAMERA; + i->std = CX25821_NORMS; + strscpy(i->name, "Composite", sizeof(i->name)); + return 0; +} + +static int cx25821_vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int cx25821_vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + return i ? -EINVAL : 0; +} + +static int cx25821_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct cx25821_channel *chan = + container_of(ctrl->handler, struct cx25821_channel, hdl); + struct cx25821_dev *dev = chan->dev; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + medusa_set_brightness(dev, ctrl->val, chan->id); + break; + case V4L2_CID_HUE: + medusa_set_hue(dev, ctrl->val, chan->id); + break; + case V4L2_CID_CONTRAST: + medusa_set_contrast(dev, ctrl->val, chan->id); + break; + case V4L2_CID_SATURATION: + medusa_set_saturation(dev, ctrl->val, chan->id); + break; + default: + return -EINVAL; + } + return 0; +} + +static int cx25821_vidioc_enum_output(struct file *file, void *priv, + struct v4l2_output *o) +{ + if (o->index) + return -EINVAL; + + o->type = V4L2_INPUT_TYPE_CAMERA; + o->std = CX25821_NORMS; + strscpy(o->name, "Composite", sizeof(o->name)); + return 0; +} + +static int cx25821_vidioc_g_output(struct file *file, void *priv, unsigned int *o) +{ + *o = 0; + return 0; +} + +static int cx25821_vidioc_s_output(struct file *file, void *priv, unsigned int o) +{ + return o ? -EINVAL : 0; +} + +static int cx25821_vidioc_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx25821_channel *chan = video_drvdata(file); + struct cx25821_dev *dev = chan->dev; + const struct cx25821_fmt *fmt; + + fmt = cx25821_format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + f->fmt.pix.width = 720; + f->fmt.pix.height = (dev->tvnorm & V4L2_STD_625_50) ? 576 : 480; + f->fmt.pix.field = V4L2_FIELD_INTERLACED; + f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + return 0; +} + +static int vidioc_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx25821_channel *chan = video_drvdata(file); + int err; + + err = cx25821_vidioc_try_fmt_vid_out(file, priv, f); + + if (0 != err) + return err; + + chan->fmt = cx25821_format_by_fourcc(f->fmt.pix.pixelformat); + chan->field = f->fmt.pix.field; + chan->width = f->fmt.pix.width; + chan->height = f->fmt.pix.height; + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_Y41P) + chan->pixel_formats = PIXEL_FRMT_411; + else + chan->pixel_formats = PIXEL_FRMT_422; + return 0; +} + +static const struct v4l2_ctrl_ops cx25821_ctrl_ops = { + .s_ctrl = cx25821_s_ctrl, +}; + +static const struct v4l2_file_operations video_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = cx25821_vidioc_querycap, + .vidioc_enum_fmt_vid_cap = cx25821_vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = cx25821_vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = cx25821_vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_g_std = cx25821_vidioc_g_std, + .vidioc_s_std = cx25821_vidioc_s_std, + .vidioc_enum_input = cx25821_vidioc_enum_input, + .vidioc_g_input = cx25821_vidioc_g_input, + .vidioc_s_input = cx25821_vidioc_s_input, + .vidioc_log_status = vidioc_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct video_device cx25821_video_device = { + .name = "cx25821-video", + .fops = &video_fops, + .release = video_device_release_empty, + .minor = -1, + .ioctl_ops = &video_ioctl_ops, + .tvnorms = CX25821_NORMS, + .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING, +}; + +static const struct v4l2_file_operations video_out_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .write = vb2_fop_write, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +static const struct v4l2_ioctl_ops video_out_ioctl_ops = { + .vidioc_querycap = cx25821_vidioc_querycap, + .vidioc_enum_fmt_vid_out = cx25821_vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_out = cx25821_vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_out = cx25821_vidioc_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = vidioc_s_fmt_vid_out, + .vidioc_g_std = cx25821_vidioc_g_std, + .vidioc_s_std = cx25821_vidioc_s_std, + .vidioc_enum_output = cx25821_vidioc_enum_output, + .vidioc_g_output = cx25821_vidioc_g_output, + .vidioc_s_output = cx25821_vidioc_s_output, + .vidioc_log_status = vidioc_log_status, +}; + +static const struct video_device cx25821_video_out_device = { + .name = "cx25821-video", + .fops = &video_out_fops, + .release = video_device_release_empty, + .minor = -1, + .ioctl_ops = &video_out_ioctl_ops, + .tvnorms = CX25821_NORMS, + .device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_READWRITE, +}; + +void cx25821_video_unregister(struct cx25821_dev *dev, int chan_num) +{ + cx_clear(PCI_INT_MSK, 1); + + if (video_is_registered(&dev->channels[chan_num].vdev)) { + video_unregister_device(&dev->channels[chan_num].vdev); + v4l2_ctrl_handler_free(&dev->channels[chan_num].hdl); + } +} + +int cx25821_video_register(struct cx25821_dev *dev) +{ + int err; + int i; + + /* initial device configuration */ + dev->tvnorm = V4L2_STD_NTSC_M; + + spin_lock_init(&dev->slock); + + for (i = 0; i < MAX_VID_CAP_CHANNEL_NUM - 1; ++i) { + struct cx25821_channel *chan = &dev->channels[i]; + struct video_device *vdev = &chan->vdev; + struct v4l2_ctrl_handler *hdl = &chan->hdl; + struct vb2_queue *q; + bool is_output = i > SRAM_CH08; + + if (i == SRAM_CH08) /* audio channel */ + continue; + + if (!is_output) { + v4l2_ctrl_handler_init(hdl, 4); + v4l2_ctrl_new_std(hdl, &cx25821_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 10000, 1, 6200); + v4l2_ctrl_new_std(hdl, &cx25821_ctrl_ops, + V4L2_CID_CONTRAST, 0, 10000, 1, 5000); + v4l2_ctrl_new_std(hdl, &cx25821_ctrl_ops, + V4L2_CID_SATURATION, 0, 10000, 1, 5000); + v4l2_ctrl_new_std(hdl, &cx25821_ctrl_ops, + V4L2_CID_HUE, 0, 10000, 1, 5000); + if (hdl->error) { + err = hdl->error; + goto fail_unreg; + } + err = v4l2_ctrl_handler_setup(hdl); + if (err) + goto fail_unreg; + } else { + chan->out = &dev->vid_out_data[i - SRAM_CH09]; + chan->out->chan = chan; + } + + chan->sram_channels = &cx25821_sram_channels[i]; + chan->width = 720; + chan->field = V4L2_FIELD_INTERLACED; + if (dev->tvnorm & V4L2_STD_625_50) + chan->height = 576; + else + chan->height = 480; + + if (chan->pixel_formats == PIXEL_FRMT_411) + chan->fmt = cx25821_format_by_fourcc(V4L2_PIX_FMT_Y41P); + else + chan->fmt = cx25821_format_by_fourcc(V4L2_PIX_FMT_YUYV); + + cx_write(chan->sram_channels->int_stat, 0xffffffff); + + INIT_LIST_HEAD(&chan->dma_vidq.active); + + q = &chan->vidq; + + q->type = is_output ? V4L2_BUF_TYPE_VIDEO_OUTPUT : + V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + q->io_modes |= is_output ? VB2_WRITE : VB2_READ; + q->gfp_flags = GFP_DMA32; + q->min_queued_buffers = 2; + q->drv_priv = chan; + q->buf_struct_size = sizeof(struct cx25821_buffer); + q->ops = &cx25821_video_qops; + q->mem_ops = &vb2_dma_sg_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &dev->lock; + q->dev = &dev->pci->dev; + + if (!is_output) { + err = vb2_queue_init(q); + if (err < 0) + goto fail_unreg; + } + + /* register v4l devices */ + *vdev = is_output ? cx25821_video_out_device : cx25821_video_device; + vdev->v4l2_dev = &dev->v4l2_dev; + if (!is_output) + vdev->ctrl_handler = hdl; + else + vdev->vfl_dir = VFL_DIR_TX; + vdev->lock = &dev->lock; + vdev->queue = q; + snprintf(vdev->name, sizeof(vdev->name), "%s #%d", dev->name, i); + video_set_drvdata(vdev, chan); + + err = video_register_device(vdev, VFL_TYPE_VIDEO, + video_nr[dev->nr]); + + if (err < 0) + goto fail_unreg; + } + + /* set PCI interrupt */ + cx_set(PCI_INT_MSK, 0xff); + + return 0; + +fail_unreg: + while (i >= 0) + cx25821_video_unregister(dev, i--); + return err; +} diff --git a/drivers/media/pci/cx25821/cx25821-video.h b/drivers/media/pci/cx25821/cx25821-video.h new file mode 100644 index 000000000..080c8490a --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821-video.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + * Based on Steven Toth cx23885 driver + */ + +#ifndef CX25821_VIDEO_H_ +#define CX25821_VIDEO_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cx25821.h" +#include +#include +#include + +#define VIDEO_DEBUG 0 + +#define dprintk(level, fmt, arg...) \ +do { \ + if (VIDEO_DEBUG >= level) \ + printk(KERN_DEBUG "%s/0: " fmt, dev->name, ##arg); \ +} while (0) + +#define FORMAT_FLAGS_PACKED 0x01 +extern int cx25821_start_video_dma(struct cx25821_dev *dev, + struct cx25821_dmaqueue *q, + struct cx25821_buffer *buf, + const struct sram_channel *channel); + +extern int cx25821_video_irq(struct cx25821_dev *dev, int chan_num, u32 status); +extern void cx25821_video_unregister(struct cx25821_dev *dev, int chan_num); +extern int cx25821_video_register(struct cx25821_dev *dev); + +#endif diff --git a/drivers/media/pci/cx25821/cx25821.h b/drivers/media/pci/cx25821/cx25821.h new file mode 100644 index 000000000..3aa7604fb --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821.h @@ -0,0 +1,426 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + * Based on Steven Toth cx23885 driver + */ + +#ifndef CX25821_H_ +#define CX25821_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "cx25821-reg.h" +#include "cx25821-medusa-reg.h" +#include "cx25821-sram.h" +#include "cx25821-audio.h" + +#include + +#define UNSET (-1U) +#define NO_SYNC_LINE (-1U) + +#define CX25821_MAXBOARDS 2 + +#define LINE_SIZE_D1 1440 + +/* Number of decoders and encoders */ +#define MAX_DECODERS 8 +#define MAX_ENCODERS 2 +#define QUAD_DECODERS 4 +#define MAX_CAMERAS 16 + +/* Max number of inputs by card */ +#define MAX_CX25821_INPUT 8 +#define RESOURCE_VIDEO0 1 +#define RESOURCE_VIDEO1 2 +#define RESOURCE_VIDEO2 4 +#define RESOURCE_VIDEO3 8 +#define RESOURCE_VIDEO4 16 +#define RESOURCE_VIDEO5 32 +#define RESOURCE_VIDEO6 64 +#define RESOURCE_VIDEO7 128 +#define RESOURCE_VIDEO8 256 +#define RESOURCE_VIDEO9 512 +#define RESOURCE_VIDEO10 1024 +#define RESOURCE_VIDEO11 2048 + +#define BUFFER_TIMEOUT (HZ) /* 0.5 seconds */ + +#define UNKNOWN_BOARD 0 +#define CX25821_BOARD 1 + +/* Currently supported by the driver */ +#define CX25821_NORMS (\ + V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_JP | V4L2_STD_NTSC_M_KR | \ + V4L2_STD_PAL_BG | V4L2_STD_PAL_DK | V4L2_STD_PAL_I | \ + V4L2_STD_PAL_M | V4L2_STD_PAL_N | V4L2_STD_PAL_H | \ + V4L2_STD_PAL_Nc) + +#define CX25821_BOARD_CONEXANT_ATHENA10 1 +#define MAX_VID_CHANNEL_NUM 12 + +/* + * Maximum capture-only channels. This can go away once video/audio output + * is fully supported in this driver. + */ +#define MAX_VID_CAP_CHANNEL_NUM 10 + +#define VID_CHANNEL_NUM 8 + +struct cx25821_fmt { + u32 fourcc; /* v4l2 format id */ + int depth; + int flags; + u32 cxformat; +}; + +struct cx25821_tvnorm { + char *name; + v4l2_std_id id; + u32 cxiformat; + u32 cxoformat; +}; + +enum cx25821_src_sel_type { + CX25821_SRC_SEL_EXT_656_VIDEO = 0, + CX25821_SRC_SEL_PARALLEL_MPEG_VIDEO +}; + +struct cx25821_riscmem { + unsigned int size; + __le32 *cpu; + __le32 *jmp; + dma_addr_t dma; +}; + +/* buffer for one video frame */ +struct cx25821_buffer { + /* common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer vb; + struct list_head queue; + + /* cx25821 specific */ + unsigned int bpl; + struct cx25821_riscmem risc; + const struct cx25821_fmt *fmt; +}; + +enum port { + CX25821_UNDEFINED = 0, + CX25821_RAW, + CX25821_264 +}; + +struct cx25821_board { + const char *name; + enum port porta; + enum port portb; + enum port portc; + + u32 clk_freq; +}; + +struct cx25821_i2c { + struct cx25821_dev *dev; + + int nr; + + /* i2c i/o */ + struct i2c_adapter i2c_adap; + struct i2c_client i2c_client; + u32 i2c_rc; + + /* cx25821 registers used for raw address */ + u32 i2c_period; + u32 reg_ctrl; + u32 reg_stat; + u32 reg_addr; + u32 reg_rdata; + u32 reg_wdata; +}; + +struct cx25821_dmaqueue { + struct list_head active; + u32 count; +}; + +struct cx25821_dev; + +struct cx25821_channel; + +struct cx25821_video_out_data { + struct cx25821_channel *chan; + int _line_size; + int _prog_cnt; + int _pixel_format; + int _is_first_frame; + int _is_running; + int _file_status; + int _lines_count; + int _frame_count; + unsigned int _risc_size; + + __le32 *_dma_virt_start_addr; + __le32 *_dma_virt_addr; + dma_addr_t _dma_phys_addr; + dma_addr_t _dma_phys_start_addr; + + unsigned int _data_buf_size; + __le32 *_data_buf_virt_addr; + dma_addr_t _data_buf_phys_addr; + + u32 upstream_riscbuf_size; + u32 upstream_databuf_size; + int is_60hz; + int _frame_index; + int cur_frame_index; + int curpos; + wait_queue_head_t waitq; +}; + +struct cx25821_channel { + unsigned id; + struct cx25821_dev *dev; + + struct v4l2_ctrl_handler hdl; + + struct video_device vdev; + struct cx25821_dmaqueue dma_vidq; + struct vb2_queue vidq; + + const struct sram_channel *sram_channels; + + const struct cx25821_fmt *fmt; + unsigned field; + unsigned int width, height; + int pixel_formats; + int use_cif_resolution; + int cif_width; + + /* video output data for the video output channel */ + struct cx25821_video_out_data *out; +}; + +struct snd_card; + +struct cx25821_dev { + struct v4l2_device v4l2_dev; + + /* pci stuff */ + struct pci_dev *pci; + unsigned char pci_rev, pci_lat; + int pci_bus, pci_slot; + u32 base_io_addr; + u32 __iomem *lmmio; + u8 __iomem *bmmio; + int pci_irqmask; + int hwrevision; + /* used by cx25821-alsa */ + struct snd_card *card; + + u32 clk_freq; + + /* I2C adapters: Master 1 & 2 (External) & Master 3 (Internal only) */ + struct cx25821_i2c i2c_bus[3]; + + int nr; + struct mutex lock; + + struct cx25821_channel channels[MAX_VID_CHANNEL_NUM]; + + /* board details */ + unsigned int board; + char name[32]; + + /* Analog video */ + unsigned int input; + v4l2_std_id tvnorm; + unsigned short _max_num_decoders; + + /* Analog Audio Upstream */ + int _audio_is_running; + int _audiopixel_format; + int _is_first_audio_frame; + int _audiofile_status; + int _audio_lines_count; + int _audioframe_count; + int _audio_upstream_channel; + int _last_index_irq; /* The last interrupt index processed. */ + + __le32 *_risc_audio_jmp_addr; + __le32 *_risc_virt_start_addr; + __le32 *_risc_virt_addr; + dma_addr_t _risc_phys_addr; + dma_addr_t _risc_phys_start_addr; + + unsigned int _audiorisc_size; + unsigned int _audiodata_buf_size; + __le32 *_audiodata_buf_virt_addr; + dma_addr_t _audiodata_buf_phys_addr; + char *_audiofilename; + u32 audio_upstream_riscbuf_size; + u32 audio_upstream_databuf_size; + int _audioframe_index; + struct work_struct _audio_work_entry; + char *input_audiofilename; + + /* V4l */ + spinlock_t slock; + + /* Video Upstream */ + struct cx25821_video_out_data vid_out_data[2]; +}; + +static inline struct cx25821_dev *get_cx25821(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct cx25821_dev, v4l2_dev); +} + +extern struct cx25821_board cx25821_boards[]; + +#define SRAM_CH00 0 /* Video A */ +#define SRAM_CH01 1 /* Video B */ +#define SRAM_CH02 2 /* Video C */ +#define SRAM_CH03 3 /* Video D */ +#define SRAM_CH04 4 /* Video E */ +#define SRAM_CH05 5 /* Video F */ +#define SRAM_CH06 6 /* Video G */ +#define SRAM_CH07 7 /* Video H */ + +#define SRAM_CH08 8 /* Audio A */ +#define SRAM_CH09 9 /* Video Upstream I */ +#define SRAM_CH10 10 /* Video Upstream J */ +#define SRAM_CH11 11 /* Audio Upstream AUD_CHANNEL_B */ + +#define VID_UPSTREAM_SRAM_CHANNEL_I SRAM_CH09 +#define VID_UPSTREAM_SRAM_CHANNEL_J SRAM_CH10 +#define AUDIO_UPSTREAM_SRAM_CHANNEL_B SRAM_CH11 + +struct sram_channel { + char *name; + u32 i; + u32 cmds_start; + u32 ctrl_start; + u32 cdt; + u32 fifo_start; + u32 fifo_size; + u32 ptr1_reg; + u32 ptr2_reg; + u32 cnt1_reg; + u32 cnt2_reg; + u32 int_msk; + u32 int_stat; + u32 int_mstat; + u32 dma_ctl; + u32 gpcnt_ctl; + u32 gpcnt; + u32 aud_length; + u32 aud_cfg; + u32 fld_aud_fifo_en; + u32 fld_aud_risc_en; + + /* For Upstream Video */ + u32 vid_fmt_ctl; + u32 vid_active_ctl1; + u32 vid_active_ctl2; + u32 vid_cdt_size; + + u32 vip_ctl; + u32 pix_frmt; + u32 jumponly; + u32 irq_bit; +}; + +extern const struct sram_channel cx25821_sram_channels[]; + +#define cx_read(reg) readl(dev->lmmio + ((reg)>>2)) +#define cx_write(reg, value) writel((value), dev->lmmio + ((reg)>>2)) + +#define cx_andor(reg, mask, value) \ + writel((readl(dev->lmmio+((reg)>>2)) & ~(mask)) |\ + ((value) & (mask)), dev->lmmio+((reg)>>2)) + +#define cx_set(reg, bit) cx_andor((reg), (bit), (bit)) +#define cx_clear(reg, bit) cx_andor((reg), (bit), 0) + +#define Set_GPIO_Bit(Bit) (1 << Bit) +#define Clear_GPIO_Bit(Bit) (~(1 << Bit)) + +#define CX25821_ERR(fmt, args...) \ + pr_err("(%d): " fmt, dev->board, ##args) +#define CX25821_WARN(fmt, args...) \ + pr_warn("(%d): " fmt, dev->board, ##args) +#define CX25821_INFO(fmt, args...) \ + pr_info("(%d): " fmt, dev->board, ##args) + +extern int cx25821_i2c_register(struct cx25821_i2c *bus); +extern int cx25821_i2c_read(struct cx25821_i2c *bus, u16 reg_addr, int *value); +extern int cx25821_i2c_write(struct cx25821_i2c *bus, u16 reg_addr, int value); +extern int cx25821_i2c_unregister(struct cx25821_i2c *bus); +extern void cx25821_gpio_init(struct cx25821_dev *dev); +extern void cx25821_set_gpiopin_direction(struct cx25821_dev *dev, + int pin_number, int pin_logic_value); + +extern int medusa_video_init(struct cx25821_dev *dev); +extern int medusa_set_videostandard(struct cx25821_dev *dev); +extern void medusa_set_resolution(struct cx25821_dev *dev, int width, + int decoder_select); +extern int medusa_set_brightness(struct cx25821_dev *dev, int brightness, + int decoder); +extern int medusa_set_contrast(struct cx25821_dev *dev, int contrast, + int decoder); +extern int medusa_set_hue(struct cx25821_dev *dev, int hue, int decoder); +extern int medusa_set_saturation(struct cx25821_dev *dev, int saturation, + int decoder); + +extern int cx25821_sram_channel_setup(struct cx25821_dev *dev, + const struct sram_channel *ch, unsigned int bpl, + u32 risc); + +extern int cx25821_riscmem_alloc(struct pci_dev *pci, + struct cx25821_riscmem *risc, + unsigned int size); +extern int cx25821_risc_buffer(struct pci_dev *pci, struct cx25821_riscmem *risc, + struct scatterlist *sglist, + unsigned int top_offset, + unsigned int bottom_offset, + unsigned int bpl, + unsigned int padding, unsigned int lines); +extern int cx25821_risc_databuffer_audio(struct pci_dev *pci, + struct cx25821_riscmem *risc, + struct scatterlist *sglist, + unsigned int bpl, + unsigned int lines, unsigned int lpi); +extern void cx25821_free_buffer(struct cx25821_dev *dev, + struct cx25821_buffer *buf); +extern void cx25821_sram_channel_dump(struct cx25821_dev *dev, + const struct sram_channel *ch); +extern void cx25821_sram_channel_dump_audio(struct cx25821_dev *dev, + const struct sram_channel *ch); + +extern struct cx25821_dev *cx25821_dev_get(struct pci_dev *pci); +extern void cx25821_print_irqbits(char *name, char *tag, char **strings, + int len, u32 bits, u32 mask); +extern void cx25821_dev_unregister(struct cx25821_dev *dev); +extern int cx25821_sram_channel_setup_audio(struct cx25821_dev *dev, + const struct sram_channel *ch, + unsigned int bpl, u32 risc); + +extern void cx25821_set_pixel_format(struct cx25821_dev *dev, int channel, + u32 format); + +#endif diff --git a/drivers/media/pci/cx88/Kconfig b/drivers/media/pci/cx88/Kconfig new file mode 100644 index 000000000..24e1e7c41 --- /dev/null +++ b/drivers/media/pci/cx88/Kconfig @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_CX88 + tristate "Conexant 2388x (bt878 successor) support" + depends on VIDEO_DEV && PCI && I2C && RC_CORE + select I2C_ALGOBIT + select VIDEOBUF2_DMA_SG + select VIDEO_TUNER + select VIDEO_TVEEPROM + select VIDEO_WM8775 if MEDIA_SUBDRV_AUTOSELECT + help + This is a video4linux driver for Conexant 2388x based + TV cards. + + To compile this driver as a module, choose M here: the + module will be called cx8800 + +config VIDEO_CX88_ALSA + tristate "Conexant 2388x DMA audio support" + depends on VIDEO_CX88 && SND + select SND_PCM + help + This is a video4linux driver for direct (DMA) audio on + Conexant 2388x based TV cards using ALSA. + + It only works with boards with function 01 enabled. + To check if your board supports, use lspci -n. + If supported, you should see 14f1:8801 or 14f1:8811 + PCI device. + + To compile this driver as a module, choose M here: the + module will be called cx88-alsa. + +config VIDEO_CX88_BLACKBIRD + tristate "Blackbird MPEG encoder support (cx2388x + cx23416)" + depends on VIDEO_CX88 + select VIDEO_CX2341X + help + This adds support for MPEG encoder cards based on the + Blackbird reference design, using the Conexant 2388x + and 23416 chips. + + To compile this driver as a module, choose M here: the + module will be called cx88-blackbird. + +config VIDEO_CX88_DVB + tristate "DVB/ATSC Support for cx2388x based TV cards" + depends on VIDEO_CX88 && DVB_CORE + select VIDEOBUF2_DVB + select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT + select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT + select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT + select DVB_OR51132 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CX22702 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT + select DVB_NXT200X if MEDIA_SUBDRV_AUTOSELECT + select DVB_CX24123 if MEDIA_SUBDRV_AUTOSELECT + select DVB_ISL6421 if MEDIA_SUBDRV_AUTOSELECT + select DVB_S5H1411 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CX24116 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0288 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6000 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0900 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT + select DVB_DS3000 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TS2020 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT + help + This adds support for DVB/ATSC cards based on the + Conexant 2388x chip. + + To compile this driver as a module, choose M here: the + module will be called cx88-dvb. + +config VIDEO_CX88_ENABLE_VP3054 + bool "VP-3054 Secondary I2C Bus Support" + default y + depends on VIDEO_CX88_DVB && DVB_MT352 + help + This adds DVB-T support for cards based on the + Conexant 2388x chip and the MT352 demodulator, + which also require support for the VP-3054 + Secondary I2C bus, such at DNTV Live! DVB-T Pro. + +config VIDEO_CX88_VP3054 + tristate + depends on VIDEO_CX88_DVB && VIDEO_CX88_ENABLE_VP3054 + default y + +config VIDEO_CX88_MPEG + tristate + depends on VIDEO_CX88_DVB || VIDEO_CX88_BLACKBIRD + default y diff --git a/drivers/media/pci/cx88/Makefile b/drivers/media/pci/cx88/Makefile new file mode 100644 index 000000000..c2a015869 --- /dev/null +++ b/drivers/media/pci/cx88/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +cx88xx-objs := cx88-cards.o cx88-core.o cx88-i2c.o cx88-tvaudio.o \ + cx88-dsp.o cx88-input.o +cx8800-objs := cx88-video.o cx88-vbi.o +cx8802-objs := cx88-mpeg.o + +obj-$(CONFIG_VIDEO_CX88) += cx88xx.o cx8800.o +obj-$(CONFIG_VIDEO_CX88_MPEG) += cx8802.o +obj-$(CONFIG_VIDEO_CX88_ALSA) += cx88-alsa.o +obj-$(CONFIG_VIDEO_CX88_BLACKBIRD) += cx88-blackbird.o +obj-$(CONFIG_VIDEO_CX88_DVB) += cx88-dvb.o +obj-$(CONFIG_VIDEO_CX88_VP3054) += cx88-vp3054-i2c.o +ccflags-y += -I $(srctree)/drivers/media/tuners +ccflags-y += -I $(srctree)/drivers/media/dvb-frontends diff --git a/drivers/media/pci/cx88/cx88-alsa.c b/drivers/media/pci/cx88/cx88-alsa.c new file mode 100644 index 000000000..d2534f90c --- /dev/null +++ b/drivers/media/pci/cx88/cx88-alsa.c @@ -0,0 +1,1009 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Support for audio capture + * PCI function #1 of the cx2388x. + * + * (c) 2007 Trent Piepho + * (c) 2005,2006 Ricardo Cerqueira + * (c) 2005 Mauro Carvalho Chehab + * Based on a dummy cx88 module by Gerd Knorr + * Based on dummy.c by Jaroslav Kysela + */ + +#include "cx88.h" +#include "cx88-reg.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define dprintk(level, fmt, arg...) do { \ + if (debug + 1 > level) \ + printk(KERN_DEBUG pr_fmt("%s: alsa: " fmt), \ + chip->core->name, ##arg); \ +} while (0) + +/* + * Data type declarations - Can be moded to a header file later + */ + +struct cx88_audio_buffer { + unsigned int bpl; + struct cx88_riscmem risc; + void *vaddr; + struct scatterlist *sglist; + int sglen; + unsigned long nr_pages; +}; + +struct cx88_audio_dev { + struct cx88_core *core; + struct cx88_dmaqueue q; + + /* pci i/o */ + struct pci_dev *pci; + + /* audio controls */ + int irq; + + struct snd_card *card; + + spinlock_t reg_lock; + atomic_t count; + + unsigned int dma_size; + unsigned int period_size; + unsigned int num_periods; + + struct cx88_audio_buffer *buf; + + struct snd_pcm_substream *substream; +}; + +/* + * Module global static vars + */ + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static const char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable cx88x soundcard. default enabled."); + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for cx88x capture interface(s)."); + +/* + * Module macros + */ + +MODULE_DESCRIPTION("ALSA driver module for cx2388x based TV cards"); +MODULE_AUTHOR("Ricardo Cerqueira"); +MODULE_AUTHOR("Mauro Carvalho Chehab "); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(CX88_VERSION); + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "enable debug messages"); + +/* + * Module specific functions + */ + +/* + * BOARD Specific: Sets audio DMA + */ + +static int _cx88_start_audio_dma(struct cx88_audio_dev *chip) +{ + struct cx88_audio_buffer *buf = chip->buf; + struct cx88_core *core = chip->core; + const struct sram_channel *audio_ch = &cx88_sram_channels[SRAM_CH25]; + + /* Make sure RISC/FIFO are off before changing FIFO/RISC settings */ + cx_clear(MO_AUD_DMACNTRL, 0x11); + + /* setup fifo + format - out channel */ + cx88_sram_channel_setup(chip->core, audio_ch, buf->bpl, buf->risc.dma); + + /* sets bpl size */ + cx_write(MO_AUDD_LNGTH, buf->bpl); + + /* reset counter */ + cx_write(MO_AUDD_GPCNTRL, GP_COUNT_CONTROL_RESET); + atomic_set(&chip->count, 0); + + dprintk(1, + "Start audio DMA, %d B/line, %d lines/FIFO, %d periods, %d byte buffer\n", + buf->bpl, cx_read(audio_ch->cmds_start + 8) >> 1, + chip->num_periods, buf->bpl * chip->num_periods); + + /* Enables corresponding bits at AUD_INT_STAT */ + cx_write(MO_AUD_INTMSK, AUD_INT_OPC_ERR | AUD_INT_DN_SYNC | + AUD_INT_DN_RISCI2 | AUD_INT_DN_RISCI1); + + /* Clean any pending interrupt bits already set */ + cx_write(MO_AUD_INTSTAT, ~0); + + /* enable audio irqs */ + cx_set(MO_PCI_INTMSK, chip->core->pci_irqmask | PCI_INT_AUDINT); + + /* start dma */ + + /* Enables Risc Processor */ + cx_set(MO_DEV_CNTRL2, (1 << 5)); + /* audio downstream FIFO and RISC enable */ + cx_set(MO_AUD_DMACNTRL, 0x11); + + if (debug) + cx88_sram_channel_dump(chip->core, audio_ch); + + return 0; +} + +/* + * BOARD Specific: Resets audio DMA + */ +static int _cx88_stop_audio_dma(struct cx88_audio_dev *chip) +{ + struct cx88_core *core = chip->core; + + dprintk(1, "Stopping audio DMA\n"); + + /* stop dma */ + cx_clear(MO_AUD_DMACNTRL, 0x11); + + /* disable irqs */ + cx_clear(MO_PCI_INTMSK, PCI_INT_AUDINT); + cx_clear(MO_AUD_INTMSK, AUD_INT_OPC_ERR | AUD_INT_DN_SYNC | + AUD_INT_DN_RISCI2 | AUD_INT_DN_RISCI1); + + if (debug) + cx88_sram_channel_dump(chip->core, + &cx88_sram_channels[SRAM_CH25]); + + return 0; +} + +#define MAX_IRQ_LOOP 50 + +/* + * BOARD Specific: IRQ dma bits + */ +static const char *cx88_aud_irqs[32] = { + "dn_risci1", "up_risci1", "rds_dn_risc1", /* 0-2 */ + NULL, /* reserved */ + "dn_risci2", "up_risci2", "rds_dn_risc2", /* 4-6 */ + NULL, /* reserved */ + "dnf_of", "upf_uf", "rds_dnf_uf", /* 8-10 */ + NULL, /* reserved */ + "dn_sync", "up_sync", "rds_dn_sync", /* 12-14 */ + NULL, /* reserved */ + "opc_err", "par_err", "rip_err", /* 16-18 */ + "pci_abort", "ber_irq", "mchg_irq" /* 19-21 */ +}; + +/* + * BOARD Specific: Threats IRQ audio specific calls + */ +static void cx8801_aud_irq(struct cx88_audio_dev *chip) +{ + struct cx88_core *core = chip->core; + u32 status, mask; + + status = cx_read(MO_AUD_INTSTAT); + mask = cx_read(MO_AUD_INTMSK); + if (0 == (status & mask)) + return; + cx_write(MO_AUD_INTSTAT, status); + if (debug > 1 || (status & mask & ~0xff)) + cx88_print_irqbits("irq aud", + cx88_aud_irqs, ARRAY_SIZE(cx88_aud_irqs), + status, mask); + /* risc op code error */ + if (status & AUD_INT_OPC_ERR) { + pr_warn("Audio risc op code error\n"); + cx_clear(MO_AUD_DMACNTRL, 0x11); + cx88_sram_channel_dump(core, &cx88_sram_channels[SRAM_CH25]); + } + if (status & AUD_INT_DN_SYNC) { + dprintk(1, "Downstream sync error\n"); + cx_write(MO_AUDD_GPCNTRL, GP_COUNT_CONTROL_RESET); + return; + } + /* risc1 downstream */ + if (status & AUD_INT_DN_RISCI1) { + atomic_set(&chip->count, cx_read(MO_AUDD_GPCNT)); + snd_pcm_period_elapsed(chip->substream); + } + /* FIXME: Any other status should deserve a special handling? */ +} + +/* + * BOARD Specific: Handles IRQ calls + */ +static irqreturn_t cx8801_irq(int irq, void *dev_id) +{ + struct cx88_audio_dev *chip = dev_id; + struct cx88_core *core = chip->core; + u32 status; + int loop, handled = 0; + + for (loop = 0; loop < MAX_IRQ_LOOP; loop++) { + status = cx_read(MO_PCI_INTSTAT) & + (core->pci_irqmask | PCI_INT_AUDINT); + if (status == 0) + goto out; + dprintk(3, "cx8801_irq loop %d/%d, status %x\n", + loop, MAX_IRQ_LOOP, status); + handled = 1; + cx_write(MO_PCI_INTSTAT, status); + + if (status & core->pci_irqmask) + cx88_core_irq(core, status); + if (status & PCI_INT_AUDINT) + cx8801_aud_irq(chip); + } + + if (loop == MAX_IRQ_LOOP) { + pr_err("IRQ loop detected, disabling interrupts\n"); + cx_clear(MO_PCI_INTMSK, PCI_INT_AUDINT); + } + + out: + return IRQ_RETVAL(handled); +} + +static int cx88_alsa_dma_init(struct cx88_audio_dev *chip, + unsigned long nr_pages) +{ + struct cx88_audio_buffer *buf = chip->buf; + struct page *pg; + int i; + + buf->vaddr = vmalloc_32(nr_pages << PAGE_SHIFT); + if (!buf->vaddr) { + dprintk(1, "vmalloc_32(%lu pages) failed\n", nr_pages); + return -ENOMEM; + } + + dprintk(1, "vmalloc is at addr %p, size=%lu\n", + buf->vaddr, nr_pages << PAGE_SHIFT); + + memset(buf->vaddr, 0, nr_pages << PAGE_SHIFT); + buf->nr_pages = nr_pages; + + buf->sglist = vzalloc(array_size(sizeof(*buf->sglist), buf->nr_pages)); + if (!buf->sglist) + goto vzalloc_err; + + sg_init_table(buf->sglist, buf->nr_pages); + for (i = 0; i < buf->nr_pages; i++) { + pg = vmalloc_to_page(buf->vaddr + i * PAGE_SIZE); + if (!pg) + goto vmalloc_to_page_err; + sg_set_page(&buf->sglist[i], pg, PAGE_SIZE, 0); + } + return 0; + +vmalloc_to_page_err: + vfree(buf->sglist); + buf->sglist = NULL; +vzalloc_err: + vfree(buf->vaddr); + buf->vaddr = NULL; + return -ENOMEM; +} + +static int cx88_alsa_dma_map(struct cx88_audio_dev *dev) +{ + struct cx88_audio_buffer *buf = dev->buf; + + buf->sglen = dma_map_sg(&dev->pci->dev, buf->sglist, + buf->nr_pages, DMA_FROM_DEVICE); + + if (buf->sglen == 0) { + pr_warn("%s: cx88_alsa_map_sg failed\n", __func__); + return -ENOMEM; + } + return 0; +} + +static int cx88_alsa_dma_unmap(struct cx88_audio_dev *dev) +{ + struct cx88_audio_buffer *buf = dev->buf; + + if (!buf->sglen) + return 0; + + dma_unmap_sg(&dev->pci->dev, buf->sglist, buf->nr_pages, + DMA_FROM_DEVICE); + buf->sglen = 0; + return 0; +} + +static int cx88_alsa_dma_free(struct cx88_audio_buffer *buf) +{ + vfree(buf->sglist); + buf->sglist = NULL; + vfree(buf->vaddr); + buf->vaddr = NULL; + return 0; +} + +static int dsp_buffer_free(struct cx88_audio_dev *chip) +{ + struct cx88_riscmem *risc = &chip->buf->risc; + + WARN_ON(!chip->dma_size); + + dprintk(2, "Freeing buffer\n"); + cx88_alsa_dma_unmap(chip); + cx88_alsa_dma_free(chip->buf); + if (risc->cpu) + dma_free_coherent(&chip->pci->dev, risc->size, risc->cpu, + risc->dma); + kfree(chip->buf); + + chip->buf = NULL; + + return 0; +} + +/* + * ALSA PCM Interface + */ + +/* + * Digital hardware definition + */ +#define DEFAULT_FIFO_SIZE 4096 +static const struct snd_pcm_hardware snd_cx88_digital_hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + /* + * Analog audio output will be full of clicks and pops if there + * are not exactly four lines in the SRAM FIFO buffer. + */ + .period_bytes_min = DEFAULT_FIFO_SIZE / 4, + .period_bytes_max = DEFAULT_FIFO_SIZE / 4, + .periods_min = 1, + .periods_max = 1024, + .buffer_bytes_max = (1024 * 1024), +}; + +/* + * audio pcm capture open callback + */ +static int snd_cx88_pcm_open(struct snd_pcm_substream *substream) +{ + struct cx88_audio_dev *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + if (!chip) { + pr_err("BUG: cx88 can't find device struct. Can't proceed with open\n"); + return -ENODEV; + } + + err = snd_pcm_hw_constraint_pow2(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + goto _error; + + chip->substream = substream; + + runtime->hw = snd_cx88_digital_hw; + + if (cx88_sram_channels[SRAM_CH25].fifo_size != DEFAULT_FIFO_SIZE) { + unsigned int bpl = cx88_sram_channels[SRAM_CH25].fifo_size / 4; + + bpl &= ~7; /* must be multiple of 8 */ + runtime->hw.period_bytes_min = bpl; + runtime->hw.period_bytes_max = bpl; + } + + return 0; +_error: + dprintk(1, "Error opening PCM!\n"); + return err; +} + +/* + * audio close callback + */ +static int snd_cx88_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* + * hw_params callback + */ +static int snd_cx88_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct cx88_audio_dev *chip = snd_pcm_substream_chip(substream); + + struct cx88_audio_buffer *buf; + int ret; + + if (substream->runtime->dma_area) { + dsp_buffer_free(chip); + substream->runtime->dma_area = NULL; + } + + chip->period_size = params_period_bytes(hw_params); + chip->num_periods = params_periods(hw_params); + chip->dma_size = chip->period_size * params_periods(hw_params); + + WARN_ON(!chip->dma_size); + WARN_ON(chip->num_periods & (chip->num_periods - 1)); + + buf = kzalloc_obj(*buf); + if (!buf) + return -ENOMEM; + + chip->buf = buf; + buf->bpl = chip->period_size; + + ret = cx88_alsa_dma_init(chip, + (PAGE_ALIGN(chip->dma_size) >> PAGE_SHIFT)); + if (ret < 0) + goto error; + + ret = cx88_alsa_dma_map(chip); + if (ret < 0) + goto error; + + ret = cx88_risc_databuffer(chip->pci, &buf->risc, buf->sglist, + chip->period_size, chip->num_periods, 1); + if (ret < 0) { + cx88_alsa_dma_unmap(chip); + goto error; + } + + /* Loop back to start of program */ + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + + substream->runtime->dma_area = chip->buf->vaddr; + substream->runtime->dma_bytes = chip->dma_size; + substream->runtime->dma_addr = 0; + return 0; + +error: + kfree(buf); + return ret; +} + +/* + * hw free callback + */ +static int snd_cx88_hw_free(struct snd_pcm_substream *substream) +{ + struct cx88_audio_dev *chip = snd_pcm_substream_chip(substream); + + if (substream->runtime->dma_area) { + dsp_buffer_free(chip); + substream->runtime->dma_area = NULL; + } + + return 0; +} + +/* + * prepare callback + */ +static int snd_cx88_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* + * trigger callback + */ +static int snd_cx88_card_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct cx88_audio_dev *chip = snd_pcm_substream_chip(substream); + int err; + + /* Local interrupts are already disabled by ALSA */ + spin_lock(&chip->reg_lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + err = _cx88_start_audio_dma(chip); + break; + case SNDRV_PCM_TRIGGER_STOP: + err = _cx88_stop_audio_dma(chip); + break; + default: + err = -EINVAL; + break; + } + + spin_unlock(&chip->reg_lock); + + return err; +} + +/* + * pointer callback + */ +static snd_pcm_uframes_t snd_cx88_pointer(struct snd_pcm_substream *substream) +{ + struct cx88_audio_dev *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + u16 count; + + count = atomic_read(&chip->count); + +// dprintk(2, "%s - count %d (+%u), period %d, frame %lu\n", __func__, +// count, new, count & (runtime->periods-1), +// runtime->period_size * (count & (runtime->periods-1))); + return runtime->period_size * (count & (runtime->periods - 1)); +} + +/* + * page callback (needed for mmap) + */ +static struct page *snd_cx88_page(struct snd_pcm_substream *substream, + unsigned long offset) +{ + void *pageptr = substream->runtime->dma_area + offset; + + return vmalloc_to_page(pageptr); +} + +/* + * operators + */ +static const struct snd_pcm_ops snd_cx88_pcm_ops = { + .open = snd_cx88_pcm_open, + .close = snd_cx88_close, + .hw_params = snd_cx88_hw_params, + .hw_free = snd_cx88_hw_free, + .prepare = snd_cx88_prepare, + .trigger = snd_cx88_card_trigger, + .pointer = snd_cx88_pointer, + .page = snd_cx88_page, +}; + +/* + * create a PCM device + */ +static int snd_cx88_pcm(struct cx88_audio_dev *chip, int device, + const char *name) +{ + int err; + struct snd_pcm *pcm; + + err = snd_pcm_new(chip->card, name, device, 0, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = chip; + strscpy(pcm->name, name, sizeof(pcm->name)); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cx88_pcm_ops); + + return 0; +} + +/* + * CONTROL INTERFACE + */ +static int snd_cx88_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 2; + info->value.integer.min = 0; + info->value.integer.max = 0x3f; + + return 0; +} + +static int snd_cx88_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol); + struct cx88_core *core = chip->core; + int vol = 0x3f - (cx_read(AUD_VOL_CTL) & 0x3f), + bal = cx_read(AUD_BAL_CTL); + + value->value.integer.value[(bal & 0x40) ? 0 : 1] = vol; + vol -= (bal & 0x3f); + value->value.integer.value[(bal & 0x40) ? 1 : 0] = vol < 0 ? 0 : vol; + + return 0; +} + +static void snd_cx88_wm8775_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol); + struct cx88_core *core = chip->core; + u16 left = value->value.integer.value[0]; + u16 right = value->value.integer.value[1]; + int v, b; + + /* Pass volume & balance onto any WM8775 */ + if (left >= right) { + v = left << 10; + b = left ? (0x8000 * right) / left : 0x8000; + } else { + v = right << 10; + b = right ? 0xffff - (0x8000 * left) / right : 0x8000; + } + wm8775_s_ctrl(core, V4L2_CID_AUDIO_VOLUME, v); + wm8775_s_ctrl(core, V4L2_CID_AUDIO_BALANCE, b); +} + +/* OK - TODO: test it */ +static int snd_cx88_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol); + struct cx88_core *core = chip->core; + int left, right, v, b; + int changed = 0; + u32 old; + + if (core->sd_wm8775) + snd_cx88_wm8775_volume_put(kcontrol, value); + + left = value->value.integer.value[0] & 0x3f; + right = value->value.integer.value[1] & 0x3f; + b = right - left; + if (b < 0) { + v = 0x3f - left; + b = (-b) | 0x40; + } else { + v = 0x3f - right; + } + /* Do we really know this will always be called with IRQs on? */ + spin_lock_irq(&chip->reg_lock); + old = cx_read(AUD_VOL_CTL); + if (v != (old & 0x3f)) { + cx_swrite(SHADOW_AUD_VOL_CTL, AUD_VOL_CTL, (old & ~0x3f) | v); + changed = 1; + } + if ((cx_read(AUD_BAL_CTL) & 0x7f) != b) { + cx_write(AUD_BAL_CTL, b); + changed = 1; + } + spin_unlock_irq(&chip->reg_lock); + + return changed; +} + +static const DECLARE_TLV_DB_SCALE(snd_cx88_db_scale, -6300, 100, 0); + +static const struct snd_kcontrol_new snd_cx88_volume = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "Analog-TV Volume", + .info = snd_cx88_volume_info, + .get = snd_cx88_volume_get, + .put = snd_cx88_volume_put, + .tlv.p = snd_cx88_db_scale, +}; + +static int snd_cx88_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol); + struct cx88_core *core = chip->core; + u32 bit = kcontrol->private_value; + + value->value.integer.value[0] = !(cx_read(AUD_VOL_CTL) & bit); + return 0; +} + +static int snd_cx88_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol); + struct cx88_core *core = chip->core; + u32 bit = kcontrol->private_value; + int ret = 0; + u32 vol; + + spin_lock_irq(&chip->reg_lock); + vol = cx_read(AUD_VOL_CTL); + if (value->value.integer.value[0] != !(vol & bit)) { + vol ^= bit; + cx_swrite(SHADOW_AUD_VOL_CTL, AUD_VOL_CTL, vol); + /* Pass mute onto any WM8775 */ + if (core->sd_wm8775 && ((1 << 6) == bit)) + wm8775_s_ctrl(core, + V4L2_CID_AUDIO_MUTE, 0 != (vol & bit)); + ret = 1; + } + spin_unlock_irq(&chip->reg_lock); + return ret; +} + +static const struct snd_kcontrol_new snd_cx88_dac_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Audio-Out Switch", + .info = snd_ctl_boolean_mono_info, + .get = snd_cx88_switch_get, + .put = snd_cx88_switch_put, + .private_value = (1 << 8), +}; + +static const struct snd_kcontrol_new snd_cx88_source_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog-TV Switch", + .info = snd_ctl_boolean_mono_info, + .get = snd_cx88_switch_get, + .put = snd_cx88_switch_put, + .private_value = (1 << 6), +}; + +static int snd_cx88_alc_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol); + struct cx88_core *core = chip->core; + s32 val; + + val = wm8775_g_ctrl(core, V4L2_CID_AUDIO_LOUDNESS); + value->value.integer.value[0] = val ? 1 : 0; + return 0; +} + +static int snd_cx88_alc_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol); + struct cx88_core *core = chip->core; + + wm8775_s_ctrl(core, V4L2_CID_AUDIO_LOUDNESS, + value->value.integer.value[0] != 0); + return 0; +} + +static const struct snd_kcontrol_new snd_cx88_alc_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line-In ALC Switch", + .info = snd_ctl_boolean_mono_info, + .get = snd_cx88_alc_get, + .put = snd_cx88_alc_put, +}; + +/* + * Basic Flow for Sound Devices + */ + +/* + * PCI ID Table - 14f1:8801 and 14f1:8811 means function 1: Audio + * Only boards with eeprom and byte 1 at eeprom=1 have it + */ + +static const struct pci_device_id cx88_audio_pci_tbl[] = { + { PCI_DEVICE(0x14f1, 0x8801) }, + { PCI_DEVICE(0x14f1, 0x8811) }, + { } +}; +MODULE_DEVICE_TABLE(pci, cx88_audio_pci_tbl); + +/* + * Chip-specific destructor + */ + +static int snd_cx88_free(struct cx88_audio_dev *chip) +{ + if (chip->irq >= 0) + free_irq(chip->irq, chip); + + cx88_core_put(chip->core, chip->pci); + + pci_disable_device(chip->pci); + return 0; +} + +/* + * Component Destructor + */ +static void snd_cx88_dev_free(struct snd_card *card) +{ + struct cx88_audio_dev *chip = card->private_data; + + snd_cx88_free(chip); +} + +/* + * Alsa Constructor - Component probe + */ + +static int devno; +static int snd_cx88_create(struct snd_card *card, struct pci_dev *pci, + struct cx88_audio_dev **rchip, + struct cx88_core **core_ptr) +{ + struct cx88_audio_dev *chip; + struct cx88_core *core; + int err; + unsigned char pci_lat; + + *rchip = NULL; + + err = pci_enable_device(pci); + if (err < 0) + return err; + + pci_set_master(pci); + + chip = card->private_data; + + core = cx88_core_get(pci); + if (!core) { + err = -EINVAL; + return err; + } + + err = dma_set_mask(&pci->dev, DMA_BIT_MASK(32)); + if (err) { + dprintk(0, "%s/1: Oops: no 32bit PCI DMA ???\n", core->name); + cx88_core_put(core, pci); + return err; + } + + /* pci init */ + chip->card = card; + chip->pci = pci; + chip->irq = -1; + spin_lock_init(&chip->reg_lock); + + chip->core = core; + + /* get irq */ + err = request_irq(chip->pci->irq, cx8801_irq, + IRQF_SHARED, chip->core->name, chip); + if (err < 0) { + dprintk(0, "%s: can't get IRQ %d\n", + chip->core->name, chip->pci->irq); + return err; + } + + /* print pci info */ + pci_read_config_byte(pci, PCI_LATENCY_TIMER, &pci_lat); + + dprintk(1, + "ALSA %s/%i: found at %s, rev: %d, irq: %d, latency: %d, mmio: 0x%llx\n", + core->name, devno, + pci_name(pci), pci->revision, pci->irq, + pci_lat, (unsigned long long)pci_resource_start(pci, 0)); + + chip->irq = pci->irq; + synchronize_irq(chip->irq); + + *rchip = chip; + *core_ptr = core; + + return 0; +} + +static int cx88_audio_initdev(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct snd_card *card; + struct cx88_audio_dev *chip; + struct cx88_core *core = NULL; + int err; + + if (devno >= SNDRV_CARDS) + return (-ENODEV); + + if (!enable[devno]) { + ++devno; + return (-ENOENT); + } + + err = snd_card_new(&pci->dev, index[devno], id[devno], THIS_MODULE, + sizeof(struct cx88_audio_dev), &card); + if (err < 0) + return err; + + card->private_free = snd_cx88_dev_free; + + err = snd_cx88_create(card, pci, &chip, &core); + if (err < 0) + goto error; + + err = snd_cx88_pcm(chip, 0, "CX88 Digital"); + if (err < 0) + goto error; + + err = snd_ctl_add(card, snd_ctl_new1(&snd_cx88_volume, chip)); + if (err < 0) + goto error; + err = snd_ctl_add(card, snd_ctl_new1(&snd_cx88_dac_switch, chip)); + if (err < 0) + goto error; + err = snd_ctl_add(card, snd_ctl_new1(&snd_cx88_source_switch, chip)); + if (err < 0) + goto error; + + /* If there's a wm8775 then add a Line-In ALC switch */ + if (core->sd_wm8775) { + err = snd_ctl_add(card, snd_ctl_new1(&snd_cx88_alc_switch, chip)); + if (err < 0) + goto error; + } + + strscpy(card->driver, "CX88x", sizeof(card->driver)); + sprintf(card->shortname, "Conexant CX%x", pci->device); + sprintf(card->longname, "%s at %#llx", + card->shortname, + (unsigned long long)pci_resource_start(pci, 0)); + strscpy(card->mixername, "CX88", sizeof(card->mixername)); + + dprintk(0, "%s/%i: ALSA support for cx2388x boards\n", + card->driver, devno); + + err = snd_card_register(card); + if (err < 0) + goto error; + pci_set_drvdata(pci, card); + + devno++; + return 0; + +error: + snd_card_free(card); + return err; +} + +/* + * ALSA destructor + */ +static void cx88_audio_finidev(struct pci_dev *pci) +{ + struct snd_card *card = pci_get_drvdata(pci); + + snd_card_free(card); + + devno--; +} + +/* + * PCI driver definition + */ + +static struct pci_driver cx88_audio_pci_driver = { + .name = "cx88_audio", + .id_table = cx88_audio_pci_tbl, + .probe = cx88_audio_initdev, + .remove = cx88_audio_finidev, +}; + +module_pci_driver(cx88_audio_pci_driver); diff --git a/drivers/media/pci/cx88/cx88-blackbird.c b/drivers/media/pci/cx88/cx88-blackbird.c new file mode 100644 index 000000000..13b8cc468 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-blackbird.c @@ -0,0 +1,1251 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Support for a cx23416 mpeg encoder via cx2388x host port. + * "blackbird" reference design. + * + * (c) 2004 Jelle Foks + * (c) 2004 Gerd Knorr + * + * (c) 2005-2006 Mauro Carvalho Chehab + * - video_ioctl2 conversion + * + * Includes parts from the ivtv driver + */ + +#include "cx88.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("driver for cx2388x/cx23416 based mpeg encoder cards"); +MODULE_AUTHOR("Jelle Foks , Gerd Knorr [SuSE Labs]"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(CX88_VERSION); + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "enable debug messages [blackbird]"); + +#define dprintk(level, fmt, arg...) do { \ + if (debug + 1 > level) \ + printk(KERN_DEBUG pr_fmt("%s: blackbird:" fmt), \ + __func__, ##arg); \ +} while (0) + +/* ------------------------------------------------------------------ */ + +#define BLACKBIRD_FIRM_IMAGE_SIZE 376836 + +/* defines below are from ivtv-driver.h */ + +#define IVTV_CMD_HW_BLOCKS_RST 0xFFFFFFFF + +/* Firmware API commands */ +#define IVTV_API_STD_TIMEOUT 500 + +enum blackbird_capture_type { + BLACKBIRD_MPEG_CAPTURE, + BLACKBIRD_RAW_CAPTURE, + BLACKBIRD_RAW_PASSTHRU_CAPTURE +}; + +enum blackbird_capture_bits { + BLACKBIRD_RAW_BITS_NONE = 0x00, + BLACKBIRD_RAW_BITS_YUV_CAPTURE = 0x01, + BLACKBIRD_RAW_BITS_PCM_CAPTURE = 0x02, + BLACKBIRD_RAW_BITS_VBI_CAPTURE = 0x04, + BLACKBIRD_RAW_BITS_PASSTHRU_CAPTURE = 0x08, + BLACKBIRD_RAW_BITS_TO_HOST_CAPTURE = 0x10 +}; + +enum blackbird_capture_end { + BLACKBIRD_END_AT_GOP, /* stop at the end of gop, generate irq */ + BLACKBIRD_END_NOW, /* stop immediately, no irq */ +}; + +enum blackbird_framerate { + BLACKBIRD_FRAMERATE_NTSC_30, /* NTSC: 30fps */ + BLACKBIRD_FRAMERATE_PAL_25 /* PAL: 25fps */ +}; + +enum blackbird_stream_port { + BLACKBIRD_OUTPUT_PORT_MEMORY, + BLACKBIRD_OUTPUT_PORT_STREAMING, + BLACKBIRD_OUTPUT_PORT_SERIAL +}; + +enum blackbird_data_xfer_status { + BLACKBIRD_MORE_BUFFERS_FOLLOW, + BLACKBIRD_LAST_BUFFER, +}; + +enum blackbird_picture_mask { + BLACKBIRD_PICTURE_MASK_NONE, + BLACKBIRD_PICTURE_MASK_I_FRAMES, + BLACKBIRD_PICTURE_MASK_I_P_FRAMES = 0x3, + BLACKBIRD_PICTURE_MASK_ALL_FRAMES = 0x7, +}; + +enum blackbird_vbi_mode_bits { + BLACKBIRD_VBI_BITS_SLICED, + BLACKBIRD_VBI_BITS_RAW, +}; + +enum blackbird_vbi_insertion_bits { + BLACKBIRD_VBI_BITS_INSERT_IN_XTENSION_USR_DATA, + BLACKBIRD_VBI_BITS_INSERT_IN_PRIVATE_PACKETS = 0x1 << 1, + BLACKBIRD_VBI_BITS_SEPARATE_STREAM = 0x2 << 1, + BLACKBIRD_VBI_BITS_SEPARATE_STREAM_USR_DATA = 0x4 << 1, + BLACKBIRD_VBI_BITS_SEPARATE_STREAM_PRV_DATA = 0x5 << 1, +}; + +enum blackbird_dma_unit { + BLACKBIRD_DMA_BYTES, + BLACKBIRD_DMA_FRAMES, +}; + +enum blackbird_dma_transfer_status_bits { + BLACKBIRD_DMA_TRANSFER_BITS_DONE = 0x01, + BLACKBIRD_DMA_TRANSFER_BITS_ERROR = 0x04, + BLACKBIRD_DMA_TRANSFER_BITS_LL_ERROR = 0x10, +}; + +enum blackbird_pause { + BLACKBIRD_PAUSE_ENCODING, + BLACKBIRD_RESUME_ENCODING, +}; + +enum blackbird_copyright { + BLACKBIRD_COPYRIGHT_OFF, + BLACKBIRD_COPYRIGHT_ON, +}; + +enum blackbird_notification_type { + BLACKBIRD_NOTIFICATION_REFRESH, +}; + +enum blackbird_notification_status { + BLACKBIRD_NOTIFICATION_OFF, + BLACKBIRD_NOTIFICATION_ON, +}; + +enum blackbird_notification_mailbox { + BLACKBIRD_NOTIFICATION_NO_MAILBOX = -1, +}; + +enum blackbird_field1_lines { + BLACKBIRD_FIELD1_SAA7114 = 0x00EF, /* 239 */ + BLACKBIRD_FIELD1_SAA7115 = 0x00F0, /* 240 */ + BLACKBIRD_FIELD1_MICRONAS = 0x0105, /* 261 */ +}; + +enum blackbird_field2_lines { + BLACKBIRD_FIELD2_SAA7114 = 0x00EF, /* 239 */ + BLACKBIRD_FIELD2_SAA7115 = 0x00F0, /* 240 */ + BLACKBIRD_FIELD2_MICRONAS = 0x0106, /* 262 */ +}; + +enum blackbird_custom_data_type { + BLACKBIRD_CUSTOM_EXTENSION_USR_DATA, + BLACKBIRD_CUSTOM_PRIVATE_PACKET, +}; + +enum blackbird_mute { + BLACKBIRD_UNMUTE, + BLACKBIRD_MUTE, +}; + +enum blackbird_mute_video_mask { + BLACKBIRD_MUTE_VIDEO_V_MASK = 0x0000FF00, + BLACKBIRD_MUTE_VIDEO_U_MASK = 0x00FF0000, + BLACKBIRD_MUTE_VIDEO_Y_MASK = 0xFF000000, +}; + +enum blackbird_mute_video_shift { + BLACKBIRD_MUTE_VIDEO_V_SHIFT = 8, + BLACKBIRD_MUTE_VIDEO_U_SHIFT = 16, + BLACKBIRD_MUTE_VIDEO_Y_SHIFT = 24, +}; + +/* Registers */ +#define IVTV_REG_ENC_SDRAM_REFRESH (0x07F8 /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_ENC_SDRAM_PRECHARGE (0x07FC /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_SPU (0x9050 /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_HW_BLOCKS (0x9054 /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_VPU (0x9058 /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_APU (0xA064 /*| IVTV_REG_OFFSET*/) + +/* ------------------------------------------------------------------ */ + +static void host_setup(struct cx88_core *core) +{ + /* toggle reset of the host */ + cx_write(MO_GPHST_SOFT_RST, 1); + udelay(100); + cx_write(MO_GPHST_SOFT_RST, 0); + udelay(100); + + /* host port setup */ + cx_write(MO_GPHST_WSC, 0x44444444U); + cx_write(MO_GPHST_XFR, 0); + cx_write(MO_GPHST_WDTH, 15); + cx_write(MO_GPHST_HDSHK, 0); + cx_write(MO_GPHST_MUX16, 0x44448888U); + cx_write(MO_GPHST_MODE, 0); +} + +/* ------------------------------------------------------------------ */ + +#define P1_MDATA0 0x390000 +#define P1_MDATA1 0x390001 +#define P1_MDATA2 0x390002 +#define P1_MDATA3 0x390003 +#define P1_MADDR2 0x390004 +#define P1_MADDR1 0x390005 +#define P1_MADDR0 0x390006 +#define P1_RDATA0 0x390008 +#define P1_RDATA1 0x390009 +#define P1_RDATA2 0x39000A +#define P1_RDATA3 0x39000B +#define P1_RADDR0 0x39000C +#define P1_RADDR1 0x39000D +#define P1_RRDWR 0x39000E + +static int wait_ready_gpio0_bit1(struct cx88_core *core, u32 state) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(1); + u32 gpio0, need; + + need = state ? 2 : 0; + for (;;) { + gpio0 = cx_read(MO_GP0_IO) & 2; + if (need == gpio0) + return 0; + if (time_after(jiffies, timeout)) + return -1; + udelay(1); + } +} + +static int memory_write(struct cx88_core *core, u32 address, u32 value) +{ + /* Warning: address is dword address (4 bytes) */ + cx_writeb(P1_MDATA0, (unsigned int)value); + cx_writeb(P1_MDATA1, (unsigned int)(value >> 8)); + cx_writeb(P1_MDATA2, (unsigned int)(value >> 16)); + cx_writeb(P1_MDATA3, (unsigned int)(value >> 24)); + cx_writeb(P1_MADDR2, (unsigned int)(address >> 16) | 0x40); + cx_writeb(P1_MADDR1, (unsigned int)(address >> 8)); + cx_writeb(P1_MADDR0, (unsigned int)address); + cx_read(P1_MDATA0); + cx_read(P1_MADDR0); + + return wait_ready_gpio0_bit1(core, 1); +} + +static int memory_read(struct cx88_core *core, u32 address, u32 *value) +{ + int retval; + u32 val; + + /* Warning: address is dword address (4 bytes) */ + cx_writeb(P1_MADDR2, (unsigned int)(address >> 16) & ~0xC0); + cx_writeb(P1_MADDR1, (unsigned int)(address >> 8)); + cx_writeb(P1_MADDR0, (unsigned int)address); + cx_read(P1_MADDR0); + + retval = wait_ready_gpio0_bit1(core, 1); + + cx_writeb(P1_MDATA3, 0); + val = (unsigned char)cx_read(P1_MDATA3) << 24; + cx_writeb(P1_MDATA2, 0); + val |= (unsigned char)cx_read(P1_MDATA2) << 16; + cx_writeb(P1_MDATA1, 0); + val |= (unsigned char)cx_read(P1_MDATA1) << 8; + cx_writeb(P1_MDATA0, 0); + val |= (unsigned char)cx_read(P1_MDATA0); + + *value = val; + return retval; +} + +static int register_write(struct cx88_core *core, u32 address, u32 value) +{ + cx_writeb(P1_RDATA0, (unsigned int)value); + cx_writeb(P1_RDATA1, (unsigned int)(value >> 8)); + cx_writeb(P1_RDATA2, (unsigned int)(value >> 16)); + cx_writeb(P1_RDATA3, (unsigned int)(value >> 24)); + cx_writeb(P1_RADDR0, (unsigned int)address); + cx_writeb(P1_RADDR1, (unsigned int)(address >> 8)); + cx_writeb(P1_RRDWR, 1); + cx_read(P1_RDATA0); + cx_read(P1_RADDR0); + + return wait_ready_gpio0_bit1(core, 1); +} + +static int register_read(struct cx88_core *core, u32 address, u32 *value) +{ + int retval; + u32 val; + + cx_writeb(P1_RADDR0, (unsigned int)address); + cx_writeb(P1_RADDR1, (unsigned int)(address >> 8)); + cx_writeb(P1_RRDWR, 0); + cx_read(P1_RADDR0); + + retval = wait_ready_gpio0_bit1(core, 1); + val = (unsigned char)cx_read(P1_RDATA0); + val |= (unsigned char)cx_read(P1_RDATA1) << 8; + val |= (unsigned char)cx_read(P1_RDATA2) << 16; + val |= (unsigned char)cx_read(P1_RDATA3) << 24; + + *value = val; + return retval; +} + +/* ------------------------------------------------------------------ */ + +static int blackbird_mbox_func(void *priv, u32 command, int in, + int out, u32 data[CX2341X_MBOX_MAX_DATA]) +{ + struct cx8802_dev *dev = priv; + unsigned long timeout; + u32 value, flag, retval; + int i; + + dprintk(1, "%s: 0x%X\n", __func__, command); + + /* + * this may not be 100% safe if we can't read any memory location + * without side effects + */ + memory_read(dev->core, dev->mailbox - 4, &value); + if (value != 0x12345678) { + dprintk(0, + "Firmware and/or mailbox pointer not initialized or corrupted\n"); + return -EIO; + } + + memory_read(dev->core, dev->mailbox, &flag); + if (flag) { + dprintk(0, "ERROR: Mailbox appears to be in use (%x)\n", flag); + return -EIO; + } + + flag |= 1; /* tell 'em we're working on it */ + memory_write(dev->core, dev->mailbox, flag); + + /* write command + args + fill remaining with zeros */ + memory_write(dev->core, dev->mailbox + 1, command); /* command code */ + /* timeout */ + memory_write(dev->core, dev->mailbox + 3, IVTV_API_STD_TIMEOUT); + for (i = 0; i < in; i++) { + memory_write(dev->core, dev->mailbox + 4 + i, data[i]); + dprintk(1, "API Input %d = %d\n", i, data[i]); + } + for (; i < CX2341X_MBOX_MAX_DATA; i++) + memory_write(dev->core, dev->mailbox + 4 + i, 0); + + flag |= 3; /* tell 'em we're done writing */ + memory_write(dev->core, dev->mailbox, flag); + + /* wait for firmware to handle the API command */ + timeout = jiffies + msecs_to_jiffies(1000); + for (;;) { + memory_read(dev->core, dev->mailbox, &flag); + if (0 != (flag & 4)) + break; + if (time_after(jiffies, timeout)) { + dprintk(0, "ERROR: API Mailbox timeout %x\n", command); + return -EIO; + } + udelay(10); + } + + /* read output values */ + for (i = 0; i < out; i++) { + memory_read(dev->core, dev->mailbox + 4 + i, data + i); + dprintk(1, "API Output %d = %d\n", i, data[i]); + } + + memory_read(dev->core, dev->mailbox + 2, &retval); + dprintk(1, "API result = %d\n", retval); + + flag = 0; + memory_write(dev->core, dev->mailbox, flag); + return retval; +} + +/* ------------------------------------------------------------------ */ + +/* + * We don't need to call the API often, so using just one mailbox + * will probably suffice + */ +static int blackbird_api_cmd(struct cx8802_dev *dev, u32 command, + u32 inputcnt, u32 outputcnt, ...) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + va_list vargs; + int i, err; + + va_start(vargs, outputcnt); + + for (i = 0; i < inputcnt; i++) + data[i] = va_arg(vargs, int); + + err = blackbird_mbox_func(dev, command, inputcnt, outputcnt, data); + for (i = 0; i < outputcnt; i++) { + int *vptr = va_arg(vargs, int *); + *vptr = data[i]; + } + va_end(vargs); + return err; +} + +static int blackbird_find_mailbox(struct cx8802_dev *dev) +{ + u32 signature[4] = {0x12345678, 0x34567812, 0x56781234, 0x78123456}; + int signaturecnt = 0; + u32 value; + int i; + + for (i = 0; i < BLACKBIRD_FIRM_IMAGE_SIZE; i++) { + memory_read(dev->core, i, &value); + if (value == signature[signaturecnt]) + signaturecnt++; + else + signaturecnt = 0; + if (signaturecnt == 4) { + dprintk(1, "Mailbox signature found\n"); + return i + 1; + } + } + dprintk(0, "Mailbox signature values not found!\n"); + return -EIO; +} + +static int blackbird_load_firmware(struct cx8802_dev *dev) +{ + static const unsigned char magic[8] = { + 0xa7, 0x0d, 0x00, 0x00, 0x66, 0xbb, 0x55, 0xaa + }; + const struct firmware *firmware; + int i, retval = 0; + u32 value = 0; + u32 checksum = 0; + __le32 *dataptr; + + retval = register_write(dev->core, IVTV_REG_VPU, 0xFFFFFFED); + retval |= register_write(dev->core, IVTV_REG_HW_BLOCKS, + IVTV_CMD_HW_BLOCKS_RST); + retval |= register_write(dev->core, IVTV_REG_ENC_SDRAM_REFRESH, + 0x80000640); + retval |= register_write(dev->core, IVTV_REG_ENC_SDRAM_PRECHARGE, + 0x1A); + usleep_range(10000, 20000); + retval |= register_write(dev->core, IVTV_REG_APU, 0); + + if (retval < 0) + dprintk(0, "Error with register_write\n"); + + retval = request_firmware(&firmware, CX2341X_FIRM_ENC_FILENAME, + &dev->pci->dev); + + if (retval != 0) { + pr_err("Hotplug firmware request failed (%s).\n", + CX2341X_FIRM_ENC_FILENAME); + pr_err("Please fix your hotplug setup, the board will not work without firmware loaded!\n"); + return -EIO; + } + + if (firmware->size != BLACKBIRD_FIRM_IMAGE_SIZE) { + pr_err("Firmware size mismatch (have %zd, expected %d)\n", + firmware->size, BLACKBIRD_FIRM_IMAGE_SIZE); + release_firmware(firmware); + return -EINVAL; + } + + if (memcmp(firmware->data, magic, 8) != 0) { + pr_err("Firmware magic mismatch, wrong file?\n"); + release_firmware(firmware); + return -EINVAL; + } + + /* transfer to the chip */ + dprintk(1, "Loading firmware ...\n"); + dataptr = (__le32 *)firmware->data; + for (i = 0; i < (firmware->size >> 2); i++) { + value = le32_to_cpu(*dataptr); + checksum += ~value; + memory_write(dev->core, i, value); + dataptr++; + } + + /* read back to verify with the checksum */ + for (i--; i >= 0; i--) { + memory_read(dev->core, i, &value); + checksum -= ~value; + } + release_firmware(firmware); + if (checksum) { + pr_err("Firmware load might have failed (checksum mismatch).\n"); + return -EIO; + } + dprintk(0, "Firmware upload successful.\n"); + + retval |= register_write(dev->core, IVTV_REG_HW_BLOCKS, + IVTV_CMD_HW_BLOCKS_RST); + retval |= register_read(dev->core, IVTV_REG_SPU, &value); + retval |= register_write(dev->core, IVTV_REG_SPU, value & 0xFFFFFFFE); + usleep_range(10000, 20000); + + retval |= register_read(dev->core, IVTV_REG_VPU, &value); + retval |= register_write(dev->core, IVTV_REG_VPU, value & 0xFFFFFFE8); + + if (retval < 0) + dprintk(0, "Error with register_write\n"); + return 0; +} + +/* + * Settings used by the windows tv app for PVR2000: + * ================================================================================================================= + * Profile | Codec | Resolution | CBR/VBR | Video Qlty | V. Bitrate | Frmrate | Audio Codec | A. Bitrate | A. Mode + * ----------------------------------------------------------------------------------------------------------------- + * MPEG-1 | MPEG1 | 352x288PAL | (CBR) | 1000:Optimal | 2000 Kbps | 25fps | MPG1 Layer2 | 224kbps | Stereo + * MPEG-2 | MPEG2 | 720x576PAL | VBR | 600 :Good | 4000 Kbps | 25fps | MPG1 Layer2 | 224kbps | Stereo + * VCD | MPEG1 | 352x288PAL | (CBR) | 1000:Optimal | 1150 Kbps | 25fps | MPG1 Layer2 | 224kbps | Stereo + * DVD | MPEG2 | 720x576PAL | VBR | 600 :Good | 6000 Kbps | 25fps | MPG1 Layer2 | 224kbps | Stereo + * DB* DVD | MPEG2 | 720x576PAL | CBR | 600 :Good | 6000 Kbps | 25fps | MPG1 Layer2 | 224kbps | Stereo + * ================================================================================================================= + * [*] DB: "DirectBurn" + */ + +static void blackbird_codec_settings(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + + /* assign frame size */ + blackbird_api_cmd(dev, CX2341X_ENC_SET_FRAME_SIZE, 2, 0, + core->height, core->width); + + dev->cxhdl.width = core->width; + dev->cxhdl.height = core->height; + cx2341x_handler_set_50hz(&dev->cxhdl, + dev->core->tvnorm & V4L2_STD_625_50); + cx2341x_handler_setup(&dev->cxhdl); +} + +static int blackbird_initialize_codec(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + int version; + int retval; + + dprintk(1, "Initialize codec\n"); + retval = blackbird_api_cmd(dev, CX2341X_ENC_PING_FW, 0, 0); /* ping */ + if (retval < 0) { + /* ping was not successful, reset and upload firmware */ + cx_write(MO_SRST_IO, 0); /* SYS_RSTO=0 */ + cx_write(MO_SRST_IO, 1); /* SYS_RSTO=1 */ + retval = blackbird_load_firmware(dev); + if (retval < 0) + return retval; + + retval = blackbird_find_mailbox(dev); + if (retval < 0) + return -1; + + dev->mailbox = retval; + + /* ping */ + retval = blackbird_api_cmd(dev, CX2341X_ENC_PING_FW, 0, 0); + if (retval < 0) { + dprintk(0, "ERROR: Firmware ping failed!\n"); + return -1; + } + + retval = blackbird_api_cmd(dev, CX2341X_ENC_GET_VERSION, + 0, 1, &version); + if (retval < 0) { + dprintk(0, + "ERROR: Firmware get encoder version failed!\n"); + return -1; + } + dprintk(0, "Firmware version is 0x%08x\n", version); + } + + cx_write(MO_PINMUX_IO, 0x88); /* 656-8bit IO and enable MPEG parallel IO */ + cx_clear(MO_INPUT_FORMAT, 0x100); /* chroma subcarrier lock to normal? */ + cx_write(MO_VBOS_CONTROL, 0x84A00); /* no 656 mode, 8-bit pixels, disable VBI */ + cx_clear(MO_OUTPUT_FORMAT, 0x0008); /* Normal Y-limits to let the mpeg encoder sync */ + + blackbird_codec_settings(dev); + + blackbird_api_cmd(dev, CX2341X_ENC_SET_NUM_VSYNC_LINES, 2, 0, + BLACKBIRD_FIELD1_SAA7115, BLACKBIRD_FIELD2_SAA7115); + + blackbird_api_cmd(dev, CX2341X_ENC_SET_PLACEHOLDER, 12, 0, + BLACKBIRD_CUSTOM_EXTENSION_USR_DATA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + return 0; +} + +static int blackbird_start_codec(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + /* start capturing to the host interface */ + u32 reg; + + int i; + int lastchange = -1; + int lastval = 0; + + for (i = 0; (i < 10) && (i < (lastchange + 4)); i++) { + reg = cx_read(AUD_STATUS); + + dprintk(1, "AUD_STATUS:%dL: 0x%x\n", i, reg); + if ((reg & 0x0F) != lastval) { + lastval = reg & 0x0F; + lastchange = i; + } + msleep(100); + } + + /* unmute audio source */ + cx_clear(AUD_VOL_CTL, (1 << 6)); + + blackbird_api_cmd(dev, CX2341X_ENC_REFRESH_INPUT, 0, 0); + + /* initialize the video input */ + blackbird_api_cmd(dev, CX2341X_ENC_INITIALIZE_INPUT, 0, 0); + + cx2341x_handler_set_busy(&dev->cxhdl, 1); + + /* start capturing to the host interface */ + blackbird_api_cmd(dev, CX2341X_ENC_START_CAPTURE, 2, 0, + BLACKBIRD_MPEG_CAPTURE, BLACKBIRD_RAW_BITS_NONE); + + return 0; +} + +static int blackbird_stop_codec(struct cx8802_dev *dev) +{ + blackbird_api_cmd(dev, CX2341X_ENC_STOP_CAPTURE, 3, 0, + BLACKBIRD_END_NOW, + BLACKBIRD_MPEG_CAPTURE, + BLACKBIRD_RAW_BITS_NONE); + + cx2341x_handler_set_busy(&dev->cxhdl, 0); + + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct cx8802_dev *dev = q->drv_priv; + + *num_planes = 1; + dev->ts_packet_size = 188 * 4; + dev->ts_packet_count = 32; + sizes[0] = dev->ts_packet_size * dev->ts_packet_count; + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx8802_dev *dev = vb->vb2_queue->drv_priv; + struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb); + + return cx8802_buf_prepare(vb->vb2_queue, dev, buf); +} + +static void buffer_finish(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx8802_dev *dev = vb->vb2_queue->drv_priv; + struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb); + struct cx88_riscmem *risc = &buf->risc; + + if (risc->cpu) + dma_free_coherent(&dev->pci->dev, risc->size, risc->cpu, + risc->dma); + memset(risc, 0, sizeof(*risc)); +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx8802_dev *dev = vb->vb2_queue->drv_priv; + struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb); + + cx8802_buf_queue(dev, buf); +} + +static int start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct cx8802_dev *dev = q->drv_priv; + struct cx88_dmaqueue *dmaq = &dev->mpegq; + struct cx8802_driver *drv; + struct cx88_buffer *buf; + unsigned long flags; + int err; + + /* Make sure we can acquire the hardware */ + drv = cx8802_get_driver(dev, CX88_MPEG_BLACKBIRD); + if (!drv) { + dprintk(1, "%s: blackbird driver is not loaded\n", __func__); + err = -ENODEV; + goto fail; + } + + err = drv->request_acquire(drv); + if (err != 0) { + dprintk(1, "%s: Unable to acquire hardware, %d\n", __func__, + err); + goto fail; + } + + if (blackbird_initialize_codec(dev) < 0) { + drv->request_release(drv); + err = -EINVAL; + goto fail; + } + + err = blackbird_start_codec(dev); + if (err == 0) { + buf = list_entry(dmaq->active.next, struct cx88_buffer, list); + cx8802_start_dma(dev, dmaq, buf); + return 0; + } + +fail: + spin_lock_irqsave(&dev->slock, flags); + while (!list_empty(&dmaq->active)) { + struct cx88_buffer *buf = list_entry(dmaq->active.next, + struct cx88_buffer, list); + + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); + } + spin_unlock_irqrestore(&dev->slock, flags); + return err; +} + +static void stop_streaming(struct vb2_queue *q) +{ + struct cx8802_dev *dev = q->drv_priv; + struct cx88_dmaqueue *dmaq = &dev->mpegq; + struct cx8802_driver *drv = NULL; + unsigned long flags; + + cx8802_cancel_buffers(dev); + blackbird_stop_codec(dev); + + /* Make sure we release the hardware */ + drv = cx8802_get_driver(dev, CX88_MPEG_BLACKBIRD); + WARN_ON(!drv); + if (drv) + drv->request_release(drv); + + spin_lock_irqsave(&dev->slock, flags); + while (!list_empty(&dmaq->active)) { + struct cx88_buffer *buf = list_entry(dmaq->active.next, + struct cx88_buffer, list); + + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&dev->slock, flags); +} + +static const struct vb2_ops blackbird_qops = { + .queue_setup = queue_setup, + .buf_prepare = buffer_prepare, + .buf_finish = buffer_finish, + .buf_queue = buffer_queue, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, +}; + +/* ------------------------------------------------------------------ */ + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct cx8802_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + strscpy(cap->driver, "cx88_blackbird", sizeof(cap->driver)); + return cx88_querycap(file, core, cap); +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index != 0) + return -EINVAL; + + f->pixelformat = V4L2_PIX_FMT_MPEG; + return 0; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx8802_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = dev->ts_packet_size * dev->ts_packet_count; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + f->fmt.pix.width = core->width; + f->fmt.pix.height = core->height; + f->fmt.pix.field = core->field; + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx8802_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + unsigned int maxw, maxh; + enum v4l2_field field; + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = dev->ts_packet_size * dev->ts_packet_count; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + maxw = norm_maxw(core->tvnorm); + maxh = norm_maxh(core->tvnorm); + + field = f->fmt.pix.field; + + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_SEQ_BT: + case V4L2_FIELD_SEQ_TB: + break; + default: + field = (f->fmt.pix.height > maxh / 2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + break; + } + if (V4L2_FIELD_HAS_T_OR_B(field)) + maxh /= 2; + + v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2, + &f->fmt.pix.height, 32, maxh, 0, 0); + f->fmt.pix.field = field; + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx8802_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + if (vb2_is_busy(&dev->vb2_mpegq)) + return -EBUSY; + if (core->v4ldev && (vb2_is_busy(&core->v4ldev->vb2_vidq) || + vb2_is_busy(&core->v4ldev->vb2_vbiq))) + return -EBUSY; + vidioc_try_fmt_vid_cap(file, priv, f); + core->width = f->fmt.pix.width; + core->height = f->fmt.pix.height; + core->field = f->fmt.pix.field; + cx88_set_scale(core, f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.field); + blackbird_api_cmd(dev, CX2341X_ENC_SET_FRAME_SIZE, 2, 0, + f->fmt.pix.height, f->fmt.pix.width); + return 0; +} + +static int vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct cx8802_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + bool streaming; + + if (unlikely(core->board.tuner_type == UNSET)) + return -EINVAL; + if (unlikely(f->tuner != 0)) + return -EINVAL; + streaming = vb2_start_streaming_called(&dev->vb2_mpegq); + if (streaming) + blackbird_stop_codec(dev); + + cx88_set_freq(core, f); + blackbird_initialize_codec(dev); + cx88_set_scale(core, core->width, core->height, core->field); + if (streaming) + blackbird_start_codec(dev); + return 0; +} + +static int vidioc_log_status(struct file *file, void *priv) +{ + struct cx8802_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + char name[32 + 2]; + + snprintf(name, sizeof(name), "%s/2", core->name); + call_all(core, core, log_status); + v4l2_ctrl_handler_log_status(&dev->cxhdl.hdl, name); + return 0; +} + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct cx8802_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + return cx88_enum_input(core, i); +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct cx8802_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + if (unlikely(core->board.tuner_type == UNSET)) + return -EINVAL; + if (unlikely(f->tuner != 0)) + return -EINVAL; + + f->frequency = core->freq; + call_all(core, tuner, g_frequency, f); + + return 0; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct cx8802_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + *i = core->input; + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + struct cx8802_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + if (i >= 4) + return -EINVAL; + if (!INPUT(i).type) + return -EINVAL; + + cx88_newstation(core); + cx88_video_mux(core, i); + return 0; +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx8802_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + u32 reg; + + if (unlikely(core->board.tuner_type == UNSET)) + return -EINVAL; + if (t->index != 0) + return -EINVAL; + + strscpy(t->name, "Television", sizeof(t->name)); + t->capability = V4L2_TUNER_CAP_NORM; + t->rangehigh = 0xffffffffUL; + call_all(core, tuner, g_tuner, t); + + cx88_get_stereo(core, t); + reg = cx_read(MO_DEVICE_STATUS); + t->signal = (reg & (1 << 5)) ? 0xffff : 0x0000; + return 0; +} + +static int vidioc_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *t) +{ + struct cx8802_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + if (core->board.tuner_type == UNSET) + return -EINVAL; + if (t->index != 0) + return -EINVAL; + + cx88_set_stereo(core, t->audmode, 1); + return 0; +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *tvnorm) +{ + struct cx8802_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + *tvnorm = core->tvnorm; + return 0; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id id) +{ + struct cx8802_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + return cx88_set_tvnorm(core, id); +} + +static const struct v4l2_file_operations mpeg_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops mpeg_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_log_status = vidioc_log_status, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct video_device cx8802_mpeg_template = { + .name = "cx8802", + .fops = &mpeg_fops, + .ioctl_ops = &mpeg_ioctl_ops, + .tvnorms = CX88_NORMS, +}; + +/* ------------------------------------------------------------------ */ + +/* The CX8802 MPEG API will call this when we can use the hardware */ +static int cx8802_blackbird_advise_acquire(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + int err = 0; + + switch (core->boardnr) { + case CX88_BOARD_HAUPPAUGE_HVR1300: + /* + * By default, core setup will leave the cx22702 out of reset, + * on the bus. + * We left the hardware on power up with the cx22702 active. + * We're being given access to re-arrange the GPIOs. + * Take the bus off the cx22702 and put the cx23416 on it. + */ + /* Toggle reset on cx22702 leaving i2c active */ + cx_set(MO_GP0_IO, 0x00000080); + udelay(1000); + cx_clear(MO_GP0_IO, 0x00000080); + udelay(50); + cx_set(MO_GP0_IO, 0x00000080); + udelay(1000); + /* tri-state the cx22702 pins */ + cx_set(MO_GP0_IO, 0x00000004); + udelay(1000); + break; + default: + err = -ENODEV; + } + return err; +} + +/* The CX8802 MPEG API will call this when we need to release the hardware */ +static int cx8802_blackbird_advise_release(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + int err = 0; + + switch (core->boardnr) { + case CX88_BOARD_HAUPPAUGE_HVR1300: + /* Exit leaving the cx23416 on the bus */ + break; + default: + err = -ENODEV; + } + return err; +} + +static void blackbird_unregister_video(struct cx8802_dev *dev) +{ + video_unregister_device(&dev->mpeg_dev); +} + +static int blackbird_register_video(struct cx8802_dev *dev) +{ + int err; + + cx88_vdev_init(dev->core, dev->pci, &dev->mpeg_dev, + &cx8802_mpeg_template, "mpeg"); + dev->mpeg_dev.ctrl_handler = &dev->cxhdl.hdl; + video_set_drvdata(&dev->mpeg_dev, dev); + dev->mpeg_dev.queue = &dev->vb2_mpegq; + dev->mpeg_dev.device_caps = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | + V4L2_CAP_VIDEO_CAPTURE; + if (dev->core->board.tuner_type != UNSET) + dev->mpeg_dev.device_caps |= V4L2_CAP_TUNER; + err = video_register_device(&dev->mpeg_dev, VFL_TYPE_VIDEO, -1); + if (err < 0) { + pr_info("can't register mpeg device\n"); + return err; + } + pr_info("registered device %s [mpeg]\n", + video_device_node_name(&dev->mpeg_dev)); + return 0; +} + +/* ----------------------------------------------------------- */ + +static int cx8802_blackbird_probe(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + struct cx8802_dev *dev = core->dvbdev; + struct vb2_queue *q; + int err; + + dprintk(1, "%s\n", __func__); + dprintk(1, " ->being probed by Card=%d Name=%s, PCI %02x:%02x\n", + core->boardnr, + core->name, + core->pci_bus, + core->pci_slot); + + err = -ENODEV; + if (!(core->board.mpeg & CX88_MPEG_BLACKBIRD)) + goto fail_core; + + dev->cxhdl.port = CX2341X_PORT_STREAMING; + dev->cxhdl.width = core->width; + dev->cxhdl.height = core->height; + dev->cxhdl.func = blackbird_mbox_func; + dev->cxhdl.priv = dev; + err = cx2341x_handler_init(&dev->cxhdl, 36); + if (err) + goto fail_core; + v4l2_ctrl_add_handler(&dev->cxhdl.hdl, &core->video_hdl, NULL, false); + + /* blackbird stuff */ + pr_info("cx23416 based mpeg encoder (blackbird reference design)\n"); + host_setup(dev->core); + + blackbird_initialize_codec(dev); + + /* initial device configuration: needed ? */ +// init_controls(core); + cx88_set_tvnorm(core, core->tvnorm); + cx88_video_mux(core, 0); + cx2341x_handler_set_50hz(&dev->cxhdl, core->height == 576); + cx2341x_handler_setup(&dev->cxhdl); + + q = &dev->vb2_mpegq; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ; + q->gfp_flags = GFP_DMA32; + q->min_queued_buffers = 2; + q->drv_priv = dev; + q->buf_struct_size = sizeof(struct cx88_buffer); + q->ops = &blackbird_qops; + q->mem_ops = &vb2_dma_sg_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &core->lock; + q->dev = &dev->pci->dev; + + err = vb2_queue_init(q); + if (err < 0) + goto fail_core; + + blackbird_register_video(dev); + + return 0; + +fail_core: + return err; +} + +static int cx8802_blackbird_remove(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + struct cx8802_dev *dev = core->dvbdev; + + /* blackbird */ + blackbird_unregister_video(drv->core->dvbdev); + v4l2_ctrl_handler_free(&dev->cxhdl.hdl); + + return 0; +} + +static struct cx8802_driver cx8802_blackbird_driver = { + .type_id = CX88_MPEG_BLACKBIRD, + .hw_access = CX8802_DRVCTL_SHARED, + .probe = cx8802_blackbird_probe, + .remove = cx8802_blackbird_remove, + .advise_acquire = cx8802_blackbird_advise_acquire, + .advise_release = cx8802_blackbird_advise_release, +}; + +static int __init blackbird_init(void) +{ + pr_info("cx2388x blackbird driver version %s loaded\n", + CX88_VERSION); + return cx8802_register_driver(&cx8802_blackbird_driver); +} + +static void __exit blackbird_fini(void) +{ + cx8802_unregister_driver(&cx8802_blackbird_driver); +} + +module_init(blackbird_init); +module_exit(blackbird_fini); diff --git a/drivers/media/pci/cx88/cx88-cards.c b/drivers/media/pci/cx88/cx88-cards.c new file mode 100644 index 000000000..7567c2388 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-cards.c @@ -0,0 +1,3856 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * device driver for Conexant 2388x based TV cards + * card-specific stuff. + * + * (c) 2003 Gerd Knorr [SuSE Labs] + */ + +#include "cx88.h" +#include "tea5767.h" +#include "xc4000.h" + +#include +#include +#include +#include +#include + +static unsigned int tuner[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; +static unsigned int radio[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; +static unsigned int card[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; + +module_param_array(tuner, int, NULL, 0444); +module_param_array(radio, int, NULL, 0444); +module_param_array(card, int, NULL, 0444); + +MODULE_PARM_DESC(tuner, "tuner type"); +MODULE_PARM_DESC(radio, "radio tuner type"); +MODULE_PARM_DESC(card, "card type"); + +static unsigned int latency = UNSET; +module_param(latency, int, 0444); +MODULE_PARM_DESC(latency, "pci latency timer"); + +static int disable_ir; +module_param(disable_ir, int, 0444); +MODULE_PARM_DESC(disable_ir, "Disable IR support"); + +#define dprintk(level, fmt, arg...) do { \ + if (cx88_core_debug >= level) \ + printk(KERN_DEBUG pr_fmt("%s: core:" fmt), \ + __func__, ##arg); \ +} while (0) + +/* ------------------------------------------------------------------ */ +/* board config info */ + +/* If radio_type !=UNSET, radio_addr should be specified + */ + +static const struct cx88_board cx88_boards[] = { + [CX88_BOARD_UNKNOWN] = { + .name = "UNKNOWN/GENERIC", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 0, + }, { + .type = CX88_VMUX_COMPOSITE2, + .vmux = 1, + }, { + .type = CX88_VMUX_COMPOSITE3, + .vmux = 2, + }, { + .type = CX88_VMUX_COMPOSITE4, + .vmux = 3, + } }, + }, + [CX88_BOARD_HAUPPAUGE] = { + .name = "Hauppauge WinTV 34xxx models", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xff00, // internal decoder + }, { + .type = CX88_VMUX_DEBUG, + .vmux = 0, + .gpio0 = 0xff01, // mono from tuner chip + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xff02, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xff02, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xff01, + }, + }, + [CX88_BOARD_GDI] = { + .name = "GDI Black Gold", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + } }, + }, + [CX88_BOARD_PIXELVIEW] = { + .name = "PixelView", + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xff00, // internal decoder + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xff10, + }, + }, + [CX88_BOARD_ATI_WONDER_PRO] = { + .name = "ATI TV Wonder Pro", + .tuner_type = TUNER_PHILIPS_4IN1, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT | TDA9887_INTERCARRIER, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x03ff, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x03fe, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x03fe, + } }, + }, + [CX88_BOARD_WINFAST2000XP_EXPERT] = { + .name = "Leadtek Winfast 2000XP Expert", + .tuner_type = TUNER_PHILIPS_4IN1, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00F5e700, + .gpio1 = 0x00003004, + .gpio2 = 0x00F5e700, + .gpio3 = 0x02000000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00F5c700, + .gpio1 = 0x00003004, + .gpio2 = 0x00F5c700, + .gpio3 = 0x02000000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00F5c700, + .gpio1 = 0x00003004, + .gpio2 = 0x00F5c700, + .gpio3 = 0x02000000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x00F5d700, + .gpio1 = 0x00003004, + .gpio2 = 0x00F5d700, + .gpio3 = 0x02000000, + }, + }, + [CX88_BOARD_AVERTV_STUDIO_303] = { + .name = "AverTV Studio 303 (M126)", + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio1 = 0xe09f, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio1 = 0xe05f, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio1 = 0xe05f, + } }, + .radio = { + .gpio1 = 0xe0df, + .type = CX88_RADIO, + }, + }, + [CX88_BOARD_MSI_TVANYWHERE_MASTER] = { + // added gpio values thanks to Michal + // values for PAL from DScaler + .name = "MSI TV-@nywhere Master", + .tuner_type = TUNER_MT2032, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT | TDA9887_INTERCARRIER_NTSC, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x000040bf, + .gpio1 = 0x000080c0, + .gpio2 = 0x0000ff40, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000040bf, + .gpio1 = 0x000080c0, + .gpio2 = 0x0000ff40, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000040bf, + .gpio1 = 0x000080c0, + .gpio2 = 0x0000ff40, + } }, + .radio = { + .type = CX88_RADIO, + .vmux = 3, + .gpio0 = 0x000040bf, + .gpio1 = 0x000080c0, + .gpio2 = 0x0000ff20, + }, + }, + [CX88_BOARD_WINFAST_DV2000] = { + .name = "Leadtek Winfast DV2000", + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0035e700, + .gpio1 = 0x00003004, + .gpio2 = 0x0035e700, + .gpio3 = 0x02000000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0035c700, + .gpio1 = 0x00003004, + .gpio2 = 0x0035c700, + .gpio3 = 0x02000000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0035c700, + .gpio1 = 0x0035c700, + .gpio2 = 0x02000000, + .gpio3 = 0x02000000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0035d700, + .gpio1 = 0x00007004, + .gpio2 = 0x0035d700, + .gpio3 = 0x02000000, + }, + }, + [CX88_BOARD_LEADTEK_PVR2000] = { + // gpio values for PAL version from regspy by DScaler + .name = "Leadtek PVR 2000", + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0000bde2, + .audioroute = 1, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0000bde6, + .audioroute = 1, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0000bde6, + .audioroute = 1, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0000bd62, + .audioroute = 1, + }, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_IODATA_GVVCP3PCI] = { + .name = "IODATA GV-VCP3/PCI", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 0, + }, { + .type = CX88_VMUX_COMPOSITE2, + .vmux = 1, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + } }, + }, + [CX88_BOARD_PROLINK_PLAYTVPVR] = { + .name = "Prolink PlayTV PVR", + .tuner_type = TUNER_PHILIPS_FM1236_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xbff0, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xbff3, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xbff3, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xbff0, + }, + }, + [CX88_BOARD_ASUS_PVR_416] = { + .name = "ASUS PVR-416", + .tuner_type = TUNER_PHILIPS_FM1236_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0000fde6, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0000fde6, // 0x0000fda6 L,R RCA audio in? + .audioroute = 1, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0000fde2, + }, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_MSI_TVANYWHERE] = { + .name = "MSI TV-@nywhere", + .tuner_type = TUNER_MT2032, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00000fbf, + .gpio2 = 0x0000fc08, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00000fbf, + .gpio2 = 0x0000fc68, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00000fbf, + .gpio2 = 0x0000fc68, + } }, + }, + [CX88_BOARD_KWORLD_DVB_T] = { + .name = "KWorld/VStream XPert DVB-T", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0700, + .gpio2 = 0x0101, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0700, + .gpio2 = 0x0101, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1] = { + .name = "DViCO FusionHDTV DVB-T1", + .tuner_type = UNSET, /* No analog tuner */ + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000027df, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000027df, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_KWORLD_LTV883] = { + .name = "KWorld LTV883RF", + .tuner_type = TUNER_TNF_8831BGFF, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x07f8, + }, { + .type = CX88_VMUX_DEBUG, + .vmux = 0, + .gpio0 = 0x07f9, // mono from tuner chip + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000007fa, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000007fa, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x000007f8, + }, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q] = { + .name = "DViCO FusionHDTV 3 Gold-Q", + .tuner_type = TUNER_MICROTUNE_4042FI5, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + /* + * GPIO[0] resets DT3302 DTV receiver + * 0 - reset asserted + * 1 - normal operation + * GPIO[1] mutes analog audio output connector + * 0 - enable selected source + * 1 - mute + * GPIO[2] selects source for analog audio output connector + * 0 - analog audio input connector on tab + * 1 - analog DAC output from CX23881 chip + * GPIO[3] selects RF input connector on tuner module + * 0 - RF connector labeled CABLE + * 1 - RF connector labeled ANT + * GPIO[4] selects high RF for QAM256 mode + * 0 - normal RF + * 1 - high RF + */ + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0f0d, + }, { + .type = CX88_VMUX_CABLE, + .vmux = 0, + .gpio0 = 0x0f05, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0f00, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0f00, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_HAUPPAUGE_DVB_T1] = { + .name = "Hauppauge Nova-T DVB-T", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_CONEXANT_DVB_T1] = { + .name = "Conexant DVB-T reference design", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_PROVIDEO_PV259] = { + .name = "Provideo PV259", + .tuner_type = TUNER_PHILIPS_FQ1216ME, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .audioroute = 1, + } }, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS] = { + .name = "DViCO FusionHDTV DVB-T Plus", + .tuner_type = UNSET, /* No analog tuner */ + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000027df, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000027df, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_DNTV_LIVE_DVB_T] = { + .name = "digitalnow DNTV Live! DVB-T", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00000700, + .gpio2 = 0x00000101, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00000700, + .gpio2 = 0x00000101, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_PCHDTV_HD3000] = { + .name = "pcHDTV HD3000 HDTV", + .tuner_type = TUNER_THOMSON_DTT761X, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + /* GPIO[2] = audio source for analog audio out connector + * 0 = analog audio input connector + * 1 = CX88 audio DACs + * + * GPIO[7] = input to CX88's audio/chroma ADC + * 0 = FM 10.7 MHz IF + * 1 = Sound 4.5 MHz IF + * + * GPIO[1,5,6] = Oren 51132 pins 27,35,28 respectively + * + * GPIO[16] = Remote control input + */ + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00008484, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00008400, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00008400, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x00008404, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_HAUPPAUGE_ROSLYN] = { + // entry added by Kaustubh D. Bhalerao + // GPIO values obtained from regspy, courtesy Sean Covel + .name = "Hauppauge WinTV 28xxx (Roslyn) models", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xed1a, + .gpio2 = 0x00ff, + }, { + .type = CX88_VMUX_DEBUG, + .vmux = 0, + .gpio0 = 0xff01, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xff02, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xed92, + .gpio2 = 0x00ff, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xed96, + .gpio2 = 0x00ff, + }, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_DIGITALLOGIC_MEC] = { + .name = "Digital-Logic MICROSPACE Entertainment Center (MEC)", + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00009d80, + .audioroute = 1, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00009d76, + .audioroute = 1, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00009d76, + .audioroute = 1, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x00009d00, + .audioroute = 1, + }, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_IODATA_GVBCTV7E] = { + .name = "IODATA GV/BCTV7E", + .tuner_type = TUNER_PHILIPS_FQ1286, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 1, + .gpio1 = 0x0000e03f, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 2, + .gpio1 = 0x0000e07f, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 3, + .gpio1 = 0x0000e07f, + } } + }, + [CX88_BOARD_PIXELVIEW_PLAYTV_ULTRA_PRO] = { + .name = "PixelView PlayTV Ultra Pro (Stereo)", + /* May be also TUNER_YMEC_TVF_5533MF for NTSC/M or PAL/M */ + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + /* + * Some variants use a tda9874 and so need the + * tvaudio module. + */ + .audio_chip = CX88_AUDIO_TVAUDIO, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xbf61, /* internal decoder */ + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xbf63, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xbf63, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xbf60, + }, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T] = { + .name = "DViCO FusionHDTV 3 Gold-T", + .tuner_type = TUNER_THOMSON_DTT761X, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x97ed, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x97e9, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x97e9, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_ADSTECH_DVB_T_PCI] = { + .name = "ADS Tech Instant TV DVB-T PCI", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0700, + .gpio2 = 0x0101, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0700, + .gpio2 = 0x0101, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TERRATEC_CINERGY_1400_DVB_T1] = { + .name = "TerraTec Cinergy 1400 DVB-T", + .tuner_type = UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 2, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD] = { + .name = "DViCO FusionHDTV 5 Gold", + .tuner_type = TUNER_LG_TDVS_H06XF, /* TDVS-H062F */ + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x87fd, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x87f9, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x87f9, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_AVERMEDIA_ULTRATV_MC_550] = { + .name = "AverMedia UltraTV Media Center PCI 550", + .tuner_type = TUNER_PHILIPS_FM1236_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 0, + .gpio0 = 0x0000cd73, + .audioroute = 1, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 1, + .gpio0 = 0x0000cd73, + .audioroute = 1, + }, { + .type = CX88_VMUX_TELEVISION, + .vmux = 3, + .gpio0 = 0x0000cdb3, + .audioroute = 1, + } }, + .radio = { + .type = CX88_RADIO, + .vmux = 2, + .gpio0 = 0x0000cdf3, + .audioroute = 1, + }, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_KWORLD_VSTREAM_EXPERT_DVD] = { + /* Alexander Wold */ + .name = "Kworld V-Stream Xpert DVD", + .tuner_type = UNSET, + .input = { { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x03000000, + .gpio1 = 0x01000000, + .gpio2 = 0x02000000, + .gpio3 = 0x00100000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x03000000, + .gpio1 = 0x01000000, + .gpio2 = 0x02000000, + .gpio3 = 0x00100000, + } }, + }, + [CX88_BOARD_ATI_HDTVWONDER] = { + .name = "ATI HDTV Wonder", + .tuner_type = TUNER_PHILIPS_TUV1236D, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00000ff7, + .gpio1 = 0x000000ff, + .gpio2 = 0x00000001, + .gpio3 = 0x00000000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00000ffe, + .gpio1 = 0x000000ff, + .gpio2 = 0x00000001, + .gpio3 = 0x00000000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00000ffe, + .gpio1 = 0x000000ff, + .gpio2 = 0x00000001, + .gpio3 = 0x00000000, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_WINFAST_DTV1000] = { + .name = "WinFast DTV1000-T", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_AVERTV_303] = { + .name = "AVerTV 303 (M126)", + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00ff, + .gpio1 = 0xe09f, + .gpio2 = 0x0010, + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00ff, + .gpio1 = 0xe05f, + .gpio2 = 0x0010, + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00ff, + .gpio1 = 0xe05f, + .gpio2 = 0x0010, + .gpio3 = 0x0000, + } }, + }, + [CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1] = { + .name = "Hauppauge Nova-S-Plus DVB-S", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .audio_chip = CX88_AUDIO_WM8775, + .i2sinputcntl = 2, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + /* 2: Line-In */ + .audioroute = 2, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + /* 2: Line-In */ + .audioroute = 2, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + /* 2: Line-In */ + .audioroute = 2, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_HAUPPAUGE_NOVASE2_S1] = { + .name = "Hauppauge Nova-SE2 DVB-S", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_KWORLD_DVBS_100] = { + .name = "KWorld DVB-S 100", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .audio_chip = CX88_AUDIO_WM8775, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + /* 2: Line-In */ + .audioroute = 2, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + /* 2: Line-In */ + .audioroute = 2, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + /* 2: Line-In */ + .audioroute = 2, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_HAUPPAUGE_HVR1100] = { + .name = "Hauppauge WinTV-HVR1100 DVB-T/Hybrid", + .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + } }, + /* fixme: Add radio support */ + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_HAUPPAUGE_HVR1100LP] = { + .name = "Hauppauge WinTV-HVR1100 DVB-T/Hybrid (Low Profile)", + .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + } }, + /* fixme: Add radio support */ + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_DNTV_LIVE_DVB_T_PRO] = { + .name = "digitalnow DNTV Live! DVB-T Pro", + .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT | TDA9887_PORT1_ACTIVE | + TDA9887_PORT2_ACTIVE, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xf80808, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xf80808, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xf80808, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xf80808, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_KWORLD_DVB_T_CX22702] = { + /* Kworld V-stream Xpert DVB-T with Thomson tuner */ + /* DTT 7579 Conexant CX22702-19 Conexant CX2388x */ + /* Manenti Marco */ + .name = "KWorld/VStream XPert DVB-T with cx22702", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0700, + .gpio2 = 0x0101, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0700, + .gpio2 = 0x0101, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL] = { + .name = "DViCO FusionHDTV DVB-T Dual Digital", + .tuner_type = UNSET, /* No analog tuner */ + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000067df, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000067df, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_KWORLD_HARDWARE_MPEG_TV_XPERT] = { + .name = "KWorld HardwareMpegTV XPert", + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x3de2, + .gpio2 = 0x00ff, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x3de6, + .audioroute = 1, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x3de6, + .audioroute = 1, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x3de6, + .gpio2 = 0x00ff, + }, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID] = { + .name = "DViCO FusionHDTV DVB-T Hybrid", + .tuner_type = TUNER_THOMSON_FE6600, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0000a75f, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0000a75b, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0000a75b, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_PCHDTV_HD5500] = { + .name = "pcHDTV HD5500 HDTV", + .tuner_type = TUNER_LG_TDVS_H06XF, /* TDVS-H064F */ + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x87fd, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x87f9, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x87f9, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_KWORLD_MCE200_DELUXE] = { + /* + * FIXME: tested TV input only, disabled composite, + * svideo and radio until they can be tested also. + */ + .name = "Kworld MCE 200 Deluxe", + .tuner_type = TUNER_TENA_9533_DI, + .radio_type = UNSET, + .tda9887_conf = TDA9887_PRESENT, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0000BDE6 + } }, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_PIXELVIEW_PLAYTV_P7000] = { + /* FIXME: SVideo, Composite and FM inputs are untested */ + .name = "PixelView PlayTV P7000", + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT | TDA9887_PORT1_ACTIVE | + TDA9887_PORT2_ACTIVE, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x5da6, + } }, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_NPGTECH_REALTV_TOP10FM] = { + .name = "NPG Tech Real TV FM Top 10", + .tuner_type = TUNER_TNF_5335MF, /* Actually a TNF9535 */ + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0788, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x078b, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x078b, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x074a, + }, + }, + [CX88_BOARD_WINFAST_DTV2000H] = { + .name = "WinFast DTV2000 H", + .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00017304, + .gpio1 = 0x00008203, + .gpio2 = 0x00017304, + .gpio3 = 0x02000000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0001d701, + .gpio1 = 0x0000b207, + .gpio2 = 0x0001d701, + .gpio3 = 0x02000000, + }, { + .type = CX88_VMUX_COMPOSITE2, + .vmux = 2, + .gpio0 = 0x0001d503, + .gpio1 = 0x0000b207, + .gpio2 = 0x0001d503, + .gpio3 = 0x02000000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 3, + .gpio0 = 0x0001d701, + .gpio1 = 0x0000b207, + .gpio2 = 0x0001d701, + .gpio3 = 0x02000000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x00015702, + .gpio1 = 0x0000f207, + .gpio2 = 0x00015702, + .gpio3 = 0x02000000, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_WINFAST_DTV2000H_J] = { + .name = "WinFast DTV2000 H rev. J", + .tuner_type = TUNER_PHILIPS_FMD1216MEX_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00017300, + .gpio1 = 0x00008207, + .gpio2 = 0x00000000, + .gpio3 = 0x02000000, + }, { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00018300, + .gpio1 = 0x0000f207, + .gpio2 = 0x00017304, + .gpio3 = 0x02000000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00018301, + .gpio1 = 0x0000f207, + .gpio2 = 0x00017304, + .gpio3 = 0x02000000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00018301, + .gpio1 = 0x0000f207, + .gpio2 = 0x00017304, + .gpio3 = 0x02000000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x00015702, + .gpio1 = 0x0000f207, + .gpio2 = 0x00015702, + .gpio3 = 0x02000000, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_GENIATECH_DVBS] = { + .name = "Geniatech DVB-S", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_HAUPPAUGE_HVR3000] = { + .name = "Hauppauge WinTV-HVR3000 TriMode Analog/DVB-S/DVB-T", + .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .audio_chip = CX88_AUDIO_WM8775, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x84bf, + /* 1: TV Audio / FM Mono */ + .audioroute = 1, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x84bf, + /* 2: Line-In */ + .audioroute = 2, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x84bf, + /* 2: Line-In */ + .audioroute = 2, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x84bf, + /* 4: FM Stereo (untested) */ + .audioroute = 8, + }, + .mpeg = CX88_MPEG_DVB, + .num_frontends = 2, + }, + [CX88_BOARD_NORWOOD_MICRO] = { + .name = "Norwood Micro TV Tuner", + .tuner_type = TUNER_TNF_5335MF, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0709, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x070b, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x070b, + } }, + }, + [CX88_BOARD_TE_DTV_250_OEM_SWANN] = { + .name = "Shenzhen Tungsten Ages Tech TE-DTV-250 / Swann OEM", + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x003fffff, + .gpio1 = 0x00e00000, + .gpio2 = 0x003fffff, + .gpio3 = 0x02000000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x003fffff, + .gpio1 = 0x00e00000, + .gpio2 = 0x003fffff, + .gpio3 = 0x02000000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x003fffff, + .gpio1 = 0x00e00000, + .gpio2 = 0x003fffff, + .gpio3 = 0x02000000, + } }, + }, + [CX88_BOARD_HAUPPAUGE_HVR1300] = { + .name = "Hauppauge WinTV-HVR1300 DVB-T/Hybrid MPEG Encoder", + .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .audio_chip = CX88_AUDIO_WM8775, + /* + * gpio0 as reported by Mike Crash + */ + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xef88, + /* 1: TV Audio / FM Mono */ + .audioroute = 1, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xef88, + /* 2: Line-In */ + .audioroute = 2, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xef88, + /* 2: Line-In */ + .audioroute = 2, + } }, + .mpeg = CX88_MPEG_DVB | CX88_MPEG_BLACKBIRD, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xef88, + /* 4: FM Stereo (untested) */ + .audioroute = 8, + }, + }, + [CX88_BOARD_SAMSUNG_SMT_7020] = { + .name = "Samsung SMT 7020 DVB-S", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_ADSTECH_PTV_390] = { + .name = "ADS Tech Instant Video PCI", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DEBUG, + .vmux = 3, + .gpio0 = 0x04ff, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x07fa, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x07fa, + } }, + }, + [CX88_BOARD_PINNACLE_PCTV_HD_800i] = { + .name = "Pinnacle PCTV HD 800i", + .tuner_type = TUNER_XC5000, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x04fb, + .gpio1 = 0x10ff, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x04fb, + .gpio1 = 0x10ef, + .audioroute = 1, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x04fb, + .gpio1 = 0x10ef, + .audioroute = 1, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO] = { + .name = "DViCO FusionHDTV 5 PCI nano", + /* xc3008 tuner, digital only for now */ + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x000027df, /* Unconfirmed */ + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000027df, /* Unconfirmed */ + .audioroute = 1, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000027df, /* Unconfirmed */ + .audioroute = 1, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_PINNACLE_HYBRID_PCTV] = { + .name = "Pinnacle Hybrid PCTV", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .radio_type = UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x004ff, + .gpio1 = 0x010ff, + .gpio2 = 0x00001, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x004fb, + .gpio1 = 0x010ef, + .audioroute = 1, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x004fb, + .gpio1 = 0x010ef, + .audioroute = 1, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x004ff, + .gpio1 = 0x010ff, + .gpio2 = 0x0ff, + }, + .mpeg = CX88_MPEG_DVB, + }, + /* Terry Wu */ + /* TV Audio : set GPIO 2, 18, 19 value to 0, 1, 0 */ + /* FM Audio : set GPIO 2, 18, 19 value to 0, 0, 0 */ + /* Line-in Audio : set GPIO 2, 18, 19 value to 0, 1, 1 */ + /* Mute Audio : set GPIO 2 value to 1 */ + [CX88_BOARD_WINFAST_TV2000_XP_GLOBAL] = { + .name = "Leadtek TV2000 XP Global", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .radio_type = UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x0000, + .gpio2 = 0x0C04, /* pin 18 = 1, pin 19 = 0 */ + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x0000, + .gpio2 = 0x0C0C, /* pin 18 = 1, pin 19 = 1 */ + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x0000, + .gpio2 = 0x0C0C, /* pin 18 = 1, pin 19 = 1 */ + .gpio3 = 0x0000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x0000, + .gpio2 = 0x0C00, /* pin 18 = 0, pin 19 = 0 */ + .gpio3 = 0x0000, + }, + }, + [CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36] = { + .name = "Leadtek TV2000 XP Global (SC4100)", + .tuner_type = TUNER_XC4000, + .tuner_addr = 0x61, + .radio_type = UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x0000, + .gpio2 = 0x0C04, /* pin 18 = 1, pin 19 = 0 */ + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x0000, + .gpio2 = 0x0C0C, /* pin 18 = 1, pin 19 = 1 */ + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x0000, + .gpio2 = 0x0C0C, /* pin 18 = 1, pin 19 = 1 */ + .gpio3 = 0x0000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x0000, + .gpio2 = 0x0C00, /* pin 18 = 0, pin 19 = 0 */ + .gpio3 = 0x0000, + }, + }, + [CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43] = { + .name = "Leadtek TV2000 XP Global (XC4100)", + .tuner_type = TUNER_XC4000, + .tuner_addr = 0x61, + .radio_type = UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6040, /* pin 14 = 1, pin 13 = 0 */ + .gpio2 = 0x0000, + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6060, /* pin 14 = 1, pin 13 = 1 */ + .gpio2 = 0x0000, + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6060, /* pin 14 = 1, pin 13 = 1 */ + .gpio2 = 0x0000, + .gpio3 = 0x0000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6000, /* pin 14 = 1, pin 13 = 0 */ + .gpio2 = 0x0000, + .gpio3 = 0x0000, + }, + }, + [CX88_BOARD_POWERCOLOR_REAL_ANGEL] = { + /* Long names may confuse LIRC. */ + .name = "PowerColor RA330", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .input = { { + /* + * Due to the way the cx88 driver is written, + * there is no way to deactivate audio pass- + * through without this entry. Furthermore, if + * the TV mux entry is first, you get audio + * from the tuner on boot for a little while. + */ + .type = CX88_VMUX_DEBUG, + .vmux = 3, + .gpio0 = 0x00ff, + .gpio1 = 0xf39d, + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00ff, + .gpio1 = 0xf35d, + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00ff, + .gpio1 = 0xf37d, + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000ff, + .gpio1 = 0x0f37d, + .gpio3 = 0x00000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x000ff, + .gpio1 = 0x0f35d, + .gpio3 = 0x00000, + }, + }, + [CX88_BOARD_GENIATECH_X8000_MT] = { + /* Also PowerColor Real Angel 330 and Geniatech X800 OEM */ + .name = "Geniatech X8000-MT DVBT", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00000000, + .gpio1 = 0x00e3e341, + .gpio2 = 0x00000000, + .gpio3 = 0x00000000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00000000, + .gpio1 = 0x00e3e361, + .gpio2 = 0x00000000, + .gpio3 = 0x00000000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00000000, + .gpio1 = 0x00e3e361, + .gpio2 = 0x00000000, + .gpio3 = 0x00000000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x00000000, + .gpio1 = 0x00e3e341, + .gpio2 = 0x00000000, + .gpio3 = 0x00000000, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_NOTONLYTV_LV3H] = { + .name = "NotOnlyTV LV3H", + .tuner_type = TUNER_XC2028, + .radio_type = UNSET, + .tuner_addr = 0x61, + .radio_addr = ADDR_UNSET, + /* if gpio1:bit9 is enabled, DVB-T won't work */ + + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0000, + .gpio1 = 0xa141, + .gpio2 = 0x0000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0000, + .gpio1 = 0xa161, + .gpio2 = 0x0000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0000, + .gpio1 = 0xa161, + .gpio2 = 0x0000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0000, + .gpio1 = 0xa141, + .gpio2 = 0x0000, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO] = { + .name = "DViCO FusionHDTV DVB-T PRO", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .radio_type = UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000067df, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000067df, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD] = { + .name = "DViCO FusionHDTV 7 Gold", + .tuner_type = TUNER_XC5000, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x10df, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x16d9, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x16d9, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_PROLINK_PV_8000GT] = { + .name = "Prolink Pixelview MPEG 8000GT", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0ff, + .gpio2 = 0x0cfb, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio2 = 0x0cfb, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio2 = 0x0cfb, + } }, + .radio = { + .type = CX88_RADIO, + .gpio2 = 0x0cfb, + }, + }, + [CX88_BOARD_PROLINK_PV_GLOBAL_XTREME] = { + .name = "Prolink Pixelview Global Extreme", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x04fb, + .gpio1 = 0x04080, + .gpio2 = 0x0cf7, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x04fb, + .gpio1 = 0x04080, + .gpio2 = 0x0cfb, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x04fb, + .gpio1 = 0x04080, + .gpio2 = 0x0cfb, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x04ff, + .gpio1 = 0x04080, + .gpio2 = 0x0cf7, + }, + }, + /* + * Both radio, analog and ATSC work with this board. + * However, for analog to work, s5h1409 gate should be open, + * otherwise, tuner-xc3028 won't be detected. + * A proper fix require using the newer i2c methods to add + * tuner-xc3028 without doing an i2c probe. + */ + [CX88_BOARD_KWORLD_ATSC_120] = { + .name = "Kworld PlusTV HD PCI 120 (ATSC 120)", + .tuner_type = TUNER_XC2028, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x000000ff, + .gpio1 = 0x0000f35d, + .gpio2 = 0x00000000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000000ff, + .gpio1 = 0x0000f37e, + .gpio2 = 0x00000000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000000ff, + .gpio1 = 0x0000f37e, + .gpio2 = 0x00000000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x000000ff, + .gpio1 = 0x0000f35d, + .gpio2 = 0x00000000, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_HAUPPAUGE_HVR4000] = { + .name = "Hauppauge WinTV-HVR4000 DVB-S/S2/T/Hybrid", + .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .audio_chip = CX88_AUDIO_WM8775, + /* + * GPIO0 (WINTV2000) + * + * Analogue SAT DVB-T + * Antenna 0xc4bf 0xc4bb + * Composite 0xc4bf 0xc4bb + * S-Video 0xc4bf 0xc4bb + * Composite1 0xc4ff 0xc4fb + * S-Video1 0xc4ff 0xc4fb + * + * BIT VALUE FUNCTION GP{x}_IO + * 0 1 I:? + * 1 1 I:? + * 2 1 O:MPEG PORT 0=DVB-T 1=DVB-S + * 3 1 I:? + * 4 1 I:? + * 5 1 I:? + * 6 0 O:INPUT SELECTOR 0=INTERNAL 1=EXPANSION + * 7 1 O:DVB-T DEMOD RESET LOW + * + * BIT VALUE FUNCTION GP{x}_OE + * 8 0 I + * 9 0 I + * a 1 O + * b 0 I + * c 0 I + * d 0 I + * e 1 O + * f 1 O + * + * WM8775 ADC + * + * 1: TV Audio / FM Mono + * 2: Line-In + * 3: Line-In Expansion + * 4: FM Stereo + */ + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xc4bf, + /* 1: TV Audio / FM Mono */ + .audioroute = 1, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xc4bf, + /* 2: Line-In */ + .audioroute = 2, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xc4bf, + /* 2: Line-In */ + .audioroute = 2, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xc4bf, + /* 4: FM Stereo */ + .audioroute = 8, + }, + .mpeg = CX88_MPEG_DVB, + .num_frontends = 2, + }, + [CX88_BOARD_HAUPPAUGE_HVR4000LITE] = { + .name = "Hauppauge WinTV-HVR4000(Lite) DVB-S/S2", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TEVII_S420] = { + .name = "TeVii S420 DVB-S", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TEVII_S460] = { + .name = "TeVii S460 DVB-S/S2", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TEVII_S464] = { + .name = "TeVii S464 DVB-S/S2", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_OMICOM_SS4_PCI] = { + .name = "Omicom SS4 DVB-S/S2 PCI", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TBS_8910] = { + .name = "TBS 8910 DVB-S", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TBS_8920] = { + .name = "TBS 8920 DVB-S/S2", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + .gpio0 = 0x8080, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_PROF_6200] = { + .name = "Prof 6200 DVB-S", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_PROF_7300] = { + .name = "PROF 7300 DVB-S/S2", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_SATTRADE_ST4200] = { + .name = "SATTRADE ST4200 DVB-S/S2", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TERRATEC_CINERGY_HT_PCI_MKII] = { + .name = "Terratec Cinergy HT PCI MKII", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .radio_type = UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x004ff, + .gpio1 = 0x010ff, + .gpio2 = 0x00001, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x004fb, + .gpio1 = 0x010ef, + .audioroute = 1, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x004fb, + .gpio1 = 0x010ef, + .audioroute = 1, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x004ff, + .gpio1 = 0x010ff, + .gpio2 = 0x0ff, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_HAUPPAUGE_IRONLY] = { + .name = "Hauppauge WinTV-IR Only", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + }, + [CX88_BOARD_WINFAST_DTV1800H] = { + .name = "Leadtek WinFast DTV1800 Hybrid", + .tuner_type = TUNER_XC2028, + .radio_type = UNSET, + .tuner_addr = 0x61, + .radio_addr = ADDR_UNSET, + /* + * GPIO setting + * + * 2: mute (0=off,1=on) + * 12: tuner reset pin + * 13: audio source (0=tuner audio,1=line in) + * 14: FM (0=on,1=off ???) + */ + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6040, /* pin 13 = 0, pin 14 = 1 */ + .gpio2 = 0x0000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6060, /* pin 13 = 1, pin 14 = 1 */ + .gpio2 = 0x0000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6060, /* pin 13 = 1, pin 14 = 1 */ + .gpio2 = 0x0000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6000, /* pin 13 = 0, pin 14 = 0 */ + .gpio2 = 0x0000, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_WINFAST_DTV1800H_XC4000] = { + .name = "Leadtek WinFast DTV1800 H (XC4000)", + .tuner_type = TUNER_XC4000, + .radio_type = UNSET, + .tuner_addr = 0x61, + .radio_addr = ADDR_UNSET, + /* + * GPIO setting + * + * 2: mute (0=off,1=on) + * 12: tuner reset pin + * 13: audio source (0=tuner audio,1=line in) + * 14: FM (0=on,1=off ???) + */ + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6040, /* pin 13 = 0, pin 14 = 1 */ + .gpio2 = 0x0000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6060, /* pin 13 = 1, pin 14 = 1 */ + .gpio2 = 0x0000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6060, /* pin 13 = 1, pin 14 = 1 */ + .gpio2 = 0x0000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6000, /* pin 13 = 0, pin 14 = 0 */ + .gpio2 = 0x0000, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_WINFAST_DTV2000H_PLUS] = { + .name = "Leadtek WinFast DTV2000 H PLUS", + .tuner_type = TUNER_XC4000, + .radio_type = UNSET, + .tuner_addr = 0x61, + .radio_addr = ADDR_UNSET, + /* + * GPIO + * 2: 1: mute audio + * 12: 0: reset XC4000 + * 13: 1: audio input is line in (0: tuner) + * 14: 0: FM radio + * 16: 0: RF input is cable + */ + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0403, + .gpio1 = 0xF0D7, + .gpio2 = 0x0101, + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_CABLE, + .vmux = 0, + .gpio0 = 0x0403, + .gpio1 = 0xF0D7, + .gpio2 = 0x0100, + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0403, /* was 0x0407 */ + .gpio1 = 0xF0F7, + .gpio2 = 0x0101, + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0403, /* was 0x0407 */ + .gpio1 = 0xF0F7, + .gpio2 = 0x0101, + .gpio3 = 0x0000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0403, + .gpio1 = 0xF097, + .gpio2 = 0x0100, + .gpio3 = 0x0000, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_PROF_7301] = { + .name = "Prof 7301 DVB-S/S2", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TWINHAN_VP1027_DVBS] = { + .name = "Twinhan VP-1027 DVB-S", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, +}; + +/* ------------------------------------------------------------------ */ +/* PCI subsystem IDs */ + +static const struct cx88_subid cx88_subids[] = { + { + .subvendor = 0x0070, + .subdevice = 0x3400, + .card = CX88_BOARD_HAUPPAUGE, + }, { + .subvendor = 0x0070, + .subdevice = 0x3401, + .card = CX88_BOARD_HAUPPAUGE, + }, { + .subvendor = 0x14c7, + .subdevice = 0x0106, + .card = CX88_BOARD_GDI, + }, { + .subvendor = 0x14c7, + .subdevice = 0x0107, /* with mpeg encoder */ + .card = CX88_BOARD_GDI, + }, { + .subvendor = PCI_VENDOR_ID_ATI, + .subdevice = 0x00f8, + .card = CX88_BOARD_ATI_WONDER_PRO, + }, { + .subvendor = PCI_VENDOR_ID_ATI, + .subdevice = 0x00f9, + .card = CX88_BOARD_ATI_WONDER_PRO, + }, { + .subvendor = 0x107d, + .subdevice = 0x6611, + .card = CX88_BOARD_WINFAST2000XP_EXPERT, + }, { + .subvendor = 0x107d, + .subdevice = 0x6613, /* NTSC */ + .card = CX88_BOARD_WINFAST2000XP_EXPERT, + }, { + .subvendor = 0x107d, + .subdevice = 0x6620, + .card = CX88_BOARD_WINFAST_DV2000, + }, { + .subvendor = 0x107d, + .subdevice = 0x663b, + .card = CX88_BOARD_LEADTEK_PVR2000, + }, { + .subvendor = 0x107d, + .subdevice = 0x663c, + .card = CX88_BOARD_LEADTEK_PVR2000, + }, { + .subvendor = 0x1461, + .subdevice = 0x000b, + .card = CX88_BOARD_AVERTV_STUDIO_303, + }, { + .subvendor = 0x1462, + .subdevice = 0x8606, + .card = CX88_BOARD_MSI_TVANYWHERE_MASTER, + }, { + .subvendor = 0x10fc, + .subdevice = 0xd003, + .card = CX88_BOARD_IODATA_GVVCP3PCI, + }, { + .subvendor = 0x1043, + .subdevice = 0x4823, /* with mpeg encoder */ + .card = CX88_BOARD_ASUS_PVR_416, + }, { + .subvendor = 0x17de, + .subdevice = 0x08a6, + .card = CX88_BOARD_KWORLD_DVB_T, + }, { + .subvendor = 0x18ac, + .subdevice = 0xd810, + .card = CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q, + }, { + .subvendor = 0x18ac, + .subdevice = 0xd820, + .card = CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T, + }, { + .subvendor = 0x18ac, + .subdevice = 0xdb00, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1, + }, { + .subvendor = 0x0070, + .subdevice = 0x9002, + .card = CX88_BOARD_HAUPPAUGE_DVB_T1, + }, { + .subvendor = 0x14f1, + .subdevice = 0x0187, + .card = CX88_BOARD_CONEXANT_DVB_T1, + }, { + .subvendor = 0x1540, + .subdevice = 0x2580, + .card = CX88_BOARD_PROVIDEO_PV259, + }, { + .subvendor = 0x18ac, + .subdevice = 0xdb10, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS, + }, { + .subvendor = 0x1554, + .subdevice = 0x4811, + .card = CX88_BOARD_PIXELVIEW, + }, { + .subvendor = 0x7063, + .subdevice = 0x3000, /* HD-3000 card */ + .card = CX88_BOARD_PCHDTV_HD3000, + }, { + .subvendor = 0x17de, + .subdevice = 0xa8a6, + .card = CX88_BOARD_DNTV_LIVE_DVB_T, + }, { + .subvendor = 0x0070, + .subdevice = 0x2801, + .card = CX88_BOARD_HAUPPAUGE_ROSLYN, + }, { + .subvendor = 0x14f1, + .subdevice = 0x0342, + .card = CX88_BOARD_DIGITALLOGIC_MEC, + }, { + .subvendor = 0x10fc, + .subdevice = 0xd035, + .card = CX88_BOARD_IODATA_GVBCTV7E, + }, { + .subvendor = 0x1421, + .subdevice = 0x0334, + .card = CX88_BOARD_ADSTECH_DVB_T_PCI, + }, { + .subvendor = 0x153b, + .subdevice = 0x1166, + .card = CX88_BOARD_TERRATEC_CINERGY_1400_DVB_T1, + }, { + .subvendor = 0x18ac, + .subdevice = 0xd500, + .card = CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD, + }, { + .subvendor = 0x1461, + .subdevice = 0x8011, + .card = CX88_BOARD_AVERMEDIA_ULTRATV_MC_550, + }, { + .subvendor = PCI_VENDOR_ID_ATI, + .subdevice = 0xa101, + .card = CX88_BOARD_ATI_HDTVWONDER, + }, { + .subvendor = 0x107d, + .subdevice = 0x665f, + .card = CX88_BOARD_WINFAST_DTV1000, + }, { + .subvendor = 0x1461, + .subdevice = 0x000a, + .card = CX88_BOARD_AVERTV_303, + }, { + .subvendor = 0x0070, + .subdevice = 0x9200, + .card = CX88_BOARD_HAUPPAUGE_NOVASE2_S1, + }, { + .subvendor = 0x0070, + .subdevice = 0x9201, + .card = CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1, + }, { + .subvendor = 0x0070, + .subdevice = 0x9202, + .card = CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1, + }, { + .subvendor = 0x17de, + .subdevice = 0x08b2, + .card = CX88_BOARD_KWORLD_DVBS_100, + }, { + .subvendor = 0x0070, + .subdevice = 0x9400, + .card = CX88_BOARD_HAUPPAUGE_HVR1100, + }, { + .subvendor = 0x0070, + .subdevice = 0x9402, + .card = CX88_BOARD_HAUPPAUGE_HVR1100, + }, { + .subvendor = 0x0070, + .subdevice = 0x9800, + .card = CX88_BOARD_HAUPPAUGE_HVR1100LP, + }, { + .subvendor = 0x0070, + .subdevice = 0x9802, + .card = CX88_BOARD_HAUPPAUGE_HVR1100LP, + }, { + .subvendor = 0x0070, + .subdevice = 0x9001, + .card = CX88_BOARD_HAUPPAUGE_DVB_T1, + }, { + .subvendor = 0x1822, + .subdevice = 0x0025, + .card = CX88_BOARD_DNTV_LIVE_DVB_T_PRO, + }, { + .subvendor = 0x17de, + .subdevice = 0x08a1, + .card = CX88_BOARD_KWORLD_DVB_T_CX22702, + }, { + .subvendor = 0x18ac, + .subdevice = 0xdb50, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL, + }, { + .subvendor = 0x18ac, + .subdevice = 0xdb54, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL, + /* Re-branded DViCO: DigitalNow DVB-T Dual */ + }, { + .subvendor = 0x18ac, + .subdevice = 0xdb11, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS, + /* Re-branded DViCO: UltraView DVB-T Plus */ + }, { + .subvendor = 0x18ac, + .subdevice = 0xdb30, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO, + }, { + .subvendor = 0x17de, + .subdevice = 0x0840, + .card = CX88_BOARD_KWORLD_HARDWARE_MPEG_TV_XPERT, + }, { + .subvendor = 0x1421, + .subdevice = 0x0305, + .card = CX88_BOARD_KWORLD_HARDWARE_MPEG_TV_XPERT, + }, { + .subvendor = 0x18ac, + .subdevice = 0xdb40, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID, + }, { + .subvendor = 0x18ac, + .subdevice = 0xdb44, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID, + }, { + .subvendor = 0x7063, + .subdevice = 0x5500, + .card = CX88_BOARD_PCHDTV_HD5500, + }, { + .subvendor = 0x17de, + .subdevice = 0x0841, + .card = CX88_BOARD_KWORLD_MCE200_DELUXE, + }, { + .subvendor = 0x1822, + .subdevice = 0x0019, + .card = CX88_BOARD_DNTV_LIVE_DVB_T_PRO, + }, { + .subvendor = 0x1554, + .subdevice = 0x4813, + .card = CX88_BOARD_PIXELVIEW_PLAYTV_P7000, + }, { + .subvendor = 0x14f1, + .subdevice = 0x0842, + .card = CX88_BOARD_NPGTECH_REALTV_TOP10FM, + }, { + .subvendor = 0x107d, + .subdevice = 0x665e, + .card = CX88_BOARD_WINFAST_DTV2000H, + }, { + .subvendor = 0x107d, + .subdevice = 0x6f2b, + .card = CX88_BOARD_WINFAST_DTV2000H_J, + }, { + .subvendor = 0x18ac, + .subdevice = 0xd800, /* FusionHDTV 3 Gold (original revision) */ + .card = CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q, + }, { + .subvendor = 0x14f1, + .subdevice = 0x0084, + .card = CX88_BOARD_GENIATECH_DVBS, + }, { + .subvendor = 0x0070, + .subdevice = 0x1404, + .card = CX88_BOARD_HAUPPAUGE_HVR3000, + }, { + .subvendor = 0x18ac, + .subdevice = 0xdc00, + .card = CX88_BOARD_SAMSUNG_SMT_7020, + }, { + .subvendor = 0x18ac, + .subdevice = 0xdccd, + .card = CX88_BOARD_SAMSUNG_SMT_7020, + }, { + .subvendor = 0x1461, + .subdevice = 0xc111, /* AverMedia M150-D */ + /* This board is known to work with the ASUS PVR416 config */ + .card = CX88_BOARD_ASUS_PVR_416, + }, { + .subvendor = 0xc180, + .subdevice = 0xc980, + .card = CX88_BOARD_TE_DTV_250_OEM_SWANN, + }, { + .subvendor = 0x0070, + .subdevice = 0x9600, + .card = CX88_BOARD_HAUPPAUGE_HVR1300, + }, { + .subvendor = 0x0070, + .subdevice = 0x9601, + .card = CX88_BOARD_HAUPPAUGE_HVR1300, + }, { + .subvendor = 0x0070, + .subdevice = 0x9602, + .card = CX88_BOARD_HAUPPAUGE_HVR1300, + }, { + .subvendor = 0x107d, + .subdevice = 0x6632, + .card = CX88_BOARD_LEADTEK_PVR2000, + }, { + .subvendor = 0x12ab, + .subdevice = 0x2300, /* Club3D Zap TV2100 */ + .card = CX88_BOARD_KWORLD_DVB_T_CX22702, + }, { + .subvendor = 0x0070, + .subdevice = 0x9000, + .card = CX88_BOARD_HAUPPAUGE_DVB_T1, + }, { + .subvendor = 0x0070, + .subdevice = 0x1400, + .card = CX88_BOARD_HAUPPAUGE_HVR3000, + }, { + .subvendor = 0x0070, + .subdevice = 0x1401, + .card = CX88_BOARD_HAUPPAUGE_HVR3000, + }, { + .subvendor = 0x0070, + .subdevice = 0x1402, + .card = CX88_BOARD_HAUPPAUGE_HVR3000, + }, { + .subvendor = 0x1421, + .subdevice = 0x0341, /* ADS Tech InstantTV DVB-S */ + .card = CX88_BOARD_KWORLD_DVBS_100, + }, { + .subvendor = 0x1421, + .subdevice = 0x0390, + .card = CX88_BOARD_ADSTECH_PTV_390, + }, { + .subvendor = 0x11bd, + .subdevice = 0x0051, + .card = CX88_BOARD_PINNACLE_PCTV_HD_800i, + }, { + .subvendor = 0x18ac, + .subdevice = 0xd530, + .card = CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO, + }, { + .subvendor = 0x12ab, + .subdevice = 0x1788, + .card = CX88_BOARD_PINNACLE_HYBRID_PCTV, + }, { + .subvendor = 0x14f1, + .subdevice = 0xea3d, + .card = CX88_BOARD_POWERCOLOR_REAL_ANGEL, + }, { + .subvendor = 0x107d, + .subdevice = 0x6f18, + .card = CX88_BOARD_WINFAST_TV2000_XP_GLOBAL, + }, { + /* Also NotOnlyTV LV3H (version 1.11 is silkscreened on the board) */ + .subvendor = 0x14f1, + .subdevice = 0x8852, + .card = CX88_BOARD_GENIATECH_X8000_MT, + }, { + .subvendor = 0x18ac, + .subdevice = 0xd610, + .card = CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD, + }, { + .subvendor = 0x1554, + .subdevice = 0x4935, + .card = CX88_BOARD_PROLINK_PV_8000GT, + }, { + .subvendor = 0x1554, + .subdevice = 0x4976, + .card = CX88_BOARD_PROLINK_PV_GLOBAL_XTREME, + }, { + .subvendor = 0x17de, + .subdevice = 0x08c1, + .card = CX88_BOARD_KWORLD_ATSC_120, + }, { + .subvendor = 0x0070, + .subdevice = 0x6900, + .card = CX88_BOARD_HAUPPAUGE_HVR4000, + }, { + .subvendor = 0x0070, + .subdevice = 0x6904, + .card = CX88_BOARD_HAUPPAUGE_HVR4000, + }, { + .subvendor = 0x0070, + .subdevice = 0x6902, + .card = CX88_BOARD_HAUPPAUGE_HVR4000, + }, { + .subvendor = 0x0070, + .subdevice = 0x6905, + .card = CX88_BOARD_HAUPPAUGE_HVR4000LITE, + }, { + .subvendor = 0x0070, + .subdevice = 0x6906, + .card = CX88_BOARD_HAUPPAUGE_HVR4000LITE, + }, { + .subvendor = 0xd420, + .subdevice = 0x9022, + .card = CX88_BOARD_TEVII_S420, + }, { + .subvendor = 0xd460, + .subdevice = 0x9022, + .card = CX88_BOARD_TEVII_S460, + }, { + .subvendor = 0xd464, + .subdevice = 0x9022, + .card = CX88_BOARD_TEVII_S464, + }, { + .subvendor = 0xA044, + .subdevice = 0x2011, + .card = CX88_BOARD_OMICOM_SS4_PCI, + }, { + .subvendor = 0x8910, + .subdevice = 0x8888, + .card = CX88_BOARD_TBS_8910, + }, { + .subvendor = 0x8920, + .subdevice = 0x8888, + .card = CX88_BOARD_TBS_8920, + }, { + .subvendor = 0xb022, + .subdevice = 0x3022, + .card = CX88_BOARD_PROF_6200, + }, { + .subvendor = 0xB033, + .subdevice = 0x3033, + .card = CX88_BOARD_PROF_7300, + }, { + .subvendor = 0xb200, + .subdevice = 0x4200, + .card = CX88_BOARD_SATTRADE_ST4200, + }, { + .subvendor = 0x153b, + .subdevice = 0x1177, + .card = CX88_BOARD_TERRATEC_CINERGY_HT_PCI_MKII, + }, { + .subvendor = 0x0070, + .subdevice = 0x9290, + .card = CX88_BOARD_HAUPPAUGE_IRONLY, + }, { + .subvendor = 0x107d, + .subdevice = 0x6654, + .card = CX88_BOARD_WINFAST_DTV1800H, + }, { + /* WinFast DTV1800 H with XC4000 tuner */ + .subvendor = 0x107d, + .subdevice = 0x6f38, + .card = CX88_BOARD_WINFAST_DTV1800H_XC4000, + }, { + .subvendor = 0x107d, + .subdevice = 0x6f42, + .card = CX88_BOARD_WINFAST_DTV2000H_PLUS, + }, { + /* PVR2000 PAL Model [107d:6630] */ + .subvendor = 0x107d, + .subdevice = 0x6630, + .card = CX88_BOARD_LEADTEK_PVR2000, + }, { + /* PVR2000 PAL Model [107d:6638] */ + .subvendor = 0x107d, + .subdevice = 0x6638, + .card = CX88_BOARD_LEADTEK_PVR2000, + }, { + /* PVR2000 NTSC Model [107d:6631] */ + .subvendor = 0x107d, + .subdevice = 0x6631, + .card = CX88_BOARD_LEADTEK_PVR2000, + }, { + /* PVR2000 NTSC Model [107d:6637] */ + .subvendor = 0x107d, + .subdevice = 0x6637, + .card = CX88_BOARD_LEADTEK_PVR2000, + }, { + /* PVR2000 NTSC Model [107d:663d] */ + .subvendor = 0x107d, + .subdevice = 0x663d, + .card = CX88_BOARD_LEADTEK_PVR2000, + }, { + /* DV2000 NTSC Model [107d:6621] */ + .subvendor = 0x107d, + .subdevice = 0x6621, + .card = CX88_BOARD_WINFAST_DV2000, + }, { + /* TV2000 XP Global [107d:6618] */ + .subvendor = 0x107d, + .subdevice = 0x6618, + .card = CX88_BOARD_WINFAST_TV2000_XP_GLOBAL, + }, { + /* TV2000 XP Global [107d:6618] */ + .subvendor = 0x107d, + .subdevice = 0x6619, + .card = CX88_BOARD_WINFAST_TV2000_XP_GLOBAL, + }, { + /* WinFast TV2000 XP Global with XC4000 tuner */ + .subvendor = 0x107d, + .subdevice = 0x6f36, + .card = CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36, + }, { + /* WinFast TV2000 XP Global with XC4000 tuner and different GPIOs */ + .subvendor = 0x107d, + .subdevice = 0x6f43, + .card = CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43, + }, { + .subvendor = 0xb034, + .subdevice = 0x3034, + .card = CX88_BOARD_PROF_7301, + }, { + .subvendor = 0x1822, + .subdevice = 0x0023, + .card = CX88_BOARD_TWINHAN_VP1027_DVBS, + }, +}; + +/* + * some leadtek specific stuff + */ +static void leadtek_eeprom(struct cx88_core *core, u8 *eeprom_data) +{ + if (eeprom_data[4] != 0x7d || + eeprom_data[5] != 0x10 || + eeprom_data[7] != 0x66) { + pr_warn("Leadtek eeprom invalid.\n"); + return; + } + + /* Terry Wu */ + switch (eeprom_data[6]) { + case 0x13: /* SSID 6613 for TV2000 XP Expert NTSC Model */ + case 0x21: /* SSID 6621 for DV2000 NTSC Model */ + case 0x31: /* SSID 6631 for PVR2000 NTSC Model */ + case 0x37: /* SSID 6637 for PVR2000 NTSC Model */ + case 0x3d: /* SSID 6637 for PVR2000 NTSC Model */ + core->board.tuner_type = TUNER_PHILIPS_FM1236_MK3; + break; + default: + core->board.tuner_type = TUNER_PHILIPS_FM1216ME_MK3; + break; + } + + pr_info("Leadtek Winfast 2000XP Expert config: tuner=%d, eeprom[0]=0x%02x\n", + core->board.tuner_type, eeprom_data[0]); +} + +static void hauppauge_eeprom(struct cx88_core *core, u8 *eeprom_data) +{ + struct tveeprom tv; + + tveeprom_hauppauge_analog(&tv, eeprom_data); + core->board.tuner_type = tv.tuner_type; + core->tuner_formats = tv.tuner_formats; + core->board.radio.type = tv.has_radio ? CX88_RADIO : 0; + core->model = tv.model; + + /* Make sure we support the board model */ + switch (tv.model) { + case 14009: /* WinTV-HVR3000 (Retail, IR, b/panel video, 3.5mm audio in) */ + case 14019: /* WinTV-HVR3000 (Retail, IR Blaster, b/panel video, 3.5mm audio in) */ + case 14029: /* WinTV-HVR3000 (Retail, IR, b/panel video, 3.5mm audio in - 880 bridge) */ + case 14109: /* WinTV-HVR3000 (Retail, IR, b/panel video, 3.5mm audio in - low profile) */ + case 14129: /* WinTV-HVR3000 (Retail, IR, b/panel video, 3.5mm audio in - 880 bridge - LP) */ + case 14559: /* WinTV-HVR3000 (OEM, no IR, b/panel video, 3.5mm audio in) */ + case 14569: /* WinTV-HVR3000 (OEM, no IR, no back panel video) */ + case 14659: /* WinTV-HVR3000 (OEM, no IR, b/panel video, RCA audio in - Low profile) */ + case 14669: /* WinTV-HVR3000 (OEM, no IR, no b/panel video - Low profile) */ + case 28552: /* WinTV-PVR 'Roslyn' (No IR) */ + case 34519: /* WinTV-PCI-FM */ + case 69009: + /* WinTV-HVR4000 (DVBS/S2/T, Video and IR, back panel inputs) */ + case 69100: /* WinTV-HVR4000LITE (DVBS/S2, IR) */ + case 69500: /* WinTV-HVR4000LITE (DVBS/S2, No IR) */ + case 69559: + /* WinTV-HVR4000 (DVBS/S2/T, Video no IR, back panel inputs) */ + case 69569: /* WinTV-HVR4000 (DVBS/S2/T, Video no IR) */ + case 90002: /* Nova-T-PCI (9002) */ + case 92001: /* Nova-S-Plus (Video and IR) */ + case 92002: /* Nova-S-Plus (Video and IR) */ + case 90003: /* Nova-T-PCI (9002 No RF out) */ + case 90500: /* Nova-T-PCI (oem) */ + case 90501: /* Nova-T-PCI (oem/IR) */ + case 92000: /* Nova-SE2 (OEM, No Video or IR) */ + case 92900: /* WinTV-IROnly (No analog or digital Video inputs) */ + case 94009: /* WinTV-HVR1100 (Video and IR Retail) */ + case 94501: /* WinTV-HVR1100 (Video and IR OEM) */ + case 96009: /* WinTV-HVR1300 (PAL Video, MPEG Video and IR RX) */ + case 96019: /* WinTV-HVR1300 (PAL Video, MPEG Video and IR RX/TX) */ + case 96559: /* WinTV-HVR1300 (PAL Video, MPEG Video no IR) */ + case 96569: /* WinTV-HVR1300 () */ + case 96659: /* WinTV-HVR1300 () */ + case 98559: /* WinTV-HVR1100LP (Video no IR, Retail - Low Profile) */ + /* known */ + break; + case CX88_BOARD_SAMSUNG_SMT_7020: + cx_set(MO_GP0_IO, 0x008989FF); + break; + default: + pr_warn("warning: unknown hauppauge model #%d\n", tv.model); + break; + } + + pr_info("hauppauge eeprom: model=%d\n", tv.model); +} + +/* + * some GDI (was: Modular Technology) specific stuff + */ + +static const struct { + int id; + int fm; + const char *name; +} gdi_tuner[] = { + [0x01] = { .id = UNSET, + .name = "NTSC_M" }, + [0x02] = { .id = UNSET, + .name = "PAL_B" }, + [0x03] = { .id = UNSET, + .name = "PAL_I" }, + [0x04] = { .id = UNSET, + .name = "PAL_D" }, + [0x05] = { .id = UNSET, + .name = "SECAM" }, + + [0x10] = { .id = UNSET, + .fm = 1, + .name = "TEMIC_4049" }, + [0x11] = { .id = TUNER_TEMIC_4136FY5, + .name = "TEMIC_4136" }, + [0x12] = { .id = UNSET, + .name = "TEMIC_4146" }, + + [0x20] = { .id = TUNER_PHILIPS_FQ1216ME, + .fm = 1, + .name = "PHILIPS_FQ1216_MK3" }, + [0x21] = { .id = UNSET, .fm = 1, + .name = "PHILIPS_FQ1236_MK3" }, + [0x22] = { .id = UNSET, + .name = "PHILIPS_FI1236_MK3" }, + [0x23] = { .id = UNSET, + .name = "PHILIPS_FI1216_MK3" }, +}; + +static void gdi_eeprom(struct cx88_core *core, u8 *eeprom_data) +{ + const char *name = (eeprom_data[0x0d] < ARRAY_SIZE(gdi_tuner)) + ? gdi_tuner[eeprom_data[0x0d]].name : NULL; + + pr_info("GDI: tuner=%s\n", name ? name : "unknown"); + if (!name) + return; + core->board.tuner_type = gdi_tuner[eeprom_data[0x0d]].id; + core->board.radio.type = gdi_tuner[eeprom_data[0x0d]].fm ? + CX88_RADIO : 0; +} + +/* + * some Divco specific stuff + */ +static int cx88_dvico_xc2028_callback(struct cx88_core *core, + int command, int arg) +{ + switch (command) { + case XC2028_TUNER_RESET: + switch (core->boardnr) { + case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO: + /* GPIO-4 xc3028 tuner */ + + cx_set(MO_GP0_IO, 0x00001000); + cx_clear(MO_GP0_IO, 0x00000010); + msleep(100); + cx_set(MO_GP0_IO, 0x00000010); + msleep(100); + break; + default: + cx_write(MO_GP0_IO, 0x101000); + mdelay(5); + cx_set(MO_GP0_IO, 0x101010); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * some Geniatech specific stuff + */ + +static int cx88_xc3028_geniatech_tuner_callback(struct cx88_core *core, + int command, int mode) +{ + switch (command) { + case XC2028_TUNER_RESET: + switch (INPUT(core->input).type) { + case CX88_RADIO: + break; + case CX88_VMUX_DVB: + cx_write(MO_GP1_IO, 0x030302); + mdelay(50); + break; + default: + cx_write(MO_GP1_IO, 0x030301); + mdelay(50); + } + cx_write(MO_GP1_IO, 0x101010); + mdelay(50); + cx_write(MO_GP1_IO, 0x101000); + mdelay(50); + cx_write(MO_GP1_IO, 0x101010); + mdelay(50); + return 0; + } + return -EINVAL; +} + +static int cx88_xc3028_winfast1800h_callback(struct cx88_core *core, + int command, int arg) +{ + switch (command) { + case XC2028_TUNER_RESET: + /* GPIO 12 (xc3028 tuner reset) */ + cx_set(MO_GP1_IO, 0x1010); + mdelay(50); + cx_clear(MO_GP1_IO, 0x10); + mdelay(75); + cx_set(MO_GP1_IO, 0x10); + mdelay(75); + return 0; + } + return -EINVAL; +} + +static int cx88_xc4000_winfast2000h_plus_callback(struct cx88_core *core, + int command, int arg) +{ + switch (command) { + case XC4000_TUNER_RESET: + /* GPIO 12 (xc4000 tuner reset) */ + cx_set(MO_GP1_IO, 0x1010); + mdelay(50); + cx_clear(MO_GP1_IO, 0x10); + mdelay(75); + cx_set(MO_GP1_IO, 0x10); + mdelay(75); + return 0; + } + return -EINVAL; +} + +/* + * some Divco specific stuff + */ +static int cx88_pv_8000gt_callback(struct cx88_core *core, + int command, int arg) +{ + switch (command) { + case XC2028_TUNER_RESET: + cx_write(MO_GP2_IO, 0xcf7); + mdelay(50); + cx_write(MO_GP2_IO, 0xef5); + mdelay(50); + cx_write(MO_GP2_IO, 0xcf7); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * some DViCO specific stuff + */ + +static void dvico_fusionhdtv_hybrid_init(struct cx88_core *core) +{ + struct i2c_msg msg = { .addr = 0x45, .flags = 0 }; + int i, err; + static u8 init_bufs[13][5] = { + { 0x10, 0x00, 0x20, 0x01, 0x03 }, + { 0x10, 0x10, 0x01, 0x00, 0x21 }, + { 0x10, 0x10, 0x10, 0x00, 0xCA }, + { 0x10, 0x10, 0x12, 0x00, 0x08 }, + { 0x10, 0x10, 0x13, 0x00, 0x0A }, + { 0x10, 0x10, 0x16, 0x01, 0xC0 }, + { 0x10, 0x10, 0x22, 0x01, 0x3D }, + { 0x10, 0x10, 0x73, 0x01, 0x2E }, + { 0x10, 0x10, 0x72, 0x00, 0xC5 }, + { 0x10, 0x10, 0x71, 0x01, 0x97 }, + { 0x10, 0x10, 0x70, 0x00, 0x0F }, + { 0x10, 0x10, 0xB0, 0x00, 0x01 }, + { 0x03, 0x0C }, + }; + + for (i = 0; i < ARRAY_SIZE(init_bufs); i++) { + msg.buf = init_bufs[i]; + msg.len = (i != 12 ? 5 : 2); + err = i2c_transfer(&core->i2c_adap, &msg, 1); + if (err != 1) { + pr_warn("dvico_fusionhdtv_hybrid_init buf %d failed (err = %d)!\n", + i, err); + return; + } + } +} + +static int cx88_xc2028_tuner_callback(struct cx88_core *core, + int command, int arg) +{ + /* Board-specific callbacks */ + switch (core->boardnr) { + case CX88_BOARD_POWERCOLOR_REAL_ANGEL: + case CX88_BOARD_GENIATECH_X8000_MT: + case CX88_BOARD_KWORLD_ATSC_120: + return cx88_xc3028_geniatech_tuner_callback(core, + command, arg); + case CX88_BOARD_PROLINK_PV_8000GT: + case CX88_BOARD_PROLINK_PV_GLOBAL_XTREME: + return cx88_pv_8000gt_callback(core, command, arg); + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO: + case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO: + return cx88_dvico_xc2028_callback(core, command, arg); + case CX88_BOARD_NOTONLYTV_LV3H: + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL: + case CX88_BOARD_WINFAST_DTV1800H: + return cx88_xc3028_winfast1800h_callback(core, command, arg); + } + + switch (command) { + case XC2028_TUNER_RESET: + switch (INPUT(core->input).type) { + case CX88_RADIO: + dprintk(1, "setting GPIO to radio!\n"); + cx_write(MO_GP0_IO, 0x4ff); + mdelay(250); + cx_write(MO_GP2_IO, 0xff); + mdelay(250); + break; + case CX88_VMUX_DVB: /* Digital TV*/ + default: /* Analog TV */ + dprintk(1, "setting GPIO to TV!\n"); + break; + } + cx_write(MO_GP1_IO, 0x101010); + mdelay(250); + cx_write(MO_GP1_IO, 0x101000); + mdelay(250); + cx_write(MO_GP1_IO, 0x101010); + mdelay(250); + return 0; + } + return -EINVAL; +} + +static int cx88_xc4000_tuner_callback(struct cx88_core *core, + int command, int arg) +{ + /* Board-specific callbacks */ + switch (core->boardnr) { + case CX88_BOARD_WINFAST_DTV1800H_XC4000: + case CX88_BOARD_WINFAST_DTV2000H_PLUS: + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36: + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43: + return cx88_xc4000_winfast2000h_plus_callback(core, + command, arg); + } + return -EINVAL; +} + +/* + * Tuner callback function. Currently only needed for the Pinnacle + * PCTV HD 800i with an xc5000 silicon tuner. This is used for both + * analog tuner attach (tuner-core.c) and dvb tuner attach (cx88-dvb.c) + */ +static int cx88_xc5000_tuner_callback(struct cx88_core *core, + int command, int arg) +{ + switch (core->boardnr) { + case CX88_BOARD_PINNACLE_PCTV_HD_800i: + if (command == 0) { /* This is the reset command from xc5000 */ + + /* + * djh - According to the engineer at PCTV Systems, + * the xc5000 reset pin is supposed to be on GPIO12. + * However, despite three nights of effort, pulling + * that GPIO low didn't reset the xc5000. While + * pulling MO_SRST_IO low does reset the xc5000, this + * also resets in the s5h1409 being reset as well. + * This causes tuning to always fail since the internal + * state of the s5h1409 does not match the driver's + * state. Given that the only two conditions in which + * the driver performs a reset is during firmware load + * and powering down the chip, I am taking out the + * reset. We know that the chip is being reset + * when the cx88 comes online, and not being able to + * do power management for this board is worse than + * not having any tuning at all. + */ + return 0; + } + + dprintk(1, "xc5000: unknown tuner callback command.\n"); + return -EINVAL; + case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD: + if (command == 0) { /* This is the reset command from xc5000 */ + cx_clear(MO_GP0_IO, 0x00000010); + usleep_range(10000, 20000); + cx_set(MO_GP0_IO, 0x00000010); + return 0; + } + + dprintk(1, "xc5000: unknown tuner callback command.\n"); + return -EINVAL; + } + return 0; /* Should never be here */ +} + +int cx88_tuner_callback(void *priv, int component, int command, int arg) +{ + struct i2c_algo_bit_data *i2c_algo = priv; + struct cx88_core *core; + + if (!i2c_algo) { + pr_err("Error - i2c private data undefined.\n"); + return -EINVAL; + } + + core = i2c_algo->data; + + if (!core) { + pr_err("Error - device struct undefined.\n"); + return -EINVAL; + } + + if (component != DVB_FRONTEND_COMPONENT_TUNER) + return -EINVAL; + + switch (core->board.tuner_type) { + case TUNER_XC2028: + dprintk(1, "Calling XC2028/3028 callback\n"); + return cx88_xc2028_tuner_callback(core, command, arg); + case TUNER_XC4000: + dprintk(1, "Calling XC4000 callback\n"); + return cx88_xc4000_tuner_callback(core, command, arg); + case TUNER_XC5000: + dprintk(1, "Calling XC5000 callback\n"); + return cx88_xc5000_tuner_callback(core, command, arg); + } + pr_err("Error: Calling callback for tuner %d\n", + core->board.tuner_type); + return -EINVAL; +} +EXPORT_SYMBOL(cx88_tuner_callback); + +/* ----------------------------------------------------------------------- */ + +static void cx88_card_list(struct cx88_core *core, struct pci_dev *pci) +{ + int i; + + if (!pci->subsystem_vendor && !pci->subsystem_device) { + pr_err("Your board has no valid PCI Subsystem ID and thus can't\n"); + pr_err("be autodetected. Please pass card= insmod option to\n"); + pr_err("workaround that. Redirect complaints to the vendor of\n"); + pr_err("the TV card\n"); + } else { + pr_err("Your board isn't known (yet) to the driver. You can\n"); + pr_err("try to pick one of the existing card configs via\n"); + pr_err("card= insmod option. Updating to the latest\n"); + pr_err("version might help as well.\n"); + } + pr_err("Here is a list of valid choices for the card= insmod option:\n"); + for (i = 0; i < ARRAY_SIZE(cx88_boards); i++) + pr_err(" card=%d -> %s\n", i, cx88_boards[i].name); +} + +static void cx88_card_setup_pre_i2c(struct cx88_core *core) +{ + switch (core->boardnr) { + case CX88_BOARD_HAUPPAUGE_HVR1300: + /* + * Bring the 702 demod up before i2c scanning/attach or + * devices are hidden. + * + * We leave here with the 702 on the bus + * + * "reset the IR receiver on GPIO[3]" + * Reported by Mike Crash + */ + cx_write(MO_GP0_IO, 0x0000ef88); + udelay(1000); + cx_clear(MO_GP0_IO, 0x00000088); + udelay(50); + cx_set(MO_GP0_IO, 0x00000088); /* 702 out of reset */ + udelay(1000); + break; + + case CX88_BOARD_PROLINK_PV_GLOBAL_XTREME: + case CX88_BOARD_PROLINK_PV_8000GT: + cx_write(MO_GP2_IO, 0xcf7); + msleep(50); + cx_write(MO_GP2_IO, 0xef5); + msleep(50); + cx_write(MO_GP2_IO, 0xcf7); + usleep_range(10000, 20000); + break; + + case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD: + /* Enable the xc5000 tuner */ + cx_set(MO_GP0_IO, 0x00001010); + break; + + case CX88_BOARD_WINFAST_DTV2000H_J: + case CX88_BOARD_HAUPPAUGE_HVR3000: + case CX88_BOARD_HAUPPAUGE_HVR4000: + /* Init GPIO */ + cx_write(MO_GP0_IO, core->board.input[0].gpio0); + udelay(1000); + cx_clear(MO_GP0_IO, 0x00000080); + udelay(50); + cx_set(MO_GP0_IO, 0x00000080); /* 702 out of reset */ + udelay(1000); + break; + + case CX88_BOARD_NOTONLYTV_LV3H: + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL: + case CX88_BOARD_WINFAST_DTV1800H: + cx88_xc3028_winfast1800h_callback(core, XC2028_TUNER_RESET, 0); + break; + + case CX88_BOARD_WINFAST_DTV1800H_XC4000: + case CX88_BOARD_WINFAST_DTV2000H_PLUS: + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36: + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43: + cx88_xc4000_winfast2000h_plus_callback(core, + XC4000_TUNER_RESET, 0); + break; + + case CX88_BOARD_TWINHAN_VP1027_DVBS: + cx_write(MO_GP0_IO, 0x00003230); + cx_write(MO_GP0_IO, 0x00003210); + usleep_range(10000, 20000); + cx_write(MO_GP0_IO, 0x00001230); + break; + } +} + +/* + * Sets board-dependent xc3028 configuration + */ +void cx88_setup_xc3028(struct cx88_core *core, struct xc2028_ctrl *ctl) +{ + memset(ctl, 0, sizeof(*ctl)); + + ctl->fname = XC2028_DEFAULT_FIRMWARE; + ctl->max_len = 64; + + switch (core->boardnr) { + case CX88_BOARD_POWERCOLOR_REAL_ANGEL: + /* Now works with firmware version 2.7 */ + if (core->i2c_algo.udelay < 16) + core->i2c_algo.udelay = 16; + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO: + case CX88_BOARD_WINFAST_DTV1800H: + ctl->demod = XC3028_FE_ZARLINK456; + break; + case CX88_BOARD_KWORLD_ATSC_120: + case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO: + ctl->demod = XC3028_FE_OREN538; + break; + case CX88_BOARD_GENIATECH_X8000_MT: + /* + * FIXME: For this board, the xc3028 never recovers after being + * powered down (the reset GPIO probably is not set properly). + * We don't have access to the hardware so we cannot determine + * which GPIO is used for xc3028, so just disable power xc3028 + * power management for now + */ + ctl->disable_power_mgmt = 1; + break; + case CX88_BOARD_NOTONLYTV_LV3H: + ctl->demod = XC3028_FE_ZARLINK456; + ctl->fname = XC3028L_DEFAULT_FIRMWARE; + ctl->read_not_reliable = 1; + break; + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL: + case CX88_BOARD_PROLINK_PV_GLOBAL_XTREME: + case CX88_BOARD_PROLINK_PV_8000GT: + /* + * Those boards uses non-MTS firmware + */ + break; + case CX88_BOARD_PINNACLE_HYBRID_PCTV: + case CX88_BOARD_TERRATEC_CINERGY_HT_PCI_MKII: + ctl->demod = XC3028_FE_ZARLINK456; + ctl->mts = 1; + break; + default: + ctl->demod = XC3028_FE_OREN538; + ctl->mts = 1; + } +} +EXPORT_SYMBOL_GPL(cx88_setup_xc3028); + +static void cx88_card_setup(struct cx88_core *core) +{ + static u8 eeprom[256]; + struct tuner_setup tun_setup; + unsigned int mode_mask = T_RADIO | T_ANALOG_TV; + + memset(&tun_setup, 0, sizeof(tun_setup)); + + if (!core->i2c_rc) { + core->i2c_client.addr = 0xa0 >> 1; + tveeprom_read(&core->i2c_client, eeprom, sizeof(eeprom)); + } + + switch (core->boardnr) { + case CX88_BOARD_HAUPPAUGE: + case CX88_BOARD_HAUPPAUGE_ROSLYN: + if (!core->i2c_rc) + hauppauge_eeprom(core, eeprom + 8); + break; + case CX88_BOARD_GDI: + if (!core->i2c_rc) + gdi_eeprom(core, eeprom); + break; + case CX88_BOARD_LEADTEK_PVR2000: + case CX88_BOARD_WINFAST_DV2000: + case CX88_BOARD_WINFAST2000XP_EXPERT: + if (!core->i2c_rc) + leadtek_eeprom(core, eeprom); + break; + case CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1: + case CX88_BOARD_HAUPPAUGE_NOVASE2_S1: + case CX88_BOARD_HAUPPAUGE_DVB_T1: + case CX88_BOARD_HAUPPAUGE_HVR1100: + case CX88_BOARD_HAUPPAUGE_HVR1100LP: + case CX88_BOARD_HAUPPAUGE_HVR3000: + case CX88_BOARD_HAUPPAUGE_HVR1300: + case CX88_BOARD_HAUPPAUGE_HVR4000: + case CX88_BOARD_HAUPPAUGE_HVR4000LITE: + case CX88_BOARD_HAUPPAUGE_IRONLY: + if (!core->i2c_rc) + hauppauge_eeprom(core, eeprom); + break; + case CX88_BOARD_KWORLD_DVBS_100: + cx_write(MO_GP0_IO, 0x000007f8); + cx_write(MO_GP1_IO, 0x00000001); + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO: + /* GPIO0:0 is hooked to demod reset */ + /* GPIO0:4 is hooked to xc3028 reset */ + cx_write(MO_GP0_IO, 0x00111100); + usleep_range(10000, 20000); + cx_write(MO_GP0_IO, 0x00111111); + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL: + /* GPIO0:6 is hooked to FX2 reset pin */ + cx_set(MO_GP0_IO, 0x00004040); + cx_clear(MO_GP0_IO, 0x00000040); + msleep(1000); + cx_set(MO_GP0_IO, 0x00004040); + fallthrough; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1: + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS: + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID: + /* GPIO0:0 is hooked to mt352 reset pin */ + cx_set(MO_GP0_IO, 0x00000101); + cx_clear(MO_GP0_IO, 0x00000001); + usleep_range(10000, 20000); + cx_set(MO_GP0_IO, 0x00000101); + if (!core->i2c_rc && + core->boardnr == CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID) + dvico_fusionhdtv_hybrid_init(core); + break; + case CX88_BOARD_KWORLD_DVB_T: + case CX88_BOARD_DNTV_LIVE_DVB_T: + cx_set(MO_GP0_IO, 0x00000707); + cx_set(MO_GP2_IO, 0x00000101); + cx_clear(MO_GP2_IO, 0x00000001); + usleep_range(10000, 20000); + cx_clear(MO_GP0_IO, 0x00000007); + cx_set(MO_GP2_IO, 0x00000101); + break; + case CX88_BOARD_DNTV_LIVE_DVB_T_PRO: + cx_write(MO_GP0_IO, 0x00080808); + break; + case CX88_BOARD_ATI_HDTVWONDER: + if (!core->i2c_rc) { + /* enable tuner */ + int i; + static const u8 buffer[][2] = { + {0x10, 0x12}, + {0x13, 0x04}, + {0x16, 0x00}, + {0x14, 0x04}, + {0x17, 0x00} + }; + core->i2c_client.addr = 0x0a; + + for (i = 0; i < ARRAY_SIZE(buffer); i++) + if (i2c_master_send(&core->i2c_client, + buffer[i], 2) != 2) + pr_warn("Unable to enable tuner(%i).\n", + i); + } + break; + case CX88_BOARD_MSI_TVANYWHERE_MASTER: + { + struct v4l2_priv_tun_config tea5767_cfg; + struct tea5767_ctrl ctl; + + memset(&ctl, 0, sizeof(ctl)); + + ctl.high_cut = 1; + ctl.st_noise = 1; + ctl.deemph_75 = 1; + ctl.xtal_freq = TEA5767_HIGH_LO_13MHz; + + tea5767_cfg.tuner = TUNER_TEA5767; + tea5767_cfg.priv = &ctl; + + call_all(core, tuner, s_config, &tea5767_cfg); + break; + } + case CX88_BOARD_TEVII_S420: + case CX88_BOARD_TEVII_S460: + case CX88_BOARD_TEVII_S464: + case CX88_BOARD_OMICOM_SS4_PCI: + case CX88_BOARD_TBS_8910: + case CX88_BOARD_TBS_8920: + case CX88_BOARD_PROF_6200: + case CX88_BOARD_PROF_7300: + case CX88_BOARD_PROF_7301: + case CX88_BOARD_SATTRADE_ST4200: + cx_write(MO_GP0_IO, 0x8000); + msleep(100); + cx_write(MO_SRST_IO, 0); + usleep_range(10000, 20000); + cx_write(MO_GP0_IO, 0x8080); + msleep(100); + cx_write(MO_SRST_IO, 1); + msleep(100); + break; + } /*end switch() */ + + /* Setup tuners */ + if (core->board.radio_type != UNSET) { + tun_setup.mode_mask = T_RADIO; + tun_setup.type = core->board.radio_type; + tun_setup.addr = core->board.radio_addr; + tun_setup.tuner_callback = cx88_tuner_callback; + call_all(core, tuner, s_type_addr, &tun_setup); + mode_mask &= ~T_RADIO; + } + + if (core->board.tuner_type != UNSET) { + tun_setup.mode_mask = mode_mask; + tun_setup.type = core->board.tuner_type; + tun_setup.addr = core->board.tuner_addr; + tun_setup.tuner_callback = cx88_tuner_callback; + + call_all(core, tuner, s_type_addr, &tun_setup); + } + + if (core->board.tda9887_conf) { + struct v4l2_priv_tun_config tda9887_cfg; + + tda9887_cfg.tuner = TUNER_TDA9887; + tda9887_cfg.priv = &core->board.tda9887_conf; + + call_all(core, tuner, s_config, &tda9887_cfg); + } + + if (core->board.tuner_type == TUNER_XC2028) { + struct v4l2_priv_tun_config xc2028_cfg; + struct xc2028_ctrl ctl; + + /* Fills device-dependent initialization parameters */ + cx88_setup_xc3028(core, &ctl); + + /* Sends parameters to xc2028/3028 tuner */ + memset(&xc2028_cfg, 0, sizeof(xc2028_cfg)); + xc2028_cfg.tuner = TUNER_XC2028; + xc2028_cfg.priv = &ctl; + dprintk(1, "Asking xc2028/3028 to load firmware %s\n", + ctl.fname); + call_all(core, tuner, s_config, &xc2028_cfg); + } + call_all(core, tuner, standby); +} + +/* ------------------------------------------------------------------ */ + +static int cx88_pci_quirks(const char *name, struct pci_dev *pci) +{ + unsigned int lat = UNSET; + u8 ctrl = 0; + u8 value; + + /* check pci quirks */ + if (pci_pci_problems & PCIPCI_TRITON) { + pr_info("quirk: PCIPCI_TRITON -- set TBFX\n"); + ctrl |= CX88X_EN_TBFX; + } + if (pci_pci_problems & PCIPCI_NATOMA) { + pr_info("quirk: PCIPCI_NATOMA -- set TBFX\n"); + ctrl |= CX88X_EN_TBFX; + } + if (pci_pci_problems & PCIPCI_VIAETBF) { + pr_info("quirk: PCIPCI_VIAETBF -- set TBFX\n"); + ctrl |= CX88X_EN_TBFX; + } + if (pci_pci_problems & PCIPCI_VSFX) { + pr_info("quirk: PCIPCI_VSFX -- set VSFX\n"); + ctrl |= CX88X_EN_VSFX; + } +#ifdef PCIPCI_ALIMAGIK + if (pci_pci_problems & PCIPCI_ALIMAGIK) { + pr_info("quirk: PCIPCI_ALIMAGIK -- latency fixup\n"); + lat = 0x0A; + } +#endif + + /* check insmod options */ + if (latency != UNSET) + lat = latency; + + /* apply stuff */ + if (ctrl) { + pci_read_config_byte(pci, CX88X_DEVCTRL, &value); + value |= ctrl; + pci_write_config_byte(pci, CX88X_DEVCTRL, value); + } + if (lat != UNSET) { + pr_info("setting pci latency timer to %d\n", latency); + pci_write_config_byte(pci, PCI_LATENCY_TIMER, latency); + } + return 0; +} + +int cx88_get_resources(const struct cx88_core *core, struct pci_dev *pci) +{ + if (request_mem_region(pci_resource_start(pci, 0), + pci_resource_len(pci, 0), + core->name)) + return 0; + pr_err("func %d: Can't get MMIO memory @ 0x%llx, subsystem: %04x:%04x\n", + PCI_FUNC(pci->devfn), + (unsigned long long)pci_resource_start(pci, 0), + pci->subsystem_vendor, pci->subsystem_device); + return -EBUSY; +} + +/* + * Allocate and initialize the cx88 core struct. One should hold the + * devlist mutex before calling this. + */ +struct cx88_core *cx88_core_create(struct pci_dev *pci, int nr) +{ + struct cx88_core *core; + int i; + + core = kzalloc_obj(*core); + if (!core) + return NULL; + + refcount_set(&core->refcount, 1); + core->pci_bus = pci->bus->number; + core->pci_slot = PCI_SLOT(pci->devfn); + core->pci_irqmask = PCI_INT_RISC_RD_BERRINT | PCI_INT_RISC_WR_BERRINT | + PCI_INT_BRDG_BERRINT | PCI_INT_SRC_DMA_BERRINT | + PCI_INT_DST_DMA_BERRINT | PCI_INT_IPB_DMA_BERRINT; + mutex_init(&core->lock); + + core->nr = nr; + sprintf(core->name, "cx88[%d]", core->nr); + + /* + * Note: Setting initial standard here would cause first call to + * cx88_set_tvnorm() to return without programming any registers. Leave + * it blank for at this point and it will get set later in + * cx8800_initdev() + */ + core->tvnorm = 0; + + core->width = 320; + core->height = 240; + core->field = V4L2_FIELD_INTERLACED; + + strscpy(core->v4l2_dev.name, core->name, sizeof(core->v4l2_dev.name)); + if (v4l2_device_register(NULL, &core->v4l2_dev)) { + kfree(core); + return NULL; + } + + if (v4l2_ctrl_handler_init(&core->video_hdl, 13)) { + v4l2_device_unregister(&core->v4l2_dev); + kfree(core); + return NULL; + } + + if (v4l2_ctrl_handler_init(&core->audio_hdl, 13)) { + v4l2_ctrl_handler_free(&core->video_hdl); + v4l2_device_unregister(&core->v4l2_dev); + kfree(core); + return NULL; + } + + if (cx88_get_resources(core, pci) != 0) { + v4l2_ctrl_handler_free(&core->video_hdl); + v4l2_ctrl_handler_free(&core->audio_hdl); + v4l2_device_unregister(&core->v4l2_dev); + kfree(core); + return NULL; + } + + /* PCI stuff */ + cx88_pci_quirks(core->name, pci); + core->lmmio = ioremap(pci_resource_start(pci, 0), + pci_resource_len(pci, 0)); + core->bmmio = (u8 __iomem *)core->lmmio; + + if (!core->lmmio) { + release_mem_region(pci_resource_start(pci, 0), + pci_resource_len(pci, 0)); + v4l2_ctrl_handler_free(&core->video_hdl); + v4l2_ctrl_handler_free(&core->audio_hdl); + v4l2_device_unregister(&core->v4l2_dev); + kfree(core); + return NULL; + } + + /* board config */ + core->boardnr = UNSET; + if (card[core->nr] < ARRAY_SIZE(cx88_boards)) + core->boardnr = card[core->nr]; + for (i = 0; core->boardnr == UNSET && i < ARRAY_SIZE(cx88_subids); i++) + if (pci->subsystem_vendor == cx88_subids[i].subvendor && + pci->subsystem_device == cx88_subids[i].subdevice) + core->boardnr = cx88_subids[i].card; + if (core->boardnr == UNSET) { + core->boardnr = CX88_BOARD_UNKNOWN; + cx88_card_list(core, pci); + } + + core->board = cx88_boards[core->boardnr]; + + if (!core->board.num_frontends && (core->board.mpeg & CX88_MPEG_DVB)) + core->board.num_frontends = 1; + + pr_info("subsystem: %04x:%04x, board: %s [card=%d,%s], frontend(s): %d\n", + pci->subsystem_vendor, pci->subsystem_device, core->board.name, + core->boardnr, card[core->nr] == core->boardnr ? + "insmod option" : "autodetected", + core->board.num_frontends); + + if (tuner[core->nr] != UNSET) + core->board.tuner_type = tuner[core->nr]; + if (radio[core->nr] != UNSET) + core->board.radio_type = radio[core->nr]; + + dprintk(1, "TV tuner type %d, Radio tuner type %d\n", + core->board.tuner_type, core->board.radio_type); + + /* init hardware */ + cx88_reset(core); + cx88_card_setup_pre_i2c(core); + cx88_i2c_init(core, pci); + + /* load tuner module, if needed */ + if (core->board.tuner_type != UNSET) { + /* + * Ignore 0x6b and 0x6f on cx88 boards. + * FusionHDTV5 RT Gold has an ir receiver at 0x6b + * and an RTC at 0x6f which can get corrupted if probed. + */ + static const unsigned short tv_addrs[] = { + 0x42, 0x43, 0x4a, 0x4b, /* tda8290 */ + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6c, 0x6d, 0x6e, + I2C_CLIENT_END + }; + int has_demod = (core->board.tda9887_conf & TDA9887_PRESENT); + + /* + * I don't trust the radio_type as is stored in the card + * definitions, so we just probe for it. + * The radio_type is sometimes missing, or set to UNSET but + * later code configures a tea5767. + */ + v4l2_i2c_new_subdev(&core->v4l2_dev, &core->i2c_adap, + "tuner", 0, + v4l2_i2c_tuner_addrs(ADDRS_RADIO)); + if (has_demod) + v4l2_i2c_new_subdev(&core->v4l2_dev, + &core->i2c_adap, "tuner", + 0, v4l2_i2c_tuner_addrs(ADDRS_DEMOD)); + if (core->board.tuner_addr == ADDR_UNSET) { + v4l2_i2c_new_subdev(&core->v4l2_dev, + &core->i2c_adap, "tuner", + 0, has_demod ? tv_addrs + 4 : tv_addrs); + } else { + v4l2_i2c_new_subdev(&core->v4l2_dev, &core->i2c_adap, + "tuner", core->board.tuner_addr, + NULL); + } + } + + cx88_card_setup(core); + if (!disable_ir) { + cx88_i2c_init_ir(core); + cx88_ir_init(core, pci); + } + + return core; +} diff --git a/drivers/media/pci/cx88/cx88-core.c b/drivers/media/pci/cx88/cx88-core.c new file mode 100644 index 000000000..52be42f9a --- /dev/null +++ b/drivers/media/pci/cx88/cx88-core.c @@ -0,0 +1,1099 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * device driver for Conexant 2388x based TV cards + * driver core + * + * (c) 2003 Gerd Knorr [SuSE Labs] + * + * (c) 2005-2006 Mauro Carvalho Chehab + * - Multituner support + * - video_ioctl2 conversion + * - PAL/M fixes + */ + +#include "cx88.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +MODULE_DESCRIPTION("v4l2 driver module for cx2388x based TV cards"); +MODULE_AUTHOR("Gerd Knorr [SuSE Labs]"); +MODULE_LICENSE("GPL v2"); + +/* ------------------------------------------------------------------ */ + +unsigned int cx88_core_debug; +module_param_named(core_debug, cx88_core_debug, int, 0644); +MODULE_PARM_DESC(core_debug, "enable debug messages [core]"); + +static unsigned int nicam; +module_param(nicam, int, 0644); +MODULE_PARM_DESC(nicam, "tv audio is nicam"); + +static unsigned int nocomb; +module_param(nocomb, int, 0644); +MODULE_PARM_DESC(nocomb, "disable comb filter"); + +#define dprintk0(fmt, arg...) \ + printk(KERN_DEBUG pr_fmt("%s: core:" fmt), \ + __func__, ##arg) \ + +#define dprintk(level, fmt, arg...) do { \ + if (cx88_core_debug >= level) \ + printk(KERN_DEBUG pr_fmt("%s: core:" fmt), \ + __func__, ##arg); \ +} while (0) + +static unsigned int cx88_devcount; +static LIST_HEAD(cx88_devlist); +static DEFINE_MUTEX(devlist); + +#define NO_SYNC_LINE (-1U) + +/* + * @lpi: lines per IRQ, or 0 to not generate irqs. Note: IRQ to be + * generated _after_ lpi lines are transferred. + */ +static __le32 *cx88_risc_field(__le32 *rp, struct scatterlist *sglist, + unsigned int offset, u32 sync_line, + unsigned int bpl, unsigned int padding, + unsigned int lines, unsigned int lpi, bool jump) +{ + struct scatterlist *sg; + unsigned int line, todo, sol; + + if (jump) { + (*rp++) = cpu_to_le32(RISC_JUMP); + (*rp++) = 0; + } + + /* sync instruction */ + if (sync_line != NO_SYNC_LINE) + *(rp++) = cpu_to_le32(RISC_RESYNC | sync_line); + + /* scan lines */ + sg = sglist; + for (line = 0; line < lines; line++) { + while (offset && offset >= sg_dma_len(sg)) { + offset -= sg_dma_len(sg); + sg = sg_next(sg); + } + if (lpi && line > 0 && !(line % lpi)) + sol = RISC_SOL | RISC_IRQ1 | RISC_CNT_INC; + else + sol = RISC_SOL; + if (bpl <= sg_dma_len(sg) - offset) { + /* fits into current chunk */ + *(rp++) = cpu_to_le32(RISC_WRITE | sol | + RISC_EOL | bpl); + *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset); + offset += bpl; + } else { + /* scanline needs to be split */ + todo = bpl; + *(rp++) = cpu_to_le32(RISC_WRITE | sol | + (sg_dma_len(sg) - offset)); + *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset); + todo -= (sg_dma_len(sg) - offset); + offset = 0; + sg = sg_next(sg); + while (todo > sg_dma_len(sg)) { + *(rp++) = cpu_to_le32(RISC_WRITE | + sg_dma_len(sg)); + *(rp++) = cpu_to_le32(sg_dma_address(sg)); + todo -= sg_dma_len(sg); + sg = sg_next(sg); + } + *(rp++) = cpu_to_le32(RISC_WRITE | RISC_EOL | todo); + *(rp++) = cpu_to_le32(sg_dma_address(sg)); + offset += todo; + } + offset += padding; + } + + return rp; +} + +int cx88_risc_buffer(struct pci_dev *pci, struct cx88_riscmem *risc, + struct scatterlist *sglist, + unsigned int top_offset, unsigned int bottom_offset, + unsigned int bpl, unsigned int padding, unsigned int lines) +{ + u32 instructions, fields; + __le32 *rp; + + fields = 0; + if (top_offset != UNSET) + fields++; + if (bottom_offset != UNSET) + fields++; + + /* + * estimate risc mem: worst case is one write per page border + + * one write per scan line + syncs + jump (all 2 dwords). Padding + * can cause next bpl to start close to a page border. First DMA + * region may be smaller than PAGE_SIZE + */ + instructions = fields * (1 + ((bpl + padding) * lines) / + PAGE_SIZE + lines); + instructions += 4; + risc->size = instructions * 8; + risc->dma = 0; + risc->cpu = dma_alloc_coherent(&pci->dev, risc->size, &risc->dma, + GFP_KERNEL); + if (!risc->cpu) + return -ENOMEM; + + /* write risc instructions */ + rp = risc->cpu; + if (top_offset != UNSET) + rp = cx88_risc_field(rp, sglist, top_offset, 0, + bpl, padding, lines, 0, true); + if (bottom_offset != UNSET) + rp = cx88_risc_field(rp, sglist, bottom_offset, 0x200, + bpl, padding, lines, 0, + top_offset == UNSET); + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + WARN_ON((risc->jmp - risc->cpu + 2) * sizeof(*risc->cpu) > risc->size); + return 0; +} +EXPORT_SYMBOL(cx88_risc_buffer); + +int cx88_risc_databuffer(struct pci_dev *pci, struct cx88_riscmem *risc, + struct scatterlist *sglist, unsigned int bpl, + unsigned int lines, unsigned int lpi) +{ + u32 instructions; + __le32 *rp; + + /* + * estimate risc mem: worst case is one write per page border + + * one write per scan line + syncs + jump (all 2 dwords). Here + * there is no padding and no sync. First DMA region may be smaller + * than PAGE_SIZE + */ + instructions = 1 + (bpl * lines) / PAGE_SIZE + lines; + instructions += 3; + risc->size = instructions * 8; + risc->dma = 0; + risc->cpu = dma_alloc_coherent(&pci->dev, risc->size, &risc->dma, + GFP_KERNEL); + if (!risc->cpu) + return -ENOMEM; + + /* write risc instructions */ + rp = risc->cpu; + rp = cx88_risc_field(rp, sglist, 0, NO_SYNC_LINE, bpl, 0, + lines, lpi, !lpi); + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + WARN_ON((risc->jmp - risc->cpu + 2) * sizeof(*risc->cpu) > risc->size); + return 0; +} +EXPORT_SYMBOL(cx88_risc_databuffer); + +/* + * our SRAM memory layout + */ + +/* + * we are going to put all thr risc programs into host memory, so we + * can use the whole SDRAM for the DMA fifos. To simplify things, we + * use a static memory layout. That surely will waste memory in case + * we don't use all DMA channels at the same time (which will be the + * case most of the time). But that still gives us enough FIFO space + * to be able to deal with insane long pci latencies ... + * + * FIFO space allocations: + * channel 21 (y video) - 10.0k + * channel 22 (u video) - 2.0k + * channel 23 (v video) - 2.0k + * channel 24 (vbi) - 4.0k + * channels 25+26 (audio) - 4.0k + * channel 28 (mpeg) - 4.0k + * channel 27 (audio rds)- 3.0k + * TOTAL = 29.0k + * + * Every channel has 160 bytes control data (64 bytes instruction + * queue and 6 CDT entries), which is close to 2k total. + * + * Address layout: + * 0x0000 - 0x03ff CMDs / reserved + * 0x0400 - 0x0bff instruction queues + CDs + * 0x0c00 - FIFOs + */ + +const struct sram_channel cx88_sram_channels[] = { + [SRAM_CH21] = { + .name = "video y / packed", + .cmds_start = 0x180040, + .ctrl_start = 0x180400, + .cdt = 0x180400 + 64, + .fifo_start = 0x180c00, + .fifo_size = 0x002800, + .ptr1_reg = MO_DMA21_PTR1, + .ptr2_reg = MO_DMA21_PTR2, + .cnt1_reg = MO_DMA21_CNT1, + .cnt2_reg = MO_DMA21_CNT2, + }, + [SRAM_CH22] = { + .name = "video u", + .cmds_start = 0x180080, + .ctrl_start = 0x1804a0, + .cdt = 0x1804a0 + 64, + .fifo_start = 0x183400, + .fifo_size = 0x000800, + .ptr1_reg = MO_DMA22_PTR1, + .ptr2_reg = MO_DMA22_PTR2, + .cnt1_reg = MO_DMA22_CNT1, + .cnt2_reg = MO_DMA22_CNT2, + }, + [SRAM_CH23] = { + .name = "video v", + .cmds_start = 0x1800c0, + .ctrl_start = 0x180540, + .cdt = 0x180540 + 64, + .fifo_start = 0x183c00, + .fifo_size = 0x000800, + .ptr1_reg = MO_DMA23_PTR1, + .ptr2_reg = MO_DMA23_PTR2, + .cnt1_reg = MO_DMA23_CNT1, + .cnt2_reg = MO_DMA23_CNT2, + }, + [SRAM_CH24] = { + .name = "vbi", + .cmds_start = 0x180100, + .ctrl_start = 0x1805e0, + .cdt = 0x1805e0 + 64, + .fifo_start = 0x184400, + .fifo_size = 0x001000, + .ptr1_reg = MO_DMA24_PTR1, + .ptr2_reg = MO_DMA24_PTR2, + .cnt1_reg = MO_DMA24_CNT1, + .cnt2_reg = MO_DMA24_CNT2, + }, + [SRAM_CH25] = { + .name = "audio from", + .cmds_start = 0x180140, + .ctrl_start = 0x180680, + .cdt = 0x180680 + 64, + .fifo_start = 0x185400, + .fifo_size = 0x001000, + .ptr1_reg = MO_DMA25_PTR1, + .ptr2_reg = MO_DMA25_PTR2, + .cnt1_reg = MO_DMA25_CNT1, + .cnt2_reg = MO_DMA25_CNT2, + }, + [SRAM_CH26] = { + .name = "audio to", + .cmds_start = 0x180180, + .ctrl_start = 0x180720, + .cdt = 0x180680 + 64, /* same as audio IN */ + .fifo_start = 0x185400, /* same as audio IN */ + .fifo_size = 0x001000, /* same as audio IN */ + .ptr1_reg = MO_DMA26_PTR1, + .ptr2_reg = MO_DMA26_PTR2, + .cnt1_reg = MO_DMA26_CNT1, + .cnt2_reg = MO_DMA26_CNT2, + }, + [SRAM_CH28] = { + .name = "mpeg", + .cmds_start = 0x180200, + .ctrl_start = 0x1807C0, + .cdt = 0x1807C0 + 64, + .fifo_start = 0x186400, + .fifo_size = 0x001000, + .ptr1_reg = MO_DMA28_PTR1, + .ptr2_reg = MO_DMA28_PTR2, + .cnt1_reg = MO_DMA28_CNT1, + .cnt2_reg = MO_DMA28_CNT2, + }, + [SRAM_CH27] = { + .name = "audio rds", + .cmds_start = 0x1801C0, + .ctrl_start = 0x180860, + .cdt = 0x180860 + 64, + .fifo_start = 0x187400, + .fifo_size = 0x000C00, + .ptr1_reg = MO_DMA27_PTR1, + .ptr2_reg = MO_DMA27_PTR2, + .cnt1_reg = MO_DMA27_CNT1, + .cnt2_reg = MO_DMA27_CNT2, + }, +}; +EXPORT_SYMBOL(cx88_sram_channels); + +int cx88_sram_channel_setup(struct cx88_core *core, + const struct sram_channel *ch, + unsigned int bpl, u32 risc) +{ + unsigned int i, lines; + u32 cdt; + + bpl = (bpl + 7) & ~7; /* alignment */ + cdt = ch->cdt; + lines = ch->fifo_size / bpl; + if (lines > 6) + lines = 6; + WARN_ON(lines < 2); + + /* write CDT */ + for (i = 0; i < lines; i++) + cx_write(cdt + 16 * i, ch->fifo_start + bpl * i); + + /* write CMDS */ + cx_write(ch->cmds_start + 0, risc); + cx_write(ch->cmds_start + 4, cdt); + cx_write(ch->cmds_start + 8, (lines * 16) >> 3); + cx_write(ch->cmds_start + 12, ch->ctrl_start); + cx_write(ch->cmds_start + 16, 64 >> 2); + for (i = 20; i < 64; i += 4) + cx_write(ch->cmds_start + i, 0); + + /* fill registers */ + cx_write(ch->ptr1_reg, ch->fifo_start); + cx_write(ch->ptr2_reg, cdt); + cx_write(ch->cnt1_reg, (bpl >> 3) - 1); + cx_write(ch->cnt2_reg, (lines * 16) >> 3); + + dprintk(2, "sram setup %s: bpl=%d lines=%d\n", ch->name, bpl, lines); + return 0; +} +EXPORT_SYMBOL(cx88_sram_channel_setup); + +/* ------------------------------------------------------------------ */ +/* debug helper code */ + +static int cx88_risc_decode(u32 risc) +{ + static const char * const instr[16] = { + [RISC_SYNC >> 28] = "sync", + [RISC_WRITE >> 28] = "write", + [RISC_WRITEC >> 28] = "writec", + [RISC_READ >> 28] = "read", + [RISC_READC >> 28] = "readc", + [RISC_JUMP >> 28] = "jump", + [RISC_SKIP >> 28] = "skip", + [RISC_WRITERM >> 28] = "writerm", + [RISC_WRITECM >> 28] = "writecm", + [RISC_WRITECR >> 28] = "writecr", + }; + static int const incr[16] = { + [RISC_WRITE >> 28] = 2, + [RISC_JUMP >> 28] = 2, + [RISC_WRITERM >> 28] = 3, + [RISC_WRITECM >> 28] = 3, + [RISC_WRITECR >> 28] = 4, + }; + static const char * const bits[] = { + "12", "13", "14", "resync", + "cnt0", "cnt1", "18", "19", + "20", "21", "22", "23", + "irq1", "irq2", "eol", "sol", + }; + int i; + + dprintk0("0x%08x [ %s", risc, + instr[risc >> 28] ? instr[risc >> 28] : "INVALID"); + for (i = ARRAY_SIZE(bits) - 1; i >= 0; i--) + if (risc & (1 << (i + 12))) + pr_cont(" %s", bits[i]); + pr_cont(" count=%d ]\n", risc & 0xfff); + return incr[risc >> 28] ? incr[risc >> 28] : 1; +} + +void cx88_sram_channel_dump(struct cx88_core *core, + const struct sram_channel *ch) +{ + static const char * const name[] = { + "initial risc", + "cdt base", + "cdt size", + "iq base", + "iq size", + "risc pc", + "iq wr ptr", + "iq rd ptr", + "cdt current", + "pci target", + "line / byte", + }; + u32 risc; + unsigned int i, j, n; + + dprintk0("%s - dma channel status dump\n", ch->name); + for (i = 0; i < ARRAY_SIZE(name); i++) + dprintk0(" cmds: %-12s: 0x%08x\n", + name[i], cx_read(ch->cmds_start + 4 * i)); + for (n = 1, i = 0; i < 4; i++) { + risc = cx_read(ch->cmds_start + 4 * (i + 11)); + pr_cont(" risc%d: ", i); + if (--n) + pr_cont("0x%08x [ arg #%d ]\n", risc, n); + else + n = cx88_risc_decode(risc); + } + for (i = 0; i < 16; i += n) { + risc = cx_read(ch->ctrl_start + 4 * i); + dprintk0(" iq %x: ", i); + n = cx88_risc_decode(risc); + for (j = 1; j < n; j++) { + risc = cx_read(ch->ctrl_start + 4 * (i + j)); + pr_cont(" iq %x: 0x%08x [ arg #%d ]\n", + i + j, risc, j); + } + } + + dprintk0("fifo: 0x%08x -> 0x%x\n", + ch->fifo_start, ch->fifo_start + ch->fifo_size); + dprintk0("ctrl: 0x%08x -> 0x%x\n", + ch->ctrl_start, ch->ctrl_start + 6 * 16); + dprintk0(" ptr1_reg: 0x%08x\n", cx_read(ch->ptr1_reg)); + dprintk0(" ptr2_reg: 0x%08x\n", cx_read(ch->ptr2_reg)); + dprintk0(" cnt1_reg: 0x%08x\n", cx_read(ch->cnt1_reg)); + dprintk0(" cnt2_reg: 0x%08x\n", cx_read(ch->cnt2_reg)); +} +EXPORT_SYMBOL(cx88_sram_channel_dump); + +static const char *cx88_pci_irqs[32] = { + "vid", "aud", "ts", "vip", "hst", "5", "6", "tm1", + "src_dma", "dst_dma", "risc_rd_err", "risc_wr_err", + "brdg_err", "src_dma_err", "dst_dma_err", "ipb_dma_err", + "i2c", "i2c_rack", "ir_smp", "gpio0", "gpio1" +}; + +void cx88_print_irqbits(const char *tag, const char *strings[], + int len, u32 bits, u32 mask) +{ + unsigned int i; + + dprintk0("%s [0x%x]", tag, bits); + for (i = 0; i < len; i++) { + if (!(bits & (1 << i))) + continue; + if (strings[i]) + pr_cont(" %s", strings[i]); + else + pr_cont(" %d", i); + if (!(mask & (1 << i))) + continue; + pr_cont("*"); + } + pr_cont("\n"); +} +EXPORT_SYMBOL(cx88_print_irqbits); + +/* ------------------------------------------------------------------ */ + +int cx88_core_irq(struct cx88_core *core, u32 status) +{ + int handled = 0; + + if (status & PCI_INT_IR_SMPINT) { + cx88_ir_irq(core); + handled++; + } + if (!handled) + cx88_print_irqbits("irq pci", + cx88_pci_irqs, ARRAY_SIZE(cx88_pci_irqs), + status, core->pci_irqmask); + return handled; +} +EXPORT_SYMBOL(cx88_core_irq); + +void cx88_wakeup(struct cx88_core *core, + struct cx88_dmaqueue *q, u32 count) +{ + struct cx88_buffer *buf; + + buf = list_entry(q->active.next, + struct cx88_buffer, list); + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + buf->vb.field = core->field; + buf->vb.sequence = q->count++; + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); +} +EXPORT_SYMBOL(cx88_wakeup); + +void cx88_shutdown(struct cx88_core *core) +{ + /* disable RISC controller + IRQs */ + cx_write(MO_DEV_CNTRL2, 0); + + /* stop dma transfers */ + cx_write(MO_VID_DMACNTRL, 0x0); + cx_write(MO_AUD_DMACNTRL, 0x0); + cx_write(MO_TS_DMACNTRL, 0x0); + cx_write(MO_VIP_DMACNTRL, 0x0); + cx_write(MO_GPHST_DMACNTRL, 0x0); + + /* stop interrupts */ + cx_write(MO_PCI_INTMSK, 0x0); + cx_write(MO_VID_INTMSK, 0x0); + cx_write(MO_AUD_INTMSK, 0x0); + cx_write(MO_TS_INTMSK, 0x0); + cx_write(MO_VIP_INTMSK, 0x0); + cx_write(MO_GPHST_INTMSK, 0x0); + + /* stop capturing */ + cx_write(VID_CAPTURE_CONTROL, 0); +} +EXPORT_SYMBOL(cx88_shutdown); + +int cx88_reset(struct cx88_core *core) +{ + dprintk(1, ""); + cx88_shutdown(core); + + /* clear irq status */ + cx_write(MO_VID_INTSTAT, 0xFFFFFFFF); // Clear PIV int + cx_write(MO_PCI_INTSTAT, 0xFFFFFFFF); // Clear PCI int + cx_write(MO_INT1_STAT, 0xFFFFFFFF); // Clear RISC int + + /* wait a bit */ + msleep(100); + + /* init sram */ + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH21], + 720 * 4, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH22], 128, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH23], 128, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH24], 128, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH25], 128, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH26], 128, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH28], + 188 * 4, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH27], 128, 0); + + /* misc init ... */ + cx_write(MO_INPUT_FORMAT, ((1 << 13) | // agc enable + (1 << 12) | // agc gain + (1 << 11) | // adaptibe agc + (0 << 10) | // chroma agc + (0 << 9) | // ckillen + (7))); + + /* setup image format */ + cx_andor(MO_COLOR_CTRL, 0x4000, 0x4000); + + /* setup FIFO Thresholds */ + cx_write(MO_PDMA_STHRSH, 0x0807); + cx_write(MO_PDMA_DTHRSH, 0x0807); + + /* fixes flashing of image */ + cx_write(MO_AGC_SYNC_TIP1, 0x0380000F); + cx_write(MO_AGC_BACK_VBI, 0x00E00555); + + cx_write(MO_VID_INTSTAT, 0xFFFFFFFF); // Clear PIV int + cx_write(MO_PCI_INTSTAT, 0xFFFFFFFF); // Clear PCI int + cx_write(MO_INT1_STAT, 0xFFFFFFFF); // Clear RISC int + + /* Reset on-board parts */ + cx_write(MO_SRST_IO, 0); + usleep_range(10000, 20000); + cx_write(MO_SRST_IO, 1); + + return 0; +} +EXPORT_SYMBOL(cx88_reset); + +/* ------------------------------------------------------------------ */ + +static inline unsigned int norm_swidth(v4l2_std_id norm) +{ + if (norm & (V4L2_STD_NTSC | V4L2_STD_PAL_M)) + return 754; + + if (norm & V4L2_STD_PAL_Nc) + return 745; + + return 922; +} + +static inline unsigned int norm_hdelay(v4l2_std_id norm) +{ + if (norm & (V4L2_STD_NTSC | V4L2_STD_PAL_M)) + return 135; + + if (norm & V4L2_STD_PAL_Nc) + return 149; + + return 186; +} + +static inline unsigned int norm_vdelay(v4l2_std_id norm) +{ + return (norm & V4L2_STD_625_50) ? 0x24 : 0x18; +} + +static inline unsigned int norm_fsc8(v4l2_std_id norm) +{ + if (norm & V4L2_STD_PAL_M) + return 28604892; // 3.575611 MHz + + if (norm & V4L2_STD_PAL_Nc) + return 28656448; // 3.582056 MHz + + if (norm & V4L2_STD_NTSC) // All NTSC/M and variants + return 28636360; // 3.57954545 MHz +/- 10 Hz + + /* + * SECAM have also different sub carrier for chroma, + * but step_db and step_dr, at cx88_set_tvnorm already handles that. + * + * The same FSC applies to PAL/BGDKIH, PAL/60, NTSC/4.43 and PAL/N + */ + + return 35468950; // 4.43361875 MHz +/- 5 Hz +} + +static inline unsigned int norm_htotal(v4l2_std_id norm) +{ + unsigned int fsc4 = norm_fsc8(norm) / 2; + + /* returns 4*FSC / vtotal / frames per seconds */ + return (norm & V4L2_STD_625_50) ? + ((fsc4 + 312) / 625 + 12) / 25 : + ((fsc4 + 262) / 525 * 1001 + 15000) / 30000; +} + +static inline unsigned int norm_vbipack(v4l2_std_id norm) +{ + return (norm & V4L2_STD_625_50) ? 511 : 400; +} + +int cx88_set_scale(struct cx88_core *core, unsigned int width, + unsigned int height, enum v4l2_field field) +{ + unsigned int swidth = norm_swidth(core->tvnorm); + unsigned int sheight = norm_maxh(core->tvnorm); + u32 value; + + dprintk(1, "set_scale: %dx%d [%s%s,%s]\n", width, height, + V4L2_FIELD_HAS_TOP(field) ? "T" : "", + V4L2_FIELD_HAS_BOTTOM(field) ? "B" : "", + v4l2_norm_to_name(core->tvnorm)); + if (!V4L2_FIELD_HAS_BOTH(field)) + height *= 2; + + // recalc H delay and scale registers + value = (width * norm_hdelay(core->tvnorm)) / swidth; + value &= 0x3fe; + cx_write(MO_HDELAY_EVEN, value); + cx_write(MO_HDELAY_ODD, value); + dprintk(1, "set_scale: hdelay 0x%04x (width %d)\n", value, swidth); + + value = (swidth * 4096 / width) - 4096; + cx_write(MO_HSCALE_EVEN, value); + cx_write(MO_HSCALE_ODD, value); + dprintk(1, "set_scale: hscale 0x%04x\n", value); + + cx_write(MO_HACTIVE_EVEN, width); + cx_write(MO_HACTIVE_ODD, width); + dprintk(1, "set_scale: hactive 0x%04x\n", width); + + // recalc V scale Register (delay is constant) + cx_write(MO_VDELAY_EVEN, norm_vdelay(core->tvnorm)); + cx_write(MO_VDELAY_ODD, norm_vdelay(core->tvnorm)); + dprintk(1, "set_scale: vdelay 0x%04x\n", norm_vdelay(core->tvnorm)); + + value = (0x10000 - (sheight * 512 / height - 512)) & 0x1fff; + cx_write(MO_VSCALE_EVEN, value); + cx_write(MO_VSCALE_ODD, value); + dprintk(1, "set_scale: vscale 0x%04x\n", value); + + cx_write(MO_VACTIVE_EVEN, sheight); + cx_write(MO_VACTIVE_ODD, sheight); + dprintk(1, "set_scale: vactive 0x%04x\n", sheight); + + // setup filters + value = 0; + value |= (1 << 19); // CFILT (default) + if (core->tvnorm & V4L2_STD_SECAM) { + value |= (1 << 15); + value |= (1 << 16); + } + if (INPUT(core->input).type == CX88_VMUX_SVIDEO) + value |= (1 << 13) | (1 << 5); + if (field == V4L2_FIELD_INTERLACED) + value |= (1 << 3); // VINT (interlaced vertical scaling) + if (width < 385) + value |= (1 << 0); // 3-tap interpolation + if (width < 193) + value |= (1 << 1); // 5-tap interpolation + if (nocomb) + value |= (3 << 5); // disable comb filter + + cx_andor(MO_FILTER_EVEN, 0x7ffc7f, value); /* preserve PEAKEN, PSEL */ + cx_andor(MO_FILTER_ODD, 0x7ffc7f, value); + dprintk(1, "set_scale: filter 0x%04x\n", value); + + return 0; +} +EXPORT_SYMBOL(cx88_set_scale); + +static const u32 xtal = 28636363; + +static int set_pll(struct cx88_core *core, int prescale, u32 ofreq) +{ + static const u32 pre[] = { 0, 0, 0, 3, 2, 1 }; + u64 pll; + u32 reg; + int i; + + if (prescale < 2) + prescale = 2; + if (prescale > 5) + prescale = 5; + + pll = ofreq * 8 * prescale * (u64)(1 << 20); + do_div(pll, xtal); + reg = (pll & 0x3ffffff) | (pre[prescale] << 26); + if (((reg >> 20) & 0x3f) < 14) { + pr_err("pll out of range\n"); + return -1; + } + + dprintk(1, "set_pll: MO_PLL_REG 0x%08x [old=0x%08x,freq=%d]\n", + reg, cx_read(MO_PLL_REG), ofreq); + cx_write(MO_PLL_REG, reg); + for (i = 0; i < 100; i++) { + reg = cx_read(MO_DEVICE_STATUS); + if (reg & (1 << 2)) { + dprintk(1, "pll locked [pre=%d,ofreq=%d]\n", + prescale, ofreq); + return 0; + } + dprintk(1, "pll not locked yet, waiting ...\n"); + usleep_range(10000, 20000); + } + dprintk(1, "pll NOT locked [pre=%d,ofreq=%d]\n", prescale, ofreq); + return -1; +} + +int cx88_start_audio_dma(struct cx88_core *core) +{ + /* constant 128 made buzz in analog Nicam-stereo for bigger fifo_size */ + int bpl = cx88_sram_channels[SRAM_CH25].fifo_size / 4; + + int rds_bpl = cx88_sram_channels[SRAM_CH27].fifo_size / AUD_RDS_LINES; + + /* If downstream RISC is enabled, bail out; ALSA is managing DMA */ + if (cx_read(MO_AUD_DMACNTRL) & 0x10) + return 0; + + /* setup fifo + format */ + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH25], bpl, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH26], bpl, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH27], + rds_bpl, 0); + + cx_write(MO_AUDD_LNGTH, bpl); /* fifo bpl size */ + cx_write(MO_AUDR_LNGTH, rds_bpl); /* fifo bpl size */ + + /* enable Up, Down and Audio RDS fifo */ + cx_write(MO_AUD_DMACNTRL, 0x0007); + + return 0; +} + +int cx88_stop_audio_dma(struct cx88_core *core) +{ + /* If downstream RISC is enabled, bail out; ALSA is managing DMA */ + if (cx_read(MO_AUD_DMACNTRL) & 0x10) + return 0; + + /* stop dma */ + cx_write(MO_AUD_DMACNTRL, 0x0000); + + return 0; +} + +static int set_tvaudio(struct cx88_core *core) +{ + v4l2_std_id norm = core->tvnorm; + + if (INPUT(core->input).type != CX88_VMUX_TELEVISION && + INPUT(core->input).type != CX88_VMUX_CABLE) + return 0; + + if (V4L2_STD_PAL_BG & norm) { + core->tvaudio = WW_BG; + + } else if (V4L2_STD_PAL_DK & norm) { + core->tvaudio = WW_DK; + + } else if (V4L2_STD_PAL_I & norm) { + core->tvaudio = WW_I; + + } else if (V4L2_STD_SECAM_L & norm) { + core->tvaudio = WW_L; + + } else if ((V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H) & + norm) { + core->tvaudio = WW_BG; + + } else if (V4L2_STD_SECAM_DK & norm) { + core->tvaudio = WW_DK; + + } else if ((V4L2_STD_NTSC_M | V4L2_STD_PAL_M | V4L2_STD_PAL_Nc) & + norm) { + core->tvaudio = WW_BTSC; + + } else if (V4L2_STD_NTSC_M_JP & norm) { + core->tvaudio = WW_EIAJ; + + } else { + pr_info("tvaudio support needs work for this tv norm [%s], sorry\n", + v4l2_norm_to_name(core->tvnorm)); + core->tvaudio = WW_NONE; + return 0; + } + + cx_andor(MO_AFECFG_IO, 0x1f, 0x0); + cx88_set_tvaudio(core); + /* cx88_set_stereo(dev,V4L2_TUNER_MODE_STEREO); */ + +/* + * This should be needed only on cx88-alsa. It seems that some cx88 chips have + * bugs and does require DMA enabled for it to work. + */ + cx88_start_audio_dma(core); + return 0; +} + +int cx88_set_tvnorm(struct cx88_core *core, v4l2_std_id norm) +{ + u32 fsc8; + u32 adc_clock; + u32 vdec_clock; + u32 step_db, step_dr; + u64 tmp64; + u32 bdelay, agcdelay, htotal; + u32 cxiformat, cxoformat; + + if (norm == core->tvnorm) + return 0; + if (core->v4ldev && (vb2_is_busy(&core->v4ldev->vb2_vidq) || + vb2_is_busy(&core->v4ldev->vb2_vbiq))) + return -EBUSY; + if (core->dvbdev && vb2_is_busy(&core->dvbdev->vb2_mpegq)) + return -EBUSY; + core->tvnorm = norm; + fsc8 = norm_fsc8(norm); + adc_clock = xtal; + vdec_clock = fsc8; + step_db = fsc8; + step_dr = fsc8; + + if (norm & V4L2_STD_NTSC_M_JP) { + cxiformat = VideoFormatNTSCJapan; + cxoformat = 0x181f0008; + } else if (norm & V4L2_STD_NTSC_443) { + cxiformat = VideoFormatNTSC443; + cxoformat = 0x181f0008; + } else if (norm & V4L2_STD_PAL_M) { + cxiformat = VideoFormatPALM; + cxoformat = 0x1c1f0008; + } else if (norm & V4L2_STD_PAL_N) { + cxiformat = VideoFormatPALN; + cxoformat = 0x1c1f0008; + } else if (norm & V4L2_STD_PAL_Nc) { + cxiformat = VideoFormatPALNC; + cxoformat = 0x1c1f0008; + } else if (norm & V4L2_STD_PAL_60) { + cxiformat = VideoFormatPAL60; + cxoformat = 0x181f0008; + } else if (norm & V4L2_STD_NTSC) { + cxiformat = VideoFormatNTSC; + cxoformat = 0x181f0008; + } else if (norm & V4L2_STD_SECAM) { + step_db = 4250000 * 8; + step_dr = 4406250 * 8; + + cxiformat = VideoFormatSECAM; + cxoformat = 0x181f0008; + } else { /* PAL */ + cxiformat = VideoFormatPAL; + cxoformat = 0x181f0008; + } + + dprintk(1, "set_tvnorm: \"%s\" fsc8=%d adc=%d vdec=%d db/dr=%d/%d\n", + v4l2_norm_to_name(core->tvnorm), fsc8, adc_clock, vdec_clock, + step_db, step_dr); + set_pll(core, 2, vdec_clock); + + dprintk(1, "set_tvnorm: MO_INPUT_FORMAT 0x%08x [old=0x%08x]\n", + cxiformat, cx_read(MO_INPUT_FORMAT) & 0x0f); + /* + * Chroma AGC must be disabled if SECAM is used, we enable it + * by default on PAL and NTSC + */ + cx_andor(MO_INPUT_FORMAT, 0x40f, + norm & V4L2_STD_SECAM ? cxiformat : cxiformat | 0x400); + + // FIXME: as-is from DScaler + dprintk(1, "set_tvnorm: MO_OUTPUT_FORMAT 0x%08x [old=0x%08x]\n", + cxoformat, cx_read(MO_OUTPUT_FORMAT)); + cx_write(MO_OUTPUT_FORMAT, cxoformat); + + // MO_SCONV_REG = adc clock / video dec clock * 2^17 + tmp64 = adc_clock * (u64)(1 << 17); + do_div(tmp64, vdec_clock); + dprintk(1, "set_tvnorm: MO_SCONV_REG 0x%08x [old=0x%08x]\n", + (u32)tmp64, cx_read(MO_SCONV_REG)); + cx_write(MO_SCONV_REG, (u32)tmp64); + + // MO_SUB_STEP = 8 * fsc / video dec clock * 2^22 + tmp64 = step_db * (u64)(1 << 22); + do_div(tmp64, vdec_clock); + dprintk(1, "set_tvnorm: MO_SUB_STEP 0x%08x [old=0x%08x]\n", + (u32)tmp64, cx_read(MO_SUB_STEP)); + cx_write(MO_SUB_STEP, (u32)tmp64); + + // MO_SUB_STEP_DR = 8 * 4406250 / video dec clock * 2^22 + tmp64 = step_dr * (u64)(1 << 22); + do_div(tmp64, vdec_clock); + dprintk(1, "set_tvnorm: MO_SUB_STEP_DR 0x%08x [old=0x%08x]\n", + (u32)tmp64, cx_read(MO_SUB_STEP_DR)); + cx_write(MO_SUB_STEP_DR, (u32)tmp64); + + // bdelay + agcdelay + bdelay = vdec_clock * 65 / 20000000 + 21; + agcdelay = vdec_clock * 68 / 20000000 + 15; + dprintk(1, + "set_tvnorm: MO_AGC_BURST 0x%08x [old=0x%08x,bdelay=%d,agcdelay=%d]\n", + (bdelay << 8) | agcdelay, cx_read(MO_AGC_BURST), + bdelay, agcdelay); + cx_write(MO_AGC_BURST, (bdelay << 8) | agcdelay); + + // htotal + tmp64 = norm_htotal(norm) * (u64)vdec_clock; + do_div(tmp64, fsc8); + htotal = (u32)tmp64; + dprintk(1, + "set_tvnorm: MO_HTOTAL 0x%08x [old=0x%08x,htotal=%d]\n", + htotal, cx_read(MO_HTOTAL), (u32)tmp64); + cx_andor(MO_HTOTAL, 0x07ff, htotal); + + // vbi stuff, set vbi offset to 10 (for 20 Clk*2 pixels), this makes + // the effective vbi offset ~244 samples, the same as the Bt8x8 + cx_write(MO_VBI_PACKET, (10 << 11) | norm_vbipack(norm)); + + // this is needed as well to set all tvnorm parameter + cx88_set_scale(core, 320, 240, V4L2_FIELD_INTERLACED); + + // audio + set_tvaudio(core); + + // tell i2c chips + call_all(core, video, s_std, norm); + + /* + * The chroma_agc control should be inaccessible + * if the video format is SECAM + */ + v4l2_ctrl_grab(core->chroma_agc, cxiformat == VideoFormatSECAM); + + // done + return 0; +} +EXPORT_SYMBOL(cx88_set_tvnorm); + +/* ------------------------------------------------------------------ */ + +void cx88_vdev_init(struct cx88_core *core, + struct pci_dev *pci, + struct video_device *vfd, + const struct video_device *template_, + const char *type) +{ + *vfd = *template_; + + /* + * The dev pointer of v4l2_device is NULL, instead we set the + * video_device dev_parent pointer to the correct PCI bus device. + * This driver is a rare example where there is one v4l2_device, + * but the video nodes have different parent (PCI) devices. + */ + vfd->v4l2_dev = &core->v4l2_dev; + vfd->dev_parent = &pci->dev; + vfd->release = video_device_release_empty; + vfd->lock = &core->lock; + snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)", + core->name, type, core->board.name); +} +EXPORT_SYMBOL(cx88_vdev_init); + +struct cx88_core *cx88_core_get(struct pci_dev *pci) +{ + struct cx88_core *core; + + mutex_lock(&devlist); + list_for_each_entry(core, &cx88_devlist, devlist) { + if (pci->bus->number != core->pci_bus) + continue; + if (PCI_SLOT(pci->devfn) != core->pci_slot) + continue; + + if (cx88_get_resources(core, pci) != 0) { + mutex_unlock(&devlist); + return NULL; + } + refcount_inc(&core->refcount); + mutex_unlock(&devlist); + return core; + } + + core = cx88_core_create(pci, cx88_devcount); + if (core) { + cx88_devcount++; + list_add_tail(&core->devlist, &cx88_devlist); + } + + mutex_unlock(&devlist); + return core; +} +EXPORT_SYMBOL(cx88_core_get); + +void cx88_core_put(struct cx88_core *core, struct pci_dev *pci) +{ + release_mem_region(pci_resource_start(pci, 0), + pci_resource_len(pci, 0)); + + if (!refcount_dec_and_test(&core->refcount)) + return; + + mutex_lock(&devlist); + cx88_ir_fini(core); + if (core->i2c_rc == 0) { + i2c_unregister_device(core->i2c_rtc); + i2c_del_adapter(&core->i2c_adap); + } + list_del(&core->devlist); + iounmap(core->lmmio); + cx88_devcount--; + mutex_unlock(&devlist); + v4l2_ctrl_handler_free(&core->video_hdl); + v4l2_ctrl_handler_free(&core->audio_hdl); + v4l2_device_unregister(&core->v4l2_dev); + kfree(core); +} +EXPORT_SYMBOL(cx88_core_put); diff --git a/drivers/media/pci/cx88/cx88-dsp.c b/drivers/media/pci/cx88/cx88-dsp.c new file mode 100644 index 000000000..958e4637f --- /dev/null +++ b/drivers/media/pci/cx88/cx88-dsp.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Stereo and SAP detection for cx88 + * + * Copyright (c) 2009 Marton Balint + */ + +#include "cx88.h" +#include "cx88-reg.h" + +#include +#include +#include +#include +#include + +#define INT_PI ((s32)(3.141592653589 * 32768.0)) + +#define compat_remainder(a, b) \ + ((float)(((s32)((a) * 100)) % ((s32)((b) * 100))) / 100.0) + +#define baseband_freq(carrier, srate, tone) ((s32)( \ + (compat_remainder(carrier + tone, srate)) / srate * 2 * INT_PI)) + +/* + * We calculate the baseband frequencies of the carrier and the pilot tones + * based on the sampling rate of the audio rds fifo. + */ + +#define FREQ_A2_CARRIER baseband_freq(54687.5, 2689.36, 0.0) +#define FREQ_A2_DUAL baseband_freq(54687.5, 2689.36, 274.1) +#define FREQ_A2_STEREO baseband_freq(54687.5, 2689.36, 117.5) + +/* + * The frequencies below are from the reference driver. They probably need + * further adjustments, because they are not tested at all. You may even need + * to play a bit with the registers of the chip to select the proper signal + * for the input of the audio rds fifo, and measure it's sampling rate to + * calculate the proper baseband frequencies... + */ + +#define FREQ_A2M_CARRIER ((s32)(2.114516 * 32768.0)) +#define FREQ_A2M_DUAL ((s32)(2.754916 * 32768.0)) +#define FREQ_A2M_STEREO ((s32)(2.462326 * 32768.0)) + +#define FREQ_EIAJ_CARRIER ((s32)(1.963495 * 32768.0)) /* 5pi/8 */ +#define FREQ_EIAJ_DUAL ((s32)(2.562118 * 32768.0)) +#define FREQ_EIAJ_STEREO ((s32)(2.601053 * 32768.0)) + +#define FREQ_BTSC_DUAL ((s32)(1.963495 * 32768.0)) /* 5pi/8 */ +#define FREQ_BTSC_DUAL_REF ((s32)(1.374446 * 32768.0)) /* 7pi/16 */ + +#define FREQ_BTSC_SAP ((s32)(2.471532 * 32768.0)) +#define FREQ_BTSC_SAP_REF ((s32)(1.730072 * 32768.0)) + +/* The spectrum of the signal should be empty between these frequencies. */ +#define FREQ_NOISE_START ((s32)(0.100000 * 32768.0)) +#define FREQ_NOISE_END ((s32)(1.200000 * 32768.0)) + +static unsigned int dsp_debug; +module_param(dsp_debug, int, 0644); +MODULE_PARM_DESC(dsp_debug, "enable audio dsp debug messages"); + +#define dprintk(level, fmt, arg...) do { \ + if (dsp_debug >= level) \ + printk(KERN_DEBUG pr_fmt("%s: dsp:" fmt), \ + __func__, ##arg); \ +} while (0) + +static s32 int_cos(u32 x) +{ + u32 t2, t4, t6, t8; + s32 ret; + u16 period = x / INT_PI; + + if (period % 2) + return -int_cos(x - INT_PI); + x = x % INT_PI; + if (x > INT_PI / 2) + return -int_cos(INT_PI / 2 - (x % (INT_PI / 2))); + /* + * Now x is between 0 and INT_PI/2. + * To calculate cos(x) we use it's Taylor polinom. + */ + t2 = x * x / 32768 / 2; + t4 = t2 * x / 32768 * x / 32768 / 3 / 4; + t6 = t4 * x / 32768 * x / 32768 / 5 / 6; + t8 = t6 * x / 32768 * x / 32768 / 7 / 8; + ret = 32768 - t2 + t4 - t6 + t8; + return ret; +} + +static u32 int_goertzel(s16 x[], u32 N, u32 freq) +{ + /* + * We use the Goertzel algorithm to determine the power of the + * given frequency in the signal + */ + s32 s_prev = 0; + s32 s_prev2 = 0; + s32 coeff = 2 * int_cos(freq); + u32 i; + + u64 tmp; + u32 divisor; + + for (i = 0; i < N; i++) { + s32 s = x[i] + ((s64)coeff * s_prev / 32768) - s_prev2; + + s_prev2 = s_prev; + s_prev = s; + } + + tmp = (s64)s_prev2 * s_prev2 + (s64)s_prev * s_prev - + (s64)coeff * s_prev2 * s_prev / 32768; + + /* + * XXX: N must be low enough so that N*N fits in s32. + * Else we need two divisions. + */ + divisor = N * N; + do_div(tmp, divisor); + + return (u32)tmp; +} + +static u32 freq_magnitude(s16 x[], u32 N, u32 freq) +{ + u32 sum = int_goertzel(x, N, freq); + + return (u32)int_sqrt(sum); +} + +static u32 noise_magnitude(s16 x[], u32 N, u32 freq_start, u32 freq_end) +{ + int i; + u32 sum = 0; + u32 freq_step; + int samples = 5; + + if (N > 192) { + /* The last 192 samples are enough for noise detection */ + x += (N - 192); + N = 192; + } + + freq_step = (freq_end - freq_start) / (samples - 1); + + for (i = 0; i < samples; i++) { + sum += int_goertzel(x, N, freq_start); + freq_start += freq_step; + } + + return (u32)int_sqrt(sum / samples); +} + +static s32 detect_a2_a2m_eiaj(struct cx88_core *core, s16 x[], u32 N) +{ + s32 carrier, stereo, dual, noise; + s32 carrier_freq, stereo_freq, dual_freq; + s32 ret; + + switch (core->tvaudio) { + case WW_BG: + case WW_DK: + carrier_freq = FREQ_A2_CARRIER; + stereo_freq = FREQ_A2_STEREO; + dual_freq = FREQ_A2_DUAL; + break; + case WW_M: + carrier_freq = FREQ_A2M_CARRIER; + stereo_freq = FREQ_A2M_STEREO; + dual_freq = FREQ_A2M_DUAL; + break; + case WW_EIAJ: + carrier_freq = FREQ_EIAJ_CARRIER; + stereo_freq = FREQ_EIAJ_STEREO; + dual_freq = FREQ_EIAJ_DUAL; + break; + default: + pr_warn("unsupported audio mode %d for %s\n", + core->tvaudio, __func__); + return UNSET; + } + + carrier = freq_magnitude(x, N, carrier_freq); + stereo = freq_magnitude(x, N, stereo_freq); + dual = freq_magnitude(x, N, dual_freq); + noise = noise_magnitude(x, N, FREQ_NOISE_START, FREQ_NOISE_END); + + dprintk(1, + "detect a2/a2m/eiaj: carrier=%d, stereo=%d, dual=%d, noise=%d\n", + carrier, stereo, dual, noise); + + if (stereo > dual) + ret = V4L2_TUNER_SUB_STEREO; + else + ret = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + + if (core->tvaudio == WW_EIAJ) { + /* EIAJ checks may need adjustments */ + if ((carrier > max(stereo, dual) * 2) && + (carrier < max(stereo, dual) * 6) && + (carrier > 20 && carrier < 200) && + (max(stereo, dual) > min(stereo, dual))) { + /* + * For EIAJ the carrier is always present, + * so we probably don't need noise detection + */ + return ret; + } + } else { + if ((carrier > max(stereo, dual) * 2) && + (carrier < max(stereo, dual) * 8) && + (carrier > 20 && carrier < 200) && + (noise < 10) && + (max(stereo, dual) > min(stereo, dual) * 2)) { + return ret; + } + } + return V4L2_TUNER_SUB_MONO; +} + +static s32 detect_btsc(struct cx88_core *core, s16 x[], u32 N) +{ + s32 sap_ref = freq_magnitude(x, N, FREQ_BTSC_SAP_REF); + s32 sap = freq_magnitude(x, N, FREQ_BTSC_SAP); + s32 dual_ref = freq_magnitude(x, N, FREQ_BTSC_DUAL_REF); + s32 dual = freq_magnitude(x, N, FREQ_BTSC_DUAL); + + dprintk(1, "detect btsc: dual_ref=%d, dual=%d, sap_ref=%d, sap=%d\n", + dual_ref, dual, sap_ref, sap); + /* FIXME: Currently not supported */ + return UNSET; +} + +static s16 *read_rds_samples(struct cx88_core *core, u32 *N) +{ + const struct sram_channel *srch = &cx88_sram_channels[SRAM_CH27]; + s16 *samples; + + unsigned int i; + unsigned int bpl = srch->fifo_size / AUD_RDS_LINES; + unsigned int spl = bpl / 4; + unsigned int sample_count = spl * (AUD_RDS_LINES - 1); + + u32 current_address = cx_read(srch->ptr1_reg); + u32 offset = (current_address - srch->fifo_start + bpl); + + dprintk(1, + "read RDS samples: current_address=%08x (offset=%08x), sample_count=%d, aud_intstat=%08x\n", + current_address, + current_address - srch->fifo_start, sample_count, + cx_read(MO_AUD_INTSTAT)); + samples = kmalloc_objs(*samples, sample_count); + if (!samples) + return NULL; + + *N = sample_count; + + for (i = 0; i < sample_count; i++) { + offset = offset % (AUD_RDS_LINES * bpl); + samples[i] = cx_read(srch->fifo_start + offset); + offset += 4; + } + + dprintk(2, "RDS samples dump: %*ph\n", sample_count, samples); + + return samples; +} + +s32 cx88_dsp_detect_stereo_sap(struct cx88_core *core) +{ + s16 *samples; + u32 N = 0; + s32 ret = UNSET; + + /* If audio RDS fifo is disabled, we can't read the samples */ + if (!(cx_read(MO_AUD_DMACNTRL) & 0x04)) + return ret; + if (!(cx_read(AUD_CTL) & EN_FMRADIO_EN_RDS)) + return ret; + + /* Wait at least 500 ms after an audio standard change */ + if (time_before(jiffies, core->last_change + msecs_to_jiffies(500))) + return ret; + + samples = read_rds_samples(core, &N); + + if (!samples) + return ret; + + switch (core->tvaudio) { + case WW_BG: + case WW_DK: + case WW_EIAJ: + case WW_M: + ret = detect_a2_a2m_eiaj(core, samples, N); + break; + case WW_BTSC: + ret = detect_btsc(core, samples, N); + break; + case WW_NONE: + case WW_I: + case WW_L: + case WW_I2SPT: + case WW_FM: + case WW_I2SADC: + break; + } + + kfree(samples); + + if (ret != UNSET) + dprintk(1, "stereo/sap detection result:%s%s%s\n", + (ret & V4L2_TUNER_SUB_MONO) ? " mono" : "", + (ret & V4L2_TUNER_SUB_STEREO) ? " stereo" : "", + (ret & V4L2_TUNER_SUB_LANG2) ? " dual" : ""); + + return ret; +} +EXPORT_SYMBOL(cx88_dsp_detect_stereo_sap); + diff --git a/drivers/media/pci/cx88/cx88-dvb.c b/drivers/media/pci/cx88/cx88-dvb.c new file mode 100644 index 000000000..c9cfceed2 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-dvb.c @@ -0,0 +1,1842 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * device driver for Conexant 2388x based TV cards + * MPEG Transport Stream (DVB) routines + * + * (c) 2004, 2005 Chris Pascoe + * (c) 2004 Gerd Knorr [SuSE Labs] + */ + +#include "cx88.h" +#include "dvb-pll.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "mt352.h" +#include "mt352_priv.h" +#include "cx88-vp3054-i2c.h" +#include "zl10353.h" +#include "cx22702.h" +#include "or51132.h" +#include "lgdt330x.h" +#include "s5h1409.h" +#include "xc4000.h" +#include "xc5000.h" +#include "nxt200x.h" +#include "cx24123.h" +#include "isl6421.h" +#include "tuner-simple.h" +#include "tda9887.h" +#include "s5h1411.h" +#include "stv0299.h" +#include "z0194a.h" +#include "stv0288.h" +#include "stb6000.h" +#include "cx24116.h" +#include "stv0900.h" +#include "stb6100.h" +#include "stb6100_proc.h" +#include "mb86a16.h" +#include "ts2020.h" +#include "ds3000.h" + +MODULE_DESCRIPTION("driver for cx2388x based DVB cards"); +MODULE_AUTHOR("Chris Pascoe "); +MODULE_AUTHOR("Gerd Knorr [SuSE Labs]"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(CX88_VERSION); + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "enable debug messages [dvb]"); + +static unsigned int dvb_buf_tscnt = 32; +module_param(dvb_buf_tscnt, int, 0644); +MODULE_PARM_DESC(dvb_buf_tscnt, "DVB Buffer TS count [dvb]"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +#define dprintk(level, fmt, arg...) do { \ + if (debug >= level) \ + printk(KERN_DEBUG pr_fmt("%s: dvb:" fmt), \ + __func__, ##arg); \ +} while (0) + +/* ------------------------------------------------------------------ */ + +static int queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct cx8802_dev *dev = q->drv_priv; + + *num_planes = 1; + dev->ts_packet_size = 188 * 4; + dev->ts_packet_count = dvb_buf_tscnt; + sizes[0] = dev->ts_packet_size * dev->ts_packet_count; + *num_buffers = dvb_buf_tscnt; + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx8802_dev *dev = vb->vb2_queue->drv_priv; + struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb); + + return cx8802_buf_prepare(vb->vb2_queue, dev, buf); +} + +static void buffer_finish(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx8802_dev *dev = vb->vb2_queue->drv_priv; + struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb); + struct cx88_riscmem *risc = &buf->risc; + + if (risc->cpu) + dma_free_coherent(&dev->pci->dev, risc->size, risc->cpu, + risc->dma); + memset(risc, 0, sizeof(*risc)); +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx8802_dev *dev = vb->vb2_queue->drv_priv; + struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb); + + cx8802_buf_queue(dev, buf); +} + +static int start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct cx8802_dev *dev = q->drv_priv; + struct cx88_dmaqueue *dmaq = &dev->mpegq; + struct cx88_buffer *buf; + + buf = list_entry(dmaq->active.next, struct cx88_buffer, list); + cx8802_start_dma(dev, dmaq, buf); + return 0; +} + +static void stop_streaming(struct vb2_queue *q) +{ + struct cx8802_dev *dev = q->drv_priv; + struct cx88_dmaqueue *dmaq = &dev->mpegq; + unsigned long flags; + + cx8802_cancel_buffers(dev); + + spin_lock_irqsave(&dev->slock, flags); + while (!list_empty(&dmaq->active)) { + struct cx88_buffer *buf = list_entry(dmaq->active.next, + struct cx88_buffer, list); + + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&dev->slock, flags); +} + +static const struct vb2_ops dvb_qops = { + .queue_setup = queue_setup, + .buf_prepare = buffer_prepare, + .buf_finish = buffer_finish, + .buf_queue = buffer_queue, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, +}; + +/* ------------------------------------------------------------------ */ + +static int cx88_dvb_bus_ctrl(struct dvb_frontend *fe, int acquire) +{ + struct cx8802_dev *dev = fe->dvb->priv; + struct cx8802_driver *drv = NULL; + int ret = 0; + int fe_id; + + fe_id = vb2_dvb_find_frontend(&dev->frontends, fe); + if (!fe_id) { + pr_err("%s() No frontend found\n", __func__); + return -EINVAL; + } + + mutex_lock(&dev->core->lock); + drv = cx8802_get_driver(dev, CX88_MPEG_DVB); + if (drv) { + if (acquire) { + dev->frontends.active_fe_id = fe_id; + ret = drv->request_acquire(drv); + } else { + ret = drv->request_release(drv); + dev->frontends.active_fe_id = 0; + } + } + mutex_unlock(&dev->core->lock); + + return ret; +} + +static void cx88_dvb_gate_ctrl(struct cx88_core *core, int open) +{ + struct vb2_dvb_frontends *f; + struct vb2_dvb_frontend *fe; + + if (!core->dvbdev) + return; + + f = &core->dvbdev->frontends; + + if (!f) + return; + + if (f->gate <= 1) /* undefined or fe0 */ + fe = vb2_dvb_get_frontend(f, 1); + else + fe = vb2_dvb_get_frontend(f, f->gate); + + if (fe && fe->dvb.frontend && fe->dvb.frontend->ops.i2c_gate_ctrl) + fe->dvb.frontend->ops.i2c_gate_ctrl(fe->dvb.frontend, open); +} + +/* ------------------------------------------------------------------ */ + +static int dvico_fusionhdtv_demod_init(struct dvb_frontend *fe) +{ + static const u8 clock_config[] = { CLOCK_CTL, 0x38, 0x39 }; + static const u8 reset[] = { RESET, 0x80 }; + static const u8 adc_ctl_1_cfg[] = { ADC_CTL_1, 0x40 }; + static const u8 agc_cfg[] = { AGC_TARGET, 0x24, 0x20 }; + static const u8 gpp_ctl_cfg[] = { GPP_CTL, 0x33 }; + static const u8 capt_range_cfg[] = { CAPT_RANGE, 0x32 }; + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(200); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + mt352_write(fe, gpp_ctl_cfg, sizeof(gpp_ctl_cfg)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + return 0; +} + +static int dvico_dual_demod_init(struct dvb_frontend *fe) +{ + static const u8 clock_config[] = { CLOCK_CTL, 0x38, 0x38 }; + static const u8 reset[] = { RESET, 0x80 }; + static const u8 adc_ctl_1_cfg[] = { ADC_CTL_1, 0x40 }; + static const u8 agc_cfg[] = { AGC_TARGET, 0x28, 0x20 }; + static const u8 gpp_ctl_cfg[] = { GPP_CTL, 0x33 }; + static const u8 capt_range_cfg[] = { CAPT_RANGE, 0x32 }; + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(200); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + mt352_write(fe, gpp_ctl_cfg, sizeof(gpp_ctl_cfg)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + + return 0; +} + +static int dntv_live_dvbt_demod_init(struct dvb_frontend *fe) +{ + static const u8 clock_config[] = { 0x89, 0x38, 0x39 }; + static const u8 reset[] = { 0x50, 0x80 }; + static const u8 adc_ctl_1_cfg[] = { 0x8E, 0x40 }; + static const u8 agc_cfg[] = { 0x67, 0x10, 0x23, 0x00, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0x40, 0x40 }; + static const u8 dntv_extra[] = { 0xB5, 0x7A }; + static const u8 capt_range_cfg[] = { 0x75, 0x32 }; + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(2000); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + udelay(2000); + mt352_write(fe, dntv_extra, sizeof(dntv_extra)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + + return 0; +} + +static const struct mt352_config dvico_fusionhdtv = { + .demod_address = 0x0f, + .demod_init = dvico_fusionhdtv_demod_init, +}; + +static const struct mt352_config dntv_live_dvbt_config = { + .demod_address = 0x0f, + .demod_init = dntv_live_dvbt_demod_init, +}; + +static const struct mt352_config dvico_fusionhdtv_dual = { + .demod_address = 0x0f, + .demod_init = dvico_dual_demod_init, +}; + +static const struct zl10353_config cx88_terratec_cinergy_ht_pci_mkii_config = { + .demod_address = (0x1e >> 1), + .no_tuner = 1, + .if2 = 45600, +}; + +static const struct mb86a16_config twinhan_vp1027 = { + .demod_address = 0x08, +}; + +#if IS_ENABLED(CONFIG_VIDEO_CX88_VP3054) +static int dntv_live_dvbt_pro_demod_init(struct dvb_frontend *fe) +{ + static const u8 clock_config[] = { 0x89, 0x38, 0x38 }; + static const u8 reset[] = { 0x50, 0x80 }; + static const u8 adc_ctl_1_cfg[] = { 0x8E, 0x40 }; + static const u8 agc_cfg[] = { 0x67, 0x10, 0x20, 0x00, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0x40, 0x40 }; + static const u8 dntv_extra[] = { 0xB5, 0x7A }; + static const u8 capt_range_cfg[] = { 0x75, 0x32 }; + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(2000); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + udelay(2000); + mt352_write(fe, dntv_extra, sizeof(dntv_extra)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + + return 0; +} + +static const struct mt352_config dntv_live_dvbt_pro_config = { + .demod_address = 0x0f, + .no_tuner = 1, + .demod_init = dntv_live_dvbt_pro_demod_init, +}; +#endif + +static const struct zl10353_config dvico_fusionhdtv_hybrid = { + .demod_address = 0x0f, + .no_tuner = 1, +}; + +static const struct zl10353_config dvico_fusionhdtv_xc3028 = { + .demod_address = 0x0f, + .if2 = 45600, + .no_tuner = 1, +}; + +static const struct mt352_config dvico_fusionhdtv_mt352_xc3028 = { + .demod_address = 0x0f, + .if2 = 4560, + .no_tuner = 1, + .demod_init = dvico_fusionhdtv_demod_init, +}; + +static const struct zl10353_config dvico_fusionhdtv_plus_v1_1 = { + .demod_address = 0x0f, +}; + +static const struct cx22702_config connexant_refboard_config = { + .demod_address = 0x43, + .output_mode = CX22702_SERIAL_OUTPUT, +}; + +static const struct cx22702_config hauppauge_hvr_config = { + .demod_address = 0x63, + .output_mode = CX22702_SERIAL_OUTPUT, +}; + +static int or51132_set_ts_param(struct dvb_frontend *fe, int is_punctured) +{ + struct cx8802_dev *dev = fe->dvb->priv; + + dev->ts_gen_cntrl = is_punctured ? 0x04 : 0x00; + return 0; +} + +static const struct or51132_config pchdtv_hd3000 = { + .demod_address = 0x15, + .set_ts_params = or51132_set_ts_param, +}; + +static int lgdt330x_pll_rf_set(struct dvb_frontend *fe, int index) +{ + struct cx8802_dev *dev = fe->dvb->priv; + struct cx88_core *core = dev->core; + + dprintk(1, "%s: index = %d\n", __func__, index); + if (index == 0) + cx_clear(MO_GP0_IO, 8); + else + cx_set(MO_GP0_IO, 8); + return 0; +} + +static int lgdt330x_set_ts_param(struct dvb_frontend *fe, int is_punctured) +{ + struct cx8802_dev *dev = fe->dvb->priv; + + if (is_punctured) + dev->ts_gen_cntrl |= 0x04; + else + dev->ts_gen_cntrl &= ~0x04; + return 0; +} + +static struct lgdt330x_config fusionhdtv_3_gold = { + .demod_chip = LGDT3302, + .serial_mpeg = 0x04, /* TPSERIAL for 3302 in TOP_CONTROL */ + .set_ts_params = lgdt330x_set_ts_param, +}; + +static const struct lgdt330x_config fusionhdtv_5_gold = { + .demod_chip = LGDT3303, + .serial_mpeg = 0x40, /* TPSERIAL for 3303 in TOP_CONTROL */ + .set_ts_params = lgdt330x_set_ts_param, +}; + +static const struct lgdt330x_config pchdtv_hd5500 = { + .demod_chip = LGDT3303, + .serial_mpeg = 0x40, /* TPSERIAL for 3303 in TOP_CONTROL */ + .set_ts_params = lgdt330x_set_ts_param, +}; + +static int nxt200x_set_ts_param(struct dvb_frontend *fe, int is_punctured) +{ + struct cx8802_dev *dev = fe->dvb->priv; + + dev->ts_gen_cntrl = is_punctured ? 0x04 : 0x00; + return 0; +} + +static const struct nxt200x_config ati_hdtvwonder = { + .demod_address = 0x0a, + .set_ts_params = nxt200x_set_ts_param, +}; + +static int cx24123_set_ts_param(struct dvb_frontend *fe, + int is_punctured) +{ + struct cx8802_dev *dev = fe->dvb->priv; + + dev->ts_gen_cntrl = 0x02; + return 0; +} + +static int kworld_dvbs_100_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage voltage) +{ + struct cx8802_dev *dev = fe->dvb->priv; + struct cx88_core *core = dev->core; + + if (voltage == SEC_VOLTAGE_OFF) + cx_write(MO_GP0_IO, 0x000006fb); + else + cx_write(MO_GP0_IO, 0x000006f9); + + if (core->prev_set_voltage) + return core->prev_set_voltage(fe, voltage); + return 0; +} + +static int geniatech_dvbs_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage voltage) +{ + struct cx8802_dev *dev = fe->dvb->priv; + struct cx88_core *core = dev->core; + + if (voltage == SEC_VOLTAGE_OFF) { + dprintk(1, "LNB Voltage OFF\n"); + cx_write(MO_GP0_IO, 0x0000efff); + } + + if (core->prev_set_voltage) + return core->prev_set_voltage(fe, voltage); + return 0; +} + +static int tevii_dvbs_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage voltage) +{ + struct cx8802_dev *dev = fe->dvb->priv; + struct cx88_core *core = dev->core; + + cx_set(MO_GP0_IO, 0x6040); + switch (voltage) { + case SEC_VOLTAGE_13: + cx_clear(MO_GP0_IO, 0x20); + break; + case SEC_VOLTAGE_18: + cx_set(MO_GP0_IO, 0x20); + break; + case SEC_VOLTAGE_OFF: + cx_clear(MO_GP0_IO, 0x20); + break; + } + + if (core->prev_set_voltage) + return core->prev_set_voltage(fe, voltage); + return 0; +} + +static int vp1027_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage voltage) +{ + struct cx8802_dev *dev = fe->dvb->priv; + struct cx88_core *core = dev->core; + + switch (voltage) { + case SEC_VOLTAGE_13: + dprintk(1, "LNB SEC Voltage=13\n"); + cx_write(MO_GP0_IO, 0x00001220); + break; + case SEC_VOLTAGE_18: + dprintk(1, "LNB SEC Voltage=18\n"); + cx_write(MO_GP0_IO, 0x00001222); + break; + case SEC_VOLTAGE_OFF: + dprintk(1, "LNB Voltage OFF\n"); + cx_write(MO_GP0_IO, 0x00001230); + break; + } + + if (core->prev_set_voltage) + return core->prev_set_voltage(fe, voltage); + return 0; +} + +static const struct cx24123_config geniatech_dvbs_config = { + .demod_address = 0x55, + .set_ts_params = cx24123_set_ts_param, +}; + +static const struct cx24123_config hauppauge_novas_config = { + .demod_address = 0x55, + .set_ts_params = cx24123_set_ts_param, +}; + +static const struct cx24123_config kworld_dvbs_100_config = { + .demod_address = 0x15, + .set_ts_params = cx24123_set_ts_param, + .lnb_polarity = 1, +}; + +static const struct s5h1409_config pinnacle_pctv_hd_800i_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_PARALLEL_OUTPUT, + .gpio = S5H1409_GPIO_ON, + .qam_if = 44000, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_NONCONTINUOUS_NONINVERTING_CLOCK, +}; + +static const struct s5h1409_config dvico_hdtv5_pci_nano_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_OFF, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, +}; + +static const struct s5h1409_config kworld_atsc_120_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_OFF, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, +}; + +static const struct xc5000_config pinnacle_pctv_hd_800i_tuner_config = { + .i2c_address = 0x64, + .if_khz = 5380, +}; + +static const struct zl10353_config cx88_pinnacle_hybrid_pctv = { + .demod_address = (0x1e >> 1), + .no_tuner = 1, + .if2 = 45600, +}; + +static const struct zl10353_config cx88_geniatech_x8000_mt = { + .demod_address = (0x1e >> 1), + .no_tuner = 1, + .disable_i2c_gate_ctrl = 1, +}; + +static const struct s5h1411_config dvico_fusionhdtv7_config = { + .output_mode = S5H1411_SERIAL_OUTPUT, + .gpio = S5H1411_GPIO_ON, + .mpeg_timing = S5H1411_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, + .qam_if = S5H1411_IF_44000, + .vsb_if = S5H1411_IF_44000, + .inversion = S5H1411_INVERSION_OFF, + .status_mode = S5H1411_DEMODLOCKING +}; + +static const struct xc5000_config dvico_fusionhdtv7_tuner_config = { + .i2c_address = 0xc2 >> 1, + .if_khz = 5380, +}; + +static int attach_xc3028(u8 addr, struct cx8802_dev *dev) +{ + struct dvb_frontend *fe; + struct vb2_dvb_frontend *fe0 = NULL; + struct xc2028_ctrl ctl; + struct xc2028_config cfg = { + .i2c_adap = &dev->core->i2c_adap, + .i2c_addr = addr, + .ctrl = &ctl, + }; + + /* Get the first frontend */ + fe0 = vb2_dvb_get_frontend(&dev->frontends, 1); + if (!fe0) + return -EINVAL; + + if (!fe0->dvb.frontend) { + pr_err("dvb frontend not attached. Can't attach xc3028\n"); + return -EINVAL; + } + + /* + * Some xc3028 devices may be hidden by an I2C gate. This is known + * to happen with some s5h1409-based devices. + * Now that I2C gate is open, sets up xc3028 configuration + */ + cx88_setup_xc3028(dev->core, &ctl); + + fe = dvb_attach(xc2028_attach, fe0->dvb.frontend, &cfg); + if (!fe) { + pr_err("xc3028 attach failed\n"); + dvb_frontend_detach(fe0->dvb.frontend); + dvb_unregister_frontend(fe0->dvb.frontend); + fe0->dvb.frontend = NULL; + return -EINVAL; + } + + pr_info("xc3028 attached\n"); + + return 0; +} + +static int attach_xc4000(struct cx8802_dev *dev, struct xc4000_config *cfg) +{ + struct dvb_frontend *fe; + struct vb2_dvb_frontend *fe0 = NULL; + + /* Get the first frontend */ + fe0 = vb2_dvb_get_frontend(&dev->frontends, 1); + if (!fe0) + return -EINVAL; + + if (!fe0->dvb.frontend) { + pr_err("dvb frontend not attached. Can't attach xc4000\n"); + return -EINVAL; + } + + fe = dvb_attach(xc4000_attach, fe0->dvb.frontend, &dev->core->i2c_adap, + cfg); + if (!fe) { + pr_err("xc4000 attach failed\n"); + dvb_frontend_detach(fe0->dvb.frontend); + dvb_unregister_frontend(fe0->dvb.frontend); + fe0->dvb.frontend = NULL; + return -EINVAL; + } + + pr_info("xc4000 attached\n"); + + return 0; +} + +static int cx24116_set_ts_param(struct dvb_frontend *fe, + int is_punctured) +{ + struct cx8802_dev *dev = fe->dvb->priv; + + dev->ts_gen_cntrl = 0x2; + + return 0; +} + +static int stv0900_set_ts_param(struct dvb_frontend *fe, + int is_punctured) +{ + struct cx8802_dev *dev = fe->dvb->priv; + + dev->ts_gen_cntrl = 0; + + return 0; +} + +static int cx24116_reset_device(struct dvb_frontend *fe) +{ + struct cx8802_dev *dev = fe->dvb->priv; + struct cx88_core *core = dev->core; + + /* Reset the part */ + /* Put the cx24116 into reset */ + cx_write(MO_SRST_IO, 0); + usleep_range(10000, 20000); + /* Take the cx24116 out of reset */ + cx_write(MO_SRST_IO, 1); + usleep_range(10000, 20000); + + return 0; +} + +static const struct cx24116_config hauppauge_hvr4000_config = { + .demod_address = 0x05, + .set_ts_params = cx24116_set_ts_param, + .reset_device = cx24116_reset_device, +}; + +static const struct cx24116_config tevii_s460_config = { + .demod_address = 0x55, + .set_ts_params = cx24116_set_ts_param, + .reset_device = cx24116_reset_device, +}; + +static int ds3000_set_ts_param(struct dvb_frontend *fe, + int is_punctured) +{ + struct cx8802_dev *dev = fe->dvb->priv; + + dev->ts_gen_cntrl = 4; + + return 0; +} + +static struct ds3000_config tevii_ds3000_config = { + .demod_address = 0x68, + .set_ts_params = ds3000_set_ts_param, +}; + +static struct ts2020_config tevii_ts2020_config = { + .tuner_address = 0x60, + .clk_out_div = 1, +}; + +static const struct stv0900_config prof_7301_stv0900_config = { + .demod_address = 0x6a, +/* demod_mode = 0,*/ + .xtal = 27000000, + .clkmode = 3,/* 0-CLKI, 2-XTALI, else AUTO */ + .diseqc_mode = 2,/* 2/3 PWM */ + .tun1_maddress = 0,/* 0x60 */ + .tun1_adc = 0,/* 2 Vpp */ + .path1_mode = 3, + .set_ts_params = stv0900_set_ts_param, +}; + +static const struct stb6100_config prof_7301_stb6100_config = { + .tuner_address = 0x60, + .refclock = 27000000, +}; + +static const struct stv0299_config tevii_tuner_sharp_config = { + .demod_address = 0x68, + .inittab = sharp_z0194a_inittab, + .mclk = 88000000UL, + .invert = 1, + .skip_reinit = 0, + .lock_output = 1, + .volt13_op0_op1 = STV0299_VOLT13_OP1, + .min_delay_ms = 100, + .set_symbol_rate = sharp_z0194a_set_symbol_rate, + .set_ts_params = cx24116_set_ts_param, +}; + +static const struct stv0288_config tevii_tuner_earda_config = { + .demod_address = 0x68, + .min_delay_ms = 100, + .set_ts_params = cx24116_set_ts_param, +}; + +static int cx8802_alloc_frontends(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + struct vb2_dvb_frontend *fe = NULL; + int i; + + mutex_init(&dev->frontends.lock); + INIT_LIST_HEAD(&dev->frontends.felist); + + if (!core->board.num_frontends) + return -ENODEV; + + pr_info("%s: allocating %d frontend(s)\n", __func__, + core->board.num_frontends); + for (i = 1; i <= core->board.num_frontends; i++) { + fe = vb2_dvb_alloc_frontend(&dev->frontends, i); + if (!fe) { + pr_err("%s() failed to alloc\n", __func__); + vb2_dvb_dealloc_frontends(&dev->frontends); + return -ENOMEM; + } + } + return 0; +} + +static const u8 samsung_smt_7020_inittab[] = { + 0x01, 0x15, + 0x02, 0x00, + 0x03, 0x00, + 0x04, 0x7D, + 0x05, 0x0F, + 0x06, 0x02, + 0x07, 0x00, + 0x08, 0x60, + + 0x0A, 0xC2, + 0x0B, 0x00, + 0x0C, 0x01, + 0x0D, 0x81, + 0x0E, 0x44, + 0x0F, 0x09, + 0x10, 0x3C, + 0x11, 0x84, + 0x12, 0xDA, + 0x13, 0x99, + 0x14, 0x8D, + 0x15, 0xCE, + 0x16, 0xE8, + 0x17, 0x43, + 0x18, 0x1C, + 0x19, 0x1B, + 0x1A, 0x1D, + + 0x1C, 0x12, + 0x1D, 0x00, + 0x1E, 0x00, + 0x1F, 0x00, + 0x20, 0x00, + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + + 0x28, 0x02, + 0x29, 0x28, + 0x2A, 0x14, + 0x2B, 0x0F, + 0x2C, 0x09, + 0x2D, 0x05, + + 0x31, 0x1F, + 0x32, 0x19, + 0x33, 0xFC, + 0x34, 0x13, + 0xff, 0xff, +}; + +static int samsung_smt_7020_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct cx8802_dev *dev = fe->dvb->priv; + u8 buf[4]; + u32 div; + struct i2c_msg msg = { + .addr = 0x61, + .flags = 0, + .buf = buf, + .len = sizeof(buf) }; + + div = c->frequency / 125; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0x84; /* 0xC4 */ + buf[3] = 0x00; + + if (c->frequency < 1500000) + buf[3] |= 0x10; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + if (i2c_transfer(&dev->core->i2c_adap, &msg, 1) != 1) + return -EIO; + + return 0; +} + +static int samsung_smt_7020_set_tone(struct dvb_frontend *fe, + enum fe_sec_tone_mode tone) +{ + struct cx8802_dev *dev = fe->dvb->priv; + struct cx88_core *core = dev->core; + + cx_set(MO_GP0_IO, 0x0800); + + switch (tone) { + case SEC_TONE_ON: + cx_set(MO_GP0_IO, 0x08); + break; + case SEC_TONE_OFF: + cx_clear(MO_GP0_IO, 0x08); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int samsung_smt_7020_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage voltage) +{ + struct cx8802_dev *dev = fe->dvb->priv; + struct cx88_core *core = dev->core; + + u8 data; + struct i2c_msg msg = { + .addr = 8, + .flags = 0, + .buf = &data, + .len = sizeof(data) }; + + cx_set(MO_GP0_IO, 0x8000); + + switch (voltage) { + case SEC_VOLTAGE_OFF: + break; + case SEC_VOLTAGE_13: + data = ISL6421_EN1 | ISL6421_LLC1; + cx_clear(MO_GP0_IO, 0x80); + break; + case SEC_VOLTAGE_18: + data = ISL6421_EN1 | ISL6421_LLC1 | ISL6421_VSEL1; + cx_clear(MO_GP0_IO, 0x80); + break; + default: + return -EINVAL; + } + + return (i2c_transfer(&dev->core->i2c_adap, &msg, 1) == 1) ? 0 : -EIO; +} + +static int samsung_smt_7020_stv0299_set_symbol_rate(struct dvb_frontend *fe, + u32 srate, u32 ratio) +{ + u8 aclk = 0; + u8 bclk = 0; + + if (srate < 1500000) { + aclk = 0xb7; + bclk = 0x47; + } else if (srate < 3000000) { + aclk = 0xb7; + bclk = 0x4b; + } else if (srate < 7000000) { + aclk = 0xb7; + bclk = 0x4f; + } else if (srate < 14000000) { + aclk = 0xb7; + bclk = 0x53; + } else if (srate < 30000000) { + aclk = 0xb6; + bclk = 0x53; + } else if (srate < 45000000) { + aclk = 0xb4; + bclk = 0x51; + } + + stv0299_writereg(fe, 0x13, aclk); + stv0299_writereg(fe, 0x14, bclk); + stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg(fe, 0x21, ratio & 0xf0); + + return 0; +} + +static const struct stv0299_config samsung_stv0299_config = { + .demod_address = 0x68, + .inittab = samsung_smt_7020_inittab, + .mclk = 88000000UL, + .invert = 0, + .skip_reinit = 0, + .lock_output = STV0299_LOCKOUTPUT_LK, + .volt13_op0_op1 = STV0299_VOLT13_OP1, + .min_delay_ms = 100, + .set_symbol_rate = samsung_smt_7020_stv0299_set_symbol_rate, +}; + +static int dvb_register(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + struct vb2_dvb_frontend *fe0, *fe1 = NULL; + int mfe_shared = 0; /* bus not shared by default */ + int res = -EINVAL; + + if (core->i2c_rc != 0) { + pr_err("no i2c-bus available, cannot attach dvb drivers\n"); + goto frontend_detach; + } + + /* Get the first frontend */ + fe0 = vb2_dvb_get_frontend(&dev->frontends, 1); + if (!fe0) + goto frontend_detach; + + /* multi-frontend gate control is undefined or defaults to fe0 */ + dev->frontends.gate = 0; + + /* Sets the gate control callback to be used by i2c command calls */ + core->gate_ctrl = cx88_dvb_gate_ctrl; + + /* init frontend(s) */ + switch (core->boardnr) { + case CX88_BOARD_HAUPPAUGE_DVB_T1: + fe0->dvb.frontend = dvb_attach(cx22702_attach, + &connexant_refboard_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, + 0x61, &core->i2c_adap, + DVB_PLL_THOMSON_DTT759X)) + goto frontend_detach; + } + break; + case CX88_BOARD_TERRATEC_CINERGY_1400_DVB_T1: + case CX88_BOARD_CONEXANT_DVB_T1: + case CX88_BOARD_KWORLD_DVB_T_CX22702: + case CX88_BOARD_WINFAST_DTV1000: + fe0->dvb.frontend = dvb_attach(cx22702_attach, + &connexant_refboard_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, + 0x60, &core->i2c_adap, + DVB_PLL_THOMSON_DTT7579)) + goto frontend_detach; + } + break; + case CX88_BOARD_WINFAST_DTV2000H: + case CX88_BOARD_HAUPPAUGE_HVR1100: + case CX88_BOARD_HAUPPAUGE_HVR1100LP: + case CX88_BOARD_HAUPPAUGE_HVR1300: + fe0->dvb.frontend = dvb_attach(cx22702_attach, + &hauppauge_hvr_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_PHILIPS_FMD1216ME_MK3)) + goto frontend_detach; + } + break; + case CX88_BOARD_WINFAST_DTV2000H_J: + fe0->dvb.frontend = dvb_attach(cx22702_attach, + &hauppauge_hvr_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_PHILIPS_FMD1216MEX_MK3)) + goto frontend_detach; + } + break; + case CX88_BOARD_HAUPPAUGE_HVR3000: + /* MFE frontend 1 */ + mfe_shared = 1; + dev->frontends.gate = 2; + /* DVB-S init */ + fe0->dvb.frontend = dvb_attach(cx24123_attach, + &hauppauge_novas_config, + &dev->core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(isl6421_attach, + fe0->dvb.frontend, + &dev->core->i2c_adap, + 0x08, ISL6421_DCL, 0x00, false)) + goto frontend_detach; + } + /* MFE frontend 2 */ + fe1 = vb2_dvb_get_frontend(&dev->frontends, 2); + if (!fe1) + goto frontend_detach; + /* DVB-T init */ + fe1->dvb.frontend = dvb_attach(cx22702_attach, + &hauppauge_hvr_config, + &dev->core->i2c_adap); + if (fe1->dvb.frontend) { + fe1->dvb.frontend->id = 1; + if (!dvb_attach(simple_tuner_attach, + fe1->dvb.frontend, + &dev->core->i2c_adap, + 0x61, TUNER_PHILIPS_FMD1216ME_MK3)) + goto frontend_detach; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS: + fe0->dvb.frontend = dvb_attach(mt352_attach, + &dvico_fusionhdtv, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, + 0x60, NULL, DVB_PLL_THOMSON_DTT7579)) + goto frontend_detach; + break; + } + /* ZL10353 replaces MT352 on later cards */ + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &dvico_fusionhdtv_plus_v1_1, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, + 0x60, NULL, DVB_PLL_THOMSON_DTT7579)) + goto frontend_detach; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL: + /* + * The tin box says DEE1601, but it seems to be DTT7579 + * compatible, with a slightly different MT352 AGC gain. + */ + fe0->dvb.frontend = dvb_attach(mt352_attach, + &dvico_fusionhdtv_dual, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, + 0x61, NULL, DVB_PLL_THOMSON_DTT7579)) + goto frontend_detach; + break; + } + /* ZL10353 replaces MT352 on later cards */ + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &dvico_fusionhdtv_plus_v1_1, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, + 0x61, NULL, DVB_PLL_THOMSON_DTT7579)) + goto frontend_detach; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1: + fe0->dvb.frontend = dvb_attach(mt352_attach, + &dvico_fusionhdtv, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, + 0x61, NULL, DVB_PLL_LG_Z201)) + goto frontend_detach; + } + break; + case CX88_BOARD_KWORLD_DVB_T: + case CX88_BOARD_DNTV_LIVE_DVB_T: + case CX88_BOARD_ADSTECH_DVB_T_PCI: + fe0->dvb.frontend = dvb_attach(mt352_attach, + &dntv_live_dvbt_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, + 0x61, NULL, DVB_PLL_UNKNOWN_1)) + goto frontend_detach; + } + break; + case CX88_BOARD_DNTV_LIVE_DVB_T_PRO: +#if IS_ENABLED(CONFIG_VIDEO_CX88_VP3054) + /* MT352 is on a secondary I2C bus made from some GPIO lines */ + fe0->dvb.frontend = dvb_attach(mt352_attach, + &dntv_live_dvbt_pro_config, + &dev->vp3054->adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_PHILIPS_FMD1216ME_MK3)) + goto frontend_detach; + } +#else + pr_err("built without vp3054 support\n"); +#endif + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &dvico_fusionhdtv_hybrid, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_THOMSON_FE6600)) + goto frontend_detach; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &dvico_fusionhdtv_xc3028, + &core->i2c_adap); + if (!fe0->dvb.frontend) + fe0->dvb.frontend = dvb_attach(mt352_attach, + &dvico_fusionhdtv_mt352_xc3028, + &core->i2c_adap); + /* + * On this board, the demod provides the I2C bus pullup. + * We must not permit gate_ctrl to be performed, or + * the xc3028 cannot communicate on the bus. + */ + if (fe0->dvb.frontend) + fe0->dvb.frontend->ops.i2c_gate_ctrl = NULL; + if (attach_xc3028(0x61, dev) < 0) + goto frontend_detach; + break; + case CX88_BOARD_PCHDTV_HD3000: + fe0->dvb.frontend = dvb_attach(or51132_attach, &pchdtv_hd3000, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_THOMSON_DTT761X)) + goto frontend_detach; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q: + dev->ts_gen_cntrl = 0x08; + + /* Do a hardware reset of chip before using it. */ + cx_clear(MO_GP0_IO, 1); + msleep(100); + cx_set(MO_GP0_IO, 1); + msleep(200); + + /* Select RF connector callback */ + fusionhdtv_3_gold.pll_rf_set = lgdt330x_pll_rf_set; + fe0->dvb.frontend = dvb_attach(lgdt330x_attach, + &fusionhdtv_3_gold, + 0x0e, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_MICROTUNE_4042FI5)) + goto frontend_detach; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T: + dev->ts_gen_cntrl = 0x08; + + /* Do a hardware reset of chip before using it. */ + cx_clear(MO_GP0_IO, 1); + msleep(100); + cx_set(MO_GP0_IO, 9); + msleep(200); + fe0->dvb.frontend = dvb_attach(lgdt330x_attach, + &fusionhdtv_3_gold, + 0x0e, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_THOMSON_DTT761X)) + goto frontend_detach; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD: + dev->ts_gen_cntrl = 0x08; + + /* Do a hardware reset of chip before using it. */ + cx_clear(MO_GP0_IO, 1); + msleep(100); + cx_set(MO_GP0_IO, 1); + msleep(200); + fe0->dvb.frontend = dvb_attach(lgdt330x_attach, + &fusionhdtv_5_gold, + 0x0e, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_LG_TDVS_H06XF)) + goto frontend_detach; + if (!dvb_attach(tda9887_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x43)) + goto frontend_detach; + } + break; + case CX88_BOARD_PCHDTV_HD5500: + dev->ts_gen_cntrl = 0x08; + + /* Do a hardware reset of chip before using it. */ + cx_clear(MO_GP0_IO, 1); + msleep(100); + cx_set(MO_GP0_IO, 1); + msleep(200); + fe0->dvb.frontend = dvb_attach(lgdt330x_attach, + &pchdtv_hd5500, + 0x59, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_LG_TDVS_H06XF)) + goto frontend_detach; + if (!dvb_attach(tda9887_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x43)) + goto frontend_detach; + } + break; + case CX88_BOARD_ATI_HDTVWONDER: + fe0->dvb.frontend = dvb_attach(nxt200x_attach, + &ati_hdtvwonder, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_PHILIPS_TUV1236D)) + goto frontend_detach; + } + break; + case CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1: + case CX88_BOARD_HAUPPAUGE_NOVASE2_S1: + fe0->dvb.frontend = dvb_attach(cx24123_attach, + &hauppauge_novas_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + bool override_tone; + + if (core->model == 92001) + override_tone = true; + else + override_tone = false; + + if (!dvb_attach(isl6421_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x08, ISL6421_DCL, + 0x00, override_tone)) + goto frontend_detach; + } + break; + case CX88_BOARD_KWORLD_DVBS_100: + fe0->dvb.frontend = dvb_attach(cx24123_attach, + &kworld_dvbs_100_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + core->prev_set_voltage = fe0->dvb.frontend->ops.set_voltage; + fe0->dvb.frontend->ops.set_voltage = kworld_dvbs_100_set_voltage; + } + break; + case CX88_BOARD_GENIATECH_DVBS: + fe0->dvb.frontend = dvb_attach(cx24123_attach, + &geniatech_dvbs_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + core->prev_set_voltage = fe0->dvb.frontend->ops.set_voltage; + fe0->dvb.frontend->ops.set_voltage = geniatech_dvbs_set_voltage; + } + break; + case CX88_BOARD_PINNACLE_PCTV_HD_800i: + fe0->dvb.frontend = dvb_attach(s5h1409_attach, + &pinnacle_pctv_hd_800i_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(xc5000_attach, fe0->dvb.frontend, + &core->i2c_adap, + &pinnacle_pctv_hd_800i_tuner_config)) + goto frontend_detach; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO: + fe0->dvb.frontend = dvb_attach(s5h1409_attach, + &dvico_hdtv5_pci_nano_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &core->i2c_adap, + .i2c_addr = 0x61, + }; + static struct xc2028_ctrl ctl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + .scode_table = XC3028_FE_OREN538, + }; + + fe = dvb_attach(xc2028_attach, + fe0->dvb.frontend, &cfg); + if (fe && fe->ops.tuner_ops.set_config) + fe->ops.tuner_ops.set_config(fe, &ctl); + } + break; + case CX88_BOARD_NOTONLYTV_LV3H: + case CX88_BOARD_PINNACLE_HYBRID_PCTV: + case CX88_BOARD_WINFAST_DTV1800H: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &cx88_pinnacle_hybrid_pctv, + &core->i2c_adap); + if (fe0->dvb.frontend) { + fe0->dvb.frontend->ops.i2c_gate_ctrl = NULL; + if (attach_xc3028(0x61, dev) < 0) + goto frontend_detach; + } + break; + case CX88_BOARD_WINFAST_DTV1800H_XC4000: + case CX88_BOARD_WINFAST_DTV2000H_PLUS: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &cx88_pinnacle_hybrid_pctv, + &core->i2c_adap); + if (fe0->dvb.frontend) { + struct xc4000_config cfg = { + .i2c_address = 0x61, + .default_pm = 0, + .dvb_amplitude = 134, + .set_smoothedcvbs = 1, + .if_khz = 4560 + }; + fe0->dvb.frontend->ops.i2c_gate_ctrl = NULL; + if (attach_xc4000(dev, &cfg) < 0) + goto frontend_detach; + } + break; + case CX88_BOARD_GENIATECH_X8000_MT: + dev->ts_gen_cntrl = 0x00; + + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &cx88_geniatech_x8000_mt, + &core->i2c_adap); + if (attach_xc3028(0x61, dev) < 0) + goto frontend_detach; + break; + case CX88_BOARD_KWORLD_ATSC_120: + fe0->dvb.frontend = dvb_attach(s5h1409_attach, + &kworld_atsc_120_config, + &core->i2c_adap); + if (attach_xc3028(0x61, dev) < 0) + goto frontend_detach; + break; + case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD: + fe0->dvb.frontend = dvb_attach(s5h1411_attach, + &dvico_fusionhdtv7_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(xc5000_attach, fe0->dvb.frontend, + &core->i2c_adap, + &dvico_fusionhdtv7_tuner_config)) + goto frontend_detach; + } + break; + case CX88_BOARD_HAUPPAUGE_HVR4000: + /* MFE frontend 1 */ + mfe_shared = 1; + dev->frontends.gate = 2; + /* DVB-S/S2 Init */ + fe0->dvb.frontend = dvb_attach(cx24116_attach, + &hauppauge_hvr4000_config, + &dev->core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(isl6421_attach, + fe0->dvb.frontend, + &dev->core->i2c_adap, + 0x08, ISL6421_DCL, 0x00, false)) + goto frontend_detach; + } + /* MFE frontend 2 */ + fe1 = vb2_dvb_get_frontend(&dev->frontends, 2); + if (!fe1) + goto frontend_detach; + /* DVB-T Init */ + fe1->dvb.frontend = dvb_attach(cx22702_attach, + &hauppauge_hvr_config, + &dev->core->i2c_adap); + if (fe1->dvb.frontend) { + fe1->dvb.frontend->id = 1; + if (!dvb_attach(simple_tuner_attach, + fe1->dvb.frontend, + &dev->core->i2c_adap, + 0x61, TUNER_PHILIPS_FMD1216ME_MK3)) + goto frontend_detach; + } + break; + case CX88_BOARD_HAUPPAUGE_HVR4000LITE: + fe0->dvb.frontend = dvb_attach(cx24116_attach, + &hauppauge_hvr4000_config, + &dev->core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(isl6421_attach, + fe0->dvb.frontend, + &dev->core->i2c_adap, + 0x08, ISL6421_DCL, 0x00, false)) + goto frontend_detach; + } + break; + case CX88_BOARD_PROF_6200: + case CX88_BOARD_TBS_8910: + case CX88_BOARD_TEVII_S420: + fe0->dvb.frontend = dvb_attach(stv0299_attach, + &tevii_tuner_sharp_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, 0x60, + &core->i2c_adap, DVB_PLL_OPERA1)) + goto frontend_detach; + core->prev_set_voltage = fe0->dvb.frontend->ops.set_voltage; + fe0->dvb.frontend->ops.set_voltage = tevii_dvbs_set_voltage; + + } else { + fe0->dvb.frontend = dvb_attach(stv0288_attach, + &tevii_tuner_earda_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(stb6000_attach, + fe0->dvb.frontend, 0x61, + &core->i2c_adap)) + goto frontend_detach; + core->prev_set_voltage = fe0->dvb.frontend->ops.set_voltage; + fe0->dvb.frontend->ops.set_voltage = tevii_dvbs_set_voltage; + } + } + break; + case CX88_BOARD_TEVII_S460: + fe0->dvb.frontend = dvb_attach(cx24116_attach, + &tevii_s460_config, + &core->i2c_adap); + if (fe0->dvb.frontend) + fe0->dvb.frontend->ops.set_voltage = tevii_dvbs_set_voltage; + break; + case CX88_BOARD_TEVII_S464: + fe0->dvb.frontend = dvb_attach(ds3000_attach, + &tevii_ds3000_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + dvb_attach(ts2020_attach, fe0->dvb.frontend, + &tevii_ts2020_config, &core->i2c_adap); + fe0->dvb.frontend->ops.set_voltage = + tevii_dvbs_set_voltage; + } + break; + case CX88_BOARD_OMICOM_SS4_PCI: + case CX88_BOARD_TBS_8920: + case CX88_BOARD_PROF_7300: + case CX88_BOARD_SATTRADE_ST4200: + fe0->dvb.frontend = dvb_attach(cx24116_attach, + &hauppauge_hvr4000_config, + &core->i2c_adap); + if (fe0->dvb.frontend) + fe0->dvb.frontend->ops.set_voltage = tevii_dvbs_set_voltage; + break; + case CX88_BOARD_TERRATEC_CINERGY_HT_PCI_MKII: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &cx88_terratec_cinergy_ht_pci_mkii_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + fe0->dvb.frontend->ops.i2c_gate_ctrl = NULL; + if (attach_xc3028(0x61, dev) < 0) + goto frontend_detach; + } + break; + case CX88_BOARD_PROF_7301:{ + struct dvb_tuner_ops *tuner_ops = NULL; + + fe0->dvb.frontend = dvb_attach(stv0900_attach, + &prof_7301_stv0900_config, + &core->i2c_adap, 0); + if (fe0->dvb.frontend) { + if (!dvb_attach(stb6100_attach, fe0->dvb.frontend, + &prof_7301_stb6100_config, + &core->i2c_adap)) + goto frontend_detach; + + tuner_ops = &fe0->dvb.frontend->ops.tuner_ops; + tuner_ops->set_frequency = stb6100_set_freq; + tuner_ops->get_frequency = stb6100_get_freq; + tuner_ops->set_bandwidth = stb6100_set_bandw; + tuner_ops->get_bandwidth = stb6100_get_bandw; + + core->prev_set_voltage = + fe0->dvb.frontend->ops.set_voltage; + fe0->dvb.frontend->ops.set_voltage = + tevii_dvbs_set_voltage; + } + break; + } + case CX88_BOARD_SAMSUNG_SMT_7020: + dev->ts_gen_cntrl = 0x08; + + cx_set(MO_GP0_IO, 0x0101); + + cx_clear(MO_GP0_IO, 0x01); + msleep(100); + cx_set(MO_GP0_IO, 0x01); + msleep(200); + + fe0->dvb.frontend = dvb_attach(stv0299_attach, + &samsung_stv0299_config, + &dev->core->i2c_adap); + if (fe0->dvb.frontend) { + fe0->dvb.frontend->ops.tuner_ops.set_params = + samsung_smt_7020_tuner_set_params; + fe0->dvb.frontend->tuner_priv = + &dev->core->i2c_adap; + fe0->dvb.frontend->ops.set_voltage = + samsung_smt_7020_set_voltage; + fe0->dvb.frontend->ops.set_tone = + samsung_smt_7020_set_tone; + } + + break; + case CX88_BOARD_TWINHAN_VP1027_DVBS: + dev->ts_gen_cntrl = 0x00; + fe0->dvb.frontend = dvb_attach(mb86a16_attach, + &twinhan_vp1027, + &core->i2c_adap); + if (fe0->dvb.frontend) { + core->prev_set_voltage = + fe0->dvb.frontend->ops.set_voltage; + fe0->dvb.frontend->ops.set_voltage = + vp1027_set_voltage; + } + break; + + default: + pr_err("The frontend of your DVB/ATSC card isn't supported yet\n"); + break; + } + + if ((NULL == fe0->dvb.frontend) || (fe1 && NULL == fe1->dvb.frontend)) { + pr_err("frontend initialization failed\n"); + goto frontend_detach; + } + /* define general-purpose callback pointer */ + fe0->dvb.frontend->callback = cx88_tuner_callback; + + /* Ensure all frontends negotiate bus access */ + fe0->dvb.frontend->ops.ts_bus_ctrl = cx88_dvb_bus_ctrl; + if (fe1) + fe1->dvb.frontend->ops.ts_bus_ctrl = cx88_dvb_bus_ctrl; + + /* Put the tuner in standby to keep it quiet */ + call_all(core, tuner, standby); + + /* register everything */ + res = vb2_dvb_register_bus(&dev->frontends, THIS_MODULE, dev, + &dev->pci->dev, NULL, adapter_nr, + mfe_shared); + if (res) + goto frontend_detach; + return res; + +frontend_detach: + core->gate_ctrl = NULL; + vb2_dvb_dealloc_frontends(&dev->frontends); + return res; +} + +/* ----------------------------------------------------------- */ + +/* CX8802 MPEG -> mini driver - We have been given the hardware */ +static int cx8802_dvb_advise_acquire(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + int err = 0; + + dprintk(1, "%s\n", __func__); + + switch (core->boardnr) { + case CX88_BOARD_HAUPPAUGE_HVR1300: + /* We arrive here with either the cx23416 or the cx22702 + * on the bus. Take the bus from the cx23416 and enable the + * cx22702 demod + */ + /* Toggle reset on cx22702 leaving i2c active */ + cx_set(MO_GP0_IO, 0x00000080); + udelay(1000); + cx_clear(MO_GP0_IO, 0x00000080); + udelay(50); + cx_set(MO_GP0_IO, 0x00000080); + udelay(1000); + /* enable the cx22702 pins */ + cx_clear(MO_GP0_IO, 0x00000004); + udelay(1000); + break; + + case CX88_BOARD_HAUPPAUGE_HVR3000: + case CX88_BOARD_HAUPPAUGE_HVR4000: + /* Toggle reset on cx22702 leaving i2c active */ + cx_set(MO_GP0_IO, 0x00000080); + udelay(1000); + cx_clear(MO_GP0_IO, 0x00000080); + udelay(50); + cx_set(MO_GP0_IO, 0x00000080); + udelay(1000); + switch (core->dvbdev->frontends.active_fe_id) { + case 1: /* DVB-S/S2 Enabled */ + /* tri-state the cx22702 pins */ + cx_set(MO_GP0_IO, 0x00000004); + /* Take the cx24116/cx24123 out of reset */ + cx_write(MO_SRST_IO, 1); + core->dvbdev->ts_gen_cntrl = 0x02; /* Parallel IO */ + break; + case 2: /* DVB-T Enabled */ + /* Put the cx24116/cx24123 into reset */ + cx_write(MO_SRST_IO, 0); + /* enable the cx22702 pins */ + cx_clear(MO_GP0_IO, 0x00000004); + core->dvbdev->ts_gen_cntrl = 0x0c; /* Serial IO */ + break; + } + udelay(1000); + break; + + case CX88_BOARD_WINFAST_DTV2000H_PLUS: + /* set RF input to AIR for DVB-T (GPIO 16) */ + cx_write(MO_GP2_IO, 0x0101); + break; + + default: + err = -ENODEV; + } + return err; +} + +/* CX8802 MPEG -> mini driver - We no longer have the hardware */ +static int cx8802_dvb_advise_release(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + int err = 0; + + dprintk(1, "%s\n", __func__); + + switch (core->boardnr) { + case CX88_BOARD_HAUPPAUGE_HVR1300: + /* Do Nothing, leave the cx22702 on the bus. */ + break; + case CX88_BOARD_HAUPPAUGE_HVR3000: + case CX88_BOARD_HAUPPAUGE_HVR4000: + break; + default: + err = -ENODEV; + } + return err; +} + +static int cx8802_dvb_probe(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + struct cx8802_dev *dev = drv->core->dvbdev; + int err; + struct vb2_dvb_frontend *fe; + int i; + + dprintk(1, "%s\n", __func__); + dprintk(1, " ->being probed by Card=%d Name=%s, PCI %02x:%02x\n", + core->boardnr, + core->name, + core->pci_bus, + core->pci_slot); + + err = -ENODEV; + if (!(core->board.mpeg & CX88_MPEG_DVB)) + goto fail_core; + + /* If vp3054 isn't enabled, a stub will just return 0 */ + err = vp3054_i2c_probe(dev); + if (err != 0) + goto fail_core; + + /* dvb stuff */ + pr_info("cx2388x based DVB/ATSC card\n"); + dev->ts_gen_cntrl = 0x0c; + + err = cx8802_alloc_frontends(dev); + if (err) + goto fail_core; + + for (i = 1; i <= core->board.num_frontends; i++) { + struct vb2_queue *q; + + fe = vb2_dvb_get_frontend(&core->dvbdev->frontends, i); + if (!fe) { + pr_err("%s() failed to get frontend(%d)\n", + __func__, i); + err = -ENODEV; + goto fail_probe; + } + q = &fe->dvb.dvbq; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ; + q->gfp_flags = GFP_DMA32; + q->min_queued_buffers = 2; + q->drv_priv = dev; + q->buf_struct_size = sizeof(struct cx88_buffer); + q->ops = &dvb_qops; + q->mem_ops = &vb2_dma_sg_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &core->lock; + q->dev = &dev->pci->dev; + + err = vb2_queue_init(q); + if (err < 0) + goto fail_probe; + + /* init struct vb2_dvb */ + fe->dvb.name = dev->core->name; + } + + err = dvb_register(dev); + if (err) + /* frontends/adapter de-allocated in dvb_register */ + pr_err("dvb_register failed (err = %d)\n", err); + return err; +fail_probe: + vb2_dvb_dealloc_frontends(&core->dvbdev->frontends); +fail_core: + return err; +} + +static int cx8802_dvb_remove(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + struct cx8802_dev *dev = drv->core->dvbdev; + + dprintk(1, "%s\n", __func__); + + vb2_dvb_unregister_bus(&dev->frontends); + + vp3054_i2c_remove(dev); + + core->gate_ctrl = NULL; + + return 0; +} + +static struct cx8802_driver cx8802_dvb_driver = { + .type_id = CX88_MPEG_DVB, + .hw_access = CX8802_DRVCTL_SHARED, + .probe = cx8802_dvb_probe, + .remove = cx8802_dvb_remove, + .advise_acquire = cx8802_dvb_advise_acquire, + .advise_release = cx8802_dvb_advise_release, +}; + +static int __init dvb_init(void) +{ + pr_info("cx2388x dvb driver version %s loaded\n", CX88_VERSION); + return cx8802_register_driver(&cx8802_dvb_driver); +} + +static void __exit dvb_fini(void) +{ + cx8802_unregister_driver(&cx8802_dvb_driver); +} + +module_init(dvb_init); +module_exit(dvb_fini); diff --git a/drivers/media/pci/cx88/cx88-i2c.c b/drivers/media/pci/cx88/cx88-i2c.c new file mode 100644 index 000000000..7fc64aef1 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-i2c.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* + * + * cx88-i2c.c -- all the i2c code is here + * + * Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + * & Marcus Metzler (mocm@thp.uni-koeln.de) + * (c) 2002 Yurij Sysoev + * (c) 1999-2003 Gerd Knorr + * (c) 2005 Mauro Carvalho Chehab + * - Multituner support and i2c address binding + */ + +#include "cx88.h" + +#include +#include +#include + +#include + +static unsigned int i2c_debug; +module_param(i2c_debug, int, 0644); +MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]"); + +static unsigned int i2c_scan; +module_param(i2c_scan, int, 0444); +MODULE_PARM_DESC(i2c_scan, "scan i2c bus at insmod time"); + +static unsigned int i2c_udelay = 5; +module_param(i2c_udelay, int, 0644); +MODULE_PARM_DESC(i2c_udelay, + "i2c delay at insmod time, in usecs (should be 5 or higher). Lower value means higher bus speed."); + +#define dprintk(level, fmt, arg...) do { \ + if (i2c_debug >= level) \ + printk(KERN_DEBUG pr_fmt("%s: i2c:" fmt), \ + __func__, ##arg); \ +} while (0) + +/* ----------------------------------------------------------------------- */ + +static void cx8800_bit_setscl(void *data, int state) +{ + struct cx88_core *core = data; + + if (state) + core->i2c_state |= 0x02; + else + core->i2c_state &= ~0x02; + cx_write(MO_I2C, core->i2c_state); + cx_read(MO_I2C); +} + +static void cx8800_bit_setsda(void *data, int state) +{ + struct cx88_core *core = data; + + if (state) + core->i2c_state |= 0x01; + else + core->i2c_state &= ~0x01; + cx_write(MO_I2C, core->i2c_state); + cx_read(MO_I2C); +} + +static int cx8800_bit_getscl(void *data) +{ + struct cx88_core *core = data; + u32 state; + + state = cx_read(MO_I2C); + return state & 0x02 ? 1 : 0; +} + +static int cx8800_bit_getsda(void *data) +{ + struct cx88_core *core = data; + u32 state; + + state = cx_read(MO_I2C); + return state & 0x01; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_algo_bit_data cx8800_i2c_algo_template = { + .setsda = cx8800_bit_setsda, + .setscl = cx8800_bit_setscl, + .getsda = cx8800_bit_getsda, + .getscl = cx8800_bit_getscl, + .udelay = 16, + .timeout = 200, +}; + +/* ----------------------------------------------------------------------- */ + +static const char * const i2c_devs[128] = { + [0x1c >> 1] = "lgdt330x", + [0x86 >> 1] = "tda9887/cx22702", + [0xa0 >> 1] = "eeprom", + [0xc0 >> 1] = "tuner (analog)", + [0xc2 >> 1] = "tuner (analog/dvb)", + [0xc8 >> 1] = "xc5000", +}; + +static void do_i2c_scan(const char *name, struct i2c_client *c) +{ + unsigned char buf; + int i, rc; + + for (i = 0; i < ARRAY_SIZE(i2c_devs); i++) { + c->addr = i; + rc = i2c_master_recv(c, &buf, 0); + if (rc < 0) + continue; + pr_info("i2c scan: found device @ 0x%x [%s]\n", + i << 1, i2c_devs[i] ? i2c_devs[i] : "???"); + } +} + +/* init + register i2c adapter */ +int cx88_i2c_init(struct cx88_core *core, struct pci_dev *pci) +{ + /* Prevents usage of invalid delay values */ + if (i2c_udelay < 5) + i2c_udelay = 5; + + core->i2c_algo = cx8800_i2c_algo_template; + + core->i2c_adap.dev.parent = &pci->dev; + strscpy(core->i2c_adap.name, core->name, sizeof(core->i2c_adap.name)); + core->i2c_adap.owner = THIS_MODULE; + core->i2c_algo.udelay = i2c_udelay; + core->i2c_algo.data = core; + i2c_set_adapdata(&core->i2c_adap, &core->v4l2_dev); + core->i2c_adap.algo_data = &core->i2c_algo; + core->i2c_client.adapter = &core->i2c_adap; + strscpy(core->i2c_client.name, "cx88xx internal", I2C_NAME_SIZE); + + cx8800_bit_setscl(core, 1); + cx8800_bit_setsda(core, 1); + + core->i2c_rc = i2c_bit_add_bus(&core->i2c_adap); + if (core->i2c_rc == 0) { + static u8 tuner_data[] = { + 0x0b, 0xdc, 0x86, 0x52 }; + static struct i2c_msg tuner_msg = { + .flags = 0, + .addr = 0xc2 >> 1, + .buf = tuner_data, + .len = 4 + }; + + dprintk(1, "i2c register ok\n"); + switch (core->boardnr) { + case CX88_BOARD_HAUPPAUGE_HVR1300: + case CX88_BOARD_HAUPPAUGE_HVR3000: + case CX88_BOARD_HAUPPAUGE_HVR4000: + pr_info("i2c init: enabling analog demod on HVR1300/3000/4000 tuner\n"); + i2c_transfer(core->i2c_client.adapter, &tuner_msg, 1); + break; + default: + break; + } + if (i2c_scan) + do_i2c_scan(core->name, &core->i2c_client); + } else + pr_err("i2c register FAILED\n"); + + return core->i2c_rc; +} diff --git a/drivers/media/pci/cx88/cx88-input.c b/drivers/media/pci/cx88/cx88-input.c new file mode 100644 index 000000000..5d9ce4f9a --- /dev/null +++ b/drivers/media/pci/cx88/cx88-input.c @@ -0,0 +1,651 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Device driver for GPIO attached remote control interfaces + * on Conexant 2388x based TV/DVB cards. + * + * Copyright (c) 2003 Pavel Machek + * Copyright (c) 2004 Gerd Knorr + * Copyright (c) 2004, 2005 Chris Pascoe + */ + +#include "cx88.h" + +#include +#include +#include +#include +#include + +#include + +#define MODULE_NAME "cx88xx" + +/* ---------------------------------------------------------------------- */ + +struct cx88_IR { + struct cx88_core *core; + struct rc_dev *dev; + + int users; + + char name[32]; + char phys[32]; + + /* sample from gpio pin 16 */ + u32 sampling; + + /* poll external decoder */ + int polling; + struct hrtimer timer; + u32 gpio_addr; + u32 last_gpio; + u32 mask_keycode; + u32 mask_keydown; + u32 mask_keyup; +}; + +static unsigned int ir_samplerate = 4; +module_param(ir_samplerate, uint, 0444); +MODULE_PARM_DESC(ir_samplerate, "IR samplerate in kHz, 1 - 20, default 4"); + +static int ir_debug; +module_param(ir_debug, int, 0644); /* debug level [IR] */ +MODULE_PARM_DESC(ir_debug, "enable debug messages [IR]"); + +#define ir_dprintk(fmt, arg...) do { \ + if (ir_debug) \ + printk(KERN_DEBUG "%s IR: " fmt, ir->core->name, ##arg);\ +} while (0) + +#define dprintk(fmt, arg...) do { \ + if (ir_debug) \ + printk(KERN_DEBUG "cx88 IR: " fmt, ##arg); \ +} while (0) + +/* ---------------------------------------------------------------------- */ + +static void cx88_ir_handle_key(struct cx88_IR *ir) +{ + struct cx88_core *core = ir->core; + u32 gpio, data, auxgpio; + + /* read gpio value */ + gpio = cx_read(ir->gpio_addr); + switch (core->boardnr) { + case CX88_BOARD_NPGTECH_REALTV_TOP10FM: + /* + * This board apparently uses a combination of 2 GPIO + * to represent the keys. Additionally, the second GPIO + * can be used for parity. + * + * Example: + * + * for key "5" + * gpio = 0x758, auxgpio = 0xe5 or 0xf5 + * for key "Power" + * gpio = 0x758, auxgpio = 0xed or 0xfd + */ + + auxgpio = cx_read(MO_GP1_IO); + /* Take out the parity part */ + gpio = (gpio & 0x7fd) + (auxgpio & 0xef); + break; + case CX88_BOARD_WINFAST_DTV1000: + case CX88_BOARD_WINFAST_DTV1800H: + case CX88_BOARD_WINFAST_DTV1800H_XC4000: + case CX88_BOARD_WINFAST_DTV2000H_PLUS: + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL: + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36: + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43: + gpio = (gpio & 0x6ff) | ((cx_read(MO_GP1_IO) << 8) & 0x900); + auxgpio = gpio; + break; + default: + auxgpio = gpio; + } + if (ir->polling) { + if (ir->last_gpio == auxgpio) + return; + ir->last_gpio = auxgpio; + } + + /* extract data */ + data = ir_extract_bits(gpio, ir->mask_keycode); + ir_dprintk("irq gpio=0x%x code=%d | %s%s%s\n", + gpio, data, + ir->polling ? "poll" : "irq", + (gpio & ir->mask_keydown) ? " down" : "", + (gpio & ir->mask_keyup) ? " up" : ""); + + if (ir->core->boardnr == CX88_BOARD_NORWOOD_MICRO) { + u32 gpio_key = cx_read(MO_GP0_IO); + + data = (data << 4) | ((gpio_key & 0xf0) >> 4); + + rc_keydown(ir->dev, RC_PROTO_UNKNOWN, data, 0); + + } else if (ir->core->boardnr == CX88_BOARD_PROLINK_PLAYTVPVR || + ir->core->boardnr == CX88_BOARD_PIXELVIEW_PLAYTV_ULTRA_PRO) { + /* bit cleared on keydown, NEC scancode, 0xAAAACC, A = 0x866b */ + u16 addr; + u8 cmd; + u32 scancode; + + addr = (data >> 8) & 0xffff; + cmd = (data >> 0) & 0x00ff; + scancode = RC_SCANCODE_NECX(addr, cmd); + + if (0 == (gpio & ir->mask_keyup)) + rc_keydown_notimeout(ir->dev, RC_PROTO_NECX, scancode, + 0); + else + rc_keyup(ir->dev); + + } else if (ir->mask_keydown) { + /* bit set on keydown */ + if (gpio & ir->mask_keydown) + rc_keydown_notimeout(ir->dev, RC_PROTO_UNKNOWN, data, + 0); + else + rc_keyup(ir->dev); + + } else if (ir->mask_keyup) { + /* bit cleared on keydown */ + if (0 == (gpio & ir->mask_keyup)) + rc_keydown_notimeout(ir->dev, RC_PROTO_UNKNOWN, data, + 0); + else + rc_keyup(ir->dev); + + } else { + /* can't distinguish keydown/up :-/ */ + rc_keydown_notimeout(ir->dev, RC_PROTO_UNKNOWN, data, 0); + rc_keyup(ir->dev); + } +} + +static enum hrtimer_restart cx88_ir_work(struct hrtimer *timer) +{ + u64 missed; + struct cx88_IR *ir = container_of(timer, struct cx88_IR, timer); + + cx88_ir_handle_key(ir); + missed = hrtimer_forward_now(&ir->timer, + ktime_set(0, ir->polling * 1000000)); + if (missed > 1) + ir_dprintk("Missed ticks %llu\n", missed - 1); + + return HRTIMER_RESTART; +} + +static int __cx88_ir_start(void *priv) +{ + struct cx88_core *core = priv; + struct cx88_IR *ir; + + if (!core || !core->ir) + return -EINVAL; + + ir = core->ir; + + if (ir->polling) { + hrtimer_setup(&ir->timer, cx88_ir_work, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hrtimer_start(&ir->timer, + ktime_set(0, ir->polling * 1000000), + HRTIMER_MODE_REL); + } + if (ir->sampling) { + core->pci_irqmask |= PCI_INT_IR_SMPINT; + cx_write(MO_DDS_IO, 0x33F286 * ir_samplerate); /* samplerate */ + cx_write(MO_DDSCFG_IO, 0x5); /* enable */ + } + return 0; +} + +static void __cx88_ir_stop(void *priv) +{ + struct cx88_core *core = priv; + struct cx88_IR *ir; + + if (!core || !core->ir) + return; + + ir = core->ir; + if (ir->sampling) { + cx_write(MO_DDSCFG_IO, 0x0); + core->pci_irqmask &= ~PCI_INT_IR_SMPINT; + } + + if (ir->polling) + hrtimer_cancel(&ir->timer); +} + +int cx88_ir_start(struct cx88_core *core) +{ + if (core->ir->users) + return __cx88_ir_start(core); + + return 0; +} +EXPORT_SYMBOL(cx88_ir_start); + +void cx88_ir_stop(struct cx88_core *core) +{ + if (core->ir->users) + __cx88_ir_stop(core); +} +EXPORT_SYMBOL(cx88_ir_stop); + +static int cx88_ir_open(struct rc_dev *rc) +{ + struct cx88_core *core = rc->priv; + + core->ir->users++; + return __cx88_ir_start(core); +} + +static void cx88_ir_close(struct rc_dev *rc) +{ + struct cx88_core *core = rc->priv; + + core->ir->users--; + if (!core->ir->users) + __cx88_ir_stop(core); +} + +/* ---------------------------------------------------------------------- */ + +int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci) +{ + struct cx88_IR *ir; + struct rc_dev *dev; + char *ir_codes = NULL; + u64 rc_proto = RC_PROTO_BIT_OTHER; + int err = -ENOMEM; + u32 hardware_mask = 0; /* For devices with a hardware mask, when + * used with a full-code IR table + */ + + ir = kzalloc_obj(*ir); + dev = rc_allocate_device(RC_DRIVER_IR_RAW); + if (!ir || !dev) + goto err_out_free; + + ir->dev = dev; + + /* detect & configure */ + switch (core->boardnr) { + case CX88_BOARD_DNTV_LIVE_DVB_T: + case CX88_BOARD_KWORLD_DVB_T: + case CX88_BOARD_KWORLD_DVB_T_CX22702: + ir_codes = RC_MAP_DNTV_LIVE_DVB_T; + ir->gpio_addr = MO_GP1_IO; + ir->mask_keycode = 0x1f; + ir->mask_keyup = 0x60; + ir->polling = 50; /* ms */ + break; + case CX88_BOARD_TERRATEC_CINERGY_1400_DVB_T1: + ir_codes = RC_MAP_CINERGY_1400; + ir->sampling = 0xeb04; /* address */ + break; + case CX88_BOARD_HAUPPAUGE: + case CX88_BOARD_HAUPPAUGE_DVB_T1: + case CX88_BOARD_HAUPPAUGE_NOVASE2_S1: + case CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1: + case CX88_BOARD_HAUPPAUGE_HVR1100: + case CX88_BOARD_HAUPPAUGE_HVR3000: + case CX88_BOARD_HAUPPAUGE_HVR4000: + case CX88_BOARD_HAUPPAUGE_HVR4000LITE: + case CX88_BOARD_PCHDTV_HD3000: + case CX88_BOARD_PCHDTV_HD5500: + case CX88_BOARD_HAUPPAUGE_IRONLY: + ir_codes = RC_MAP_HAUPPAUGE; + ir->sampling = 1; + break; + case CX88_BOARD_WINFAST_DTV2000H: + case CX88_BOARD_WINFAST_DTV2000H_J: + case CX88_BOARD_WINFAST_DTV1800H: + case CX88_BOARD_WINFAST_DTV1800H_XC4000: + case CX88_BOARD_WINFAST_DTV2000H_PLUS: + ir_codes = RC_MAP_WINFAST; + ir->gpio_addr = MO_GP0_IO; + ir->mask_keycode = 0x8f8; + ir->mask_keyup = 0x100; + ir->polling = 50; /* ms */ + break; + case CX88_BOARD_WINFAST2000XP_EXPERT: + case CX88_BOARD_WINFAST_DTV1000: + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL: + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36: + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43: + ir_codes = RC_MAP_WINFAST; + ir->gpio_addr = MO_GP0_IO; + ir->mask_keycode = 0x8f8; + ir->mask_keyup = 0x100; + ir->polling = 1; /* ms */ + break; + case CX88_BOARD_IODATA_GVBCTV7E: + ir_codes = RC_MAP_IODATA_BCTV7E; + ir->gpio_addr = MO_GP0_IO; + ir->mask_keycode = 0xfd; + ir->mask_keydown = 0x02; + ir->polling = 5; /* ms */ + break; + case CX88_BOARD_PROLINK_PLAYTVPVR: + case CX88_BOARD_PIXELVIEW_PLAYTV_ULTRA_PRO: + /* + * It seems that this hardware is paired with NEC extended + * address 0x866b. So, unfortunately, its usage with other + * IR's with different address won't work. Still, there are + * other IR's from the same manufacturer that works, like the + * 002-T mini RC, provided with newer PV hardware + */ + ir_codes = RC_MAP_PIXELVIEW_MK12; + rc_proto = RC_PROTO_BIT_NECX; + ir->gpio_addr = MO_GP1_IO; + ir->mask_keyup = 0x80; + ir->polling = 10; /* ms */ + hardware_mask = 0x3f; /* Hardware returns only 6 bits from command part */ + break; + case CX88_BOARD_PROLINK_PV_8000GT: + case CX88_BOARD_PROLINK_PV_GLOBAL_XTREME: + ir_codes = RC_MAP_PIXELVIEW_NEW; + ir->gpio_addr = MO_GP1_IO; + ir->mask_keycode = 0x3f; + ir->mask_keyup = 0x80; + ir->polling = 1; /* ms */ + break; + case CX88_BOARD_KWORLD_LTV883: + ir_codes = RC_MAP_PIXELVIEW; + ir->gpio_addr = MO_GP1_IO; + ir->mask_keycode = 0x1f; + ir->mask_keyup = 0x60; + ir->polling = 1; /* ms */ + break; + case CX88_BOARD_ADSTECH_DVB_T_PCI: + ir_codes = RC_MAP_ADSTECH_DVB_T_PCI; + ir->gpio_addr = MO_GP1_IO; + ir->mask_keycode = 0xbf; + ir->mask_keyup = 0x40; + ir->polling = 50; /* ms */ + break; + case CX88_BOARD_MSI_TVANYWHERE_MASTER: + ir_codes = RC_MAP_MSI_TVANYWHERE; + ir->gpio_addr = MO_GP1_IO; + ir->mask_keycode = 0x1f; + ir->mask_keyup = 0x40; + ir->polling = 1; /* ms */ + break; + case CX88_BOARD_AVERTV_303: + case CX88_BOARD_AVERTV_STUDIO_303: + ir_codes = RC_MAP_AVERTV_303; + ir->gpio_addr = MO_GP2_IO; + ir->mask_keycode = 0xfb; + ir->mask_keydown = 0x02; + ir->polling = 50; /* ms */ + break; + case CX88_BOARD_OMICOM_SS4_PCI: + case CX88_BOARD_SATTRADE_ST4200: + case CX88_BOARD_TBS_8920: + case CX88_BOARD_TBS_8910: + case CX88_BOARD_PROF_7300: + case CX88_BOARD_PROF_7301: + case CX88_BOARD_PROF_6200: + ir_codes = RC_MAP_TBS_NEC; + ir->sampling = 0xff00; /* address */ + break; + case CX88_BOARD_TEVII_S464: + case CX88_BOARD_TEVII_S460: + case CX88_BOARD_TEVII_S420: + ir_codes = RC_MAP_TEVII_NEC; + ir->sampling = 0xff00; /* address */ + break; + case CX88_BOARD_DNTV_LIVE_DVB_T_PRO: + ir_codes = RC_MAP_DNTV_LIVE_DVBT_PRO; + ir->sampling = 0xff00; /* address */ + break; + case CX88_BOARD_NORWOOD_MICRO: + ir_codes = RC_MAP_NORWOOD; + ir->gpio_addr = MO_GP1_IO; + ir->mask_keycode = 0x0e; + ir->mask_keyup = 0x80; + ir->polling = 50; /* ms */ + break; + case CX88_BOARD_NPGTECH_REALTV_TOP10FM: + ir_codes = RC_MAP_NPGTECH; + ir->gpio_addr = MO_GP0_IO; + ir->mask_keycode = 0xfa; + ir->polling = 50; /* ms */ + break; + case CX88_BOARD_PINNACLE_PCTV_HD_800i: + ir_codes = RC_MAP_PINNACLE_PCTV_HD; + ir->sampling = 1; + break; + case CX88_BOARD_POWERCOLOR_REAL_ANGEL: + ir_codes = RC_MAP_POWERCOLOR_REAL_ANGEL; + ir->gpio_addr = MO_GP2_IO; + ir->mask_keycode = 0x7e; + ir->polling = 100; /* ms */ + break; + case CX88_BOARD_TWINHAN_VP1027_DVBS: + ir_codes = RC_MAP_TWINHAN_VP1027_DVBS; + ir->sampling = 0xff00; /* address */ + break; + } + + if (!ir_codes) { + err = -ENODEV; + goto err_out_free; + } + + /* + * The usage of mask_keycode were very convenient, due to several + * reasons. Among others, the scancode tables were using the scancode + * as the index elements. So, the less bits it was used, the smaller + * the table were stored. After the input changes, the better is to use + * the full scancodes, since it allows replacing the IR remote by + * another one. Unfortunately, there are still some hardware, like + * Pixelview Ultra Pro, where only part of the scancode is sent via + * GPIO. So, there's no way to get the full scancode. Due to that, + * hardware_mask were introduced here: it represents those hardware + * that has such limits. + */ + if (hardware_mask && !ir->mask_keycode) + ir->mask_keycode = hardware_mask; + + /* init input device */ + snprintf(ir->name, sizeof(ir->name), "cx88 IR (%s)", core->board.name); + snprintf(ir->phys, sizeof(ir->phys), "pci-%s/ir0", pci_name(pci)); + + dev->device_name = ir->name; + dev->input_phys = ir->phys; + dev->input_id.bustype = BUS_PCI; + dev->input_id.version = 1; + if (pci->subsystem_vendor) { + dev->input_id.vendor = pci->subsystem_vendor; + dev->input_id.product = pci->subsystem_device; + } else { + dev->input_id.vendor = pci->vendor; + dev->input_id.product = pci->device; + } + dev->dev.parent = &pci->dev; + dev->map_name = ir_codes; + dev->driver_name = MODULE_NAME; + dev->priv = core; + dev->open = cx88_ir_open; + dev->close = cx88_ir_close; + dev->scancode_mask = hardware_mask; + + if (ir->sampling) { + dev->timeout = MS_TO_US(10); /* 10 ms */ + } else { + dev->driver_type = RC_DRIVER_SCANCODE; + dev->allowed_protocols = rc_proto; + } + + ir->core = core; + core->ir = ir; + + /* all done */ + err = rc_register_device(dev); + if (err) + goto err_out_free; + + return 0; + +err_out_free: + rc_free_device(dev); + core->ir = NULL; + kfree(ir); + return err; +} + +int cx88_ir_fini(struct cx88_core *core) +{ + struct cx88_IR *ir = core->ir; + + /* skip detach on non attached boards */ + if (!ir) + return 0; + + rc_unregister_device(ir->dev); + cx88_ir_stop(core); + rc_free_device(ir->dev); + kfree(ir); + + /* done */ + core->ir = NULL; + return 0; +} + +/* ---------------------------------------------------------------------- */ + +void cx88_ir_irq(struct cx88_core *core) +{ + struct cx88_IR *ir = core->ir; + u32 samples; + unsigned int todo, bits; + struct ir_raw_event ev = {}; + + if (!ir || !ir->sampling) + return; + + /* + * Samples are stored in a 32 bit register, oldest sample in + * the msb. A set bit represents space and an unset bit + * represents a pulse. + */ + samples = cx_read(MO_SAMPLE_IO); + + if (samples == 0xff && ir->dev->idle) + return; + + for (todo = 32; todo > 0; todo -= bits) { + ev.pulse = samples & 0x80000000 ? false : true; + bits = min(todo, 32U - fls(ev.pulse ? samples : ~samples)); + ev.duration = (bits * (USEC_PER_SEC / 1000)) / ir_samplerate; + ir_raw_event_store_with_filter(ir->dev, &ev); + samples <<= bits; + } + ir_raw_event_handle(ir->dev); +} + +static int get_key_pvr2000(struct IR_i2c *ir, enum rc_proto *protocol, + u32 *scancode, u8 *toggle) +{ + int flags, code; + + /* poll IR chip */ + flags = i2c_smbus_read_byte_data(ir->c, 0x10); + if (flags < 0) { + dprintk("read error\n"); + return 0; + } + /* key pressed ? */ + if (0 == (flags & 0x80)) + return 0; + + /* read actual key code */ + code = i2c_smbus_read_byte_data(ir->c, 0x00); + if (code < 0) { + dprintk("read error\n"); + return 0; + } + + dprintk("IR Key/Flags: (0x%02x/0x%02x)\n", + code & 0xff, flags & 0xff); + + *protocol = RC_PROTO_UNKNOWN; + *scancode = code & 0xff; + *toggle = 0; + return 1; +} + +void cx88_i2c_init_ir(struct cx88_core *core) +{ + struct i2c_board_info info; + static const unsigned short default_addr_list[] = { + 0x18, 0x33, 0x6b, 0x71, + I2C_CLIENT_END + }; + static const unsigned short pvr2000_addr_list[] = { + 0x18, 0x1a, + I2C_CLIENT_END + }; + const unsigned short *addr_list = default_addr_list; + const unsigned short *addrp; + /* Instantiate the IR receiver device, if present */ + if (core->i2c_rc != 0) + return; + + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "ir_video", I2C_NAME_SIZE); + + switch (core->boardnr) { + case CX88_BOARD_LEADTEK_PVR2000: + addr_list = pvr2000_addr_list; + core->init_data.name = "cx88 Leadtek PVR 2000 remote"; + core->init_data.type = RC_PROTO_BIT_UNKNOWN; + core->init_data.get_key = get_key_pvr2000; + core->init_data.ir_codes = RC_MAP_EMPTY; + break; + } + + /* + * We can't call i2c_new_scanned_device() because it uses + * quick writes for probing and at least some RC receiver + * devices only reply to reads. + * Also, Hauppauge XVR needs to be specified, as address 0x71 + * conflicts with another remote type used with saa7134 + */ + for (addrp = addr_list; *addrp != I2C_CLIENT_END; addrp++) { + info.platform_data = NULL; + memset(&core->init_data, 0, sizeof(core->init_data)); + + if (*addrp == 0x71) { + /* Hauppauge Z8F0811 */ + strscpy(info.type, "ir_z8f0811_haup", I2C_NAME_SIZE); + core->init_data.name = core->board.name; + core->init_data.ir_codes = RC_MAP_HAUPPAUGE; + core->init_data.type = RC_PROTO_BIT_RC5 | + RC_PROTO_BIT_RC6_MCE | RC_PROTO_BIT_RC6_6A_32; + core->init_data.internal_get_key_func = IR_KBD_GET_KEY_HAUP_XVR; + + info.platform_data = &core->init_data; + } + if (i2c_smbus_xfer(&core->i2c_adap, *addrp, 0, + I2C_SMBUS_READ, 0, + I2C_SMBUS_QUICK, NULL) >= 0) { + info.addr = *addrp; + i2c_new_client_device(&core->i2c_adap, &info); + break; + } + } +} + +/* ---------------------------------------------------------------------- */ + +MODULE_AUTHOR("Gerd Knorr, Pavel Machek, Chris Pascoe"); +MODULE_DESCRIPTION("input driver for cx88 GPIO-based IR remote controls"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/cx88/cx88-mpeg.c b/drivers/media/pci/cx88/cx88-mpeg.c new file mode 100644 index 000000000..0c07ad335 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-mpeg.c @@ -0,0 +1,805 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Support for the mpeg transport stream transfers + * PCI function #2 of the cx2388x. + * + * (c) 2004 Jelle Foks + * (c) 2004 Chris Pascoe + * (c) 2004 Gerd Knorr + */ + +#include "cx88.h" + +#include +#include +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ + +MODULE_DESCRIPTION("mpeg driver for cx2388x based TV cards"); +MODULE_AUTHOR("Jelle Foks "); +MODULE_AUTHOR("Chris Pascoe "); +MODULE_AUTHOR("Gerd Knorr [SuSE Labs]"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(CX88_VERSION); + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "enable debug messages [mpeg]"); + +#define dprintk(level, fmt, arg...) do { \ + if (debug + 1 > level) \ + printk(KERN_DEBUG pr_fmt("%s: mpeg:" fmt), \ + __func__, ##arg); \ +} while (0) + +#if defined(CONFIG_MODULES) && defined(MODULE) +static void request_module_async(struct work_struct *work) +{ + struct cx8802_dev *dev = container_of(work, struct cx8802_dev, + request_module_wk); + + if (dev->core->board.mpeg & CX88_MPEG_DVB) + request_module("cx88-dvb"); + if (dev->core->board.mpeg & CX88_MPEG_BLACKBIRD) + request_module("cx88-blackbird"); +} + +static void request_modules(struct cx8802_dev *dev) +{ + INIT_WORK(&dev->request_module_wk, request_module_async); + schedule_work(&dev->request_module_wk); +} + +static void flush_request_modules(struct cx8802_dev *dev) +{ + flush_work(&dev->request_module_wk); +} +#else +#define request_modules(dev) +#define flush_request_modules(dev) +#endif /* CONFIG_MODULES */ + +static LIST_HEAD(cx8802_devlist); +static DEFINE_MUTEX(cx8802_mutex); +/* ------------------------------------------------------------------ */ + +int cx8802_start_dma(struct cx8802_dev *dev, + struct cx88_dmaqueue *q, + struct cx88_buffer *buf) +{ + struct cx88_core *core = dev->core; + + dprintk(1, "w: %d, h: %d, f: %d\n", + core->width, core->height, core->field); + + /* setup fifo + format */ + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH28], + dev->ts_packet_size, buf->risc.dma); + + /* write TS length to chip */ + cx_write(MO_TS_LNGTH, dev->ts_packet_size); + + /* + * FIXME: this needs a review. + * also: move to cx88-blackbird + cx88-dvb source files? + */ + + dprintk(1, "core->active_type_id = 0x%08x\n", core->active_type_id); + + if ((core->active_type_id == CX88_MPEG_DVB) && + (core->board.mpeg & CX88_MPEG_DVB)) { + dprintk(1, "cx8802_start_dma doing .dvb\n"); + /* negedge driven & software reset */ + cx_write(TS_GEN_CNTRL, 0x0040 | dev->ts_gen_cntrl); + udelay(100); + cx_write(MO_PINMUX_IO, 0x00); + cx_write(TS_HW_SOP_CNTRL, 0x47 << 16 | 188 << 4 | 0x01); + switch (core->boardnr) { + case CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q: + case CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T: + case CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD: + case CX88_BOARD_PCHDTV_HD5500: + cx_write(TS_SOP_STAT, 1 << 13); + break; + case CX88_BOARD_SAMSUNG_SMT_7020: + cx_write(TS_SOP_STAT, 0x00); + break; + case CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1: + case CX88_BOARD_HAUPPAUGE_NOVASE2_S1: + /* Enable MPEG parallel IO and video signal pins */ + cx_write(MO_PINMUX_IO, 0x88); + udelay(100); + break; + case CX88_BOARD_HAUPPAUGE_HVR1300: + /* Enable MPEG parallel IO and video signal pins */ + cx_write(MO_PINMUX_IO, 0x88); + cx_write(TS_SOP_STAT, 0); + cx_write(TS_VALERR_CNTRL, 0); + break; + case CX88_BOARD_PINNACLE_PCTV_HD_800i: + /* Enable MPEG parallel IO and video signal pins */ + cx_write(MO_PINMUX_IO, 0x88); + cx_write(TS_HW_SOP_CNTRL, (0x47 << 16) | (188 << 4)); + dev->ts_gen_cntrl = 5; + cx_write(TS_SOP_STAT, 0); + cx_write(TS_VALERR_CNTRL, 0); + udelay(100); + break; + default: + cx_write(TS_SOP_STAT, 0x00); + break; + } + cx_write(TS_GEN_CNTRL, dev->ts_gen_cntrl); + udelay(100); + } else if ((core->active_type_id == CX88_MPEG_BLACKBIRD) && + (core->board.mpeg & CX88_MPEG_BLACKBIRD)) { + dprintk(1, "cx8802_start_dma doing .blackbird\n"); + cx_write(MO_PINMUX_IO, 0x88); /* enable MPEG parallel IO */ + + /* punctured clock TS & posedge driven & software reset */ + cx_write(TS_GEN_CNTRL, 0x46); + udelay(100); + + cx_write(TS_HW_SOP_CNTRL, 0x408); /* mpeg start byte */ + cx_write(TS_VALERR_CNTRL, 0x2000); + + /* punctured clock TS & posedge driven */ + cx_write(TS_GEN_CNTRL, 0x06); + udelay(100); + } else { + pr_err("%s() Failed. Unsupported value in .mpeg (0x%08x)\n", + __func__, core->board.mpeg); + return -EINVAL; + } + + /* reset counter */ + cx_write(MO_TS_GPCNTRL, GP_COUNT_CONTROL_RESET); + q->count = 0; + + /* clear interrupt status register */ + cx_write(MO_TS_INTSTAT, 0x1f1111); + + /* enable irqs */ + dprintk(1, "setting the interrupt mask\n"); + cx_set(MO_PCI_INTMSK, core->pci_irqmask | PCI_INT_TSINT); + cx_set(MO_TS_INTMSK, 0x1f0011); + + /* start dma */ + cx_set(MO_DEV_CNTRL2, (1 << 5)); + cx_set(MO_TS_DMACNTRL, 0x11); + return 0; +} +EXPORT_SYMBOL(cx8802_start_dma); + +static int cx8802_stop_dma(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + + dprintk(1, "\n"); + + /* stop dma */ + cx_clear(MO_TS_DMACNTRL, 0x11); + + /* disable irqs */ + cx_clear(MO_PCI_INTMSK, PCI_INT_TSINT); + cx_clear(MO_TS_INTMSK, 0x1f0011); + + /* Reset the controller */ + cx_write(TS_GEN_CNTRL, 0xcd); + return 0; +} + +static int cx8802_restart_queue(struct cx8802_dev *dev, + struct cx88_dmaqueue *q) +{ + struct cx88_buffer *buf; + + dprintk(1, "\n"); + if (list_empty(&q->active)) + return 0; + + buf = list_entry(q->active.next, struct cx88_buffer, list); + dprintk(2, "restart_queue [%p/%d]: restart dma\n", + buf, buf->vb.vb2_buf.index); + cx8802_start_dma(dev, q, buf); + return 0; +} + +/* ------------------------------------------------------------------ */ + +int cx8802_buf_prepare(struct vb2_queue *q, struct cx8802_dev *dev, + struct cx88_buffer *buf) +{ + int size = dev->ts_packet_size * dev->ts_packet_count; + struct sg_table *sgt = vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0); + struct cx88_riscmem *risc = &buf->risc; + int rc; + + if (vb2_plane_size(&buf->vb.vb2_buf, 0) < size) + return -EINVAL; + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size); + + rc = cx88_risc_databuffer(dev->pci, risc, sgt->sgl, + dev->ts_packet_size, dev->ts_packet_count, 0); + if (rc) { + if (risc->cpu) + dma_free_coherent(&dev->pci->dev, risc->size, + risc->cpu, risc->dma); + memset(risc, 0, sizeof(*risc)); + return rc; + } + return 0; +} +EXPORT_SYMBOL(cx8802_buf_prepare); + +void cx8802_buf_queue(struct cx8802_dev *dev, struct cx88_buffer *buf) +{ + struct cx88_buffer *prev; + struct cx88_dmaqueue *cx88q = &dev->mpegq; + + dprintk(1, "\n"); + /* add jump to start */ + buf->risc.cpu[1] = cpu_to_le32(buf->risc.dma + 8); + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma + 8); + + if (list_empty(&cx88q->active)) { + dprintk(1, "queue is empty - first active\n"); + list_add_tail(&buf->list, &cx88q->active); + dprintk(1, "[%p/%d] %s - first active\n", + buf, buf->vb.vb2_buf.index, __func__); + + } else { + buf->risc.cpu[0] |= cpu_to_le32(RISC_IRQ1); + dprintk(1, "queue is not empty - append to active\n"); + prev = list_entry(cx88q->active.prev, struct cx88_buffer, list); + list_add_tail(&buf->list, &cx88q->active); + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(1, "[%p/%d] %s - append to active\n", + buf, buf->vb.vb2_buf.index, __func__); + } +} +EXPORT_SYMBOL(cx8802_buf_queue); + +/* ----------------------------------------------------------- */ + +static void do_cancel_buffers(struct cx8802_dev *dev) +{ + struct cx88_dmaqueue *q = &dev->mpegq; + struct cx88_buffer *buf; + unsigned long flags; + + spin_lock_irqsave(&dev->slock, flags); + while (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx88_buffer, list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&dev->slock, flags); +} + +void cx8802_cancel_buffers(struct cx8802_dev *dev) +{ + dprintk(1, "\n"); + cx8802_stop_dma(dev); + do_cancel_buffers(dev); +} +EXPORT_SYMBOL(cx8802_cancel_buffers); + +static const char *cx88_mpeg_irqs[32] = { + "ts_risci1", NULL, NULL, NULL, + "ts_risci2", NULL, NULL, NULL, + "ts_oflow", NULL, NULL, NULL, + "ts_sync", NULL, NULL, NULL, + "opc_err", "par_err", "rip_err", "pci_abort", + "ts_err?", +}; + +static void cx8802_mpeg_irq(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + u32 status, mask, count; + + dprintk(1, "\n"); + status = cx_read(MO_TS_INTSTAT); + mask = cx_read(MO_TS_INTMSK); + if (0 == (status & mask)) + return; + + cx_write(MO_TS_INTSTAT, status); + + if (debug || (status & mask & ~0xff)) + cx88_print_irqbits("irq mpeg ", + cx88_mpeg_irqs, ARRAY_SIZE(cx88_mpeg_irqs), + status, mask); + + /* risc op code error */ + if (status & (1 << 16)) { + pr_warn("mpeg risc op code error\n"); + cx_clear(MO_TS_DMACNTRL, 0x11); + cx88_sram_channel_dump(dev->core, + &cx88_sram_channels[SRAM_CH28]); + } + + /* risc1 y */ + if (status & 0x01) { + dprintk(1, "wake up\n"); + spin_lock(&dev->slock); + count = cx_read(MO_TS_GPCNT); + cx88_wakeup(dev->core, &dev->mpegq, count); + spin_unlock(&dev->slock); + } + + /* other general errors */ + if (status & 0x1f0100) { + dprintk(0, "general errors: 0x%08x\n", status & 0x1f0100); + spin_lock(&dev->slock); + cx8802_stop_dma(dev); + spin_unlock(&dev->slock); + } +} + +#define MAX_IRQ_LOOP 10 + +static irqreturn_t cx8802_irq(int irq, void *dev_id) +{ + struct cx8802_dev *dev = dev_id; + struct cx88_core *core = dev->core; + u32 status; + int loop, handled = 0; + + for (loop = 0; loop < MAX_IRQ_LOOP; loop++) { + status = cx_read(MO_PCI_INTSTAT) & + (core->pci_irqmask | PCI_INT_TSINT); + if (status == 0) + goto out; + dprintk(1, "cx8802_irq\n"); + dprintk(1, " loop: %d/%d\n", loop, MAX_IRQ_LOOP); + dprintk(1, " status: %d\n", status); + handled = 1; + cx_write(MO_PCI_INTSTAT, status); + + if (status & core->pci_irqmask) + cx88_core_irq(core, status); + if (status & PCI_INT_TSINT) + cx8802_mpeg_irq(dev); + } + if (loop == MAX_IRQ_LOOP) { + dprintk(0, "clearing mask\n"); + pr_warn("irq loop -- clearing mask\n"); + cx_write(MO_PCI_INTMSK, 0); + } + + out: + return IRQ_RETVAL(handled); +} + +static int cx8802_init_common(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + int err; + + /* pci init */ + if (pci_enable_device(dev->pci)) + return -EIO; + pci_set_master(dev->pci); + err = dma_set_mask(&dev->pci->dev, DMA_BIT_MASK(32)); + if (err) { + pr_err("Oops: no 32bit PCI DMA ???\n"); + return -EIO; + } + + dev->pci_rev = dev->pci->revision; + pci_read_config_byte(dev->pci, PCI_LATENCY_TIMER, &dev->pci_lat); + pr_info("found at %s, rev: %d, irq: %d, latency: %d, mmio: 0x%llx\n", + pci_name(dev->pci), dev->pci_rev, dev->pci->irq, + dev->pci_lat, + (unsigned long long)pci_resource_start(dev->pci, 0)); + + /* initialize driver struct */ + spin_lock_init(&dev->slock); + + /* init dma queue */ + INIT_LIST_HEAD(&dev->mpegq.active); + + /* get irq */ + err = request_irq(dev->pci->irq, cx8802_irq, + IRQF_SHARED, dev->core->name, dev); + if (err < 0) { + pr_err("can't get IRQ %d\n", dev->pci->irq); + return err; + } + cx_set(MO_PCI_INTMSK, core->pci_irqmask); + + /* everything worked */ + pci_set_drvdata(dev->pci, dev); + return 0; +} + +static void cx8802_fini_common(struct cx8802_dev *dev) +{ + dprintk(2, "\n"); + cx8802_stop_dma(dev); + pci_disable_device(dev->pci); + + /* unregister stuff */ + free_irq(dev->pci->irq, dev); +} + +/* ----------------------------------------------------------- */ + +static int cx8802_suspend_common(struct pci_dev *pci_dev, pm_message_t state) +{ + struct cx8802_dev *dev = pci_get_drvdata(pci_dev); + unsigned long flags; + + /* stop mpeg dma */ + spin_lock_irqsave(&dev->slock, flags); + if (!list_empty(&dev->mpegq.active)) { + dprintk(2, "suspend\n"); + pr_info("suspend mpeg\n"); + cx8802_stop_dma(dev); + } + spin_unlock_irqrestore(&dev->slock, flags); + + /* FIXME -- shutdown device */ + cx88_shutdown(dev->core); + + pci_save_state(pci_dev); + if (pci_set_power_state(pci_dev, + pci_choose_state(pci_dev, state)) != 0) { + pci_disable_device(pci_dev); + dev->state.disabled = 1; + } + return 0; +} + +static int cx8802_resume_common(struct pci_dev *pci_dev) +{ + struct cx8802_dev *dev = pci_get_drvdata(pci_dev); + unsigned long flags; + int err; + + if (dev->state.disabled) { + err = pci_enable_device(pci_dev); + if (err) { + pr_err("can't enable device\n"); + return err; + } + dev->state.disabled = 0; + } + err = pci_set_power_state(pci_dev, PCI_D0); + if (err) { + pr_err("can't enable device\n"); + pci_disable_device(pci_dev); + dev->state.disabled = 1; + + return err; + } + pci_restore_state(pci_dev); + + /* FIXME: re-initialize hardware */ + cx88_reset(dev->core); + + /* restart video+vbi capture */ + spin_lock_irqsave(&dev->slock, flags); + if (!list_empty(&dev->mpegq.active)) { + pr_info("resume mpeg\n"); + cx8802_restart_queue(dev, &dev->mpegq); + } + spin_unlock_irqrestore(&dev->slock, flags); + + return 0; +} + +struct cx8802_driver *cx8802_get_driver(struct cx8802_dev *dev, + enum cx88_board_type btype) +{ + struct cx8802_driver *d; + + list_for_each_entry(d, &dev->drvlist, drvlist) + if (d->type_id == btype) + return d; + + return NULL; +} +EXPORT_SYMBOL(cx8802_get_driver); + +/* Driver asked for hardware access. */ +static int cx8802_request_acquire(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + unsigned int i; + + /* Fail a request for hardware if the device is busy. */ + if (core->active_type_id != CX88_BOARD_NONE && + core->active_type_id != drv->type_id) + return -EBUSY; + + if (drv->type_id == CX88_MPEG_DVB) { + /* When switching to DVB, always set the input to the tuner */ + core->last_analog_input = core->input; + core->input = 0; + for (i = 0; + i < ARRAY_SIZE(core->board.input); + i++) { + if (core->board.input[i].type == CX88_VMUX_DVB) { + core->input = i; + break; + } + } + } + + if (drv->advise_acquire) { + core->active_ref++; + if (core->active_type_id == CX88_BOARD_NONE) { + core->active_type_id = drv->type_id; + drv->advise_acquire(drv); + } + + dprintk(1, "Post acquire GPIO=%x\n", cx_read(MO_GP0_IO)); + } + + return 0; +} + +/* Driver asked to release hardware. */ +static int cx8802_request_release(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + + if (drv->advise_release && --core->active_ref == 0) { + if (drv->type_id == CX88_MPEG_DVB) { + /* + * If the DVB driver is releasing, reset the input + * state to the last configured analog input + */ + core->input = core->last_analog_input; + } + + drv->advise_release(drv); + core->active_type_id = CX88_BOARD_NONE; + dprintk(1, "Post release GPIO=%x\n", cx_read(MO_GP0_IO)); + } + + return 0; +} + +static int cx8802_check_driver(struct cx8802_driver *drv) +{ + if (!drv) + return -ENODEV; + + if ((drv->type_id != CX88_MPEG_DVB) && + (drv->type_id != CX88_MPEG_BLACKBIRD)) + return -EINVAL; + + if ((drv->hw_access != CX8802_DRVCTL_SHARED) && + (drv->hw_access != CX8802_DRVCTL_EXCLUSIVE)) + return -EINVAL; + + if ((!drv->probe) || + (!drv->remove) || + (!drv->advise_acquire) || + (!drv->advise_release)) + return -EINVAL; + + return 0; +} + +int cx8802_register_driver(struct cx8802_driver *drv) +{ + struct cx8802_dev *dev; + struct cx8802_driver *driver; + int err, i = 0; + + pr_info("registering cx8802 driver, type: %s access: %s\n", + drv->type_id == CX88_MPEG_DVB ? "dvb" : "blackbird", + drv->hw_access == CX8802_DRVCTL_SHARED ? + "shared" : "exclusive"); + + err = cx8802_check_driver(drv); + if (err) { + pr_err("cx8802_driver is invalid\n"); + return err; + } + + mutex_lock(&cx8802_mutex); + + list_for_each_entry(dev, &cx8802_devlist, devlist) { + pr_info("subsystem: %04x:%04x, board: %s [card=%d]\n", + dev->pci->subsystem_vendor, + dev->pci->subsystem_device, dev->core->board.name, + dev->core->boardnr); + + /* Bring up a new struct for each driver instance */ + driver = kzalloc_obj(*drv); + if (!driver) { + err = -ENOMEM; + goto out; + } + + /* Snapshot of the driver registration data */ + drv->core = dev->core; + drv->suspend = cx8802_suspend_common; + drv->resume = cx8802_resume_common; + drv->request_acquire = cx8802_request_acquire; + drv->request_release = cx8802_request_release; + memcpy(driver, drv, sizeof(*driver)); + + mutex_lock(&drv->core->lock); + err = drv->probe(driver); + if (err == 0) { + i++; + list_add_tail(&driver->drvlist, &dev->drvlist); + } else { + pr_err("cx8802 probe failed, err = %d\n", err); + kfree(driver); + } + mutex_unlock(&drv->core->lock); + } + + err = i ? 0 : -ENODEV; +out: + mutex_unlock(&cx8802_mutex); + return err; +} +EXPORT_SYMBOL(cx8802_register_driver); + +int cx8802_unregister_driver(struct cx8802_driver *drv) +{ + struct cx8802_dev *dev; + struct cx8802_driver *d, *dtmp; + int err = 0; + + pr_info("unregistering cx8802 driver, type: %s access: %s\n", + drv->type_id == CX88_MPEG_DVB ? "dvb" : "blackbird", + drv->hw_access == CX8802_DRVCTL_SHARED ? + "shared" : "exclusive"); + + mutex_lock(&cx8802_mutex); + + list_for_each_entry(dev, &cx8802_devlist, devlist) { + pr_info("subsystem: %04x:%04x, board: %s [card=%d]\n", + dev->pci->subsystem_vendor, + dev->pci->subsystem_device, dev->core->board.name, + dev->core->boardnr); + + mutex_lock(&dev->core->lock); + + list_for_each_entry_safe(d, dtmp, &dev->drvlist, drvlist) { + /* only unregister the correct driver type */ + if (d->type_id != drv->type_id) + continue; + + err = d->remove(d); + if (err == 0) { + list_del(&d->drvlist); + kfree(d); + } else + pr_err("cx8802 driver remove failed (%d)\n", + err); + } + + mutex_unlock(&dev->core->lock); + } + + mutex_unlock(&cx8802_mutex); + + return err; +} +EXPORT_SYMBOL(cx8802_unregister_driver); + +/* ----------------------------------------------------------- */ +static int cx8802_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct cx8802_dev *dev; + struct cx88_core *core; + int err; + + /* general setup */ + core = cx88_core_get(pci_dev); + if (!core) + return -EINVAL; + + pr_info("cx2388x 8802 Driver Manager\n"); + + err = -ENODEV; + if (!core->board.mpeg) + goto fail_core; + + err = -ENOMEM; + dev = kzalloc_obj(*dev); + if (!dev) + goto fail_core; + dev->pci = pci_dev; + dev->core = core; + + /* Maintain a reference so cx88-video can query the 8802 device. */ + core->dvbdev = dev; + + err = cx8802_init_common(dev); + if (err != 0) + goto fail_dev; + + INIT_LIST_HEAD(&dev->drvlist); + mutex_lock(&cx8802_mutex); + list_add_tail(&dev->devlist, &cx8802_devlist); + mutex_unlock(&cx8802_mutex); + + /* now autoload cx88-dvb or cx88-blackbird */ + request_modules(dev); + return 0; + + fail_dev: + kfree(dev); + fail_core: + core->dvbdev = NULL; + cx88_core_put(core, pci_dev); + return err; +} + +static void cx8802_remove(struct pci_dev *pci_dev) +{ + struct cx8802_dev *dev; + + dev = pci_get_drvdata(pci_dev); + + dprintk(1, "%s\n", __func__); + + flush_request_modules(dev); + + mutex_lock(&dev->core->lock); + + if (!list_empty(&dev->drvlist)) { + struct cx8802_driver *drv, *tmp; + int err; + + pr_warn("Trying to remove cx8802 driver while cx8802 sub-drivers still loaded?!\n"); + + list_for_each_entry_safe(drv, tmp, &dev->drvlist, drvlist) { + err = drv->remove(drv); + if (err == 0) { + list_del(&drv->drvlist); + } else + pr_err("cx8802 driver remove failed (%d)\n", + err); + kfree(drv); + } + } + + mutex_unlock(&dev->core->lock); + + /* Destroy any 8802 reference. */ + dev->core->dvbdev = NULL; + + /* common */ + cx8802_fini_common(dev); + cx88_core_put(dev->core, dev->pci); + kfree(dev); +} + +static const struct pci_device_id cx8802_pci_tbl[] = { + { + PCI_DEVICE(0x14f1, 0x8802), + }, { + /* --- end of list --- */ + } +}; +MODULE_DEVICE_TABLE(pci, cx8802_pci_tbl); + +static struct pci_driver cx8802_pci_driver = { + .name = "cx88-mpeg driver manager", + .id_table = cx8802_pci_tbl, + .probe = cx8802_probe, + .remove = cx8802_remove, +}; + +module_pci_driver(cx8802_pci_driver); diff --git a/drivers/media/pci/cx88/cx88-reg.h b/drivers/media/pci/cx88/cx88-reg.h new file mode 100644 index 000000000..866f85f86 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-reg.h @@ -0,0 +1,816 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx88x-hw.h - CX2388x register offsets + * + * Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + * 2001 Michael Eskin + * 2002 Yurij Sysoev + * 2003 Gerd Knorr + */ + +#ifndef _CX88_REG_H_ +#define _CX88_REG_H_ + +/* + * PCI IDs and config space + */ + +#ifndef PCI_VENDOR_ID_CONEXANT +# define PCI_VENDOR_ID_CONEXANT 0x14F1 +#endif +#ifndef PCI_DEVICE_ID_CX2300_VID +# define PCI_DEVICE_ID_CX2300_VID 0x8800 +#endif + +#define CX88X_DEVCTRL 0x40 +#define CX88X_EN_TBFX 0x02 +#define CX88X_EN_VSFX 0x04 + +/* + * PCI controller registers + */ + +/* Command and Status Register */ +#define F0_CMD_STAT_MM 0x2f0004 +#define F1_CMD_STAT_MM 0x2f0104 +#define F2_CMD_STAT_MM 0x2f0204 +#define F3_CMD_STAT_MM 0x2f0304 +#define F4_CMD_STAT_MM 0x2f0404 + +/* Device Control #1 */ +#define F0_DEV_CNTRL1_MM 0x2f0040 +#define F1_DEV_CNTRL1_MM 0x2f0140 +#define F2_DEV_CNTRL1_MM 0x2f0240 +#define F3_DEV_CNTRL1_MM 0x2f0340 +#define F4_DEV_CNTRL1_MM 0x2f0440 + +/* Device Control #1 */ +#define F0_BAR0_MM 0x2f0010 +#define F1_BAR0_MM 0x2f0110 +#define F2_BAR0_MM 0x2f0210 +#define F3_BAR0_MM 0x2f0310 +#define F4_BAR0_MM 0x2f0410 + +/* + * DMA Controller registers + */ + +#define MO_PDMA_STHRSH 0x200000 // Source threshold +#define MO_PDMA_STADRS 0x200004 // Source target address +#define MO_PDMA_SIADRS 0x200008 // Source internal address +#define MO_PDMA_SCNTRL 0x20000C // Source control +#define MO_PDMA_DTHRSH 0x200010 // Destination threshold +#define MO_PDMA_DTADRS 0x200014 // Destination target address +#define MO_PDMA_DIADRS 0x200018 // Destination internal address +#define MO_PDMA_DCNTRL 0x20001C // Destination control +#define MO_LD_SSID 0x200030 // Load subsystem ID +#define MO_DEV_CNTRL2 0x200034 // Device control +#define MO_PCI_INTMSK 0x200040 // PCI interrupt mask +#define MO_PCI_INTSTAT 0x200044 // PCI interrupt status +#define MO_PCI_INTMSTAT 0x200048 // PCI interrupt masked status +#define MO_VID_INTMSK 0x200050 // Video interrupt mask +#define MO_VID_INTSTAT 0x200054 // Video interrupt status +#define MO_VID_INTMSTAT 0x200058 // Video interrupt masked status +#define MO_VID_INTSSTAT 0x20005C // Video interrupt set status +#define MO_AUD_INTMSK 0x200060 // Audio interrupt mask +#define MO_AUD_INTSTAT 0x200064 // Audio interrupt status +#define MO_AUD_INTMSTAT 0x200068 // Audio interrupt masked status +#define MO_AUD_INTSSTAT 0x20006C // Audio interrupt set status +#define MO_TS_INTMSK 0x200070 // Transport stream interrupt mask +#define MO_TS_INTSTAT 0x200074 // Transport stream interrupt status +#define MO_TS_INTMSTAT 0x200078 // Transport stream interrupt mask status +#define MO_TS_INTSSTAT 0x20007C // Transport stream interrupt set status +#define MO_VIP_INTMSK 0x200080 // VIP interrupt mask +#define MO_VIP_INTSTAT 0x200084 // VIP interrupt status +#define MO_VIP_INTMSTAT 0x200088 // VIP interrupt masked status +#define MO_VIP_INTSSTAT 0x20008C // VIP interrupt set status +#define MO_GPHST_INTMSK 0x200090 // Host interrupt mask +#define MO_GPHST_INTSTAT 0x200094 // Host interrupt status +#define MO_GPHST_INTMSTAT 0x200098 // Host interrupt masked status +#define MO_GPHST_INTSSTAT 0x20009C // Host interrupt set status + +// DMA Channels 1-6 belong to SPIPE +#define MO_DMA7_PTR1 0x300018 // {24}RW* DMA Current Ptr : Ch#7 +#define MO_DMA8_PTR1 0x30001C // {24}RW* DMA Current Ptr : Ch#8 + +// DMA Channels 9-20 belong to SPIPE +#define MO_DMA21_PTR1 0x300080 // {24}R0* DMA Current Ptr : Ch#21 +#define MO_DMA22_PTR1 0x300084 // {24}R0* DMA Current Ptr : Ch#22 +#define MO_DMA23_PTR1 0x300088 // {24}R0* DMA Current Ptr : Ch#23 +#define MO_DMA24_PTR1 0x30008C // {24}R0* DMA Current Ptr : Ch#24 +#define MO_DMA25_PTR1 0x300090 // {24}R0* DMA Current Ptr : Ch#25 +#define MO_DMA26_PTR1 0x300094 // {24}R0* DMA Current Ptr : Ch#26 +#define MO_DMA27_PTR1 0x300098 // {24}R0* DMA Current Ptr : Ch#27 +#define MO_DMA28_PTR1 0x30009C // {24}R0* DMA Current Ptr : Ch#28 +#define MO_DMA29_PTR1 0x3000A0 // {24}R0* DMA Current Ptr : Ch#29 +#define MO_DMA30_PTR1 0x3000A4 // {24}R0* DMA Current Ptr : Ch#30 +#define MO_DMA31_PTR1 0x3000A8 // {24}R0* DMA Current Ptr : Ch#31 +#define MO_DMA32_PTR1 0x3000AC // {24}R0* DMA Current Ptr : Ch#32 + +#define MO_DMA21_PTR2 0x3000C0 // {24}RW* DMA Tab Ptr : Ch#21 +#define MO_DMA22_PTR2 0x3000C4 // {24}RW* DMA Tab Ptr : Ch#22 +#define MO_DMA23_PTR2 0x3000C8 // {24}RW* DMA Tab Ptr : Ch#23 +#define MO_DMA24_PTR2 0x3000CC // {24}RW* DMA Tab Ptr : Ch#24 +#define MO_DMA25_PTR2 0x3000D0 // {24}RW* DMA Tab Ptr : Ch#25 +#define MO_DMA26_PTR2 0x3000D4 // {24}RW* DMA Tab Ptr : Ch#26 +#define MO_DMA27_PTR2 0x3000D8 // {24}RW* DMA Tab Ptr : Ch#27 +#define MO_DMA28_PTR2 0x3000DC // {24}RW* DMA Tab Ptr : Ch#28 +#define MO_DMA29_PTR2 0x3000E0 // {24}RW* DMA Tab Ptr : Ch#29 +#define MO_DMA30_PTR2 0x3000E4 // {24}RW* DMA Tab Ptr : Ch#30 +#define MO_DMA31_PTR2 0x3000E8 // {24}RW* DMA Tab Ptr : Ch#31 +#define MO_DMA32_PTR2 0x3000EC // {24}RW* DMA Tab Ptr : Ch#32 + +#define MO_DMA21_CNT1 0x300100 // {11}RW* DMA Buffer Size : Ch#21 +#define MO_DMA22_CNT1 0x300104 // {11}RW* DMA Buffer Size : Ch#22 +#define MO_DMA23_CNT1 0x300108 // {11}RW* DMA Buffer Size : Ch#23 +#define MO_DMA24_CNT1 0x30010C // {11}RW* DMA Buffer Size : Ch#24 +#define MO_DMA25_CNT1 0x300110 // {11}RW* DMA Buffer Size : Ch#25 +#define MO_DMA26_CNT1 0x300114 // {11}RW* DMA Buffer Size : Ch#26 +#define MO_DMA27_CNT1 0x300118 // {11}RW* DMA Buffer Size : Ch#27 +#define MO_DMA28_CNT1 0x30011C // {11}RW* DMA Buffer Size : Ch#28 +#define MO_DMA29_CNT1 0x300120 // {11}RW* DMA Buffer Size : Ch#29 +#define MO_DMA30_CNT1 0x300124 // {11}RW* DMA Buffer Size : Ch#30 +#define MO_DMA31_CNT1 0x300128 // {11}RW* DMA Buffer Size : Ch#31 +#define MO_DMA32_CNT1 0x30012C // {11}RW* DMA Buffer Size : Ch#32 + +#define MO_DMA21_CNT2 0x300140 // {11}RW* DMA Table Size : Ch#21 +#define MO_DMA22_CNT2 0x300144 // {11}RW* DMA Table Size : Ch#22 +#define MO_DMA23_CNT2 0x300148 // {11}RW* DMA Table Size : Ch#23 +#define MO_DMA24_CNT2 0x30014C // {11}RW* DMA Table Size : Ch#24 +#define MO_DMA25_CNT2 0x300150 // {11}RW* DMA Table Size : Ch#25 +#define MO_DMA26_CNT2 0x300154 // {11}RW* DMA Table Size : Ch#26 +#define MO_DMA27_CNT2 0x300158 // {11}RW* DMA Table Size : Ch#27 +#define MO_DMA28_CNT2 0x30015C // {11}RW* DMA Table Size : Ch#28 +#define MO_DMA29_CNT2 0x300160 // {11}RW* DMA Table Size : Ch#29 +#define MO_DMA30_CNT2 0x300164 // {11}RW* DMA Table Size : Ch#30 +#define MO_DMA31_CNT2 0x300168 // {11}RW* DMA Table Size : Ch#31 +#define MO_DMA32_CNT2 0x30016C // {11}RW* DMA Table Size : Ch#32 + +/* + * Video registers + */ + +#define MO_VIDY_DMA 0x310000 // {64}RWp Video Y +#define MO_VIDU_DMA 0x310008 // {64}RWp Video U +#define MO_VIDV_DMA 0x310010 // {64}RWp Video V +#define MO_VBI_DMA 0x310018 // {64}RWp VBI (Vertical blanking interval) + +#define MO_DEVICE_STATUS 0x310100 +#define MO_INPUT_FORMAT 0x310104 +#define MO_AGC_BURST 0x31010c +#define MO_CONTR_BRIGHT 0x310110 +#define MO_UV_SATURATION 0x310114 +#define MO_HUE 0x310118 +#define MO_HTOTAL 0x310120 +#define MO_HDELAY_EVEN 0x310124 +#define MO_HDELAY_ODD 0x310128 +#define MO_VDELAY_ODD 0x31012c +#define MO_VDELAY_EVEN 0x310130 +#define MO_HACTIVE_EVEN 0x31013c +#define MO_HACTIVE_ODD 0x310140 +#define MO_VACTIVE_EVEN 0x310144 +#define MO_VACTIVE_ODD 0x310148 +#define MO_HSCALE_EVEN 0x31014c +#define MO_HSCALE_ODD 0x310150 +#define MO_VSCALE_EVEN 0x310154 +#define MO_FILTER_EVEN 0x31015c +#define MO_VSCALE_ODD 0x310158 +#define MO_FILTER_ODD 0x310160 +#define MO_OUTPUT_FORMAT 0x310164 + +#define MO_PLL_REG 0x310168 // PLL register +#define MO_PLL_ADJ_CTRL 0x31016c // PLL adjust control register +#define MO_SCONV_REG 0x310170 // sample rate conversion register +#define MO_SCONV_FIFO 0x310174 // sample rate conversion fifo +#define MO_SUB_STEP 0x310178 // subcarrier step size +#define MO_SUB_STEP_DR 0x31017c // subcarrier step size for DR line + +#define MO_CAPTURE_CTRL 0x310180 // capture control +#define MO_COLOR_CTRL 0x310184 +#define MO_VBI_PACKET 0x310188 // vbi packet size / delay +#define MO_FIELD_COUNT 0x310190 // field counter +#define MO_VIP_CONFIG 0x310194 +#define MO_VBOS_CONTROL 0x3101a8 + +#define MO_AGC_BACK_VBI 0x310200 +#define MO_AGC_SYNC_TIP1 0x310208 + +#define MO_VIDY_GPCNT 0x31C020 // {16}RO Video Y general purpose counter +#define MO_VIDU_GPCNT 0x31C024 // {16}RO Video U general purpose counter +#define MO_VIDV_GPCNT 0x31C028 // {16}RO Video V general purpose counter +#define MO_VBI_GPCNT 0x31C02C // {16}RO VBI general purpose counter +#define MO_VIDY_GPCNTRL 0x31C030 // {2}WO Video Y general purpose control +#define MO_VIDU_GPCNTRL 0x31C034 // {2}WO Video U general purpose control +#define MO_VIDV_GPCNTRL 0x31C038 // {2}WO Video V general purpose control +#define MO_VBI_GPCNTRL 0x31C03C // {2}WO VBI general purpose counter +#define MO_VID_DMACNTRL 0x31C040 // {8}RW Video DMA control +#define MO_VID_XFR_STAT 0x31C044 // {1}RO Video transfer status + +/* + * audio registers + */ + +#define MO_AUDD_DMA 0x320000 // {64}RWp Audio downstream +#define MO_AUDU_DMA 0x320008 // {64}RWp Audio upstream +#define MO_AUDR_DMA 0x320010 // {64}RWp Audio RDS (downstream) +#define MO_AUDD_GPCNT 0x32C020 // {16}RO Audio down general purpose counter +#define MO_AUDU_GPCNT 0x32C024 // {16}RO Audio up general purpose counter +#define MO_AUDR_GPCNT 0x32C028 // {16}RO Audio RDS general purpose counter +#define MO_AUDD_GPCNTRL 0x32C030 // {2}WO Audio down general purpose control +#define MO_AUDU_GPCNTRL 0x32C034 // {2}WO Audio up general purpose control +#define MO_AUDR_GPCNTRL 0x32C038 // {2}WO Audio RDS general purpose control +#define MO_AUD_DMACNTRL 0x32C040 // {6}RW Audio DMA control +#define MO_AUD_XFR_STAT 0x32C044 // {1}RO Audio transfer status +#define MO_AUDD_LNGTH 0x32C048 // {12}RW Audio down line length +#define MO_AUDR_LNGTH 0x32C04C // {12}RW Audio RDS line length + +#define AUD_INIT 0x320100 +#define AUD_INIT_LD 0x320104 +#define AUD_SOFT_RESET 0x320108 +#define AUD_I2SINPUTCNTL 0x320120 +#define AUD_BAUDRATE 0x320124 +#define AUD_I2SOUTPUTCNTL 0x320128 +#define AAGC_HYST 0x320134 +#define AAGC_GAIN 0x320138 +#define AAGC_DEF 0x32013c +#define AUD_IIR1_0_SEL 0x320150 +#define AUD_IIR1_0_SHIFT 0x320154 +#define AUD_IIR1_1_SEL 0x320158 +#define AUD_IIR1_1_SHIFT 0x32015c +#define AUD_IIR1_2_SEL 0x320160 +#define AUD_IIR1_2_SHIFT 0x320164 +#define AUD_IIR1_3_SEL 0x320168 +#define AUD_IIR1_3_SHIFT 0x32016c +#define AUD_IIR1_4_SEL 0x320170 +#define AUD_IIR1_4_SHIFT 0x32017c +#define AUD_IIR1_5_SEL 0x320180 +#define AUD_IIR1_5_SHIFT 0x320184 +#define AUD_IIR2_0_SEL 0x320190 +#define AUD_IIR2_0_SHIFT 0x320194 +#define AUD_IIR2_1_SEL 0x320198 +#define AUD_IIR2_1_SHIFT 0x32019c +#define AUD_IIR2_2_SEL 0x3201a0 +#define AUD_IIR2_2_SHIFT 0x3201a4 +#define AUD_IIR2_3_SEL 0x3201a8 +#define AUD_IIR2_3_SHIFT 0x3201ac +#define AUD_IIR3_0_SEL 0x3201c0 +#define AUD_IIR3_0_SHIFT 0x3201c4 +#define AUD_IIR3_1_SEL 0x3201c8 +#define AUD_IIR3_1_SHIFT 0x3201cc +#define AUD_IIR3_2_SEL 0x3201d0 +#define AUD_IIR3_2_SHIFT 0x3201d4 +#define AUD_IIR4_0_SEL 0x3201e0 +#define AUD_IIR4_0_SHIFT 0x3201e4 +#define AUD_IIR4_1_SEL 0x3201e8 +#define AUD_IIR4_1_SHIFT 0x3201ec +#define AUD_IIR4_2_SEL 0x3201f0 +#define AUD_IIR4_2_SHIFT 0x3201f4 +#define AUD_IIR4_0_CA0 0x320200 +#define AUD_IIR4_0_CA1 0x320204 +#define AUD_IIR4_0_CA2 0x320208 +#define AUD_IIR4_0_CB0 0x32020c +#define AUD_IIR4_0_CB1 0x320210 +#define AUD_IIR4_1_CA0 0x320214 +#define AUD_IIR4_1_CA1 0x320218 +#define AUD_IIR4_1_CA2 0x32021c +#define AUD_IIR4_1_CB0 0x320220 +#define AUD_IIR4_1_CB1 0x320224 +#define AUD_IIR4_2_CA0 0x320228 +#define AUD_IIR4_2_CA1 0x32022c +#define AUD_IIR4_2_CA2 0x320230 +#define AUD_IIR4_2_CB0 0x320234 +#define AUD_IIR4_2_CB1 0x320238 +#define AUD_HP_MD_IIR4_1 0x320250 +#define AUD_HP_PROG_IIR4_1 0x320254 +#define AUD_FM_MODE_ENABLE 0x320258 +#define AUD_POLY0_DDS_CONSTANT 0x320270 +#define AUD_DN0_FREQ 0x320274 +#define AUD_DN1_FREQ 0x320278 +#define AUD_DN1_FREQ_SHIFT 0x32027c +#define AUD_DN1_AFC 0x320280 +#define AUD_DN1_SRC_SEL 0x320284 +#define AUD_DN1_SHFT 0x320288 +#define AUD_DN2_FREQ 0x32028c +#define AUD_DN2_FREQ_SHIFT 0x320290 +#define AUD_DN2_AFC 0x320294 +#define AUD_DN2_SRC_SEL 0x320298 +#define AUD_DN2_SHFT 0x32029c +#define AUD_CRDC0_SRC_SEL 0x320300 +#define AUD_CRDC0_SHIFT 0x320304 +#define AUD_CORDIC_SHIFT_0 0x320308 +#define AUD_CRDC1_SRC_SEL 0x32030c +#define AUD_CRDC1_SHIFT 0x320310 +#define AUD_CORDIC_SHIFT_1 0x320314 +#define AUD_DCOC_0_SRC 0x320320 +#define AUD_DCOC0_SHIFT 0x320324 +#define AUD_DCOC_0_SHIFT_IN0 0x320328 +#define AUD_DCOC_0_SHIFT_IN1 0x32032c +#define AUD_DCOC_1_SRC 0x320330 +#define AUD_DCOC1_SHIFT 0x320334 +#define AUD_DCOC_1_SHIFT_IN0 0x320338 +#define AUD_DCOC_1_SHIFT_IN1 0x32033c +#define AUD_DCOC_2_SRC 0x320340 +#define AUD_DCOC2_SHIFT 0x320344 +#define AUD_DCOC_2_SHIFT_IN0 0x320348 +#define AUD_DCOC_2_SHIFT_IN1 0x32034c +#define AUD_DCOC_PASS_IN 0x320350 +#define AUD_PDET_SRC 0x320370 +#define AUD_PDET_SHIFT 0x320374 +#define AUD_PILOT_BQD_1_K0 0x320380 +#define AUD_PILOT_BQD_1_K1 0x320384 +#define AUD_PILOT_BQD_1_K2 0x320388 +#define AUD_PILOT_BQD_1_K3 0x32038c +#define AUD_PILOT_BQD_1_K4 0x320390 +#define AUD_PILOT_BQD_2_K0 0x320394 +#define AUD_PILOT_BQD_2_K1 0x320398 +#define AUD_PILOT_BQD_2_K2 0x32039c +#define AUD_PILOT_BQD_2_K3 0x3203a0 +#define AUD_PILOT_BQD_2_K4 0x3203a4 +#define AUD_THR_FR 0x3203c0 +#define AUD_X_PROG 0x3203c4 +#define AUD_Y_PROG 0x3203c8 +#define AUD_HARMONIC_MULT 0x3203cc +#define AUD_C1_UP_THR 0x3203d0 +#define AUD_C1_LO_THR 0x3203d4 +#define AUD_C2_UP_THR 0x3203d8 +#define AUD_C2_LO_THR 0x3203dc +#define AUD_PLL_EN 0x320400 +#define AUD_PLL_SRC 0x320404 +#define AUD_PLL_SHIFT 0x320408 +#define AUD_PLL_IF_SEL 0x32040c +#define AUD_PLL_IF_SHIFT 0x320410 +#define AUD_BIQUAD_PLL_K0 0x320414 +#define AUD_BIQUAD_PLL_K1 0x320418 +#define AUD_BIQUAD_PLL_K2 0x32041c +#define AUD_BIQUAD_PLL_K3 0x320420 +#define AUD_BIQUAD_PLL_K4 0x320424 +#define AUD_DEEMPH0_SRC_SEL 0x320440 +#define AUD_DEEMPH0_SHIFT 0x320444 +#define AUD_DEEMPH0_G0 0x320448 +#define AUD_DEEMPH0_A0 0x32044c +#define AUD_DEEMPH0_B0 0x320450 +#define AUD_DEEMPH0_A1 0x320454 +#define AUD_DEEMPH0_B1 0x320458 +#define AUD_DEEMPH1_SRC_SEL 0x32045c +#define AUD_DEEMPH1_SHIFT 0x320460 +#define AUD_DEEMPH1_G0 0x320464 +#define AUD_DEEMPH1_A0 0x320468 +#define AUD_DEEMPH1_B0 0x32046c +#define AUD_DEEMPH1_A1 0x320470 +#define AUD_DEEMPH1_B1 0x320474 +#define AUD_OUT0_SEL 0x320490 +#define AUD_OUT0_SHIFT 0x320494 +#define AUD_OUT1_SEL 0x320498 +#define AUD_OUT1_SHIFT 0x32049c +#define AUD_RDSI_SEL 0x3204a0 +#define AUD_RDSI_SHIFT 0x3204a4 +#define AUD_RDSQ_SEL 0x3204a8 +#define AUD_RDSQ_SHIFT 0x3204ac +#define AUD_DBX_IN_GAIN 0x320500 +#define AUD_DBX_WBE_GAIN 0x320504 +#define AUD_DBX_SE_GAIN 0x320508 +#define AUD_DBX_RMS_WBE 0x32050c +#define AUD_DBX_RMS_SE 0x320510 +#define AUD_DBX_SE_BYPASS 0x320514 +#define AUD_FAWDETCTL 0x320530 +#define AUD_FAWDETWINCTL 0x320534 +#define AUD_DEEMPHGAIN_R 0x320538 +#define AUD_DEEMPHNUMER1_R 0x32053c +#define AUD_DEEMPHNUMER2_R 0x320540 +#define AUD_DEEMPHDENOM1_R 0x320544 +#define AUD_DEEMPHDENOM2_R 0x320548 +#define AUD_ERRLOGPERIOD_R 0x32054c +#define AUD_ERRINTRPTTHSHLD1_R 0x320550 +#define AUD_ERRINTRPTTHSHLD2_R 0x320554 +#define AUD_ERRINTRPTTHSHLD3_R 0x320558 +#define AUD_NICAM_STATUS1 0x32055c +#define AUD_NICAM_STATUS2 0x320560 +#define AUD_ERRLOG1 0x320564 +#define AUD_ERRLOG2 0x320568 +#define AUD_ERRLOG3 0x32056c +#define AUD_DAC_BYPASS_L 0x320580 +#define AUD_DAC_BYPASS_R 0x320584 +#define AUD_DAC_BYPASS_CTL 0x320588 +#define AUD_CTL 0x32058c +#define AUD_STATUS 0x320590 +#define AUD_VOL_CTL 0x320594 +#define AUD_BAL_CTL 0x320598 +#define AUD_START_TIMER 0x3205b0 +#define AUD_MODE_CHG_TIMER 0x3205b4 +#define AUD_POLYPH80SCALEFAC 0x3205b8 +#define AUD_DMD_RA_DDS 0x3205bc +#define AUD_I2S_RA_DDS 0x3205c0 +#define AUD_RATE_THRES_DMD 0x3205d0 +#define AUD_RATE_THRES_I2S 0x3205d4 +#define AUD_RATE_ADJ1 0x3205d8 +#define AUD_RATE_ADJ2 0x3205dc +#define AUD_RATE_ADJ3 0x3205e0 +#define AUD_RATE_ADJ4 0x3205e4 +#define AUD_RATE_ADJ5 0x3205e8 +#define AUD_APB_IN_RATE_ADJ 0x3205ec +#define AUD_I2SCNTL 0x3205ec +#define AUD_PHASE_FIX_CTL 0x3205f0 +#define AUD_PLL_PRESCALE 0x320600 +#define AUD_PLL_DDS 0x320604 +#define AUD_PLL_INT 0x320608 +#define AUD_PLL_FRAC 0x32060c +#define AUD_PLL_JTAG 0x320620 +#define AUD_PLL_SPMP 0x320624 +#define AUD_AFE_12DB_EN 0x320628 + +// Audio QAM Register Addresses +#define AUD_PDF_DDS_CNST_BYTE2 0x320d01 +#define AUD_PDF_DDS_CNST_BYTE1 0x320d02 +#define AUD_PDF_DDS_CNST_BYTE0 0x320d03 +#define AUD_PHACC_FREQ_8MSB 0x320d2a +#define AUD_PHACC_FREQ_8LSB 0x320d2b +#define AUD_QAM_MODE 0x320d04 + +/* + * transport stream registers + */ + +#define MO_TS_DMA 0x330000 // {64}RWp Transport stream downstream +#define MO_TS_GPCNT 0x33C020 // {16}RO TS general purpose counter +#define MO_TS_GPCNTRL 0x33C030 // {2}WO TS general purpose control +#define MO_TS_DMACNTRL 0x33C040 // {6}RW TS DMA control +#define MO_TS_XFR_STAT 0x33C044 // {1}RO TS transfer status +#define MO_TS_LNGTH 0x33C048 // {12}RW TS line length + +#define TS_HW_SOP_CNTRL 0x33C04C +#define TS_GEN_CNTRL 0x33C050 +#define TS_BD_PKT_STAT 0x33C054 +#define TS_SOP_STAT 0x33C058 +#define TS_FIFO_OVFL_STAT 0x33C05C +#define TS_VALERR_CNTRL 0x33C060 + +/* + * VIP registers + */ + +#define MO_VIPD_DMA 0x340000 // {64}RWp VIP downstream +#define MO_VIPU_DMA 0x340008 // {64}RWp VIP upstream +#define MO_VIPD_GPCNT 0x34C020 // {16}RO VIP down general purpose counter +#define MO_VIPU_GPCNT 0x34C024 // {16}RO VIP up general purpose counter +#define MO_VIPD_GPCNTRL 0x34C030 // {2}WO VIP down general purpose control +#define MO_VIPU_GPCNTRL 0x34C034 // {2}WO VIP up general purpose control +#define MO_VIP_DMACNTRL 0x34C040 // {6}RW VIP DMA control +#define MO_VIP_XFR_STAT 0x34C044 // {1}RO VIP transfer status +#define MO_VIP_CFG 0x340048 // VIP configuration +#define MO_VIPU_CNTRL 0x34004C // VIP upstream control #1 +#define MO_VIPD_CNTRL 0x340050 // VIP downstream control #2 +#define MO_VIPD_LNGTH 0x340054 // VIP downstream line length +#define MO_VIP_BRSTLN 0x340058 // VIP burst length +#define MO_VIP_INTCNTRL 0x34C05C // VIP Interrupt Control +#define MO_VIP_XFTERM 0x340060 // VIP transfer terminate + +/* + * misc registers + */ + +#define MO_M2M_DMA 0x350000 // {64}RWp Mem2Mem DMA Bfr +#define MO_GP0_IO 0x350010 // {32}RW* GPIOoutput enablesdata I/O +#define MO_GP1_IO 0x350014 // {32}RW* GPIOoutput enablesdata I/O +#define MO_GP2_IO 0x350018 // {32}RW* GPIOoutput enablesdata I/O +#define MO_GP3_IO 0x35001C // {32}RW* GPIO Mode/Ctrloutput enables +#define MO_GPIO 0x350020 // {32}RW* GPIO I2C Ctrldata I/O +#define MO_GPOE 0x350024 // {32}RW GPIO I2C Ctrloutput enables +#define MO_GP_ISM 0x350028 // {16}WO GPIO Intr Sens/Pol + +#define MO_PLL_B 0x35C008 // {32}RW* PLL Control for ASB bus clks +#define MO_M2M_CNT 0x35C024 // {32}RW Mem2Mem DMA Cnt +#define MO_M2M_XSUM 0x35C028 // {32}RO M2M XOR-Checksum +#define MO_CRC 0x35C02C // {16}RW CRC16 init/result +#define MO_CRC_D 0x35C030 // {32}WO CRC16 new data in +#define MO_TM_CNT_LDW 0x35C034 // {32}RO Timer : Counter low dword +#define MO_TM_CNT_UW 0x35C038 // {16}RO Timer : Counter high word +#define MO_TM_LMT_LDW 0x35C03C // {32}RW Timer : Limit low dword +#define MO_TM_LMT_UW 0x35C040 // {32}RW Timer : Limit high word +#define MO_PINMUX_IO 0x35C044 // {8}RW Pin Mux Control +#define MO_TSTSEL_IO 0x35C048 // {2}RW Pin Mux Control +#define MO_AFECFG_IO 0x35C04C // AFE configuration reg +#define MO_DDS_IO 0x35C050 // DDS Increment reg +#define MO_DDSCFG_IO 0x35C054 // DDS Configuration reg +#define MO_SAMPLE_IO 0x35C058 // IRIn sample reg +#define MO_SRST_IO 0x35C05C // Output system reset reg + +#define MO_INT1_MSK 0x35C060 // DMA RISC interrupt mask +#define MO_INT1_STAT 0x35C064 // DMA RISC interrupt status +#define MO_INT1_MSTAT 0x35C068 // DMA RISC interrupt masked status + +/* + * i2c bus registers + */ + +#define MO_I2C 0x368000 // I2C data/control +#define MO_I2C_DIV (0xf<<4) +#define MO_I2C_SYNC (1<<3) +#define MO_I2C_W3B (1<<2) +#define MO_I2C_SCL (1<<1) +#define MO_I2C_SDA (1<<0) + + +/* + * general purpose host registers + * + * FIXME: tyops? s/0x35/0x38/ ?? + */ + +#define MO_GPHSTD_DMA 0x350000 // {64}RWp Host downstream +#define MO_GPHSTU_DMA 0x350008 // {64}RWp Host upstream +#define MO_GPHSTU_CNTRL 0x380048 // Host upstream control #1 +#define MO_GPHSTD_CNTRL 0x38004C // Host downstream control #2 +#define MO_GPHSTD_LNGTH 0x380050 // Host downstream line length +#define MO_GPHST_WSC 0x380054 // Host wait state control +#define MO_GPHST_XFR 0x380058 // Host transfer control +#define MO_GPHST_WDTH 0x38005C // Host interface width +#define MO_GPHST_HDSHK 0x380060 // Host peripheral handshake +#define MO_GPHST_MUX16 0x380064 // Host muxed 16-bit transfer parameters +#define MO_GPHST_MODE 0x380068 // Host mode select + +#define MO_GPHSTD_GPCNT 0x35C020 // Host down general purpose counter +#define MO_GPHSTU_GPCNT 0x35C024 // Host up general purpose counter +#define MO_GPHSTD_GPCNTRL 0x38C030 // Host down general purpose control +#define MO_GPHSTU_GPCNTRL 0x38C034 // Host up general purpose control +#define MO_GPHST_DMACNTRL 0x38C040 // Host DMA control +#define MO_GPHST_XFR_STAT 0x38C044 // Host transfer status +#define MO_GPHST_SOFT_RST 0x38C06C // Host software reset + +/* + * RISC instructions + */ + +#define RISC_SYNC 0x80000000 +#define RISC_SYNC_ODD 0x80000000 +#define RISC_SYNC_EVEN 0x80000200 +#define RISC_RESYNC 0x80008000 +#define RISC_RESYNC_ODD 0x80008000 +#define RISC_RESYNC_EVEN 0x80008200 +#define RISC_WRITE 0x10000000 +#define RISC_WRITEC 0x50000000 +#define RISC_READ 0x90000000 +#define RISC_READC 0xA0000000 +#define RISC_JUMP 0x70000000 +#define RISC_SKIP 0x20000000 +#define RISC_WRITERM 0xB0000000 +#define RISC_WRITECM 0xC0000000 +#define RISC_WRITECR 0xD0000000 +#define RISC_IMM 0x00000001 + +#define RISC_SOL 0x08000000 +#define RISC_EOL 0x04000000 + +#define RISC_IRQ2 0x02000000 +#define RISC_IRQ1 0x01000000 + +#define RISC_CNT_NONE 0x00000000 +#define RISC_CNT_INC 0x00010000 +#define RISC_CNT_RSVR 0x00020000 +#define RISC_CNT_RESET 0x00030000 +#define RISC_JMP_SRP 0x01 + +/* + * various constants + */ + +// DMA +/* Interrupt mask/status */ +#define PCI_INT_VIDINT (1 << 0) +#define PCI_INT_AUDINT (1 << 1) +#define PCI_INT_TSINT (1 << 2) +#define PCI_INT_VIPINT (1 << 3) +#define PCI_INT_HSTINT (1 << 4) +#define PCI_INT_TM1INT (1 << 5) +#define PCI_INT_SRCDMAINT (1 << 6) +#define PCI_INT_DSTDMAINT (1 << 7) +#define PCI_INT_RISC_RD_BERRINT (1 << 10) +#define PCI_INT_RISC_WR_BERRINT (1 << 11) +#define PCI_INT_BRDG_BERRINT (1 << 12) +#define PCI_INT_SRC_DMA_BERRINT (1 << 13) +#define PCI_INT_DST_DMA_BERRINT (1 << 14) +#define PCI_INT_IPB_DMA_BERRINT (1 << 15) +#define PCI_INT_I2CDONE (1 << 16) +#define PCI_INT_I2CRACK (1 << 17) +#define PCI_INT_IR_SMPINT (1 << 18) +#define PCI_INT_GPIO_INT0 (1 << 19) +#define PCI_INT_GPIO_INT1 (1 << 20) + +#define SEL_BTSC 0x01 +#define SEL_EIAJ 0x02 +#define SEL_A2 0x04 +#define SEL_SAP 0x08 +#define SEL_NICAM 0x10 +#define SEL_FMRADIO 0x20 + +// AUD_CTL +#define AUD_INT_DN_RISCI1 (1 << 0) +#define AUD_INT_UP_RISCI1 (1 << 1) +#define AUD_INT_RDS_DN_RISCI1 (1 << 2) +#define AUD_INT_DN_RISCI2 (1 << 4) /* yes, 3 is skipped */ +#define AUD_INT_UP_RISCI2 (1 << 5) +#define AUD_INT_RDS_DN_RISCI2 (1 << 6) +#define AUD_INT_DN_SYNC (1 << 12) +#define AUD_INT_UP_SYNC (1 << 13) +#define AUD_INT_RDS_DN_SYNC (1 << 14) +#define AUD_INT_OPC_ERR (1 << 16) +#define AUD_INT_BER_IRQ (1 << 20) +#define AUD_INT_MCHG_IRQ (1 << 21) + +#define EN_BTSC_FORCE_MONO 0 +#define EN_BTSC_FORCE_STEREO 1 +#define EN_BTSC_FORCE_SAP 2 +#define EN_BTSC_AUTO_STEREO 3 +#define EN_BTSC_AUTO_SAP 4 + +#define EN_A2_FORCE_MONO1 8 +#define EN_A2_FORCE_MONO2 9 +#define EN_A2_FORCE_STEREO 10 +#define EN_A2_AUTO_MONO2 11 +#define EN_A2_AUTO_STEREO 12 + +#define EN_EIAJ_FORCE_MONO1 16 +#define EN_EIAJ_FORCE_MONO2 17 +#define EN_EIAJ_FORCE_STEREO 18 +#define EN_EIAJ_AUTO_MONO2 19 +#define EN_EIAJ_AUTO_STEREO 20 + +#define EN_NICAM_FORCE_MONO1 32 +#define EN_NICAM_FORCE_MONO2 33 +#define EN_NICAM_FORCE_STEREO 34 +#define EN_NICAM_AUTO_MONO2 35 +#define EN_NICAM_AUTO_STEREO 36 + +#define EN_FMRADIO_FORCE_MONO 24 +#define EN_FMRADIO_FORCE_STEREO 25 +#define EN_FMRADIO_AUTO_STEREO 26 + +#define EN_NICAM_AUTO_FALLBACK 0x00000040 +#define EN_FMRADIO_EN_RDS 0x00000200 +#define EN_NICAM_TRY_AGAIN_BIT 0x00000400 +#define EN_DAC_ENABLE 0x00001000 +#define EN_I2SOUT_ENABLE 0x00002000 +#define EN_I2SIN_STR2DAC 0x00004000 +#define EN_I2SIN_ENABLE 0x00008000 + +#define EN_DMTRX_SUMDIFF (0 << 7) +#define EN_DMTRX_SUMR (1 << 7) +#define EN_DMTRX_LR (2 << 7) +#define EN_DMTRX_MONO (3 << 7) +#define EN_DMTRX_BYPASS (1 << 11) + +// Video +#define VID_CAPTURE_CONTROL 0x310180 + +#define CX23880_CAP_CTL_CAPTURE_VBI_ODD (1<<3) +#define CX23880_CAP_CTL_CAPTURE_VBI_EVEN (1<<2) +#define CX23880_CAP_CTL_CAPTURE_ODD (1<<1) +#define CX23880_CAP_CTL_CAPTURE_EVEN (1<<0) + +#define VideoInputMux0 0x0 +#define VideoInputMux1 0x1 +#define VideoInputMux2 0x2 +#define VideoInputMux3 0x3 +#define VideoInputTuner 0x0 +#define VideoInputComposite 0x1 +#define VideoInputSVideo 0x2 +#define VideoInputOther 0x3 + +#define Xtal0 0x1 +#define Xtal1 0x2 +#define XtalAuto 0x3 + +#define VideoFormatAuto 0x0 +#define VideoFormatNTSC 0x1 +#define VideoFormatNTSCJapan 0x2 +#define VideoFormatNTSC443 0x3 +#define VideoFormatPAL 0x4 +#define VideoFormatPALB 0x4 +#define VideoFormatPALD 0x4 +#define VideoFormatPALG 0x4 +#define VideoFormatPALH 0x4 +#define VideoFormatPALI 0x4 +#define VideoFormatPALBDGHI 0x4 +#define VideoFormatPALM 0x5 +#define VideoFormatPALN 0x6 +#define VideoFormatPALNC 0x7 +#define VideoFormatPAL60 0x8 +#define VideoFormatSECAM 0x9 + +#define VideoFormatAuto27MHz 0x10 +#define VideoFormatNTSC27MHz 0x11 +#define VideoFormatNTSCJapan27MHz 0x12 +#define VideoFormatNTSC44327MHz 0x13 +#define VideoFormatPAL27MHz 0x14 +#define VideoFormatPALB27MHz 0x14 +#define VideoFormatPALD27MHz 0x14 +#define VideoFormatPALG27MHz 0x14 +#define VideoFormatPALH27MHz 0x14 +#define VideoFormatPALI27MHz 0x14 +#define VideoFormatPALBDGHI27MHz 0x14 +#define VideoFormatPALM27MHz 0x15 +#define VideoFormatPALN27MHz 0x16 +#define VideoFormatPALNC27MHz 0x17 +#define VideoFormatPAL6027MHz 0x18 +#define VideoFormatSECAM27MHz 0x19 + +#define NominalUSECAM 0x87 +#define NominalVSECAM 0x85 +#define NominalUNTSC 0xFE +#define NominalVNTSC 0xB4 + +#define NominalContrast 0xD8 + +#define HFilterAutoFormat 0x0 +#define HFilterCIF 0x1 +#define HFilterQCIF 0x2 +#define HFilterICON 0x3 + +#define VFilter2TapInterpolate 0 +#define VFilter3TapInterpolate 1 +#define VFilter4TapInterpolate 2 +#define VFilter5TapInterpolate 3 +#define VFilter2TapNoInterpolate 4 +#define VFilter3TapNoInterpolate 5 +#define VFilter4TapNoInterpolate 6 +#define VFilter5TapNoInterpolate 7 + +#define ColorFormatRGB32 0x0000 +#define ColorFormatRGB24 0x0011 +#define ColorFormatRGB16 0x0022 +#define ColorFormatRGB15 0x0033 +#define ColorFormatYUY2 0x0044 +#define ColorFormatBTYUV 0x0055 +#define ColorFormatY8 0x0066 +#define ColorFormatRGB8 0x0077 +#define ColorFormatPL422 0x0088 +#define ColorFormatPL411 0x0099 +#define ColorFormatYUV12 0x00AA +#define ColorFormatYUV9 0x00BB +#define ColorFormatRAW 0x00EE +#define ColorFormatBSWAP 0x0300 +#define ColorFormatWSWAP 0x0c00 +#define ColorFormatEvenMask 0x050f +#define ColorFormatOddMask 0x0af0 +#define ColorFormatGamma 0x1000 + +#define Interlaced 0x1 +#define NonInterlaced 0x0 + +#define FieldEven 0x1 +#define FieldOdd 0x0 + +#define TGReadWriteMode 0x0 +#define TGEnableMode 0x1 + +#define DV_CbAlign 0x0 +#define DV_Y0Align 0x1 +#define DV_CrAlign 0x2 +#define DV_Y1Align 0x3 + +#define DVF_Analog 0x0 +#define DVF_CCIR656 0x1 +#define DVF_ByteStream 0x2 +#define DVF_ExtVSYNC 0x4 +#define DVF_ExtField 0x5 + +#define CHANNEL_VID_Y 0x1 +#define CHANNEL_VID_U 0x2 +#define CHANNEL_VID_V 0x3 +#define CHANNEL_VID_VBI 0x4 +#define CHANNEL_AUD_DN 0x5 +#define CHANNEL_AUD_UP 0x6 +#define CHANNEL_AUD_RDS_DN 0x7 +#define CHANNEL_MPEG_DN 0x8 +#define CHANNEL_VIP_DN 0x9 +#define CHANNEL_VIP_UP 0xA +#define CHANNEL_HOST_DN 0xB +#define CHANNEL_HOST_UP 0xC +#define CHANNEL_FIRST 0x1 +#define CHANNEL_LAST 0xC + +#define GP_COUNT_CONTROL_NONE 0x0 +#define GP_COUNT_CONTROL_INC 0x1 +#define GP_COUNT_CONTROL_RESERVED 0x2 +#define GP_COUNT_CONTROL_RESET 0x3 + +#define PLL_PRESCALE_BY_2 2 +#define PLL_PRESCALE_BY_3 3 +#define PLL_PRESCALE_BY_4 4 +#define PLL_PRESCALE_BY_5 5 + +#define HLNotchFilter4xFsc 0 +#define HLNotchFilterSquare 1 +#define HLNotchFilter135NTSC 2 +#define HLNotchFilter135PAL 3 + +#define NTSC_8x_SUB_CARRIER 28.63636E6 +#define PAL_8x_SUB_CARRIER 35.46895E6 + +// Default analog settings +#define DEFAULT_HUE_NTSC 0x00 +#define DEFAULT_BRIGHTNESS_NTSC 0x00 +#define DEFAULT_CONTRAST_NTSC 0x39 +#define DEFAULT_SAT_U_NTSC 0x7F +#define DEFAULT_SAT_V_NTSC 0x5A + +#endif /* _CX88_REG_H_ */ diff --git a/drivers/media/pci/cx88/cx88-tvaudio.c b/drivers/media/pci/cx88/cx88-tvaudio.c new file mode 100644 index 000000000..9d25d77fb --- /dev/null +++ b/drivers/media/pci/cx88/cx88-tvaudio.c @@ -0,0 +1,1046 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx88x-audio.c - Conexant CX23880/23881 audio downstream driver driver + * + * (c) 2001 Michael Eskin, Tom Zakrajsek [Windows version] + * (c) 2002 Yurij Sysoev + * (c) 2003 Gerd Knorr + * + * ----------------------------------------------------------------------- + * + * Lot of voodoo here. Even the data sheet doesn't help to + * understand what is going on here, the documentation for the audio + * part of the cx2388x chip is *very* bad. + * + * Some of this comes from party done linux driver sources I got from + * [undocumented]. + * + * Some comes from the dscaler sources, one of the dscaler driver guy works + * for Conexant ... + * + * ----------------------------------------------------------------------- + */ + +#include "cx88.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static unsigned int audio_debug; +module_param(audio_debug, int, 0644); +MODULE_PARM_DESC(audio_debug, "enable debug messages [audio]"); + +static unsigned int always_analog; +module_param(always_analog, int, 0644); +MODULE_PARM_DESC(always_analog, "force analog audio out"); + +static unsigned int radio_deemphasis; +module_param(radio_deemphasis, int, 0644); +MODULE_PARM_DESC(radio_deemphasis, + "Radio deemphasis time constant, 0=None, 1=50us (elsewhere), 2=75us (USA)"); + +#define dprintk(fmt, arg...) do { \ + if (audio_debug) \ + printk(KERN_DEBUG pr_fmt("%s: tvaudio:" fmt), \ + __func__, ##arg); \ +} while (0) +/* ----------------------------------------------------------- */ + +static const char * const aud_ctl_names[64] = { + [EN_BTSC_FORCE_MONO] = "BTSC_FORCE_MONO", + [EN_BTSC_FORCE_STEREO] = "BTSC_FORCE_STEREO", + [EN_BTSC_FORCE_SAP] = "BTSC_FORCE_SAP", + [EN_BTSC_AUTO_STEREO] = "BTSC_AUTO_STEREO", + [EN_BTSC_AUTO_SAP] = "BTSC_AUTO_SAP", + [EN_A2_FORCE_MONO1] = "A2_FORCE_MONO1", + [EN_A2_FORCE_MONO2] = "A2_FORCE_MONO2", + [EN_A2_FORCE_STEREO] = "A2_FORCE_STEREO", + [EN_A2_AUTO_MONO2] = "A2_AUTO_MONO2", + [EN_A2_AUTO_STEREO] = "A2_AUTO_STEREO", + [EN_EIAJ_FORCE_MONO1] = "EIAJ_FORCE_MONO1", + [EN_EIAJ_FORCE_MONO2] = "EIAJ_FORCE_MONO2", + [EN_EIAJ_FORCE_STEREO] = "EIAJ_FORCE_STEREO", + [EN_EIAJ_AUTO_MONO2] = "EIAJ_AUTO_MONO2", + [EN_EIAJ_AUTO_STEREO] = "EIAJ_AUTO_STEREO", + [EN_NICAM_FORCE_MONO1] = "NICAM_FORCE_MONO1", + [EN_NICAM_FORCE_MONO2] = "NICAM_FORCE_MONO2", + [EN_NICAM_FORCE_STEREO] = "NICAM_FORCE_STEREO", + [EN_NICAM_AUTO_MONO2] = "NICAM_AUTO_MONO2", + [EN_NICAM_AUTO_STEREO] = "NICAM_AUTO_STEREO", + [EN_FMRADIO_FORCE_MONO] = "FMRADIO_FORCE_MONO", + [EN_FMRADIO_FORCE_STEREO] = "FMRADIO_FORCE_STEREO", + [EN_FMRADIO_AUTO_STEREO] = "FMRADIO_AUTO_STEREO", +}; + +struct rlist { + u32 reg; + u32 val; +}; + +static void set_audio_registers(struct cx88_core *core, const struct rlist *l) +{ + int i; + + for (i = 0; l[i].reg; i++) { + switch (l[i].reg) { + case AUD_PDF_DDS_CNST_BYTE2: + case AUD_PDF_DDS_CNST_BYTE1: + case AUD_PDF_DDS_CNST_BYTE0: + case AUD_QAM_MODE: + case AUD_PHACC_FREQ_8MSB: + case AUD_PHACC_FREQ_8LSB: + cx_writeb(l[i].reg, l[i].val); + break; + default: + cx_write(l[i].reg, l[i].val); + break; + } + } +} + +static void set_audio_start(struct cx88_core *core, u32 mode) +{ + /* mute */ + cx_write(AUD_VOL_CTL, (1 << 6)); + + /* start programming */ + cx_write(AUD_INIT, mode); + cx_write(AUD_INIT_LD, 0x0001); + cx_write(AUD_SOFT_RESET, 0x0001); +} + +static void set_audio_finish(struct cx88_core *core, u32 ctl) +{ + u32 volume; + + /* restart dma; This avoids buzz in NICAM and is good in others */ + cx88_stop_audio_dma(core); + cx_write(AUD_RATE_THRES_DMD, 0x000000C0); + cx88_start_audio_dma(core); + + if (core->board.mpeg & CX88_MPEG_BLACKBIRD) { + cx_write(AUD_I2SINPUTCNTL, 4); + cx_write(AUD_BAUDRATE, 1); + /* + * 'pass-thru mode': this enables the i2s + * output to the mpeg encoder + */ + cx_set(AUD_CTL, EN_I2SOUT_ENABLE); + cx_write(AUD_I2SOUTPUTCNTL, 1); + cx_write(AUD_I2SCNTL, 0); + /* cx_write(AUD_APB_IN_RATE_ADJ, 0); */ + } + if ((always_analog) || (!(core->board.mpeg & CX88_MPEG_BLACKBIRD))) { + ctl |= EN_DAC_ENABLE; + cx_write(AUD_CTL, ctl); + } + + /* finish programming */ + cx_write(AUD_SOFT_RESET, 0x0000); + + /* unmute */ + volume = cx_sread(SHADOW_AUD_VOL_CTL); + cx_swrite(SHADOW_AUD_VOL_CTL, AUD_VOL_CTL, volume); + + core->last_change = jiffies; +} + +/* ----------------------------------------------------------- */ + +static void set_audio_standard_BTSC(struct cx88_core *core, unsigned int sap, + u32 mode) +{ + static const struct rlist btsc[] = { + {AUD_AFE_12DB_EN, 0x00000001}, + {AUD_OUT1_SEL, 0x00000013}, + {AUD_OUT1_SHIFT, 0x00000000}, + {AUD_POLY0_DDS_CONSTANT, 0x0012010c}, + {AUD_DMD_RA_DDS, 0x00c3e7aa}, + {AUD_DBX_IN_GAIN, 0x00004734}, + {AUD_DBX_WBE_GAIN, 0x00004640}, + {AUD_DBX_SE_GAIN, 0x00008d31}, + {AUD_DCOC_0_SRC, 0x0000001a}, + {AUD_IIR1_4_SEL, 0x00000021}, + {AUD_DCOC_PASS_IN, 0x00000003}, + {AUD_DCOC_0_SHIFT_IN0, 0x0000000a}, + {AUD_DCOC_0_SHIFT_IN1, 0x00000008}, + {AUD_DCOC_1_SHIFT_IN0, 0x0000000a}, + {AUD_DCOC_1_SHIFT_IN1, 0x00000008}, + {AUD_DN0_FREQ, 0x0000283b}, + {AUD_DN2_SRC_SEL, 0x00000008}, + {AUD_DN2_FREQ, 0x00003000}, + {AUD_DN2_AFC, 0x00000002}, + {AUD_DN2_SHFT, 0x00000000}, + {AUD_IIR2_2_SEL, 0x00000020}, + {AUD_IIR2_2_SHIFT, 0x00000000}, + {AUD_IIR2_3_SEL, 0x0000001f}, + {AUD_IIR2_3_SHIFT, 0x00000000}, + {AUD_CRDC1_SRC_SEL, 0x000003ce}, + {AUD_CRDC1_SHIFT, 0x00000000}, + {AUD_CORDIC_SHIFT_1, 0x00000007}, + {AUD_DCOC_1_SRC, 0x0000001b}, + {AUD_DCOC1_SHIFT, 0x00000000}, + {AUD_RDSI_SEL, 0x00000008}, + {AUD_RDSQ_SEL, 0x00000008}, + {AUD_RDSI_SHIFT, 0x00000000}, + {AUD_RDSQ_SHIFT, 0x00000000}, + {AUD_POLYPH80SCALEFAC, 0x00000003}, + { /* end of list */ }, + }; + static const struct rlist btsc_sap[] = { + {AUD_AFE_12DB_EN, 0x00000001}, + {AUD_DBX_IN_GAIN, 0x00007200}, + {AUD_DBX_WBE_GAIN, 0x00006200}, + {AUD_DBX_SE_GAIN, 0x00006200}, + {AUD_IIR1_1_SEL, 0x00000000}, + {AUD_IIR1_3_SEL, 0x00000001}, + {AUD_DN1_SRC_SEL, 0x00000007}, + {AUD_IIR1_4_SHIFT, 0x00000006}, + {AUD_IIR2_1_SHIFT, 0x00000000}, + {AUD_IIR2_2_SHIFT, 0x00000000}, + {AUD_IIR3_0_SHIFT, 0x00000000}, + {AUD_IIR3_1_SHIFT, 0x00000000}, + {AUD_IIR3_0_SEL, 0x0000000d}, + {AUD_IIR3_1_SEL, 0x0000000e}, + {AUD_DEEMPH1_SRC_SEL, 0x00000014}, + {AUD_DEEMPH1_SHIFT, 0x00000000}, + {AUD_DEEMPH1_G0, 0x00004000}, + {AUD_DEEMPH1_A0, 0x00000000}, + {AUD_DEEMPH1_B0, 0x00000000}, + {AUD_DEEMPH1_A1, 0x00000000}, + {AUD_DEEMPH1_B1, 0x00000000}, + {AUD_OUT0_SEL, 0x0000003f}, + {AUD_OUT1_SEL, 0x0000003f}, + {AUD_DN1_AFC, 0x00000002}, + {AUD_DCOC_0_SHIFT_IN0, 0x0000000a}, + {AUD_DCOC_0_SHIFT_IN1, 0x00000008}, + {AUD_DCOC_1_SHIFT_IN0, 0x0000000a}, + {AUD_DCOC_1_SHIFT_IN1, 0x00000008}, + {AUD_IIR1_0_SEL, 0x0000001d}, + {AUD_IIR1_2_SEL, 0x0000001e}, + {AUD_IIR2_1_SEL, 0x00000002}, + {AUD_IIR2_2_SEL, 0x00000004}, + {AUD_IIR3_2_SEL, 0x0000000f}, + {AUD_DCOC2_SHIFT, 0x00000001}, + {AUD_IIR3_2_SHIFT, 0x00000001}, + {AUD_DEEMPH0_SRC_SEL, 0x00000014}, + {AUD_CORDIC_SHIFT_1, 0x00000006}, + {AUD_POLY0_DDS_CONSTANT, 0x000e4db2}, + {AUD_DMD_RA_DDS, 0x00f696e6}, + {AUD_IIR2_3_SEL, 0x00000025}, + {AUD_IIR1_4_SEL, 0x00000021}, + {AUD_DN1_FREQ, 0x0000c965}, + {AUD_DCOC_PASS_IN, 0x00000003}, + {AUD_DCOC_0_SRC, 0x0000001a}, + {AUD_DCOC_1_SRC, 0x0000001b}, + {AUD_DCOC1_SHIFT, 0x00000000}, + {AUD_RDSI_SEL, 0x00000009}, + {AUD_RDSQ_SEL, 0x00000009}, + {AUD_RDSI_SHIFT, 0x00000000}, + {AUD_RDSQ_SHIFT, 0x00000000}, + {AUD_POLYPH80SCALEFAC, 0x00000003}, + { /* end of list */ }, + }; + + mode |= EN_FMRADIO_EN_RDS; + + if (sap) { + dprintk("%s SAP (status: unknown)\n", __func__); + set_audio_start(core, SEL_SAP); + set_audio_registers(core, btsc_sap); + set_audio_finish(core, mode); + } else { + dprintk("%s (status: known-good)\n", __func__); + set_audio_start(core, SEL_BTSC); + set_audio_registers(core, btsc); + set_audio_finish(core, mode); + } +} + +static void set_audio_standard_NICAM(struct cx88_core *core, u32 mode) +{ + static const struct rlist nicam_l[] = { + {AUD_AFE_12DB_EN, 0x00000001}, + {AUD_RATE_ADJ1, 0x00000060}, + {AUD_RATE_ADJ2, 0x000000F9}, + {AUD_RATE_ADJ3, 0x000001CC}, + {AUD_RATE_ADJ4, 0x000002B3}, + {AUD_RATE_ADJ5, 0x00000726}, + {AUD_DEEMPHDENOM1_R, 0x0000F3D0}, + {AUD_DEEMPHDENOM2_R, 0x00000000}, + {AUD_ERRLOGPERIOD_R, 0x00000064}, + {AUD_ERRINTRPTTHSHLD1_R, 0x00000FFF}, + {AUD_ERRINTRPTTHSHLD2_R, 0x0000001F}, + {AUD_ERRINTRPTTHSHLD3_R, 0x0000000F}, + {AUD_POLYPH80SCALEFAC, 0x00000003}, + {AUD_DMD_RA_DDS, 0x00C00000}, + {AUD_PLL_INT, 0x0000001E}, + {AUD_PLL_DDS, 0x00000000}, + {AUD_PLL_FRAC, 0x0000E542}, + {AUD_START_TIMER, 0x00000000}, + {AUD_DEEMPHNUMER1_R, 0x000353DE}, + {AUD_DEEMPHNUMER2_R, 0x000001B1}, + {AUD_PDF_DDS_CNST_BYTE2, 0x06}, + {AUD_PDF_DDS_CNST_BYTE1, 0x82}, + {AUD_PDF_DDS_CNST_BYTE0, 0x12}, + {AUD_QAM_MODE, 0x05}, + {AUD_PHACC_FREQ_8MSB, 0x34}, + {AUD_PHACC_FREQ_8LSB, 0x4C}, + {AUD_DEEMPHGAIN_R, 0x00006680}, + {AUD_RATE_THRES_DMD, 0x000000C0}, + { /* end of list */ }, + }; + + static const struct rlist nicam_bgdki_common[] = { + {AUD_AFE_12DB_EN, 0x00000001}, + {AUD_RATE_ADJ1, 0x00000010}, + {AUD_RATE_ADJ2, 0x00000040}, + {AUD_RATE_ADJ3, 0x00000100}, + {AUD_RATE_ADJ4, 0x00000400}, + {AUD_RATE_ADJ5, 0x00001000}, + {AUD_ERRLOGPERIOD_R, 0x00000fff}, + {AUD_ERRINTRPTTHSHLD1_R, 0x000003ff}, + {AUD_ERRINTRPTTHSHLD2_R, 0x000000ff}, + {AUD_ERRINTRPTTHSHLD3_R, 0x0000003f}, + {AUD_POLYPH80SCALEFAC, 0x00000003}, + {AUD_DEEMPHGAIN_R, 0x000023c2}, + {AUD_DEEMPHNUMER1_R, 0x0002a7bc}, + {AUD_DEEMPHNUMER2_R, 0x0003023e}, + {AUD_DEEMPHDENOM1_R, 0x0000f3d0}, + {AUD_DEEMPHDENOM2_R, 0x00000000}, + {AUD_PDF_DDS_CNST_BYTE2, 0x06}, + {AUD_PDF_DDS_CNST_BYTE1, 0x82}, + {AUD_QAM_MODE, 0x05}, + { /* end of list */ }, + }; + + static const struct rlist nicam_i[] = { + {AUD_PDF_DDS_CNST_BYTE0, 0x12}, + {AUD_PHACC_FREQ_8MSB, 0x3a}, + {AUD_PHACC_FREQ_8LSB, 0x93}, + { /* end of list */ }, + }; + + static const struct rlist nicam_default[] = { + {AUD_PDF_DDS_CNST_BYTE0, 0x16}, + {AUD_PHACC_FREQ_8MSB, 0x34}, + {AUD_PHACC_FREQ_8LSB, 0x4c}, + { /* end of list */ }, + }; + + set_audio_start(core, SEL_NICAM); + switch (core->tvaudio) { + case WW_L: + dprintk("%s SECAM-L NICAM (status: devel)\n", __func__); + set_audio_registers(core, nicam_l); + break; + case WW_I: + dprintk("%s PAL-I NICAM (status: known-good)\n", __func__); + set_audio_registers(core, nicam_bgdki_common); + set_audio_registers(core, nicam_i); + break; + case WW_NONE: + case WW_BTSC: + case WW_BG: + case WW_DK: + case WW_EIAJ: + case WW_I2SPT: + case WW_FM: + case WW_I2SADC: + case WW_M: + dprintk("%s PAL-BGDK NICAM (status: known-good)\n", __func__); + set_audio_registers(core, nicam_bgdki_common); + set_audio_registers(core, nicam_default); + break; + } + + mode |= EN_DMTRX_LR | EN_DMTRX_BYPASS; + set_audio_finish(core, mode); +} + +static void set_audio_standard_A2(struct cx88_core *core, u32 mode) +{ + static const struct rlist a2_bgdk_common[] = { + {AUD_ERRLOGPERIOD_R, 0x00000064}, + {AUD_ERRINTRPTTHSHLD1_R, 0x00000fff}, + {AUD_ERRINTRPTTHSHLD2_R, 0x0000001f}, + {AUD_ERRINTRPTTHSHLD3_R, 0x0000000f}, + {AUD_PDF_DDS_CNST_BYTE2, 0x06}, + {AUD_PDF_DDS_CNST_BYTE1, 0x82}, + {AUD_PDF_DDS_CNST_BYTE0, 0x12}, + {AUD_QAM_MODE, 0x05}, + {AUD_PHACC_FREQ_8MSB, 0x34}, + {AUD_PHACC_FREQ_8LSB, 0x4c}, + {AUD_RATE_ADJ1, 0x00000100}, + {AUD_RATE_ADJ2, 0x00000200}, + {AUD_RATE_ADJ3, 0x00000300}, + {AUD_RATE_ADJ4, 0x00000400}, + {AUD_RATE_ADJ5, 0x00000500}, + {AUD_THR_FR, 0x00000000}, + {AAGC_HYST, 0x0000001a}, + {AUD_PILOT_BQD_1_K0, 0x0000755b}, + {AUD_PILOT_BQD_1_K1, 0x00551340}, + {AUD_PILOT_BQD_1_K2, 0x006d30be}, + {AUD_PILOT_BQD_1_K3, 0xffd394af}, + {AUD_PILOT_BQD_1_K4, 0x00400000}, + {AUD_PILOT_BQD_2_K0, 0x00040000}, + {AUD_PILOT_BQD_2_K1, 0x002a4841}, + {AUD_PILOT_BQD_2_K2, 0x00400000}, + {AUD_PILOT_BQD_2_K3, 0x00000000}, + {AUD_PILOT_BQD_2_K4, 0x00000000}, + {AUD_MODE_CHG_TIMER, 0x00000040}, + {AUD_AFE_12DB_EN, 0x00000001}, + {AUD_CORDIC_SHIFT_0, 0x00000007}, + {AUD_CORDIC_SHIFT_1, 0x00000007}, + {AUD_DEEMPH0_G0, 0x00000380}, + {AUD_DEEMPH1_G0, 0x00000380}, + {AUD_DCOC_0_SRC, 0x0000001a}, + {AUD_DCOC0_SHIFT, 0x00000000}, + {AUD_DCOC_0_SHIFT_IN0, 0x0000000a}, + {AUD_DCOC_0_SHIFT_IN1, 0x00000008}, + {AUD_DCOC_PASS_IN, 0x00000003}, + {AUD_IIR3_0_SEL, 0x00000021}, + {AUD_DN2_AFC, 0x00000002}, + {AUD_DCOC_1_SRC, 0x0000001b}, + {AUD_DCOC1_SHIFT, 0x00000000}, + {AUD_DCOC_1_SHIFT_IN0, 0x0000000a}, + {AUD_DCOC_1_SHIFT_IN1, 0x00000008}, + {AUD_IIR3_1_SEL, 0x00000023}, + {AUD_RDSI_SEL, 0x00000017}, + {AUD_RDSI_SHIFT, 0x00000000}, + {AUD_RDSQ_SEL, 0x00000017}, + {AUD_RDSQ_SHIFT, 0x00000000}, + {AUD_PLL_INT, 0x0000001e}, + {AUD_PLL_DDS, 0x00000000}, + {AUD_PLL_FRAC, 0x0000e542}, + {AUD_POLYPH80SCALEFAC, 0x00000001}, + {AUD_START_TIMER, 0x00000000}, + { /* end of list */ }, + }; + + static const struct rlist a2_bg[] = { + {AUD_DMD_RA_DDS, 0x002a4f2f}, + {AUD_C1_UP_THR, 0x00007000}, + {AUD_C1_LO_THR, 0x00005400}, + {AUD_C2_UP_THR, 0x00005400}, + {AUD_C2_LO_THR, 0x00003000}, + { /* end of list */ }, + }; + + static const struct rlist a2_dk[] = { + {AUD_DMD_RA_DDS, 0x002a4f2f}, + {AUD_C1_UP_THR, 0x00007000}, + {AUD_C1_LO_THR, 0x00005400}, + {AUD_C2_UP_THR, 0x00005400}, + {AUD_C2_LO_THR, 0x00003000}, + {AUD_DN0_FREQ, 0x00003a1c}, + {AUD_DN2_FREQ, 0x0000d2e0}, + { /* end of list */ }, + }; + + static const struct rlist a1_i[] = { + {AUD_ERRLOGPERIOD_R, 0x00000064}, + {AUD_ERRINTRPTTHSHLD1_R, 0x00000fff}, + {AUD_ERRINTRPTTHSHLD2_R, 0x0000001f}, + {AUD_ERRINTRPTTHSHLD3_R, 0x0000000f}, + {AUD_PDF_DDS_CNST_BYTE2, 0x06}, + {AUD_PDF_DDS_CNST_BYTE1, 0x82}, + {AUD_PDF_DDS_CNST_BYTE0, 0x12}, + {AUD_QAM_MODE, 0x05}, + {AUD_PHACC_FREQ_8MSB, 0x3a}, + {AUD_PHACC_FREQ_8LSB, 0x93}, + {AUD_DMD_RA_DDS, 0x002a4f2f}, + {AUD_PLL_INT, 0x0000001e}, + {AUD_PLL_DDS, 0x00000004}, + {AUD_PLL_FRAC, 0x0000e542}, + {AUD_RATE_ADJ1, 0x00000100}, + {AUD_RATE_ADJ2, 0x00000200}, + {AUD_RATE_ADJ3, 0x00000300}, + {AUD_RATE_ADJ4, 0x00000400}, + {AUD_RATE_ADJ5, 0x00000500}, + {AUD_THR_FR, 0x00000000}, + {AUD_PILOT_BQD_1_K0, 0x0000755b}, + {AUD_PILOT_BQD_1_K1, 0x00551340}, + {AUD_PILOT_BQD_1_K2, 0x006d30be}, + {AUD_PILOT_BQD_1_K3, 0xffd394af}, + {AUD_PILOT_BQD_1_K4, 0x00400000}, + {AUD_PILOT_BQD_2_K0, 0x00040000}, + {AUD_PILOT_BQD_2_K1, 0x002a4841}, + {AUD_PILOT_BQD_2_K2, 0x00400000}, + {AUD_PILOT_BQD_2_K3, 0x00000000}, + {AUD_PILOT_BQD_2_K4, 0x00000000}, + {AUD_MODE_CHG_TIMER, 0x00000060}, + {AUD_AFE_12DB_EN, 0x00000001}, + {AAGC_HYST, 0x0000000a}, + {AUD_CORDIC_SHIFT_0, 0x00000007}, + {AUD_CORDIC_SHIFT_1, 0x00000007}, + {AUD_C1_UP_THR, 0x00007000}, + {AUD_C1_LO_THR, 0x00005400}, + {AUD_C2_UP_THR, 0x00005400}, + {AUD_C2_LO_THR, 0x00003000}, + {AUD_DCOC_0_SRC, 0x0000001a}, + {AUD_DCOC0_SHIFT, 0x00000000}, + {AUD_DCOC_0_SHIFT_IN0, 0x0000000a}, + {AUD_DCOC_0_SHIFT_IN1, 0x00000008}, + {AUD_DCOC_PASS_IN, 0x00000003}, + {AUD_IIR3_0_SEL, 0x00000021}, + {AUD_DN2_AFC, 0x00000002}, + {AUD_DCOC_1_SRC, 0x0000001b}, + {AUD_DCOC1_SHIFT, 0x00000000}, + {AUD_DCOC_1_SHIFT_IN0, 0x0000000a}, + {AUD_DCOC_1_SHIFT_IN1, 0x00000008}, + {AUD_IIR3_1_SEL, 0x00000023}, + {AUD_DN0_FREQ, 0x000035a3}, + {AUD_DN2_FREQ, 0x000029c7}, + {AUD_CRDC0_SRC_SEL, 0x00000511}, + {AUD_IIR1_0_SEL, 0x00000001}, + {AUD_IIR1_1_SEL, 0x00000000}, + {AUD_IIR3_2_SEL, 0x00000003}, + {AUD_IIR3_2_SHIFT, 0x00000000}, + {AUD_IIR3_0_SEL, 0x00000002}, + {AUD_IIR2_0_SEL, 0x00000021}, + {AUD_IIR2_0_SHIFT, 0x00000002}, + {AUD_DEEMPH0_SRC_SEL, 0x0000000b}, + {AUD_DEEMPH1_SRC_SEL, 0x0000000b}, + {AUD_POLYPH80SCALEFAC, 0x00000001}, + {AUD_START_TIMER, 0x00000000}, + { /* end of list */ }, + }; + + static const struct rlist am_l[] = { + {AUD_ERRLOGPERIOD_R, 0x00000064}, + {AUD_ERRINTRPTTHSHLD1_R, 0x00000FFF}, + {AUD_ERRINTRPTTHSHLD2_R, 0x0000001F}, + {AUD_ERRINTRPTTHSHLD3_R, 0x0000000F}, + {AUD_PDF_DDS_CNST_BYTE2, 0x48}, + {AUD_PDF_DDS_CNST_BYTE1, 0x3D}, + {AUD_QAM_MODE, 0x00}, + {AUD_PDF_DDS_CNST_BYTE0, 0xf5}, + {AUD_PHACC_FREQ_8MSB, 0x3a}, + {AUD_PHACC_FREQ_8LSB, 0x4a}, + {AUD_DEEMPHGAIN_R, 0x00006680}, + {AUD_DEEMPHNUMER1_R, 0x000353DE}, + {AUD_DEEMPHNUMER2_R, 0x000001B1}, + {AUD_DEEMPHDENOM1_R, 0x0000F3D0}, + {AUD_DEEMPHDENOM2_R, 0x00000000}, + {AUD_FM_MODE_ENABLE, 0x00000007}, + {AUD_POLYPH80SCALEFAC, 0x00000003}, + {AUD_AFE_12DB_EN, 0x00000001}, + {AAGC_GAIN, 0x00000000}, + {AAGC_HYST, 0x00000018}, + {AAGC_DEF, 0x00000020}, + {AUD_DN0_FREQ, 0x00000000}, + {AUD_POLY0_DDS_CONSTANT, 0x000E4DB2}, + {AUD_DCOC_0_SRC, 0x00000021}, + {AUD_IIR1_0_SEL, 0x00000000}, + {AUD_IIR1_0_SHIFT, 0x00000007}, + {AUD_IIR1_1_SEL, 0x00000002}, + {AUD_IIR1_1_SHIFT, 0x00000000}, + {AUD_DCOC_1_SRC, 0x00000003}, + {AUD_DCOC1_SHIFT, 0x00000000}, + {AUD_DCOC_PASS_IN, 0x00000000}, + {AUD_IIR1_2_SEL, 0x00000023}, + {AUD_IIR1_2_SHIFT, 0x00000000}, + {AUD_IIR1_3_SEL, 0x00000004}, + {AUD_IIR1_3_SHIFT, 0x00000007}, + {AUD_IIR1_4_SEL, 0x00000005}, + {AUD_IIR1_4_SHIFT, 0x00000007}, + {AUD_IIR3_0_SEL, 0x00000007}, + {AUD_IIR3_0_SHIFT, 0x00000000}, + {AUD_DEEMPH0_SRC_SEL, 0x00000011}, + {AUD_DEEMPH0_SHIFT, 0x00000000}, + {AUD_DEEMPH0_G0, 0x00007000}, + {AUD_DEEMPH0_A0, 0x00000000}, + {AUD_DEEMPH0_B0, 0x00000000}, + {AUD_DEEMPH0_A1, 0x00000000}, + {AUD_DEEMPH0_B1, 0x00000000}, + {AUD_DEEMPH1_SRC_SEL, 0x00000011}, + {AUD_DEEMPH1_SHIFT, 0x00000000}, + {AUD_DEEMPH1_G0, 0x00007000}, + {AUD_DEEMPH1_A0, 0x00000000}, + {AUD_DEEMPH1_B0, 0x00000000}, + {AUD_DEEMPH1_A1, 0x00000000}, + {AUD_DEEMPH1_B1, 0x00000000}, + {AUD_OUT0_SEL, 0x0000003F}, + {AUD_OUT1_SEL, 0x0000003F}, + {AUD_DMD_RA_DDS, 0x00F5C285}, + {AUD_PLL_INT, 0x0000001E}, + {AUD_PLL_DDS, 0x00000000}, + {AUD_PLL_FRAC, 0x0000E542}, + {AUD_RATE_ADJ1, 0x00000100}, + {AUD_RATE_ADJ2, 0x00000200}, + {AUD_RATE_ADJ3, 0x00000300}, + {AUD_RATE_ADJ4, 0x00000400}, + {AUD_RATE_ADJ5, 0x00000500}, + {AUD_RATE_THRES_DMD, 0x000000C0}, + { /* end of list */ }, + }; + + static const struct rlist a2_deemph50[] = { + {AUD_DEEMPH0_G0, 0x00000380}, + {AUD_DEEMPH1_G0, 0x00000380}, + {AUD_DEEMPHGAIN_R, 0x000011e1}, + {AUD_DEEMPHNUMER1_R, 0x0002a7bc}, + {AUD_DEEMPHNUMER2_R, 0x0003023c}, + { /* end of list */ }, + }; + + set_audio_start(core, SEL_A2); + switch (core->tvaudio) { + case WW_BG: + dprintk("%s PAL-BG A1/2 (status: known-good)\n", __func__); + set_audio_registers(core, a2_bgdk_common); + set_audio_registers(core, a2_bg); + set_audio_registers(core, a2_deemph50); + break; + case WW_DK: + dprintk("%s PAL-DK A1/2 (status: known-good)\n", __func__); + set_audio_registers(core, a2_bgdk_common); + set_audio_registers(core, a2_dk); + set_audio_registers(core, a2_deemph50); + break; + case WW_I: + dprintk("%s PAL-I A1 (status: known-good)\n", __func__); + set_audio_registers(core, a1_i); + set_audio_registers(core, a2_deemph50); + break; + case WW_L: + dprintk("%s AM-L (status: devel)\n", __func__); + set_audio_registers(core, am_l); + break; + case WW_NONE: + case WW_BTSC: + case WW_EIAJ: + case WW_I2SPT: + case WW_FM: + case WW_I2SADC: + case WW_M: + dprintk("%s Warning: wrong value\n", __func__); + return; + } + + mode |= EN_FMRADIO_EN_RDS | EN_DMTRX_SUMDIFF; + set_audio_finish(core, mode); +} + +static void set_audio_standard_EIAJ(struct cx88_core *core) +{ + static const struct rlist eiaj[] = { + /* TODO: eiaj register settings are not there yet ... */ + + { /* end of list */ }, + }; + dprintk("%s (status: unknown)\n", __func__); + + set_audio_start(core, SEL_EIAJ); + set_audio_registers(core, eiaj); + set_audio_finish(core, EN_EIAJ_AUTO_STEREO); +} + +static void set_audio_standard_FM(struct cx88_core *core, + enum cx88_deemph_type deemph) +{ + static const struct rlist fm_deemph_50[] = { + {AUD_DEEMPH0_G0, 0x0C45}, + {AUD_DEEMPH0_A0, 0x6262}, + {AUD_DEEMPH0_B0, 0x1C29}, + {AUD_DEEMPH0_A1, 0x3FC66}, + {AUD_DEEMPH0_B1, 0x399A}, + + {AUD_DEEMPH1_G0, 0x0D80}, + {AUD_DEEMPH1_A0, 0x6262}, + {AUD_DEEMPH1_B0, 0x1C29}, + {AUD_DEEMPH1_A1, 0x3FC66}, + {AUD_DEEMPH1_B1, 0x399A}, + + {AUD_POLYPH80SCALEFAC, 0x0003}, + { /* end of list */ }, + }; + static const struct rlist fm_deemph_75[] = { + {AUD_DEEMPH0_G0, 0x091B}, + {AUD_DEEMPH0_A0, 0x6B68}, + {AUD_DEEMPH0_B0, 0x11EC}, + {AUD_DEEMPH0_A1, 0x3FC66}, + {AUD_DEEMPH0_B1, 0x399A}, + + {AUD_DEEMPH1_G0, 0x0AA0}, + {AUD_DEEMPH1_A0, 0x6B68}, + {AUD_DEEMPH1_B0, 0x11EC}, + {AUD_DEEMPH1_A1, 0x3FC66}, + {AUD_DEEMPH1_B1, 0x399A}, + + {AUD_POLYPH80SCALEFAC, 0x0003}, + { /* end of list */ }, + }; + + /* + * It is enough to leave default values? + * + * No, it's not! The deemphasis registers are reset to the 75us + * values by default. Analyzing the spectrum of the decoded audio + * reveals that "no deemphasis" is the same as 75 us, while the 50 us + * setting results in less deemphasis. + */ + static const struct rlist fm_no_deemph[] = { + {AUD_POLYPH80SCALEFAC, 0x0003}, + { /* end of list */ }, + }; + + dprintk("%s (status: unknown)\n", __func__); + set_audio_start(core, SEL_FMRADIO); + + switch (deemph) { + default: + case FM_NO_DEEMPH: + set_audio_registers(core, fm_no_deemph); + break; + + case FM_DEEMPH_50: + set_audio_registers(core, fm_deemph_50); + break; + + case FM_DEEMPH_75: + set_audio_registers(core, fm_deemph_75); + break; + } + + set_audio_finish(core, EN_FMRADIO_AUTO_STEREO); +} + +/* ----------------------------------------------------------- */ + +static int cx88_detect_nicam(struct cx88_core *core) +{ + int i, j = 0; + + dprintk("start nicam autodetect.\n"); + + for (i = 0; i < 6; i++) { + /* if bit1=1 then nicam is detected */ + j += ((cx_read(AUD_NICAM_STATUS2) & 0x02) >> 1); + + if (j == 1) { + dprintk("nicam is detected.\n"); + return 1; + } + + /* wait a little bit for next reading status */ + usleep_range(10000, 20000); + } + + dprintk("nicam is not detected.\n"); + return 0; +} + +void cx88_set_tvaudio(struct cx88_core *core) +{ + switch (core->tvaudio) { + case WW_BTSC: + set_audio_standard_BTSC(core, 0, EN_BTSC_AUTO_STEREO); + break; + case WW_BG: + case WW_DK: + case WW_M: + case WW_I: + case WW_L: + /* prepare all dsp registers */ + set_audio_standard_A2(core, EN_A2_FORCE_MONO1); + + /* + * set nicam mode - otherwise + * AUD_NICAM_STATUS2 contains wrong values + */ + set_audio_standard_NICAM(core, EN_NICAM_AUTO_STEREO); + if (cx88_detect_nicam(core) == 0) { + /* fall back to fm / am mono */ + set_audio_standard_A2(core, EN_A2_FORCE_MONO1); + core->audiomode_current = V4L2_TUNER_MODE_MONO; + core->use_nicam = 0; + } else { + core->use_nicam = 1; + } + break; + case WW_EIAJ: + set_audio_standard_EIAJ(core); + break; + case WW_FM: + set_audio_standard_FM(core, radio_deemphasis); + break; + case WW_I2SADC: + set_audio_start(core, 0x01); + /* + * Slave/Philips/Autobaud + * NB on Nova-S bit1 NPhilipsSony appears to be inverted: + * 0= Sony, 1=Philips + */ + cx_write(AUD_I2SINPUTCNTL, core->board.i2sinputcntl); + /* Switch to "I2S ADC mode" */ + cx_write(AUD_I2SCNTL, 0x1); + set_audio_finish(core, EN_I2SIN_ENABLE); + break; + case WW_NONE: + case WW_I2SPT: + pr_info("unknown tv audio mode [%d]\n", core->tvaudio); + break; + } +} +EXPORT_SYMBOL(cx88_set_tvaudio); + +void cx88_newstation(struct cx88_core *core) +{ + core->audiomode_manual = UNSET; + core->last_change = jiffies; +} +EXPORT_SYMBOL(cx88_newstation); + +void cx88_get_stereo(struct cx88_core *core, struct v4l2_tuner *t) +{ + static const char * const m[] = { "stereo", "dual mono", + "mono", "sap" }; + static const char * const p[] = { "no pilot", "pilot c1", + "pilot c2", "?" }; + u32 reg, mode, pilot; + + reg = cx_read(AUD_STATUS); + mode = reg & 0x03; + pilot = (reg >> 2) & 0x03; + + if (core->astat != reg) + dprintk("AUD_STATUS: 0x%x [%s/%s] ctl=%s\n", + reg, m[mode], p[pilot], + aud_ctl_names[cx_read(AUD_CTL) & 63]); + core->astat = reg; + + t->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_SAP | + V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2; + t->rxsubchans = UNSET; + t->audmode = V4L2_TUNER_MODE_MONO; + + switch (mode) { + case 0: + t->audmode = V4L2_TUNER_MODE_STEREO; + break; + case 1: + t->audmode = V4L2_TUNER_MODE_LANG2; + break; + case 2: + t->audmode = V4L2_TUNER_MODE_MONO; + break; + case 3: + t->audmode = V4L2_TUNER_MODE_SAP; + break; + } + + switch (core->tvaudio) { + case WW_BTSC: + case WW_BG: + case WW_DK: + case WW_M: + case WW_EIAJ: + if (!core->use_nicam) { + t->rxsubchans = cx88_dsp_detect_stereo_sap(core); + break; + } + break; + case WW_NONE: + case WW_I: + case WW_L: + case WW_I2SPT: + case WW_FM: + case WW_I2SADC: + /* nothing */ + break; + } + + /* If software stereo detection is not supported... */ + if (t->rxsubchans == UNSET) { + t->rxsubchans = V4L2_TUNER_SUB_MONO; + /* + * If the hardware itself detected stereo, also return + * stereo as an available subchannel + */ + if (t->audmode == V4L2_TUNER_MODE_STEREO) + t->rxsubchans |= V4L2_TUNER_SUB_STEREO; + } +} +EXPORT_SYMBOL(cx88_get_stereo); + + +void cx88_set_stereo(struct cx88_core *core, u32 mode, int manual) +{ + u32 ctl = UNSET; + u32 mask = UNSET; + + if (manual) { + core->audiomode_manual = mode; + } else { + if (core->audiomode_manual != UNSET) + return; + } + core->audiomode_current = mode; + + switch (core->tvaudio) { + case WW_BTSC: + switch (mode) { + case V4L2_TUNER_MODE_MONO: + set_audio_standard_BTSC(core, 0, EN_BTSC_FORCE_MONO); + break; + case V4L2_TUNER_MODE_LANG1: + set_audio_standard_BTSC(core, 0, EN_BTSC_AUTO_STEREO); + break; + case V4L2_TUNER_MODE_LANG2: + set_audio_standard_BTSC(core, 1, EN_BTSC_FORCE_SAP); + break; + case V4L2_TUNER_MODE_STEREO: + case V4L2_TUNER_MODE_LANG1_LANG2: + set_audio_standard_BTSC(core, 0, EN_BTSC_FORCE_STEREO); + break; + } + break; + case WW_BG: + case WW_DK: + case WW_M: + case WW_I: + case WW_L: + if (core->use_nicam == 1) { + switch (mode) { + case V4L2_TUNER_MODE_MONO: + case V4L2_TUNER_MODE_LANG1: + set_audio_standard_NICAM(core, + EN_NICAM_FORCE_MONO1); + break; + case V4L2_TUNER_MODE_LANG2: + set_audio_standard_NICAM(core, + EN_NICAM_FORCE_MONO2); + break; + case V4L2_TUNER_MODE_STEREO: + case V4L2_TUNER_MODE_LANG1_LANG2: + set_audio_standard_NICAM(core, + EN_NICAM_FORCE_STEREO); + break; + } + } else { + if ((core->tvaudio == WW_I) || + (core->tvaudio == WW_L)) { + /* fall back to fm / am mono */ + set_audio_standard_A2(core, EN_A2_FORCE_MONO1); + } else { + /* TODO: Add A2 autodection */ + mask = 0x3f; + switch (mode) { + case V4L2_TUNER_MODE_MONO: + case V4L2_TUNER_MODE_LANG1: + ctl = EN_A2_FORCE_MONO1; + break; + case V4L2_TUNER_MODE_LANG2: + ctl = EN_A2_FORCE_MONO2; + break; + case V4L2_TUNER_MODE_STEREO: + case V4L2_TUNER_MODE_LANG1_LANG2: + ctl = EN_A2_FORCE_STEREO; + break; + } + } + } + break; + case WW_FM: + switch (mode) { + case V4L2_TUNER_MODE_MONO: + ctl = EN_FMRADIO_FORCE_MONO; + mask = 0x3f; + break; + case V4L2_TUNER_MODE_STEREO: + ctl = EN_FMRADIO_AUTO_STEREO; + mask = 0x3f; + break; + } + break; + case WW_I2SADC: + case WW_NONE: + case WW_EIAJ: + case WW_I2SPT: + /* DO NOTHING */ + break; + } + + if (ctl != UNSET) { + dprintk("cx88_set_stereo: mask 0x%x, ctl 0x%x [status=0x%x,ctl=0x%x,vol=0x%x]\n", + mask, ctl, cx_read(AUD_STATUS), + cx_read(AUD_CTL), cx_sread(SHADOW_AUD_VOL_CTL)); + cx_andor(AUD_CTL, mask, ctl); + } +} +EXPORT_SYMBOL(cx88_set_stereo); + +int cx88_audio_thread(void *data) +{ + struct cx88_core *core = data; + struct v4l2_tuner t; + u32 mode = 0; + + dprintk("cx88: tvaudio thread started\n"); + set_freezable(); + for (;;) { + msleep_interruptible(1000); + if (kthread_should_stop()) + break; + try_to_freeze(); + + switch (core->tvaudio) { + case WW_BG: + case WW_DK: + case WW_M: + case WW_I: + case WW_L: + if (core->use_nicam) + goto hw_autodetect; + + /* just monitor the audio status for now ... */ + memset(&t, 0, sizeof(t)); + cx88_get_stereo(core, &t); + + if (core->audiomode_manual != UNSET) + /* manually set, don't do anything. */ + continue; + + /* monitor signal and set stereo if available */ + if (t.rxsubchans & V4L2_TUNER_SUB_STEREO) + mode = V4L2_TUNER_MODE_STEREO; + else + mode = V4L2_TUNER_MODE_MONO; + if (mode == core->audiomode_current) + continue; + /* automatically switch to best available mode */ + cx88_set_stereo(core, mode, 0); + break; + case WW_NONE: + case WW_BTSC: + case WW_EIAJ: + case WW_I2SPT: + case WW_FM: + case WW_I2SADC: +hw_autodetect: + /* + * stereo autodetection is supported by hardware so + * we don't need to do it manually. Do nothing. + */ + break; + } + } + + dprintk("cx88: tvaudio thread exiting\n"); + return 0; +} +EXPORT_SYMBOL(cx88_audio_thread); diff --git a/drivers/media/pci/cx88/cx88-vbi.c b/drivers/media/pci/cx88/cx88-vbi.c new file mode 100644 index 000000000..e3e379e6f --- /dev/null +++ b/drivers/media/pci/cx88/cx88-vbi.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + */ + +#include "cx88.h" + +#include +#include +#include + +static unsigned int vbi_debug; +module_param(vbi_debug, int, 0644); +MODULE_PARM_DESC(vbi_debug, "enable debug messages [vbi]"); + +#define dprintk(level, fmt, arg...) do { \ + if (vbi_debug >= level) \ + printk(KERN_DEBUG pr_fmt("%s: vbi:" fmt), \ + __func__, ##arg); \ +} while (0) + +/* ------------------------------------------------------------------ */ + +int cx8800_vbi_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx8800_dev *dev = video_drvdata(file); + + f->fmt.vbi.samples_per_line = VBI_LINE_LENGTH; + f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + f->fmt.vbi.offset = 244; + + if (dev->core->tvnorm & V4L2_STD_525_60) { + /* ntsc */ + f->fmt.vbi.sampling_rate = 28636363; + f->fmt.vbi.start[0] = 10; + f->fmt.vbi.start[1] = 273; + f->fmt.vbi.count[0] = VBI_LINE_NTSC_COUNT; + f->fmt.vbi.count[1] = VBI_LINE_NTSC_COUNT; + + } else if (dev->core->tvnorm & V4L2_STD_625_50) { + /* pal */ + f->fmt.vbi.sampling_rate = 35468950; + f->fmt.vbi.start[0] = V4L2_VBI_ITU_625_F1_START + 5; + f->fmt.vbi.start[1] = V4L2_VBI_ITU_625_F2_START + 5; + f->fmt.vbi.count[0] = VBI_LINE_PAL_COUNT; + f->fmt.vbi.count[1] = VBI_LINE_PAL_COUNT; + } + return 0; +} + +static int cx8800_start_vbi_dma(struct cx8800_dev *dev, + struct cx88_dmaqueue *q, + struct cx88_buffer *buf) +{ + struct cx88_core *core = dev->core; + + /* setup fifo + format */ + cx88_sram_channel_setup(dev->core, &cx88_sram_channels[SRAM_CH24], + VBI_LINE_LENGTH, buf->risc.dma); + + cx_write(MO_VBOS_CONTROL, (1 << 18) | /* comb filter delay fixup */ + (1 << 15) | /* enable vbi capture */ + (1 << 11)); + + /* reset counter */ + cx_write(MO_VBI_GPCNTRL, GP_COUNT_CONTROL_RESET); + q->count = 0; + + /* enable irqs */ + cx_set(MO_PCI_INTMSK, core->pci_irqmask | PCI_INT_VIDINT); + cx_set(MO_VID_INTMSK, 0x0f0088); + + /* enable capture */ + cx_set(VID_CAPTURE_CONTROL, 0x18); + + /* start dma */ + cx_set(MO_DEV_CNTRL2, (1 << 5)); + cx_set(MO_VID_DMACNTRL, 0x88); + + return 0; +} + +void cx8800_stop_vbi_dma(struct cx8800_dev *dev) +{ + struct cx88_core *core = dev->core; + + /* stop dma */ + cx_clear(MO_VID_DMACNTRL, 0x88); + + /* disable capture */ + cx_clear(VID_CAPTURE_CONTROL, 0x18); + + /* disable irqs */ + cx_clear(MO_PCI_INTMSK, PCI_INT_VIDINT); + cx_clear(MO_VID_INTMSK, 0x0f0088); +} + +int cx8800_restart_vbi_queue(struct cx8800_dev *dev, + struct cx88_dmaqueue *q) +{ + struct cx88_buffer *buf; + + if (list_empty(&q->active)) + return 0; + + buf = list_entry(q->active.next, struct cx88_buffer, list); + dprintk(2, "restart_queue [%p/%d]: restart dma\n", + buf, buf->vb.vb2_buf.index); + cx8800_start_vbi_dma(dev, q, buf); + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct cx8800_dev *dev = q->drv_priv; + + *num_planes = 1; + if (dev->core->tvnorm & V4L2_STD_525_60) + sizes[0] = VBI_LINE_NTSC_COUNT * VBI_LINE_LENGTH * 2; + else + sizes[0] = VBI_LINE_PAL_COUNT * VBI_LINE_LENGTH * 2; + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx8800_dev *dev = vb->vb2_queue->drv_priv; + struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb); + struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, 0); + unsigned int lines; + unsigned int size; + + if (dev->core->tvnorm & V4L2_STD_525_60) + lines = VBI_LINE_NTSC_COUNT; + else + lines = VBI_LINE_PAL_COUNT; + size = lines * VBI_LINE_LENGTH * 2; + if (vb2_plane_size(vb, 0) < size) + return -EINVAL; + vb2_set_plane_payload(vb, 0, size); + + return cx88_risc_buffer(dev->pci, &buf->risc, sgt->sgl, + 0, VBI_LINE_LENGTH * lines, + VBI_LINE_LENGTH, 0, + lines); +} + +static void buffer_finish(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx8800_dev *dev = vb->vb2_queue->drv_priv; + struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb); + struct cx88_riscmem *risc = &buf->risc; + + if (risc->cpu) + dma_free_coherent(&dev->pci->dev, risc->size, risc->cpu, + risc->dma); + memset(risc, 0, sizeof(*risc)); +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx8800_dev *dev = vb->vb2_queue->drv_priv; + struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb); + struct cx88_buffer *prev; + struct cx88_dmaqueue *q = &dev->vbiq; + + /* add jump to start */ + buf->risc.cpu[1] = cpu_to_le32(buf->risc.dma + 8); + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma + 8); + + if (list_empty(&q->active)) { + list_add_tail(&buf->list, &q->active); + dprintk(2, "[%p/%d] vbi_queue - first active\n", + buf, buf->vb.vb2_buf.index); + + } else { + buf->risc.cpu[0] |= cpu_to_le32(RISC_IRQ1); + prev = list_entry(q->active.prev, struct cx88_buffer, list); + list_add_tail(&buf->list, &q->active); + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(2, "[%p/%d] buffer_queue - append to active\n", + buf, buf->vb.vb2_buf.index); + } +} + +static int start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct cx8800_dev *dev = q->drv_priv; + struct cx88_dmaqueue *dmaq = &dev->vbiq; + struct cx88_buffer *buf = list_entry(dmaq->active.next, + struct cx88_buffer, list); + + cx8800_start_vbi_dma(dev, dmaq, buf); + return 0; +} + +static void stop_streaming(struct vb2_queue *q) +{ + struct cx8800_dev *dev = q->drv_priv; + struct cx88_core *core = dev->core; + struct cx88_dmaqueue *dmaq = &dev->vbiq; + unsigned long flags; + + cx_clear(MO_VID_DMACNTRL, 0x11); + cx_clear(VID_CAPTURE_CONTROL, 0x06); + cx8800_stop_vbi_dma(dev); + spin_lock_irqsave(&dev->slock, flags); + while (!list_empty(&dmaq->active)) { + struct cx88_buffer *buf = list_entry(dmaq->active.next, + struct cx88_buffer, list); + + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&dev->slock, flags); +} + +const struct vb2_ops cx8800_vbi_qops = { + .queue_setup = queue_setup, + .buf_prepare = buffer_prepare, + .buf_finish = buffer_finish, + .buf_queue = buffer_queue, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, +}; diff --git a/drivers/media/pci/cx88/cx88-video.c b/drivers/media/pci/cx88/cx88-video.c new file mode 100644 index 000000000..eaa46a2f9 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-video.c @@ -0,0 +1,1630 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * device driver for Conexant 2388x based TV cards + * video4linux video interface + * + * (c) 2003-04 Gerd Knorr [SuSE Labs] + * + * (c) 2005-2006 Mauro Carvalho Chehab + * - Multituner support + * - video_ioctl2 conversion + * - PAL/M fixes + */ + +#include "cx88.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +MODULE_DESCRIPTION("v4l2 driver module for cx2388x based TV cards"); +MODULE_AUTHOR("Gerd Knorr [SuSE Labs]"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(CX88_VERSION); + +/* ------------------------------------------------------------------ */ + +static unsigned int video_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; +static unsigned int vbi_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; +static unsigned int radio_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; + +module_param_array(video_nr, int, NULL, 0444); +module_param_array(vbi_nr, int, NULL, 0444); +module_param_array(radio_nr, int, NULL, 0444); + +MODULE_PARM_DESC(video_nr, "video device numbers"); +MODULE_PARM_DESC(vbi_nr, "vbi device numbers"); +MODULE_PARM_DESC(radio_nr, "radio device numbers"); + +static unsigned int video_debug; +module_param(video_debug, int, 0644); +MODULE_PARM_DESC(video_debug, "enable debug messages [video]"); + +static unsigned int irq_debug; +module_param(irq_debug, int, 0644); +MODULE_PARM_DESC(irq_debug, "enable debug messages [IRQ handler]"); + +#define dprintk(level, fmt, arg...) do { \ + if (video_debug >= level) \ + printk(KERN_DEBUG pr_fmt("%s: video:" fmt), \ + __func__, ##arg); \ +} while (0) + +/* ------------------------------------------------------------------- */ +/* static data */ + +static const struct cx8800_fmt formats[] = { + { + .fourcc = V4L2_PIX_FMT_GREY, + .cxformat = ColorFormatY8, + .depth = 8, + .flags = FORMAT_FLAGS_PACKED, + }, { + .fourcc = V4L2_PIX_FMT_RGB555, + .cxformat = ColorFormatRGB15, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, { + .fourcc = V4L2_PIX_FMT_RGB555X, + .cxformat = ColorFormatRGB15 | ColorFormatBSWAP, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .cxformat = ColorFormatRGB16, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, { + .fourcc = V4L2_PIX_FMT_RGB565X, + .cxformat = ColorFormatRGB16 | ColorFormatBSWAP, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, { + .fourcc = V4L2_PIX_FMT_BGR24, + .cxformat = ColorFormatRGB24, + .depth = 24, + .flags = FORMAT_FLAGS_PACKED, + }, { + .fourcc = V4L2_PIX_FMT_BGR32, + .cxformat = ColorFormatRGB32, + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + }, { + .fourcc = V4L2_PIX_FMT_RGB32, + .cxformat = ColorFormatRGB32 | ColorFormatBSWAP | + ColorFormatWSWAP, + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .cxformat = ColorFormatYUY2, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .cxformat = ColorFormatYUY2 | ColorFormatBSWAP, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, +}; + +static const struct cx8800_fmt *format_by_fourcc(unsigned int fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) + if (formats[i].fourcc == fourcc) + return formats + i; + return NULL; +} + +/* ------------------------------------------------------------------- */ + +struct cx88_ctrl { + /* control information */ + u32 id; + s32 minimum; + s32 maximum; + u32 step; + s32 default_value; + + /* control register information */ + u32 off; + u32 reg; + u32 sreg; + u32 mask; + u32 shift; +}; + +static const struct cx88_ctrl cx8800_vid_ctls[] = { + /* --- video --- */ + { + .id = V4L2_CID_BRIGHTNESS, + .minimum = 0x00, + .maximum = 0xff, + .step = 1, + .default_value = 0x7f, + .off = 128, + .reg = MO_CONTR_BRIGHT, + .mask = 0x00ff, + .shift = 0, + }, { + .id = V4L2_CID_CONTRAST, + .minimum = 0, + .maximum = 0xff, + .step = 1, + .default_value = 0x3f, + .off = 0, + .reg = MO_CONTR_BRIGHT, + .mask = 0xff00, + .shift = 8, + }, { + .id = V4L2_CID_HUE, + .minimum = 0, + .maximum = 0xff, + .step = 1, + .default_value = 0x7f, + .off = 128, + .reg = MO_HUE, + .mask = 0x00ff, + .shift = 0, + }, { + /* strictly, this only describes only U saturation. + * V saturation is handled specially through code. + */ + .id = V4L2_CID_SATURATION, + .minimum = 0, + .maximum = 0xff, + .step = 1, + .default_value = 0x7f, + .off = 0, + .reg = MO_UV_SATURATION, + .mask = 0x00ff, + .shift = 0, + }, { + .id = V4L2_CID_SHARPNESS, + .minimum = 0, + .maximum = 4, + .step = 1, + .default_value = 0x0, + .off = 0, + /* + * NOTE: the value is converted and written to both even + * and odd registers in the code + */ + .reg = MO_FILTER_ODD, + .mask = 7 << 7, + .shift = 7, + }, { + .id = V4L2_CID_CHROMA_AGC, + .minimum = 0, + .maximum = 1, + .default_value = 0x1, + .reg = MO_INPUT_FORMAT, + .mask = 1 << 10, + .shift = 10, + }, { + .id = V4L2_CID_COLOR_KILLER, + .minimum = 0, + .maximum = 1, + .default_value = 0x1, + .reg = MO_INPUT_FORMAT, + .mask = 1 << 9, + .shift = 9, + }, { + .id = V4L2_CID_BAND_STOP_FILTER, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0x0, + .off = 0, + .reg = MO_HTOTAL, + .mask = 3 << 11, + .shift = 11, + } +}; + +static const struct cx88_ctrl cx8800_aud_ctls[] = { + { + /* --- audio --- */ + .id = V4L2_CID_AUDIO_MUTE, + .minimum = 0, + .maximum = 1, + .default_value = 1, + .reg = AUD_VOL_CTL, + .sreg = SHADOW_AUD_VOL_CTL, + .mask = (1 << 6), + .shift = 6, + }, { + .id = V4L2_CID_AUDIO_VOLUME, + .minimum = 0, + .maximum = 0x3f, + .step = 1, + .default_value = 0x3f, + .reg = AUD_VOL_CTL, + .sreg = SHADOW_AUD_VOL_CTL, + .mask = 0x3f, + .shift = 0, + }, { + .id = V4L2_CID_AUDIO_BALANCE, + .minimum = 0, + .maximum = 0x7f, + .step = 1, + .default_value = 0x40, + .reg = AUD_BAL_CTL, + .sreg = SHADOW_AUD_BAL_CTL, + .mask = 0x7f, + .shift = 0, + } +}; + +enum { + CX8800_VID_CTLS = ARRAY_SIZE(cx8800_vid_ctls), + CX8800_AUD_CTLS = ARRAY_SIZE(cx8800_aud_ctls), +}; + +/* ------------------------------------------------------------------ */ + +int cx88_video_mux(struct cx88_core *core, unsigned int input) +{ + /* struct cx88_core *core = dev->core; */ + + dprintk(1, "video_mux: %d [vmux=%d,gpio=0x%x,0x%x,0x%x,0x%x]\n", + input, INPUT(input).vmux, + INPUT(input).gpio0, INPUT(input).gpio1, + INPUT(input).gpio2, INPUT(input).gpio3); + core->input = input; + cx_andor(MO_INPUT_FORMAT, 0x03 << 14, INPUT(input).vmux << 14); + cx_write(MO_GP3_IO, INPUT(input).gpio3); + cx_write(MO_GP0_IO, INPUT(input).gpio0); + cx_write(MO_GP1_IO, INPUT(input).gpio1); + cx_write(MO_GP2_IO, INPUT(input).gpio2); + + switch (INPUT(input).type) { + case CX88_VMUX_SVIDEO: + cx_set(MO_AFECFG_IO, 0x00000001); + cx_set(MO_INPUT_FORMAT, 0x00010010); + cx_set(MO_FILTER_EVEN, 0x00002020); + cx_set(MO_FILTER_ODD, 0x00002020); + break; + default: + cx_clear(MO_AFECFG_IO, 0x00000001); + cx_clear(MO_INPUT_FORMAT, 0x00010010); + cx_clear(MO_FILTER_EVEN, 0x00002020); + cx_clear(MO_FILTER_ODD, 0x00002020); + break; + } + + /* + * if there are audioroutes defined, we have an external + * ADC to deal with audio + */ + if (INPUT(input).audioroute) { + /* + * The wm8775 module has the "2" route hardwired into + * the initialization. Some boards may use different + * routes for different inputs. HVR-1300 surely does + */ + if (core->sd_wm8775) { + call_all(core, audio, s_routing, + INPUT(input).audioroute, 0, 0); + } + /* + * cx2388's C-ADC is connected to the tuner only. + * When used with S-Video, that ADC is busy dealing with + * chroma, so an external must be used for baseband audio + */ + if (INPUT(input).type != CX88_VMUX_TELEVISION && + INPUT(input).type != CX88_VMUX_CABLE) { + /* "I2S ADC mode" */ + core->tvaudio = WW_I2SADC; + cx88_set_tvaudio(core); + } else { + /* Normal mode */ + cx_write(AUD_I2SCNTL, 0x0); + cx_clear(AUD_CTL, EN_I2SIN_ENABLE); + } + } + + return 0; +} +EXPORT_SYMBOL(cx88_video_mux); + +/* ------------------------------------------------------------------ */ + +static int start_video_dma(struct cx8800_dev *dev, + struct cx88_dmaqueue *q, + struct cx88_buffer *buf) +{ + struct cx88_core *core = dev->core; + + /* setup fifo + format */ + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH21], + buf->bpl, buf->risc.dma); + cx88_set_scale(core, core->width, core->height, core->field); + cx_write(MO_COLOR_CTRL, dev->fmt->cxformat | ColorFormatGamma); + + /* reset counter */ + cx_write(MO_VIDY_GPCNTRL, GP_COUNT_CONTROL_RESET); + q->count = 0; + + /* enable irqs */ + cx_set(MO_PCI_INTMSK, core->pci_irqmask | PCI_INT_VIDINT); + + /* + * Enables corresponding bits at PCI_INT_STAT: + * bits 0 to 4: video, audio, transport stream, VIP, Host + * bit 7: timer + * bits 8 and 9: DMA complete for: SRC, DST + * bits 10 and 11: BERR signal asserted for RISC: RD, WR + * bits 12 to 15: BERR signal asserted for: BRDG, SRC, DST, IPB + */ + cx_set(MO_VID_INTMSK, 0x0f0011); + + /* enable capture */ + cx_set(VID_CAPTURE_CONTROL, 0x06); + + /* start dma */ + cx_set(MO_DEV_CNTRL2, (1 << 5)); + cx_set(MO_VID_DMACNTRL, 0x11); /* Planar Y and packed FIFO and RISC enable */ + + return 0; +} + +static int __maybe_unused stop_video_dma(struct cx8800_dev *dev) +{ + struct cx88_core *core = dev->core; + + /* stop dma */ + cx_clear(MO_VID_DMACNTRL, 0x11); + + /* disable capture */ + cx_clear(VID_CAPTURE_CONTROL, 0x06); + + /* disable irqs */ + cx_clear(MO_PCI_INTMSK, PCI_INT_VIDINT); + cx_clear(MO_VID_INTMSK, 0x0f0011); + return 0; +} + +static int __maybe_unused restart_video_queue(struct cx8800_dev *dev, + struct cx88_dmaqueue *q) +{ + struct cx88_buffer *buf; + + if (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx88_buffer, list); + dprintk(2, "restart_queue [%p/%d]: restart dma\n", + buf, buf->vb.vb2_buf.index); + start_video_dma(dev, q, buf); + } + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct cx8800_dev *dev = q->drv_priv; + struct cx88_core *core = dev->core; + + *num_planes = 1; + sizes[0] = (dev->fmt->depth * core->width * core->height) >> 3; + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + int ret; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx8800_dev *dev = vb->vb2_queue->drv_priv; + struct cx88_core *core = dev->core; + struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb); + struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, 0); + + buf->bpl = core->width * dev->fmt->depth >> 3; + + if (vb2_plane_size(vb, 0) < core->height * buf->bpl) + return -EINVAL; + vb2_set_plane_payload(vb, 0, core->height * buf->bpl); + + switch (core->field) { + case V4L2_FIELD_TOP: + ret = cx88_risc_buffer(dev->pci, &buf->risc, + sgt->sgl, 0, UNSET, + buf->bpl, 0, core->height); + break; + case V4L2_FIELD_BOTTOM: + ret = cx88_risc_buffer(dev->pci, &buf->risc, + sgt->sgl, UNSET, 0, + buf->bpl, 0, core->height); + break; + case V4L2_FIELD_SEQ_TB: + ret = cx88_risc_buffer(dev->pci, &buf->risc, + sgt->sgl, + 0, buf->bpl * (core->height >> 1), + buf->bpl, 0, + core->height >> 1); + break; + case V4L2_FIELD_SEQ_BT: + ret = cx88_risc_buffer(dev->pci, &buf->risc, + sgt->sgl, + buf->bpl * (core->height >> 1), 0, + buf->bpl, 0, + core->height >> 1); + break; + case V4L2_FIELD_INTERLACED: + default: + ret = cx88_risc_buffer(dev->pci, &buf->risc, + sgt->sgl, 0, buf->bpl, + buf->bpl, buf->bpl, + core->height >> 1); + break; + } + dprintk(2, + "[%p/%d] %s - %dx%d %dbpp 0x%08x - dma=0x%08lx\n", + buf, buf->vb.vb2_buf.index, __func__, + core->width, core->height, dev->fmt->depth, dev->fmt->fourcc, + (unsigned long)buf->risc.dma); + return ret; +} + +static void buffer_finish(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx8800_dev *dev = vb->vb2_queue->drv_priv; + struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb); + struct cx88_riscmem *risc = &buf->risc; + + if (risc->cpu) + dma_free_coherent(&dev->pci->dev, risc->size, risc->cpu, + risc->dma); + memset(risc, 0, sizeof(*risc)); +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct cx8800_dev *dev = vb->vb2_queue->drv_priv; + struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb); + struct cx88_buffer *prev; + struct cx88_dmaqueue *q = &dev->vidq; + + /* add jump to start */ + buf->risc.cpu[1] = cpu_to_le32(buf->risc.dma + 8); + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma + 8); + + if (list_empty(&q->active)) { + list_add_tail(&buf->list, &q->active); + dprintk(2, "[%p/%d] buffer_queue - first active\n", + buf, buf->vb.vb2_buf.index); + + } else { + buf->risc.cpu[0] |= cpu_to_le32(RISC_IRQ1); + prev = list_entry(q->active.prev, struct cx88_buffer, list); + list_add_tail(&buf->list, &q->active); + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(2, "[%p/%d] buffer_queue - append to active\n", + buf, buf->vb.vb2_buf.index); + } +} + +static int start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct cx8800_dev *dev = q->drv_priv; + struct cx88_dmaqueue *dmaq = &dev->vidq; + struct cx88_buffer *buf = list_entry(dmaq->active.next, + struct cx88_buffer, list); + + start_video_dma(dev, dmaq, buf); + return 0; +} + +static void stop_streaming(struct vb2_queue *q) +{ + struct cx8800_dev *dev = q->drv_priv; + struct cx88_core *core = dev->core; + struct cx88_dmaqueue *dmaq = &dev->vidq; + unsigned long flags; + + cx_clear(MO_VID_DMACNTRL, 0x11); + cx_clear(VID_CAPTURE_CONTROL, 0x06); + spin_lock_irqsave(&dev->slock, flags); + while (!list_empty(&dmaq->active)) { + struct cx88_buffer *buf = list_entry(dmaq->active.next, + struct cx88_buffer, list); + + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&dev->slock, flags); +} + +static const struct vb2_ops cx8800_video_qops = { + .queue_setup = queue_setup, + .buf_prepare = buffer_prepare, + .buf_finish = buffer_finish, + .buf_queue = buffer_queue, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, +}; + +/* ------------------------------------------------------------------ */ + +static int radio_open(struct file *file) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + int ret = v4l2_fh_open(file); + + if (ret) + return ret; + + cx_write(MO_GP3_IO, core->board.radio.gpio3); + cx_write(MO_GP0_IO, core->board.radio.gpio0); + cx_write(MO_GP1_IO, core->board.radio.gpio1); + cx_write(MO_GP2_IO, core->board.radio.gpio2); + if (core->board.radio.audioroute) { + if (core->sd_wm8775) { + call_all(core, audio, s_routing, + core->board.radio.audioroute, 0, 0); + } + /* "I2S ADC mode" */ + core->tvaudio = WW_I2SADC; + cx88_set_tvaudio(core); + } else { + /* FM Mode */ + core->tvaudio = WW_FM; + cx88_set_tvaudio(core); + cx88_set_stereo(core, V4L2_TUNER_MODE_STEREO, 1); + } + call_all(core, tuner, s_radio); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* VIDEO CTRL IOCTLS */ + +static int cx8800_s_vid_ctrl(struct v4l2_ctrl *ctrl) +{ + struct cx88_core *core = + container_of(ctrl->handler, struct cx88_core, video_hdl); + const struct cx88_ctrl *cc = ctrl->priv; + u32 value, mask; + + mask = cc->mask; + switch (ctrl->id) { + case V4L2_CID_SATURATION: + /* special v_sat handling */ + + value = ((ctrl->val - cc->off) << cc->shift) & cc->mask; + + if (core->tvnorm & V4L2_STD_SECAM) { + /* For SECAM, both U and V sat should be equal */ + value = value << 8 | value; + } else { + /* Keeps U Saturation proportional to V Sat */ + value = (value * 0x5a) / 0x7f << 8 | value; + } + mask = 0xffff; + break; + case V4L2_CID_SHARPNESS: + /* 0b000, 0b100, 0b101, 0b110, or 0b111 */ + value = (ctrl->val < 1 ? 0 : ((ctrl->val + 3) << 7)); + /* needs to be set for both fields */ + cx_andor(MO_FILTER_EVEN, mask, value); + break; + case V4L2_CID_CHROMA_AGC: + value = ((ctrl->val - cc->off) << cc->shift) & cc->mask; + break; + default: + value = ((ctrl->val - cc->off) << cc->shift) & cc->mask; + break; + } + dprintk(1, + "set_control id=0x%X(%s) ctrl=0x%02x, reg=0x%02x val=0x%02x (mask 0x%02x)%s\n", + ctrl->id, ctrl->name, ctrl->val, cc->reg, value, + mask, cc->sreg ? " [shadowed]" : ""); + if (cc->sreg) + cx_sandor(cc->sreg, cc->reg, mask, value); + else + cx_andor(cc->reg, mask, value); + return 0; +} + +static int cx8800_s_aud_ctrl(struct v4l2_ctrl *ctrl) +{ + struct cx88_core *core = + container_of(ctrl->handler, struct cx88_core, audio_hdl); + const struct cx88_ctrl *cc = ctrl->priv; + u32 value, mask; + + /* Pass changes onto any WM8775 */ + if (core->sd_wm8775) { + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + wm8775_s_ctrl(core, ctrl->id, ctrl->val); + break; + case V4L2_CID_AUDIO_VOLUME: + wm8775_s_ctrl(core, ctrl->id, (ctrl->val) ? + (0x90 + ctrl->val) << 8 : 0); + break; + case V4L2_CID_AUDIO_BALANCE: + wm8775_s_ctrl(core, ctrl->id, ctrl->val << 9); + break; + default: + break; + } + } + + mask = cc->mask; + switch (ctrl->id) { + case V4L2_CID_AUDIO_BALANCE: + value = (ctrl->val < 0x40) ? + (0x7f - ctrl->val) : (ctrl->val - 0x40); + break; + case V4L2_CID_AUDIO_VOLUME: + value = 0x3f - (ctrl->val & 0x3f); + break; + default: + value = ((ctrl->val - cc->off) << cc->shift) & cc->mask; + break; + } + dprintk(1, + "set_control id=0x%X(%s) ctrl=0x%02x, reg=0x%02x val=0x%02x (mask 0x%02x)%s\n", + ctrl->id, ctrl->name, ctrl->val, cc->reg, value, + mask, cc->sreg ? " [shadowed]" : ""); + if (cc->sreg) + cx_sandor(cc->sreg, cc->reg, mask, value); + else + cx_andor(cc->reg, mask, value); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* VIDEO IOCTLS */ + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + f->fmt.pix.width = core->width; + f->fmt.pix.height = core->height; + f->fmt.pix.field = core->field; + f->fmt.pix.pixelformat = dev->fmt->fourcc; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * dev->fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + const struct cx8800_fmt *fmt; + enum v4l2_field field; + unsigned int maxw, maxh; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (!fmt) + return -EINVAL; + + maxw = norm_maxw(core->tvnorm); + maxh = norm_maxh(core->tvnorm); + + field = f->fmt.pix.field; + + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_SEQ_BT: + case V4L2_FIELD_SEQ_TB: + break; + default: + field = (f->fmt.pix.height > maxh / 2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + break; + } + if (V4L2_FIELD_HAS_T_OR_B(field)) + maxh /= 2; + + v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2, + &f->fmt.pix.height, 32, maxh, 0, 0); + f->fmt.pix.field = field; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + int err = vidioc_try_fmt_vid_cap(file, priv, f); + + if (err != 0) + return err; + if (vb2_is_busy(&dev->vb2_vidq) || vb2_is_busy(&dev->vb2_vbiq)) + return -EBUSY; + if (core->dvbdev && vb2_is_busy(&core->dvbdev->vb2_mpegq)) + return -EBUSY; + dev->fmt = format_by_fourcc(f->fmt.pix.pixelformat); + core->width = f->fmt.pix.width; + core->height = f->fmt.pix.height; + core->field = f->fmt.pix.field; + return 0; +} + +int cx88_querycap(struct file *file, struct cx88_core *core, + struct v4l2_capability *cap) +{ + strscpy(cap->card, core->board.name, sizeof(cap->card)); + cap->capabilities = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | + V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VBI_CAPTURE | + V4L2_CAP_DEVICE_CAPS; + if (core->board.tuner_type != UNSET) + cap->capabilities |= V4L2_CAP_TUNER; + if (core->board.radio.type == CX88_RADIO) + cap->capabilities |= V4L2_CAP_RADIO; + return 0; +} +EXPORT_SYMBOL(cx88_querycap); + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + strscpy(cap->driver, "cx8800", sizeof(cap->driver)); + return cx88_querycap(file, core, cap); +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (unlikely(f->index >= ARRAY_SIZE(formats))) + return -EINVAL; + + f->pixelformat = formats[f->index].fourcc; + + return 0; +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *tvnorm) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + *tvnorm = core->tvnorm; + return 0; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id tvnorms) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + return cx88_set_tvnorm(core, tvnorms); +} + +/* only one input in this sample driver */ +int cx88_enum_input(struct cx88_core *core, struct v4l2_input *i) +{ + static const char * const iname[] = { + [CX88_VMUX_COMPOSITE1] = "Composite1", + [CX88_VMUX_COMPOSITE2] = "Composite2", + [CX88_VMUX_COMPOSITE3] = "Composite3", + [CX88_VMUX_COMPOSITE4] = "Composite4", + [CX88_VMUX_SVIDEO] = "S-Video", + [CX88_VMUX_TELEVISION] = "Television", + [CX88_VMUX_CABLE] = "Cable TV", + [CX88_VMUX_DVB] = "DVB", + [CX88_VMUX_DEBUG] = "for debug only", + }; + unsigned int n = i->index; + + if (n >= 4) + return -EINVAL; + if (!INPUT(n).type) + return -EINVAL; + i->type = V4L2_INPUT_TYPE_CAMERA; + strscpy(i->name, iname[INPUT(n).type], sizeof(i->name)); + if ((INPUT(n).type == CX88_VMUX_TELEVISION) || + (INPUT(n).type == CX88_VMUX_CABLE)) + i->type = V4L2_INPUT_TYPE_TUNER; + + i->std = CX88_NORMS; + return 0; +} +EXPORT_SYMBOL(cx88_enum_input); + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + return cx88_enum_input(core, i); +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + *i = core->input; + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + if (i >= 4) + return -EINVAL; + if (!INPUT(i).type) + return -EINVAL; + + cx88_newstation(core); + cx88_video_mux(core, i); + return 0; +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + u32 reg; + + if (unlikely(core->board.tuner_type == UNSET)) + return -EINVAL; + if (t->index != 0) + return -EINVAL; + + strscpy(t->name, "Television", sizeof(t->name)); + t->capability = V4L2_TUNER_CAP_NORM; + t->rangehigh = 0xffffffffUL; + call_all(core, tuner, g_tuner, t); + + cx88_get_stereo(core, t); + reg = cx_read(MO_DEVICE_STATUS); + t->signal = (reg & (1 << 5)) ? 0xffff : 0x0000; + return 0; +} + +static int vidioc_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *t) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + if (core->board.tuner_type == UNSET) + return -EINVAL; + if (t->index != 0) + return -EINVAL; + + cx88_set_stereo(core, t->audmode, 1); + return 0; +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + if (unlikely(core->board.tuner_type == UNSET)) + return -EINVAL; + if (f->tuner) + return -EINVAL; + + f->frequency = core->freq; + + call_all(core, tuner, g_frequency, f); + + return 0; +} + +int cx88_set_freq(struct cx88_core *core, + const struct v4l2_frequency *f) +{ + struct v4l2_frequency new_freq = *f; + + if (unlikely(core->board.tuner_type == UNSET)) + return -EINVAL; + if (unlikely(f->tuner != 0)) + return -EINVAL; + + cx88_newstation(core); + call_all(core, tuner, s_frequency, f); + call_all(core, tuner, g_frequency, &new_freq); + core->freq = new_freq.frequency; + + /* When changing channels it is required to reset TVAUDIO */ + usleep_range(10000, 20000); + cx88_set_tvaudio(core); + + return 0; +} +EXPORT_SYMBOL(cx88_set_freq); + +static int vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + return cx88_set_freq(core, f); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int vidioc_g_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + /* cx2388x has a 24-bit register space */ + reg->val = cx_read(reg->reg & 0xfffffc); + reg->size = 4; + return 0; +} + +static int vidioc_s_register(struct file *file, void *fh, + const struct v4l2_dbg_register *reg) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + cx_write(reg->reg & 0xfffffc, reg->val); + return 0; +} +#endif + +/* ----------------------------------------------------------- */ +/* RADIO ESPECIFIC IOCTLS */ +/* ----------------------------------------------------------- */ + +static int radio_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + if (unlikely(t->index > 0)) + return -EINVAL; + + strscpy(t->name, "Radio", sizeof(t->name)); + + call_all(core, tuner, g_tuner, t); + return 0; +} + +static int radio_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *t) +{ + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + + if (t->index != 0) + return -EINVAL; + + call_all(core, tuner, s_tuner, t); + return 0; +} + +/* ----------------------------------------------------------- */ + +static const char *cx88_vid_irqs[32] = { + "y_risci1", "u_risci1", "v_risci1", "vbi_risc1", + "y_risci2", "u_risci2", "v_risci2", "vbi_risc2", + "y_oflow", "u_oflow", "v_oflow", "vbi_oflow", + "y_sync", "u_sync", "v_sync", "vbi_sync", + "opc_err", "par_err", "rip_err", "pci_abort", +}; + +static void cx8800_vid_irq(struct cx8800_dev *dev) +{ + struct cx88_core *core = dev->core; + u32 status, mask, count; + + status = cx_read(MO_VID_INTSTAT); + mask = cx_read(MO_VID_INTMSK); + if (0 == (status & mask)) + return; + cx_write(MO_VID_INTSTAT, status); + if (irq_debug || (status & mask & ~0xff)) + cx88_print_irqbits("irq vid", + cx88_vid_irqs, ARRAY_SIZE(cx88_vid_irqs), + status, mask); + + /* risc op code error */ + if (status & (1 << 16)) { + pr_warn("video risc op code error\n"); + cx_clear(MO_VID_DMACNTRL, 0x11); + cx_clear(VID_CAPTURE_CONTROL, 0x06); + cx88_sram_channel_dump(core, &cx88_sram_channels[SRAM_CH21]); + } + + /* risc1 y */ + if (status & 0x01) { + spin_lock(&dev->slock); + count = cx_read(MO_VIDY_GPCNT); + cx88_wakeup(core, &dev->vidq, count); + spin_unlock(&dev->slock); + } + + /* risc1 vbi */ + if (status & 0x08) { + spin_lock(&dev->slock); + count = cx_read(MO_VBI_GPCNT); + cx88_wakeup(core, &dev->vbiq, count); + spin_unlock(&dev->slock); + } +} + +static irqreturn_t cx8800_irq(int irq, void *dev_id) +{ + struct cx8800_dev *dev = dev_id; + struct cx88_core *core = dev->core; + u32 status; + int loop, handled = 0; + + for (loop = 0; loop < 10; loop++) { + status = cx_read(MO_PCI_INTSTAT) & + (core->pci_irqmask | PCI_INT_VIDINT); + if (status == 0) + goto out; + cx_write(MO_PCI_INTSTAT, status); + handled = 1; + + if (status & core->pci_irqmask) + cx88_core_irq(core, status); + if (status & PCI_INT_VIDINT) + cx8800_vid_irq(dev); + } + if (loop == 10) { + pr_warn("irq loop -- clearing mask\n"); + cx_write(MO_PCI_INTMSK, 0); + } + + out: + return IRQ_RETVAL(handled); +} + +/* ----------------------------------------------------------- */ +/* exported stuff */ + +static const struct v4l2_file_operations video_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = vidioc_g_register, + .vidioc_s_register = vidioc_s_register, +#endif +}; + +static const struct video_device cx8800_video_template = { + .name = "cx8800-video", + .fops = &video_fops, + .ioctl_ops = &video_ioctl_ops, + .tvnorms = CX88_NORMS, +}; + +static const struct v4l2_ioctl_ops vbi_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_g_fmt_vbi_cap = cx8800_vbi_fmt, + .vidioc_try_fmt_vbi_cap = cx8800_vbi_fmt, + .vidioc_s_fmt_vbi_cap = cx8800_vbi_fmt, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = vidioc_g_register, + .vidioc_s_register = vidioc_s_register, +#endif +}; + +static const struct video_device cx8800_vbi_template = { + .name = "cx8800-vbi", + .fops = &video_fops, + .ioctl_ops = &vbi_ioctl_ops, + .tvnorms = CX88_NORMS, +}; + +static const struct v4l2_file_operations radio_fops = { + .owner = THIS_MODULE, + .open = radio_open, + .poll = v4l2_ctrl_poll, + .release = v4l2_fh_release, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops radio_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_g_tuner = radio_g_tuner, + .vidioc_s_tuner = radio_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = vidioc_g_register, + .vidioc_s_register = vidioc_s_register, +#endif +}; + +static const struct video_device cx8800_radio_template = { + .name = "cx8800-radio", + .fops = &radio_fops, + .ioctl_ops = &radio_ioctl_ops, +}; + +static const struct v4l2_ctrl_ops cx8800_ctrl_vid_ops = { + .s_ctrl = cx8800_s_vid_ctrl, +}; + +static const struct v4l2_ctrl_ops cx8800_ctrl_aud_ops = { + .s_ctrl = cx8800_s_aud_ctrl, +}; + +/* ----------------------------------------------------------- */ + +static void cx8800_unregister_video(struct cx8800_dev *dev) +{ + video_unregister_device(&dev->radio_dev); + video_unregister_device(&dev->vbi_dev); + video_unregister_device(&dev->video_dev); +} + +static int cx8800_initdev(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct cx8800_dev *dev; + struct cx88_core *core; + struct vb2_queue *q; + int err; + int i; + + dev = kzalloc_obj(*dev); + if (!dev) + return -ENOMEM; + + /* pci init */ + dev->pci = pci_dev; + if (pci_enable_device(pci_dev)) { + err = -EIO; + goto fail_free; + } + core = cx88_core_get(dev->pci); + if (!core) { + err = -EINVAL; + goto fail_disable; + } + dev->core = core; + + /* print pci info */ + dev->pci_rev = pci_dev->revision; + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat); + pr_info("found at %s, rev: %d, irq: %d, latency: %d, mmio: 0x%llx\n", + pci_name(pci_dev), dev->pci_rev, pci_dev->irq, + dev->pci_lat, + (unsigned long long)pci_resource_start(pci_dev, 0)); + + pci_set_master(pci_dev); + err = dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(32)); + if (err) { + pr_err("Oops: no 32bit PCI DMA ???\n"); + goto fail_core; + } + + /* initialize driver struct */ + spin_lock_init(&dev->slock); + + /* init video dma queues */ + INIT_LIST_HEAD(&dev->vidq.active); + + /* init vbi dma queues */ + INIT_LIST_HEAD(&dev->vbiq.active); + + /* get irq */ + err = request_irq(pci_dev->irq, cx8800_irq, + IRQF_SHARED, core->name, dev); + if (err < 0) { + pr_err("can't get IRQ %d\n", pci_dev->irq); + goto fail_core; + } + cx_set(MO_PCI_INTMSK, core->pci_irqmask); + + for (i = 0; i < CX8800_AUD_CTLS; i++) { + const struct cx88_ctrl *cc = &cx8800_aud_ctls[i]; + struct v4l2_ctrl *vc; + + vc = v4l2_ctrl_new_std(&core->audio_hdl, &cx8800_ctrl_aud_ops, + cc->id, cc->minimum, cc->maximum, + cc->step, cc->default_value); + if (!vc) { + err = core->audio_hdl.error; + goto fail_irq; + } + vc->priv = (void *)cc; + } + + for (i = 0; i < CX8800_VID_CTLS; i++) { + const struct cx88_ctrl *cc = &cx8800_vid_ctls[i]; + struct v4l2_ctrl *vc; + + vc = v4l2_ctrl_new_std(&core->video_hdl, &cx8800_ctrl_vid_ops, + cc->id, cc->minimum, cc->maximum, + cc->step, cc->default_value); + if (!vc) { + err = core->video_hdl.error; + goto fail_irq; + } + vc->priv = (void *)cc; + if (vc->id == V4L2_CID_CHROMA_AGC) + core->chroma_agc = vc; + } + v4l2_ctrl_add_handler(&core->video_hdl, &core->audio_hdl, NULL, false); + + /* load and configure helper modules */ + + if (core->board.audio_chip == CX88_AUDIO_WM8775) { + struct i2c_board_info wm8775_info = { + .type = "wm8775", + .addr = 0x36 >> 1, + .platform_data = &core->wm8775_data, + }; + struct v4l2_subdev *sd; + + if (core->boardnr == CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1) + core->wm8775_data.is_nova_s = true; + else + core->wm8775_data.is_nova_s = false; + + sd = v4l2_i2c_new_subdev_board(&core->v4l2_dev, &core->i2c_adap, + &wm8775_info, NULL); + if (sd) { + core->sd_wm8775 = sd; + sd->grp_id = WM8775_GID; + } + } + + if (core->board.audio_chip == CX88_AUDIO_TVAUDIO) { + /* + * This probes for a tda9874 as is used on some + * Pixelview Ultra boards. + */ + v4l2_i2c_new_subdev(&core->v4l2_dev, &core->i2c_adap, + "tvaudio", 0, I2C_ADDRS(0xb0 >> 1)); + } + + switch (core->boardnr) { + case CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD: + case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD: { + static const struct i2c_board_info rtc_info = { + I2C_BOARD_INFO("isl1208", 0x6f) + }; + + request_module("rtc-isl1208"); + core->i2c_rtc = i2c_new_client_device(&core->i2c_adap, &rtc_info); + } + fallthrough; + case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO: + case CX88_BOARD_NOTONLYTV_LV3H: + request_module("ir-kbd-i2c"); + } + + /* Sets device info at pci_dev */ + pci_set_drvdata(pci_dev, dev); + + dev->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24); + + /* Maintain a reference so cx88-blackbird can query the 8800 device. */ + core->v4ldev = dev; + + /* initial device configuration */ + mutex_lock(&core->lock); + cx88_set_tvnorm(core, V4L2_STD_NTSC_M); + v4l2_ctrl_handler_setup(&core->video_hdl); + v4l2_ctrl_handler_setup(&core->audio_hdl); + cx88_video_mux(core, 0); + + q = &dev->vb2_vidq; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ; + q->gfp_flags = GFP_DMA32; + q->min_queued_buffers = 2; + q->drv_priv = dev; + q->buf_struct_size = sizeof(struct cx88_buffer); + q->ops = &cx8800_video_qops; + q->mem_ops = &vb2_dma_sg_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &core->lock; + q->dev = &dev->pci->dev; + + err = vb2_queue_init(q); + if (err < 0) + goto fail_unreg; + + q = &dev->vb2_vbiq; + q->type = V4L2_BUF_TYPE_VBI_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ; + q->gfp_flags = GFP_DMA32; + q->min_queued_buffers = 2; + q->drv_priv = dev; + q->buf_struct_size = sizeof(struct cx88_buffer); + q->ops = &cx8800_vbi_qops; + q->mem_ops = &vb2_dma_sg_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &core->lock; + q->dev = &dev->pci->dev; + + err = vb2_queue_init(q); + if (err < 0) + goto fail_unreg; + + /* register v4l devices */ + cx88_vdev_init(core, dev->pci, &dev->video_dev, + &cx8800_video_template, "video"); + video_set_drvdata(&dev->video_dev, dev); + dev->video_dev.ctrl_handler = &core->video_hdl; + dev->video_dev.queue = &dev->vb2_vidq; + dev->video_dev.device_caps = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | + V4L2_CAP_VIDEO_CAPTURE; + if (core->board.tuner_type != UNSET) + dev->video_dev.device_caps |= V4L2_CAP_TUNER; + err = video_register_device(&dev->video_dev, VFL_TYPE_VIDEO, + video_nr[core->nr]); + if (err < 0) { + pr_err("can't register video device\n"); + goto fail_unreg; + } + pr_info("registered device %s [v4l2]\n", + video_device_node_name(&dev->video_dev)); + + cx88_vdev_init(core, dev->pci, &dev->vbi_dev, + &cx8800_vbi_template, "vbi"); + video_set_drvdata(&dev->vbi_dev, dev); + dev->vbi_dev.queue = &dev->vb2_vbiq; + dev->vbi_dev.device_caps = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | + V4L2_CAP_VBI_CAPTURE; + if (core->board.tuner_type != UNSET) + dev->vbi_dev.device_caps |= V4L2_CAP_TUNER; + err = video_register_device(&dev->vbi_dev, VFL_TYPE_VBI, + vbi_nr[core->nr]); + if (err < 0) { + pr_err("can't register vbi device\n"); + goto fail_unreg; + } + pr_info("registered device %s\n", + video_device_node_name(&dev->vbi_dev)); + + if (core->board.radio.type == CX88_RADIO) { + cx88_vdev_init(core, dev->pci, &dev->radio_dev, + &cx8800_radio_template, "radio"); + video_set_drvdata(&dev->radio_dev, dev); + dev->radio_dev.ctrl_handler = &core->audio_hdl; + dev->radio_dev.device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER; + err = video_register_device(&dev->radio_dev, VFL_TYPE_RADIO, + radio_nr[core->nr]); + if (err < 0) { + pr_err("can't register radio device\n"); + goto fail_unreg; + } + pr_info("registered device %s\n", + video_device_node_name(&dev->radio_dev)); + } + + /* start tvaudio thread */ + if (core->board.tuner_type != UNSET) { + core->kthread = kthread_run(cx88_audio_thread, + core, "cx88 tvaudio"); + if (IS_ERR(core->kthread)) { + err = PTR_ERR(core->kthread); + pr_err("failed to create cx88 audio thread, err=%d\n", + err); + } + } + mutex_unlock(&core->lock); + + return 0; + +fail_unreg: + cx8800_unregister_video(dev); + mutex_unlock(&core->lock); +fail_irq: + free_irq(pci_dev->irq, dev); +fail_core: + core->v4ldev = NULL; + cx88_core_put(core, dev->pci); +fail_disable: + pci_disable_device(pci_dev); +fail_free: + kfree(dev); + return err; +} + +static void cx8800_finidev(struct pci_dev *pci_dev) +{ + struct cx8800_dev *dev = pci_get_drvdata(pci_dev); + struct cx88_core *core = dev->core; + + /* stop thread */ + if (core->kthread) { + kthread_stop(core->kthread); + core->kthread = NULL; + } + + if (core->ir) + cx88_ir_stop(core); + + cx88_shutdown(core); /* FIXME */ + + /* unregister stuff */ + + free_irq(pci_dev->irq, dev); + cx8800_unregister_video(dev); + pci_disable_device(pci_dev); + + core->v4ldev = NULL; + + /* free memory */ + cx88_core_put(core, dev->pci); + kfree(dev); +} + +static int __maybe_unused cx8800_suspend(struct device *dev_d) +{ + struct cx8800_dev *dev = dev_get_drvdata(dev_d); + struct cx88_core *core = dev->core; + unsigned long flags; + + /* stop video+vbi capture */ + spin_lock_irqsave(&dev->slock, flags); + if (!list_empty(&dev->vidq.active)) { + pr_info("suspend video\n"); + stop_video_dma(dev); + } + if (!list_empty(&dev->vbiq.active)) { + pr_info("suspend vbi\n"); + cx8800_stop_vbi_dma(dev); + } + spin_unlock_irqrestore(&dev->slock, flags); + + if (core->ir) + cx88_ir_stop(core); + /* FIXME -- shutdown device */ + cx88_shutdown(core); + + dev->state.disabled = 1; + return 0; +} + +static int __maybe_unused cx8800_resume(struct device *dev_d) +{ + struct cx8800_dev *dev = dev_get_drvdata(dev_d); + struct cx88_core *core = dev->core; + unsigned long flags; + + dev->state.disabled = 0; + + /* FIXME: re-initialize hardware */ + cx88_reset(core); + if (core->ir) + cx88_ir_start(core); + + cx_set(MO_PCI_INTMSK, core->pci_irqmask); + + /* restart video+vbi capture */ + spin_lock_irqsave(&dev->slock, flags); + if (!list_empty(&dev->vidq.active)) { + pr_info("resume video\n"); + restart_video_queue(dev, &dev->vidq); + } + if (!list_empty(&dev->vbiq.active)) { + pr_info("resume vbi\n"); + cx8800_restart_vbi_queue(dev, &dev->vbiq); + } + spin_unlock_irqrestore(&dev->slock, flags); + + return 0; +} + +/* ----------------------------------------------------------- */ + +static const struct pci_device_id cx8800_pci_tbl[] = { + { + PCI_DEVICE(0x14f1, 0x8800), + }, { + /* --- end of list --- */ + } +}; +MODULE_DEVICE_TABLE(pci, cx8800_pci_tbl); + +static SIMPLE_DEV_PM_OPS(cx8800_pm_ops, cx8800_suspend, cx8800_resume); + +static struct pci_driver cx8800_pci_driver = { + .name = "cx8800", + .id_table = cx8800_pci_tbl, + .probe = cx8800_initdev, + .remove = cx8800_finidev, + .driver.pm = &cx8800_pm_ops, +}; + +module_pci_driver(cx8800_pci_driver); diff --git a/drivers/media/pci/cx88/cx88-vp3054-i2c.c b/drivers/media/pci/cx88/cx88-vp3054-i2c.c new file mode 100644 index 000000000..dbec12588 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-vp3054-i2c.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cx88-vp3054-i2c.c -- support for the secondary I2C bus of the + * DNTV Live! DVB-T Pro (VP-3054), wired as: + * GPIO[0] -> SCL, GPIO[1] -> SDA + * + * (c) 2005 Chris Pascoe + */ + +#include "cx88.h" +#include "cx88-vp3054-i2c.h" + +#include +#include +#include +#include + +MODULE_DESCRIPTION("driver for cx2388x VP3054 design"); +MODULE_AUTHOR("Chris Pascoe "); +MODULE_LICENSE("GPL"); + +/* ----------------------------------------------------------------------- */ + +static void vp3054_bit_setscl(void *data, int state) +{ + struct cx8802_dev *dev = data; + struct cx88_core *core = dev->core; + struct vp3054_i2c_state *vp3054_i2c = dev->vp3054; + + if (state) { + vp3054_i2c->state |= 0x0001; /* SCL high */ + vp3054_i2c->state &= ~0x0100; /* external pullup */ + } else { + vp3054_i2c->state &= ~0x0001; /* SCL low */ + vp3054_i2c->state |= 0x0100; /* drive pin */ + } + cx_write(MO_GP0_IO, 0x010000 | vp3054_i2c->state); + cx_read(MO_GP0_IO); +} + +static void vp3054_bit_setsda(void *data, int state) +{ + struct cx8802_dev *dev = data; + struct cx88_core *core = dev->core; + struct vp3054_i2c_state *vp3054_i2c = dev->vp3054; + + if (state) { + vp3054_i2c->state |= 0x0002; /* SDA high */ + vp3054_i2c->state &= ~0x0200; /* tristate pin */ + } else { + vp3054_i2c->state &= ~0x0002; /* SDA low */ + vp3054_i2c->state |= 0x0200; /* drive pin */ + } + cx_write(MO_GP0_IO, 0x020000 | vp3054_i2c->state); + cx_read(MO_GP0_IO); +} + +static int vp3054_bit_getscl(void *data) +{ + struct cx8802_dev *dev = data; + struct cx88_core *core = dev->core; + u32 state; + + state = cx_read(MO_GP0_IO); + return (state & 0x01) ? 1 : 0; +} + +static int vp3054_bit_getsda(void *data) +{ + struct cx8802_dev *dev = data; + struct cx88_core *core = dev->core; + u32 state; + + state = cx_read(MO_GP0_IO); + return (state & 0x02) ? 1 : 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_algo_bit_data vp3054_i2c_algo_template = { + .setsda = vp3054_bit_setsda, + .setscl = vp3054_bit_setscl, + .getsda = vp3054_bit_getsda, + .getscl = vp3054_bit_getscl, + .udelay = 16, + .timeout = 200, +}; + +/* ----------------------------------------------------------------------- */ + +int vp3054_i2c_probe(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + struct vp3054_i2c_state *vp3054_i2c; + int rc; + + if (core->boardnr != CX88_BOARD_DNTV_LIVE_DVB_T_PRO) + return 0; + + vp3054_i2c = kzalloc_obj(*vp3054_i2c); + if (!vp3054_i2c) + return -ENOMEM; + dev->vp3054 = vp3054_i2c; + + vp3054_i2c->algo = vp3054_i2c_algo_template; + + vp3054_i2c->adap.dev.parent = &dev->pci->dev; + strscpy(vp3054_i2c->adap.name, core->name, + sizeof(vp3054_i2c->adap.name)); + vp3054_i2c->adap.owner = THIS_MODULE; + vp3054_i2c->algo.data = dev; + i2c_set_adapdata(&vp3054_i2c->adap, dev); + vp3054_i2c->adap.algo_data = &vp3054_i2c->algo; + + vp3054_bit_setscl(dev, 1); + vp3054_bit_setsda(dev, 1); + + rc = i2c_bit_add_bus(&vp3054_i2c->adap); + if (rc != 0) { + pr_err("vp3054_i2c register FAILED\n"); + + kfree(dev->vp3054); + dev->vp3054 = NULL; + } + + return rc; +} +EXPORT_SYMBOL(vp3054_i2c_probe); + +void vp3054_i2c_remove(struct cx8802_dev *dev) +{ + struct vp3054_i2c_state *vp3054_i2c = dev->vp3054; + + if (!vp3054_i2c || + dev->core->boardnr != CX88_BOARD_DNTV_LIVE_DVB_T_PRO) + return; + + i2c_del_adapter(&vp3054_i2c->adap); + kfree(vp3054_i2c); +} +EXPORT_SYMBOL(vp3054_i2c_remove); diff --git a/drivers/media/pci/cx88/cx88-vp3054-i2c.h b/drivers/media/pci/cx88/cx88-vp3054-i2c.h new file mode 100644 index 000000000..ead3d0d63 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-vp3054-i2c.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * cx88-vp3054-i2c.h -- support for the secondary I2C bus of the + * DNTV Live! DVB-T Pro (VP-3054), wired as: + * GPIO[0] -> SCL, GPIO[1] -> SDA + * + * (c) 2005 Chris Pascoe + */ + +/* ----------------------------------------------------------------------- */ +struct vp3054_i2c_state { + struct i2c_adapter adap; + struct i2c_algo_bit_data algo; + u32 state; +}; + +/* ----------------------------------------------------------------------- */ +#if IS_ENABLED(CONFIG_VIDEO_CX88_VP3054) +int vp3054_i2c_probe(struct cx8802_dev *dev); +void vp3054_i2c_remove(struct cx8802_dev *dev); +#else +static inline int vp3054_i2c_probe(struct cx8802_dev *dev) +{ return 0; } +static inline void vp3054_i2c_remove(struct cx8802_dev *dev) +{ } +#endif diff --git a/drivers/media/pci/cx88/cx88.h b/drivers/media/pci/cx88/cx88.h new file mode 100644 index 000000000..2ff3226a5 --- /dev/null +++ b/drivers/media/pci/cx88/cx88.h @@ -0,0 +1,731 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * v4l2 device driver for cx2388x based TV cards + * + * (c) 2003,04 Gerd Knorr [SUSE Labs] + */ + +#ifndef CX88_H +#define CX88_H + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cx88-reg.h" +#include "xc2028.h" + +#include + +#define CX88_VERSION "1.0.0" + +#define UNSET (-1U) + +#define CX88_MAXBOARDS 8 + +/* Max number of inputs by card */ +#define MAX_CX88_INPUT 8 + +/* ----------------------------------------------------------- */ +/* defines and enums */ + +/* Currently unsupported by the driver: PAL/H, NTSC/Kr, SECAM/LC */ +#define CX88_NORMS (V4L2_STD_ALL \ + & ~V4L2_STD_PAL_H \ + & ~V4L2_STD_NTSC_M_KR \ + & ~V4L2_STD_SECAM_LC) + +#define FORMAT_FLAGS_PACKED 0x01 +#define FORMAT_FLAGS_PLANAR 0x02 + +#define VBI_LINE_PAL_COUNT 18 +#define VBI_LINE_NTSC_COUNT 12 +#define VBI_LINE_LENGTH 2048 + +#define AUD_RDS_LINES 4 + +/* need "shadow" registers for some write-only ones ... */ +#define SHADOW_AUD_VOL_CTL 1 +#define SHADOW_AUD_BAL_CTL 2 +#define SHADOW_MAX 3 + +/* FM Radio deemphasis type */ +enum cx88_deemph_type { + FM_NO_DEEMPH = 0, + FM_DEEMPH_50, + FM_DEEMPH_75 +}; + +enum cx88_board_type { + CX88_BOARD_NONE = 0, + CX88_MPEG_DVB, + CX88_MPEG_BLACKBIRD +}; + +enum cx8802_board_access { + CX8802_DRVCTL_SHARED = 1, + CX8802_DRVCTL_EXCLUSIVE = 2, +}; + +/* ----------------------------------------------------------- */ +/* tv norms */ + +static inline unsigned int norm_maxw(v4l2_std_id norm) +{ + return 720; +} + +static inline unsigned int norm_maxh(v4l2_std_id norm) +{ + return (norm & V4L2_STD_525_60) ? 480 : 576; +} + +/* ----------------------------------------------------------- */ +/* static data */ + +struct cx8800_fmt { + u32 fourcc; /* v4l2 format id */ + int depth; + int flags; + u32 cxformat; +}; + +/* ----------------------------------------------------------- */ +/* SRAM memory management data (see cx88-core.c) */ + +#define SRAM_CH21 0 /* video */ +#define SRAM_CH22 1 +#define SRAM_CH23 2 +#define SRAM_CH24 3 /* vbi */ +#define SRAM_CH25 4 /* audio */ +#define SRAM_CH26 5 +#define SRAM_CH28 6 /* mpeg */ +#define SRAM_CH27 7 /* audio rds */ +/* more */ + +struct sram_channel { + const char *name; + u32 cmds_start; + u32 ctrl_start; + u32 cdt; + u32 fifo_start; + u32 fifo_size; + u32 ptr1_reg; + u32 ptr2_reg; + u32 cnt1_reg; + u32 cnt2_reg; +}; + +extern const struct sram_channel cx88_sram_channels[]; + +/* ----------------------------------------------------------- */ +/* card configuration */ + +#define CX88_BOARD_NOAUTO UNSET +#define CX88_BOARD_UNKNOWN 0 +#define CX88_BOARD_HAUPPAUGE 1 +#define CX88_BOARD_GDI 2 +#define CX88_BOARD_PIXELVIEW 3 +#define CX88_BOARD_ATI_WONDER_PRO 4 +#define CX88_BOARD_WINFAST2000XP_EXPERT 5 +#define CX88_BOARD_AVERTV_STUDIO_303 6 +#define CX88_BOARD_MSI_TVANYWHERE_MASTER 7 +#define CX88_BOARD_WINFAST_DV2000 8 +#define CX88_BOARD_LEADTEK_PVR2000 9 +#define CX88_BOARD_IODATA_GVVCP3PCI 10 +#define CX88_BOARD_PROLINK_PLAYTVPVR 11 +#define CX88_BOARD_ASUS_PVR_416 12 +#define CX88_BOARD_MSI_TVANYWHERE 13 +#define CX88_BOARD_KWORLD_DVB_T 14 +#define CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1 15 +#define CX88_BOARD_KWORLD_LTV883 16 +#define CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q 17 +#define CX88_BOARD_HAUPPAUGE_DVB_T1 18 +#define CX88_BOARD_CONEXANT_DVB_T1 19 +#define CX88_BOARD_PROVIDEO_PV259 20 +#define CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS 21 +#define CX88_BOARD_PCHDTV_HD3000 22 +#define CX88_BOARD_DNTV_LIVE_DVB_T 23 +#define CX88_BOARD_HAUPPAUGE_ROSLYN 24 +#define CX88_BOARD_DIGITALLOGIC_MEC 25 +#define CX88_BOARD_IODATA_GVBCTV7E 26 +#define CX88_BOARD_PIXELVIEW_PLAYTV_ULTRA_PRO 27 +#define CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T 28 +#define CX88_BOARD_ADSTECH_DVB_T_PCI 29 +#define CX88_BOARD_TERRATEC_CINERGY_1400_DVB_T1 30 +#define CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD 31 +#define CX88_BOARD_AVERMEDIA_ULTRATV_MC_550 32 +#define CX88_BOARD_KWORLD_VSTREAM_EXPERT_DVD 33 +#define CX88_BOARD_ATI_HDTVWONDER 34 +#define CX88_BOARD_WINFAST_DTV1000 35 +#define CX88_BOARD_AVERTV_303 36 +#define CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1 37 +#define CX88_BOARD_HAUPPAUGE_NOVASE2_S1 38 +#define CX88_BOARD_KWORLD_DVBS_100 39 +#define CX88_BOARD_HAUPPAUGE_HVR1100 40 +#define CX88_BOARD_HAUPPAUGE_HVR1100LP 41 +#define CX88_BOARD_DNTV_LIVE_DVB_T_PRO 42 +#define CX88_BOARD_KWORLD_DVB_T_CX22702 43 +#define CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL 44 +#define CX88_BOARD_KWORLD_HARDWARE_MPEG_TV_XPERT 45 +#define CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID 46 +#define CX88_BOARD_PCHDTV_HD5500 47 +#define CX88_BOARD_KWORLD_MCE200_DELUXE 48 +#define CX88_BOARD_PIXELVIEW_PLAYTV_P7000 49 +#define CX88_BOARD_NPGTECH_REALTV_TOP10FM 50 +#define CX88_BOARD_WINFAST_DTV2000H 51 +#define CX88_BOARD_GENIATECH_DVBS 52 +#define CX88_BOARD_HAUPPAUGE_HVR3000 53 +#define CX88_BOARD_NORWOOD_MICRO 54 +#define CX88_BOARD_TE_DTV_250_OEM_SWANN 55 +#define CX88_BOARD_HAUPPAUGE_HVR1300 56 +#define CX88_BOARD_ADSTECH_PTV_390 57 +#define CX88_BOARD_PINNACLE_PCTV_HD_800i 58 +#define CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO 59 +#define CX88_BOARD_PINNACLE_HYBRID_PCTV 60 +#define CX88_BOARD_WINFAST_TV2000_XP_GLOBAL 61 +#define CX88_BOARD_POWERCOLOR_REAL_ANGEL 62 +#define CX88_BOARD_GENIATECH_X8000_MT 63 +#define CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO 64 +#define CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD 65 +#define CX88_BOARD_PROLINK_PV_8000GT 66 +#define CX88_BOARD_KWORLD_ATSC_120 67 +#define CX88_BOARD_HAUPPAUGE_HVR4000 68 +#define CX88_BOARD_HAUPPAUGE_HVR4000LITE 69 +#define CX88_BOARD_TEVII_S460 70 +#define CX88_BOARD_OMICOM_SS4_PCI 71 +#define CX88_BOARD_TBS_8920 72 +#define CX88_BOARD_TEVII_S420 73 +#define CX88_BOARD_PROLINK_PV_GLOBAL_XTREME 74 +#define CX88_BOARD_PROF_7300 75 +#define CX88_BOARD_SATTRADE_ST4200 76 +#define CX88_BOARD_TBS_8910 77 +#define CX88_BOARD_PROF_6200 78 +#define CX88_BOARD_TERRATEC_CINERGY_HT_PCI_MKII 79 +#define CX88_BOARD_HAUPPAUGE_IRONLY 80 +#define CX88_BOARD_WINFAST_DTV1800H 81 +#define CX88_BOARD_WINFAST_DTV2000H_J 82 +#define CX88_BOARD_PROF_7301 83 +#define CX88_BOARD_SAMSUNG_SMT_7020 84 +#define CX88_BOARD_TWINHAN_VP1027_DVBS 85 +#define CX88_BOARD_TEVII_S464 86 +#define CX88_BOARD_WINFAST_DTV2000H_PLUS 87 +#define CX88_BOARD_WINFAST_DTV1800H_XC4000 88 +#define CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36 89 +#define CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43 90 +#define CX88_BOARD_NOTONLYTV_LV3H 91 + +enum cx88_itype { + CX88_VMUX_COMPOSITE1 = 1, + CX88_VMUX_COMPOSITE2, + CX88_VMUX_COMPOSITE3, + CX88_VMUX_COMPOSITE4, + CX88_VMUX_SVIDEO, + CX88_VMUX_TELEVISION, + CX88_VMUX_CABLE, + CX88_VMUX_DVB, + CX88_VMUX_DEBUG, + CX88_RADIO, +}; + +struct cx88_input { + enum cx88_itype type; + u32 gpio0, gpio1, gpio2, gpio3; + unsigned int vmux:2; + unsigned int audioroute:4; +}; + +enum cx88_audio_chip { + CX88_AUDIO_WM8775 = 1, + CX88_AUDIO_TVAUDIO, +}; + +struct cx88_board { + const char *name; + unsigned int tuner_type; + unsigned int radio_type; + unsigned char tuner_addr; + unsigned char radio_addr; + int tda9887_conf; + struct cx88_input input[MAX_CX88_INPUT]; + struct cx88_input radio; + enum cx88_board_type mpeg; + enum cx88_audio_chip audio_chip; + int num_frontends; + + /* Used for I2S devices */ + int i2sinputcntl; +}; + +struct cx88_subid { + u16 subvendor; + u16 subdevice; + u32 card; +}; + +enum cx88_tvaudio { + WW_NONE = 1, + WW_BTSC, + WW_BG, + WW_DK, + WW_I, + WW_L, + WW_EIAJ, + WW_I2SPT, + WW_FM, + WW_I2SADC, + WW_M +}; + +#define INPUT(nr) (core->board.input[nr]) + +/* ----------------------------------------------------------- */ +/* device / file handle status */ + +#define RESOURCE_OVERLAY 1 +#define RESOURCE_VIDEO 2 +#define RESOURCE_VBI 4 + +#define BUFFER_TIMEOUT msecs_to_jiffies(2000) + +struct cx88_riscmem { + unsigned int size; + __le32 *cpu; + __le32 *jmp; + dma_addr_t dma; +}; + +/* buffer for one video frame */ +struct cx88_buffer { + /* common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer vb; + struct list_head list; + + /* cx88 specific */ + unsigned int bpl; + struct cx88_riscmem risc; +}; + +struct cx88_dmaqueue { + struct list_head active; + u32 count; +}; + +struct cx8800_dev; +struct cx8802_dev; + +struct cx88_core { + struct list_head devlist; + refcount_t refcount; + + /* board name */ + int nr; + char name[32]; + u32 model; + + /* pci stuff */ + int pci_bus; + int pci_slot; + u32 __iomem *lmmio; + u8 __iomem *bmmio; + u32 shadow[SHADOW_MAX]; + int pci_irqmask; + + /* i2c i/o */ + struct i2c_adapter i2c_adap; + struct i2c_algo_bit_data i2c_algo; + struct i2c_client i2c_client; + u32 i2c_state, i2c_rc; + + /* config info -- analog */ + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler video_hdl; + struct v4l2_ctrl *chroma_agc; + struct v4l2_ctrl_handler audio_hdl; + struct v4l2_subdev *sd_wm8775; + struct i2c_client *i2c_rtc; + unsigned int boardnr; + struct cx88_board board; + + /* Supported V4L _STD_ tuner formats */ + unsigned int tuner_formats; + + /* config info -- dvb */ +#if IS_ENABLED(CONFIG_VIDEO_CX88_DVB) + int (*prev_set_voltage)(struct dvb_frontend *fe, + enum fe_sec_voltage voltage); +#endif + void (*gate_ctrl)(struct cx88_core *core, int open); + + /* state info */ + struct task_struct *kthread; + v4l2_std_id tvnorm; + unsigned int width, height; + unsigned int field; + enum cx88_tvaudio tvaudio; + u32 audiomode_manual; + u32 audiomode_current; + u32 input; + u32 last_analog_input; + u32 astat; + u32 use_nicam; + unsigned long last_change; + + /* IR remote control state */ + struct cx88_IR *ir; + + /* I2C remote data */ + struct IR_i2c_init_data init_data; + struct wm8775_platform_data wm8775_data; + + struct mutex lock; + /* various v4l controls */ + u32 freq; + + /* + * cx88-video needs to access cx8802 for hybrid tuner pll access and + * for vb2_is_busy() checks. + */ + struct cx8802_dev *dvbdev; + /* cx88-blackbird needs to access cx8800 for vb2_is_busy() checks */ + struct cx8800_dev *v4ldev; + enum cx88_board_type active_type_id; + int active_ref; + int active_fe_id; +}; + +static inline struct cx88_core *to_core(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct cx88_core, v4l2_dev); +} + +#define call_hw(core, grpid, o, f, args...) \ + do { \ + if (!core->i2c_rc) { \ + if (core->gate_ctrl) \ + core->gate_ctrl(core, 1); \ + v4l2_device_call_all(&core->v4l2_dev, \ + grpid, o, f, ##args); \ + if (core->gate_ctrl) \ + core->gate_ctrl(core, 0); \ + } \ + } while (0) + +#define call_all(core, o, f, args...) call_hw(core, 0, o, f, ##args) + +#define WM8775_GID (1 << 0) + +#define wm8775_s_ctrl(core, id, val) \ + do { \ + struct v4l2_ctrl *ctrl_ = \ + v4l2_ctrl_find(core->sd_wm8775->ctrl_handler, id);\ + if (ctrl_ && !core->i2c_rc) { \ + if (core->gate_ctrl) \ + core->gate_ctrl(core, 1); \ + v4l2_ctrl_s_ctrl(ctrl_, val); \ + if (core->gate_ctrl) \ + core->gate_ctrl(core, 0); \ + } \ + } while (0) + +#define wm8775_g_ctrl(core, id) \ + ({ \ + struct v4l2_ctrl *ctrl_ = \ + v4l2_ctrl_find(core->sd_wm8775->ctrl_handler, id);\ + s32 val = 0; \ + if (ctrl_ && !core->i2c_rc) { \ + if (core->gate_ctrl) \ + core->gate_ctrl(core, 1); \ + val = v4l2_ctrl_g_ctrl(ctrl_); \ + if (core->gate_ctrl) \ + core->gate_ctrl(core, 0); \ + } \ + val; \ + }) + +/* ----------------------------------------------------------- */ +/* function 0: video stuff */ + +struct cx8800_suspend_state { + int disabled; +}; + +struct cx8800_dev { + struct cx88_core *core; + spinlock_t slock; + + /* various device info */ + unsigned int resources; + struct video_device video_dev; + struct video_device vbi_dev; + struct video_device radio_dev; + + /* pci i/o */ + struct pci_dev *pci; + unsigned char pci_rev, pci_lat; + + const struct cx8800_fmt *fmt; + + /* capture queues */ + struct cx88_dmaqueue vidq; + struct vb2_queue vb2_vidq; + struct cx88_dmaqueue vbiq; + struct vb2_queue vb2_vbiq; + + /* various v4l controls */ + + /* other global state info */ + struct cx8800_suspend_state state; +}; + +/* ----------------------------------------------------------- */ +/* function 1: audio/alsa stuff */ +/* =============> moved to cx88-alsa.c <====================== */ + +/* ----------------------------------------------------------- */ +/* function 2: mpeg stuff */ + +struct cx8802_suspend_state { + int disabled; +}; + +struct cx8802_driver { + struct cx88_core *core; + + /* List of drivers attached to device */ + struct list_head drvlist; + + /* Type of driver and access required */ + enum cx88_board_type type_id; + enum cx8802_board_access hw_access; + + /* MPEG 8802 internal only */ + int (*suspend)(struct pci_dev *pci_dev, pm_message_t state); + int (*resume)(struct pci_dev *pci_dev); + + /* Callers to the following functions must hold core->lock */ + + /* MPEG 8802 -> mini driver - Driver probe and configuration */ + int (*probe)(struct cx8802_driver *drv); + int (*remove)(struct cx8802_driver *drv); + + /* MPEG 8802 -> mini driver - Access for hardware control */ + int (*advise_acquire)(struct cx8802_driver *drv); + int (*advise_release)(struct cx8802_driver *drv); + + /* MPEG 8802 <- mini driver - Access for hardware control */ + int (*request_acquire)(struct cx8802_driver *drv); + int (*request_release)(struct cx8802_driver *drv); +}; + +struct cx8802_dev { + struct cx88_core *core; + spinlock_t slock; + + /* pci i/o */ + struct pci_dev *pci; + unsigned char pci_rev, pci_lat; + + /* dma queues */ + struct cx88_dmaqueue mpegq; + struct vb2_queue vb2_mpegq; + u32 ts_packet_size; + u32 ts_packet_count; + + /* other global state info */ + struct cx8802_suspend_state state; + + /* for blackbird only */ + struct list_head devlist; +#if IS_ENABLED(CONFIG_VIDEO_CX88_BLACKBIRD) + struct video_device mpeg_dev; + u32 mailbox; + + /* mpeg params */ + struct cx2341x_handler cxhdl; + +#endif + +#if IS_ENABLED(CONFIG_VIDEO_CX88_DVB) + /* for dvb only */ + struct vb2_dvb_frontends frontends; +#endif + +#if IS_ENABLED(CONFIG_VIDEO_CX88_VP3054) + /* For VP3045 secondary I2C bus support */ + struct vp3054_i2c_state *vp3054; +#endif + /* for switching modulation types */ + unsigned char ts_gen_cntrl; + + /* List of attached drivers; must hold core->lock to access */ + struct list_head drvlist; + + struct work_struct request_module_wk; +}; + +/* ----------------------------------------------------------- */ + +#define cx_read(reg) readl(core->lmmio + ((reg) >> 2)) +#define cx_write(reg, value) writel((value), core->lmmio + ((reg) >> 2)) +#define cx_writeb(reg, value) writeb((value), core->bmmio + (reg)) + +#define cx_andor(reg, mask, value) \ + writel((readl(core->lmmio + ((reg) >> 2)) & ~(mask)) |\ + ((value) & (mask)), core->lmmio + ((reg) >> 2)) +#define cx_set(reg, bit) cx_andor((reg), (bit), (bit)) +#define cx_clear(reg, bit) cx_andor((reg), (bit), 0) + +#define cx_wait(d) { if (need_resched()) schedule(); else udelay(d); } + +/* shadow registers */ +#define cx_sread(sreg) (core->shadow[sreg]) +#define cx_swrite(sreg, reg, value) \ + (core->shadow[sreg] = value, \ + writel(core->shadow[sreg], core->lmmio + ((reg) >> 2))) +#define cx_sandor(sreg, reg, mask, value) \ + (core->shadow[sreg] = (core->shadow[sreg] & ~(mask)) | \ + ((value) & (mask)), \ + writel(core->shadow[sreg], \ + core->lmmio + ((reg) >> 2))) + +/* ----------------------------------------------------------- */ +/* cx88-core.c */ + +extern unsigned int cx88_core_debug; + +void cx88_print_irqbits(const char *tag, const char *strings[], + int len, u32 bits, u32 mask); + +int cx88_core_irq(struct cx88_core *core, u32 status); +void cx88_wakeup(struct cx88_core *core, + struct cx88_dmaqueue *q, u32 count); +void cx88_shutdown(struct cx88_core *core); +int cx88_reset(struct cx88_core *core); + +extern int +cx88_risc_buffer(struct pci_dev *pci, struct cx88_riscmem *risc, + struct scatterlist *sglist, + unsigned int top_offset, unsigned int bottom_offset, + unsigned int bpl, unsigned int padding, unsigned int lines); +extern int +cx88_risc_databuffer(struct pci_dev *pci, struct cx88_riscmem *risc, + struct scatterlist *sglist, unsigned int bpl, + unsigned int lines, unsigned int lpi); + +void cx88_risc_disasm(struct cx88_core *core, + struct cx88_riscmem *risc); +int cx88_sram_channel_setup(struct cx88_core *core, + const struct sram_channel *ch, + unsigned int bpl, u32 risc); +void cx88_sram_channel_dump(struct cx88_core *core, + const struct sram_channel *ch); + +int cx88_set_scale(struct cx88_core *core, unsigned int width, + unsigned int height, enum v4l2_field field); +int cx88_set_tvnorm(struct cx88_core *core, v4l2_std_id norm); + +void cx88_vdev_init(struct cx88_core *core, + struct pci_dev *pci, + struct video_device *vfd, + const struct video_device *template_, + const char *type); +struct cx88_core *cx88_core_get(struct pci_dev *pci); +void cx88_core_put(struct cx88_core *core, + struct pci_dev *pci); + +int cx88_start_audio_dma(struct cx88_core *core); +int cx88_stop_audio_dma(struct cx88_core *core); + +/* ----------------------------------------------------------- */ +/* cx88-vbi.c */ + +/* Can be used as g_vbi_fmt, try_vbi_fmt and s_vbi_fmt */ +int cx8800_vbi_fmt(struct file *file, void *priv, + struct v4l2_format *f); + +void cx8800_stop_vbi_dma(struct cx8800_dev *dev); +int cx8800_restart_vbi_queue(struct cx8800_dev *dev, struct cx88_dmaqueue *q); + +extern const struct vb2_ops cx8800_vbi_qops; + +/* ----------------------------------------------------------- */ +/* cx88-i2c.c */ + +int cx88_i2c_init(struct cx88_core *core, struct pci_dev *pci); + +/* ----------------------------------------------------------- */ +/* cx88-cards.c */ + +int cx88_tuner_callback(void *dev, int component, int command, int arg); +int cx88_get_resources(const struct cx88_core *core, + struct pci_dev *pci); +struct cx88_core *cx88_core_create(struct pci_dev *pci, int nr); +void cx88_setup_xc3028(struct cx88_core *core, struct xc2028_ctrl *ctl); + +/* ----------------------------------------------------------- */ +/* cx88-tvaudio.c */ + +void cx88_set_tvaudio(struct cx88_core *core); +void cx88_newstation(struct cx88_core *core); +void cx88_get_stereo(struct cx88_core *core, struct v4l2_tuner *t); +void cx88_set_stereo(struct cx88_core *core, u32 mode, int manual); +int cx88_audio_thread(void *data); + +int cx8802_register_driver(struct cx8802_driver *drv); +int cx8802_unregister_driver(struct cx8802_driver *drv); + +/* Caller must hold core->lock */ +struct cx8802_driver *cx8802_get_driver(struct cx8802_dev *dev, + enum cx88_board_type btype); + +/* ----------------------------------------------------------- */ +/* cx88-dsp.c */ + +s32 cx88_dsp_detect_stereo_sap(struct cx88_core *core); + +/* ----------------------------------------------------------- */ +/* cx88-input.c */ + +int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci); +int cx88_ir_fini(struct cx88_core *core); +void cx88_ir_irq(struct cx88_core *core); +int cx88_ir_start(struct cx88_core *core); +void cx88_ir_stop(struct cx88_core *core); +void cx88_i2c_init_ir(struct cx88_core *core); + +/* ----------------------------------------------------------- */ +/* cx88-mpeg.c */ + +int cx8802_buf_prepare(struct vb2_queue *q, struct cx8802_dev *dev, + struct cx88_buffer *buf); +void cx8802_buf_queue(struct cx8802_dev *dev, struct cx88_buffer *buf); +void cx8802_cancel_buffers(struct cx8802_dev *dev); +int cx8802_start_dma(struct cx8802_dev *dev, + struct cx88_dmaqueue *q, + struct cx88_buffer *buf); + +/* ----------------------------------------------------------- */ +/* cx88-video.c*/ +int cx88_enum_input(struct cx88_core *core, struct v4l2_input *i); +int cx88_set_freq(struct cx88_core *core, const struct v4l2_frequency *f); +int cx88_video_mux(struct cx88_core *core, unsigned int input); +int cx88_querycap(struct file *file, struct cx88_core *core, + struct v4l2_capability *cap); + +#endif diff --git a/drivers/media/pci/ddbridge/Kconfig b/drivers/media/pci/ddbridge/Kconfig new file mode 100644 index 000000000..169efd558 --- /dev/null +++ b/drivers/media/pci/ddbridge/Kconfig @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DVB_DDBRIDGE + tristate "Digital Devices bridge support" + depends on DVB_CORE && PCI && I2C + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV6110x if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV090x if MEDIA_SUBDRV_AUTOSELECT + select DVB_DRXK if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA18271C2DD if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0367 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CXD2841ER if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0910 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV6111 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LNBH25 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18212 if MEDIA_SUBDRV_AUTOSELECT + select DVB_MXL5XX if MEDIA_SUBDRV_AUTOSELECT + select DVB_CXD2099 if MEDIA_SUBDRV_AUTOSELECT + help + Support for cards with the Digital Devices PCI express bridge: + - Octopus PCIe Bridge + - Octopus mini PCIe Bridge + - Octopus LE + - DuoFlex S2 Octopus + - DuoFlex CT Octopus + - cineS2(v6) + - CineCTv6 and DuoFlex CT (STV0367-based) + - CineCTv7 and DuoFlex CT2/C2T2/C2T2I (Sony CXD28xx-based) + - MaxA8 series + - CineS2 V7/V7A and DuoFlex S2 V4 (ST STV0910-based) + - Max S4/8 + + Say Y if you own such a card and want to use it. + +config DVB_DDBRIDGE_MSIENABLE + bool "Enable Message Signaled Interrupts (MSI) per default (EXPERIMENTAL)" + depends on DVB_DDBRIDGE + depends on PCI_MSI + help + Use PCI MSI (Message Signaled Interrupts) per default. Enabling this + might lead to I2C errors originating from the bridge in conjunction + with certain SATA controllers, requiring a reload of the ddbridge + module. MSI can still be disabled by passing msi=0 as option, as + this will just change the msi option default value. + + If you're unsure, concerned about stability and don't want to pass + module options in case of troubles, say N. diff --git a/drivers/media/pci/ddbridge/Makefile b/drivers/media/pci/ddbridge/Makefile new file mode 100644 index 000000000..5e7eab811 --- /dev/null +++ b/drivers/media/pci/ddbridge/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the ddbridge device driver +# + +ddbridge-objs := ddbridge-main.o ddbridge-core.o ddbridge-ci.o \ + ddbridge-hw.o ddbridge-i2c.o ddbridge-max.o ddbridge-mci.o \ + ddbridge-sx8.o + +obj-$(CONFIG_DVB_DDBRIDGE) += ddbridge.o ddbridge-dummy-fe.o + +ccflags-y += -I $(srctree)/drivers/media/dvb-frontends/ +ccflags-y += -I $(srctree)/drivers/media/tuners/ diff --git a/drivers/media/pci/ddbridge/ddbridge-ci.c b/drivers/media/pci/ddbridge/ddbridge-ci.c new file mode 100644 index 000000000..e1d1f277f --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-ci.c @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ddbridge-ci.c: Digital Devices bridge CI (DuoFlex, CI Bridge) support + * + * Copyright (C) 2010-2017 Digital Devices GmbH + * Marcus Metzler + * Ralph Metzler + */ + +#include "ddbridge.h" +#include "ddbridge-regs.h" +#include "ddbridge-ci.h" +#include "ddbridge-io.h" +#include "ddbridge-i2c.h" + +#include "cxd2099.h" + +/* Octopus CI internal CI interface */ + +static int wait_ci_ready(struct ddb_ci *ci) +{ + u32 count = 10; + + ndelay(500); + do { + if (ddbreadl(ci->port->dev, + CI_CONTROL(ci->nr)) & CI_READY) + break; + usleep_range(1, 2); + if ((--count) == 0) + return -1; + } while (1); + return 0; +} + +static int read_attribute_mem(struct dvb_ca_en50221 *ca, + int slot, int address) +{ + struct ddb_ci *ci = ca->data; + u32 val, off = (address >> 1) & (CI_BUFFER_SIZE - 1); + + if (address > CI_BUFFER_SIZE) + return -1; + ddbwritel(ci->port->dev, CI_READ_CMD | (1 << 16) | address, + CI_DO_READ_ATTRIBUTES(ci->nr)); + wait_ci_ready(ci); + val = 0xff & ddbreadl(ci->port->dev, CI_BUFFER(ci->nr) + off); + return val; +} + +static int write_attribute_mem(struct dvb_ca_en50221 *ca, int slot, + int address, u8 value) +{ + struct ddb_ci *ci = ca->data; + + ddbwritel(ci->port->dev, CI_WRITE_CMD | (value << 16) | address, + CI_DO_ATTRIBUTE_RW(ci->nr)); + wait_ci_ready(ci); + return 0; +} + +static int read_cam_control(struct dvb_ca_en50221 *ca, + int slot, u8 address) +{ + u32 count = 100; + struct ddb_ci *ci = ca->data; + u32 res; + + ddbwritel(ci->port->dev, CI_READ_CMD | address, + CI_DO_IO_RW(ci->nr)); + ndelay(500); + do { + res = ddbreadl(ci->port->dev, CI_READDATA(ci->nr)); + if (res & CI_READY) + break; + usleep_range(1, 2); + if ((--count) == 0) + return -1; + } while (1); + return 0xff & res; +} + +static int write_cam_control(struct dvb_ca_en50221 *ca, int slot, + u8 address, u8 value) +{ + struct ddb_ci *ci = ca->data; + + ddbwritel(ci->port->dev, CI_WRITE_CMD | (value << 16) | address, + CI_DO_IO_RW(ci->nr)); + wait_ci_ready(ci); + return 0; +} + +static int slot_reset(struct dvb_ca_en50221 *ca, int slot) +{ + struct ddb_ci *ci = ca->data; + + ddbwritel(ci->port->dev, CI_POWER_ON, + CI_CONTROL(ci->nr)); + msleep(100); + ddbwritel(ci->port->dev, CI_POWER_ON | CI_RESET_CAM, + CI_CONTROL(ci->nr)); + ddbwritel(ci->port->dev, CI_ENABLE | CI_POWER_ON | CI_RESET_CAM, + CI_CONTROL(ci->nr)); + usleep_range(20, 25); + ddbwritel(ci->port->dev, CI_ENABLE | CI_POWER_ON, + CI_CONTROL(ci->nr)); + return 0; +} + +static int slot_shutdown(struct dvb_ca_en50221 *ca, int slot) +{ + struct ddb_ci *ci = ca->data; + + ddbwritel(ci->port->dev, 0, CI_CONTROL(ci->nr)); + msleep(300); + return 0; +} + +static int slot_ts_enable(struct dvb_ca_en50221 *ca, int slot) +{ + struct ddb_ci *ci = ca->data; + u32 val = ddbreadl(ci->port->dev, CI_CONTROL(ci->nr)); + + ddbwritel(ci->port->dev, val | CI_BYPASS_DISABLE, + CI_CONTROL(ci->nr)); + return 0; +} + +static int poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open) +{ + struct ddb_ci *ci = ca->data; + u32 val = ddbreadl(ci->port->dev, CI_CONTROL(ci->nr)); + int stat = 0; + + if (val & CI_CAM_DETECT) + stat |= DVB_CA_EN50221_POLL_CAM_PRESENT; + if (val & CI_CAM_READY) + stat |= DVB_CA_EN50221_POLL_CAM_READY; + return stat; +} + +static struct dvb_ca_en50221 en_templ = { + .read_attribute_mem = read_attribute_mem, + .write_attribute_mem = write_attribute_mem, + .read_cam_control = read_cam_control, + .write_cam_control = write_cam_control, + .slot_reset = slot_reset, + .slot_shutdown = slot_shutdown, + .slot_ts_enable = slot_ts_enable, + .poll_slot_status = poll_slot_status, +}; + +static void ci_attach(struct ddb_port *port) +{ + struct ddb_ci *ci; + + ci = kzalloc_obj(*ci); + if (!ci) + return; + memcpy(&ci->en, &en_templ, sizeof(en_templ)); + ci->en.data = ci; + port->en = &ci->en; + port->en_freedata = 1; + ci->port = port; + ci->nr = port->nr - 2; +} + +/* DuoFlex Dual CI support */ + +static int write_creg(struct ddb_ci *ci, u8 data, u8 mask) +{ + struct i2c_adapter *i2c = &ci->port->i2c->adap; + u8 adr = (ci->port->type == DDB_CI_EXTERNAL_XO2) ? 0x12 : 0x13; + + ci->port->creg = (ci->port->creg & ~mask) | data; + return i2c_write_reg(i2c, adr, 0x02, ci->port->creg); +} + +static int read_attribute_mem_xo2(struct dvb_ca_en50221 *ca, + int slot, int address) +{ + struct ddb_ci *ci = ca->data; + struct i2c_adapter *i2c = &ci->port->i2c->adap; + u8 adr = (ci->port->type == DDB_CI_EXTERNAL_XO2) ? 0x12 : 0x13; + int res; + u8 val; + + res = i2c_read_reg16(i2c, adr, 0x8000 | address, &val); + return res ? res : val; +} + +static int write_attribute_mem_xo2(struct dvb_ca_en50221 *ca, int slot, + int address, u8 value) +{ + struct ddb_ci *ci = ca->data; + struct i2c_adapter *i2c = &ci->port->i2c->adap; + u8 adr = (ci->port->type == DDB_CI_EXTERNAL_XO2) ? 0x12 : 0x13; + + return i2c_write_reg16(i2c, adr, 0x8000 | address, value); +} + +static int read_cam_control_xo2(struct dvb_ca_en50221 *ca, + int slot, u8 address) +{ + struct ddb_ci *ci = ca->data; + struct i2c_adapter *i2c = &ci->port->i2c->adap; + u8 adr = (ci->port->type == DDB_CI_EXTERNAL_XO2) ? 0x12 : 0x13; + u8 val; + int res; + + res = i2c_read_reg(i2c, adr, 0x20 | (address & 3), &val); + return res ? res : val; +} + +static int write_cam_control_xo2(struct dvb_ca_en50221 *ca, int slot, + u8 address, u8 value) +{ + struct ddb_ci *ci = ca->data; + struct i2c_adapter *i2c = &ci->port->i2c->adap; + u8 adr = (ci->port->type == DDB_CI_EXTERNAL_XO2) ? 0x12 : 0x13; + + return i2c_write_reg(i2c, adr, 0x20 | (address & 3), value); +} + +static int slot_reset_xo2(struct dvb_ca_en50221 *ca, int slot) +{ + struct ddb_ci *ci = ca->data; + + dev_dbg(ci->port->dev->dev, "%s\n", __func__); + write_creg(ci, 0x01, 0x01); + write_creg(ci, 0x04, 0x04); + msleep(20); + write_creg(ci, 0x02, 0x02); + write_creg(ci, 0x00, 0x04); + write_creg(ci, 0x18, 0x18); + return 0; +} + +static int slot_shutdown_xo2(struct dvb_ca_en50221 *ca, int slot) +{ + struct ddb_ci *ci = ca->data; + + dev_dbg(ci->port->dev->dev, "%s\n", __func__); + write_creg(ci, 0x10, 0xff); + write_creg(ci, 0x08, 0x08); + return 0; +} + +static int slot_ts_enable_xo2(struct dvb_ca_en50221 *ca, int slot) +{ + struct ddb_ci *ci = ca->data; + + dev_dbg(ci->port->dev->dev, "%s\n", __func__); + write_creg(ci, 0x00, 0x10); + return 0; +} + +static int poll_slot_status_xo2(struct dvb_ca_en50221 *ca, int slot, int open) +{ + struct ddb_ci *ci = ca->data; + struct i2c_adapter *i2c = &ci->port->i2c->adap; + u8 adr = (ci->port->type == DDB_CI_EXTERNAL_XO2) ? 0x12 : 0x13; + u8 val = 0; + int stat = 0; + + i2c_read_reg(i2c, adr, 0x01, &val); + + if (val & 2) + stat |= DVB_CA_EN50221_POLL_CAM_PRESENT; + if (val & 1) + stat |= DVB_CA_EN50221_POLL_CAM_READY; + return stat; +} + +static struct dvb_ca_en50221 en_xo2_templ = { + .read_attribute_mem = read_attribute_mem_xo2, + .write_attribute_mem = write_attribute_mem_xo2, + .read_cam_control = read_cam_control_xo2, + .write_cam_control = write_cam_control_xo2, + .slot_reset = slot_reset_xo2, + .slot_shutdown = slot_shutdown_xo2, + .slot_ts_enable = slot_ts_enable_xo2, + .poll_slot_status = poll_slot_status_xo2, +}; + +static void ci_xo2_attach(struct ddb_port *port) +{ + struct ddb_ci *ci; + + ci = kzalloc_obj(*ci); + if (!ci) + return; + memcpy(&ci->en, &en_xo2_templ, sizeof(en_xo2_templ)); + ci->en.data = ci; + port->en = &ci->en; + port->en_freedata = 1; + ci->port = port; + ci->nr = port->nr - 2; + ci->port->creg = 0; + write_creg(ci, 0x10, 0xff); + write_creg(ci, 0x08, 0x08); +} + +static const struct cxd2099_cfg cxd_cfgtmpl = { + .bitrate = 72000, + .polarity = 1, + .clock_mode = 1, + .max_i2c = 512, +}; + +static int ci_cxd2099_attach(struct ddb_port *port, u32 bitrate) +{ + struct cxd2099_cfg cxd_cfg = cxd_cfgtmpl; + struct i2c_client *client; + + cxd_cfg.bitrate = bitrate; + cxd_cfg.en = &port->en; + + client = dvb_module_probe("cxd2099", NULL, &port->i2c->adap, + 0x40, &cxd_cfg); + if (!client) + goto err; + + port->dvb[0].i2c_client[0] = client; + port->en_freedata = 0; + return 0; + +err: + dev_err(port->dev->dev, "CXD2099AR attach failed\n"); + return -ENODEV; +} + +int ddb_ci_attach(struct ddb_port *port, u32 bitrate) +{ + int ret; + + switch (port->type) { + case DDB_CI_EXTERNAL_SONY: + ret = ci_cxd2099_attach(port, bitrate); + if (ret) + return -ENODEV; + break; + case DDB_CI_EXTERNAL_XO2: + case DDB_CI_EXTERNAL_XO2_B: + ci_xo2_attach(port); + break; + case DDB_CI_INTERNAL: + ci_attach(port); + break; + default: + return -ENODEV; + } + + if (!port->en) + return -ENODEV; + dvb_ca_en50221_init(port->dvb[0].adap, port->en, 0, 1); + return 0; +} + +void ddb_ci_detach(struct ddb_port *port) +{ + if (port->dvb[0].dev) + dvb_unregister_device(port->dvb[0].dev); + if (port->en) { + dvb_ca_en50221_release(port->en); + + dvb_module_release(port->dvb[0].i2c_client[0]); + port->dvb[0].i2c_client[0] = NULL; + + /* free alloc'ed memory if needed */ + if (port->en_freedata) + kfree(port->en->data); + + port->en = NULL; + } +} diff --git a/drivers/media/pci/ddbridge/ddbridge-ci.h b/drivers/media/pci/ddbridge/ddbridge-ci.h new file mode 100644 index 000000000..41cd97e52 --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-ci.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ddbridge-ci.h: Digital Devices bridge CI (DuoFlex, CI Bridge) support + * + * Copyright (C) 2010-2017 Digital Devices GmbH + * Marcus Metzler + * Ralph Metzler + */ + +#ifndef __DDBRIDGE_CI_H__ +#define __DDBRIDGE_CI_H__ + +#include "ddbridge.h" + +/******************************************************************************/ + +int ddb_ci_attach(struct ddb_port *port, u32 bitrate); +void ddb_ci_detach(struct ddb_port *port); + +#endif /* __DDBRIDGE_CI_H__ */ diff --git a/drivers/media/pci/ddbridge/ddbridge-core.c b/drivers/media/pci/ddbridge/ddbridge-core.c new file mode 100644 index 000000000..d240e291b --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-core.c @@ -0,0 +1,3438 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ddbridge-core.c: Digital Devices bridge core functions + * + * Copyright (C) 2010-2017 Digital Devices GmbH + * Marcus Metzler + * Ralph Metzler + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ddbridge.h" +#include "ddbridge-i2c.h" +#include "ddbridge-regs.h" +#include "ddbridge-max.h" +#include "ddbridge-ci.h" +#include "ddbridge-io.h" + +#include "tda18271c2dd.h" +#include "stv6110x.h" +#include "stv090x.h" +#include "lnbh24.h" +#include "drxk.h" +#include "stv0367.h" +#include "stv0367_priv.h" +#include "cxd2841er.h" +#include "tda18212.h" +#include "stv0910.h" +#include "stv6111.h" +#include "lnbh25.h" +#include "cxd2099.h" +#include "ddbridge-dummy-fe.h" + +/****************************************************************************/ + +#define DDB_MAX_ADAPTER 64 + +/****************************************************************************/ + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static int adapter_alloc; +module_param(adapter_alloc, int, 0444); +MODULE_PARM_DESC(adapter_alloc, + "0-one adapter per io, 1-one per tab with io, 2-one per tab, 3-one for all"); + +static int ci_bitrate = 70000; +module_param(ci_bitrate, int, 0444); +MODULE_PARM_DESC(ci_bitrate, " Bitrate in KHz for output to CI."); + +static int ts_loop = -1; +module_param(ts_loop, int, 0444); +MODULE_PARM_DESC(ts_loop, "TS in/out test loop on port ts_loop"); + +static int xo2_speed = 2; +module_param(xo2_speed, int, 0444); +MODULE_PARM_DESC(xo2_speed, "default transfer speed for xo2 based duoflex, 0=55,1=75,2=90,3=104 MBit/s, default=2, use attribute to change for individual cards"); + +#ifdef __arm__ +static int alt_dma = 1; +#else +static int alt_dma; +#endif +module_param(alt_dma, int, 0444); +MODULE_PARM_DESC(alt_dma, "use alternative DMA buffer handling"); + +static int no_init; +module_param(no_init, int, 0444); +MODULE_PARM_DESC(no_init, "do not initialize most devices"); + +static int stv0910_single; +module_param(stv0910_single, int, 0444); +MODULE_PARM_DESC(stv0910_single, "use stv0910 cards as single demods"); + +static int dma_buf_num = 8; +module_param(dma_buf_num, int, 0444); +MODULE_PARM_DESC(dma_buf_num, "Number of DMA buffers, possible values: 8-32"); + +static int dma_buf_size = 21; +module_param(dma_buf_size, int, 0444); +MODULE_PARM_DESC(dma_buf_size, + "DMA buffer size as multiple of 128*47, possible values: 1-43"); + +static int dummy_tuner; +module_param(dummy_tuner, int, 0444); +MODULE_PARM_DESC(dummy_tuner, + "attach dummy tuner to port 0 on Octopus V3 or Octopus Mini cards"); + +/****************************************************************************/ + +static DEFINE_MUTEX(redirect_lock); + +static struct workqueue_struct *ddb_wq; + +static struct ddb *ddbs[DDB_MAX_ADAPTER]; + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +struct ddb_irq *ddb_irq_set(struct ddb *dev, u32 link, u32 nr, + void (*handler)(void *), void *data) +{ + struct ddb_irq *irq = &dev->link[link].irq[nr]; + + irq->handler = handler; + irq->data = data; + return irq; +} + +static void ddb_set_dma_table(struct ddb_io *io) +{ + struct ddb *dev = io->port->dev; + struct ddb_dma *dma = io->dma; + u32 i; + u64 mem; + + if (!dma) + return; + for (i = 0; i < dma->num; i++) { + mem = dma->pbuf[i]; + ddbwritel(dev, mem & 0xffffffff, dma->bufregs + i * 8); + ddbwritel(dev, mem >> 32, dma->bufregs + i * 8 + 4); + } + dma->bufval = ((dma->div & 0x0f) << 16) | + ((dma->num & 0x1f) << 11) | + ((dma->size >> 7) & 0x7ff); +} + +static void ddb_set_dma_tables(struct ddb *dev) +{ + u32 i; + + for (i = 0; i < DDB_MAX_PORT; i++) { + if (dev->port[i].input[0]) + ddb_set_dma_table(dev->port[i].input[0]); + if (dev->port[i].input[1]) + ddb_set_dma_table(dev->port[i].input[1]); + if (dev->port[i].output) + ddb_set_dma_table(dev->port[i].output); + } +} + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +static void ddb_redirect_dma(struct ddb *dev, + struct ddb_dma *sdma, + struct ddb_dma *ddma) +{ + u32 i, base; + u64 mem; + + sdma->bufval = ddma->bufval; + base = sdma->bufregs; + for (i = 0; i < ddma->num; i++) { + mem = ddma->pbuf[i]; + ddbwritel(dev, mem & 0xffffffff, base + i * 8); + ddbwritel(dev, mem >> 32, base + i * 8 + 4); + } +} + +static int ddb_unredirect(struct ddb_port *port) +{ + struct ddb_input *oredi, *iredi = NULL; + struct ddb_output *iredo = NULL; + + /* dev_info(port->dev->dev, + * "unredirect %d.%d\n", port->dev->nr, port->nr); + */ + mutex_lock(&redirect_lock); + if (port->output->dma->running) { + mutex_unlock(&redirect_lock); + return -EBUSY; + } + oredi = port->output->redi; + if (!oredi) + goto done; + if (port->input[0]) { + iredi = port->input[0]->redi; + iredo = port->input[0]->redo; + + if (iredo) { + iredo->port->output->redi = oredi; + if (iredo->port->input[0]) { + iredo->port->input[0]->redi = iredi; + ddb_redirect_dma(oredi->port->dev, + oredi->dma, iredo->dma); + } + port->input[0]->redo = NULL; + ddb_set_dma_table(port->input[0]); + } + oredi->redi = iredi; + port->input[0]->redi = NULL; + } + oredi->redo = NULL; + port->output->redi = NULL; + + ddb_set_dma_table(oredi); +done: + mutex_unlock(&redirect_lock); + return 0; +} + +static int ddb_redirect(u32 i, u32 p) +{ + struct ddb *idev = ddbs[(i >> 4) & 0x3f]; + struct ddb_input *input, *input2; + struct ddb *pdev = ddbs[(p >> 4) & 0x3f]; + struct ddb_port *port; + + if (!idev || !pdev) + return -EINVAL; + if (!idev->has_dma || !pdev->has_dma) + return -EINVAL; + + port = &pdev->port[p & 0x0f]; + if (!port->output) + return -EINVAL; + if (ddb_unredirect(port)) + return -EBUSY; + + if (i == 8) + return 0; + + input = &idev->input[i & 7]; + if (!input) + return -EINVAL; + + mutex_lock(&redirect_lock); + if (port->output->dma->running || input->dma->running) { + mutex_unlock(&redirect_lock); + return -EBUSY; + } + input2 = port->input[0]; + if (input2) { + if (input->redi) { + input2->redi = input->redi; + input->redi = NULL; + } else { + input2->redi = input; + } + } + input->redo = port->output; + port->output->redi = input; + + ddb_redirect_dma(input->port->dev, input->dma, port->output->dma); + mutex_unlock(&redirect_lock); + return 0; +} + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +static void dma_free(struct pci_dev *pdev, struct ddb_dma *dma, int dir) +{ + int i; + + if (!dma) + return; + for (i = 0; i < dma->num; i++) { + if (dma->vbuf[i]) { + if (alt_dma) { + dma_unmap_single(&pdev->dev, dma->pbuf[i], + dma->size, + dir ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + kfree(dma->vbuf[i]); + dma->vbuf[i] = NULL; + } else { + dma_free_coherent(&pdev->dev, dma->size, + dma->vbuf[i], dma->pbuf[i]); + } + + dma->vbuf[i] = NULL; + } + } +} + +static int dma_alloc(struct pci_dev *pdev, struct ddb_dma *dma, int dir) +{ + int i; + + if (!dma) + return 0; + for (i = 0; i < dma->num; i++) { + if (alt_dma) { + dma->vbuf[i] = kmalloc(dma->size, __GFP_RETRY_MAYFAIL); + if (!dma->vbuf[i]) + return -ENOMEM; + dma->pbuf[i] = dma_map_single(&pdev->dev, + dma->vbuf[i], + dma->size, + dir ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + if (dma_mapping_error(&pdev->dev, dma->pbuf[i])) { + kfree(dma->vbuf[i]); + dma->vbuf[i] = NULL; + return -ENOMEM; + } + } else { + dma->vbuf[i] = dma_alloc_coherent(&pdev->dev, + dma->size, + &dma->pbuf[i], + GFP_KERNEL); + if (!dma->vbuf[i]) + return -ENOMEM; + } + } + return 0; +} + +int ddb_buffers_alloc(struct ddb *dev) +{ + int i; + struct ddb_port *port; + + for (i = 0; i < dev->port_num; i++) { + port = &dev->port[i]; + switch (port->class) { + case DDB_PORT_TUNER: + if (port->input[0]->dma) + if (dma_alloc(dev->pdev, port->input[0]->dma, 0) + < 0) + return -1; + if (port->input[1]->dma) + if (dma_alloc(dev->pdev, port->input[1]->dma, 0) + < 0) + return -1; + break; + case DDB_PORT_CI: + case DDB_PORT_LOOP: + if (port->input[0]->dma) + if (dma_alloc(dev->pdev, port->input[0]->dma, 0) + < 0) + return -1; + if (port->output->dma) + if (dma_alloc(dev->pdev, port->output->dma, 1) + < 0) + return -1; + break; + default: + break; + } + } + ddb_set_dma_tables(dev); + return 0; +} + +void ddb_buffers_free(struct ddb *dev) +{ + int i; + struct ddb_port *port; + + for (i = 0; i < dev->port_num; i++) { + port = &dev->port[i]; + + if (port->input[0] && port->input[0]->dma) + dma_free(dev->pdev, port->input[0]->dma, 0); + if (port->input[1] && port->input[1]->dma) + dma_free(dev->pdev, port->input[1]->dma, 0); + if (port->output && port->output->dma) + dma_free(dev->pdev, port->output->dma, 1); + } +} + +static void calc_con(struct ddb_output *output, u32 *con, u32 *con2, u32 flags) +{ + struct ddb *dev = output->port->dev; + u32 bitrate = output->port->obr, max_bitrate = 72000; + u32 gap = 4, nco = 0; + + *con = 0x1c; + if (output->port->gap != 0xffffffff) { + flags |= 1; + gap = output->port->gap; + max_bitrate = 0; + } + if (dev->link[0].info->type == DDB_OCTOPUS_CI && output->port->nr > 1) { + *con = 0x10c; + if (dev->link[0].ids.regmapid >= 0x10003 && !(flags & 1)) { + if (!(flags & 2)) { + /* NCO */ + max_bitrate = 0; + gap = 0; + if (bitrate != 72000) { + if (bitrate >= 96000) { + *con |= 0x800; + } else { + *con |= 0x1000; + nco = (bitrate * 8192 + 71999) + / 72000; + } + } + } else { + /* Divider and gap */ + *con |= 0x1810; + if (bitrate <= 64000) { + max_bitrate = 64000; + nco = 8; + } else if (bitrate <= 72000) { + max_bitrate = 72000; + nco = 7; + } else { + max_bitrate = 96000; + nco = 5; + } + } + } else { + if (bitrate > 72000) { + *con |= 0x810; /* 96 MBit/s and gap */ + max_bitrate = 96000; + } + *con |= 0x10; /* enable gap */ + } + } + if (max_bitrate > 0) { + if (bitrate > max_bitrate) + bitrate = max_bitrate; + if (bitrate < 31000) + bitrate = 31000; + gap = ((max_bitrate - bitrate) * 94) / bitrate; + if (gap < 2) + *con &= ~0x10; /* Disable gap */ + else + gap -= 2; + if (gap > 127) + gap = 127; + } + + *con2 = (nco << 16) | gap; +} + +static void ddb_output_start(struct ddb_output *output) +{ + struct ddb *dev = output->port->dev; + u32 con = 0x11c, con2 = 0; + + spin_lock_irq(&output->dma->lock); + output->dma->cbuf = 0; + output->dma->coff = 0; + output->dma->stat = 0; + ddbwritel(dev, 0, DMA_BUFFER_CONTROL(output->dma)); + + if (output->port->input[0]->port->class == DDB_PORT_LOOP) + con = (1UL << 13) | 0x14; + else + calc_con(output, &con, &con2, 0); + + ddbwritel(dev, 0, TS_CONTROL(output)); + ddbwritel(dev, 2, TS_CONTROL(output)); + ddbwritel(dev, 0, TS_CONTROL(output)); + ddbwritel(dev, con, TS_CONTROL(output)); + ddbwritel(dev, con2, TS_CONTROL2(output)); + + ddbwritel(dev, output->dma->bufval, + DMA_BUFFER_SIZE(output->dma)); + ddbwritel(dev, 0, DMA_BUFFER_ACK(output->dma)); + ddbwritel(dev, 1, DMA_BASE_READ); + ddbwritel(dev, 7, DMA_BUFFER_CONTROL(output->dma)); + + ddbwritel(dev, con | 1, TS_CONTROL(output)); + + output->dma->running = 1; + spin_unlock_irq(&output->dma->lock); +} + +static void ddb_output_stop(struct ddb_output *output) +{ + struct ddb *dev = output->port->dev; + + spin_lock_irq(&output->dma->lock); + + ddbwritel(dev, 0, TS_CONTROL(output)); + + ddbwritel(dev, 0, DMA_BUFFER_CONTROL(output->dma)); + output->dma->running = 0; + spin_unlock_irq(&output->dma->lock); +} + +static void ddb_input_stop(struct ddb_input *input) +{ + struct ddb *dev = input->port->dev; + u32 tag = DDB_LINK_TAG(input->port->lnr); + + spin_lock_irq(&input->dma->lock); + + ddbwritel(dev, 0, tag | TS_CONTROL(input)); + + ddbwritel(dev, 0, DMA_BUFFER_CONTROL(input->dma)); + input->dma->running = 0; + spin_unlock_irq(&input->dma->lock); +} + +static void ddb_input_start(struct ddb_input *input) +{ + struct ddb *dev = input->port->dev; + + spin_lock_irq(&input->dma->lock); + input->dma->cbuf = 0; + input->dma->coff = 0; + input->dma->stat = 0; + ddbwritel(dev, 0, DMA_BUFFER_CONTROL(input->dma)); + + ddbwritel(dev, 0, TS_CONTROL(input)); + ddbwritel(dev, 2, TS_CONTROL(input)); + ddbwritel(dev, 0, TS_CONTROL(input)); + + ddbwritel(dev, input->dma->bufval, + DMA_BUFFER_SIZE(input->dma)); + ddbwritel(dev, 0, DMA_BUFFER_ACK(input->dma)); + ddbwritel(dev, 1, DMA_BASE_WRITE); + ddbwritel(dev, 3, DMA_BUFFER_CONTROL(input->dma)); + + ddbwritel(dev, 0x09, TS_CONTROL(input)); + + if (input->port->type == DDB_TUNER_DUMMY) + ddbwritel(dev, 0x000fff01, TS_CONTROL2(input)); + + input->dma->running = 1; + spin_unlock_irq(&input->dma->lock); +} + +static void ddb_input_start_all(struct ddb_input *input) +{ + struct ddb_input *i = input; + struct ddb_output *o; + + mutex_lock(&redirect_lock); + while (i && (o = i->redo)) { + ddb_output_start(o); + i = o->port->input[0]; + if (i) + ddb_input_start(i); + } + ddb_input_start(input); + mutex_unlock(&redirect_lock); +} + +static void ddb_input_stop_all(struct ddb_input *input) +{ + struct ddb_input *i = input; + struct ddb_output *o; + + mutex_lock(&redirect_lock); + ddb_input_stop(input); + while (i && (o = i->redo)) { + ddb_output_stop(o); + i = o->port->input[0]; + if (i) + ddb_input_stop(i); + } + mutex_unlock(&redirect_lock); +} + +static u32 ddb_output_free(struct ddb_output *output) +{ + u32 idx, off, stat = output->dma->stat; + s32 diff; + + idx = (stat >> 11) & 0x1f; + off = (stat & 0x7ff) << 7; + + if (output->dma->cbuf != idx) { + if ((((output->dma->cbuf + 1) % output->dma->num) == idx) && + (output->dma->size - output->dma->coff <= (2 * 188))) + return 0; + return 188; + } + diff = off - output->dma->coff; + if (diff <= 0 || diff > (2 * 188)) + return 188; + return 0; +} + +static ssize_t ddb_output_write(struct ddb_output *output, + const __user u8 *buf, size_t count) +{ + struct ddb *dev = output->port->dev; + u32 idx, off, stat = output->dma->stat; + u32 left = count, len; + + idx = (stat >> 11) & 0x1f; + off = (stat & 0x7ff) << 7; + + while (left) { + len = output->dma->size - output->dma->coff; + if ((((output->dma->cbuf + 1) % output->dma->num) == idx) && + off == 0) { + if (len <= 188) + break; + len -= 188; + } + if (output->dma->cbuf == idx) { + if (off > output->dma->coff) { + len = off - output->dma->coff; + len -= (len % 188); + if (len <= 188) + break; + len -= 188; + } + } + if (len > left) + len = left; + if (copy_from_user(output->dma->vbuf[output->dma->cbuf] + + output->dma->coff, + buf, len)) + return -EIO; + if (alt_dma) + dma_sync_single_for_device( + dev->dev, + output->dma->pbuf[output->dma->cbuf], + output->dma->size, DMA_TO_DEVICE); + left -= len; + buf += len; + output->dma->coff += len; + if (output->dma->coff == output->dma->size) { + output->dma->coff = 0; + output->dma->cbuf = ((output->dma->cbuf + 1) % + output->dma->num); + } + ddbwritel(dev, + (output->dma->cbuf << 11) | + (output->dma->coff >> 7), + DMA_BUFFER_ACK(output->dma)); + } + return count - left; +} + +static u32 ddb_input_avail(struct ddb_input *input) +{ + struct ddb *dev = input->port->dev; + u32 idx, off, stat = input->dma->stat; + u32 ctrl = ddbreadl(dev, DMA_BUFFER_CONTROL(input->dma)); + + idx = (stat >> 11) & 0x1f; + off = (stat & 0x7ff) << 7; + + if (ctrl & 4) { + dev_err(dev->dev, "IA %d %d %08x\n", idx, off, ctrl); + ddbwritel(dev, stat, DMA_BUFFER_ACK(input->dma)); + return 0; + } + if (input->dma->cbuf != idx) + return 188; + return 0; +} + +static ssize_t ddb_input_read(struct ddb_input *input, + __user u8 *buf, size_t count) +{ + struct ddb *dev = input->port->dev; + u32 left = count; + u32 idx, free, stat = input->dma->stat; + int ret; + + idx = (stat >> 11) & 0x1f; + + while (left) { + if (input->dma->cbuf == idx) + return count - left; + free = input->dma->size - input->dma->coff; + if (free > left) + free = left; + if (alt_dma) + dma_sync_single_for_cpu( + dev->dev, + input->dma->pbuf[input->dma->cbuf], + input->dma->size, DMA_FROM_DEVICE); + ret = copy_to_user(buf, input->dma->vbuf[input->dma->cbuf] + + input->dma->coff, free); + if (ret) + return -EFAULT; + input->dma->coff += free; + if (input->dma->coff == input->dma->size) { + input->dma->coff = 0; + input->dma->cbuf = (input->dma->cbuf + 1) % + input->dma->num; + } + left -= free; + buf += free; + ddbwritel(dev, + (input->dma->cbuf << 11) | (input->dma->coff >> 7), + DMA_BUFFER_ACK(input->dma)); + } + return count; +} + +/****************************************************************************/ +/****************************************************************************/ + +static ssize_t ts_write(struct file *file, const __user char *buf, + size_t count, loff_t *ppos) +{ + struct dvb_device *dvbdev = file->private_data; + struct ddb_output *output = dvbdev->priv; + struct ddb *dev = output->port->dev; + size_t left = count; + int stat; + + if (!dev->has_dma) + return -EINVAL; + while (left) { + if (ddb_output_free(output) < 188) { + if (file->f_flags & O_NONBLOCK) + break; + if (wait_event_interruptible( + output->dma->wq, + ddb_output_free(output) >= 188) < 0) + break; + } + stat = ddb_output_write(output, buf, left); + if (stat < 0) + return stat; + buf += stat; + left -= stat; + } + return (left == count) ? -EAGAIN : (count - left); +} + +static ssize_t ts_read(struct file *file, __user char *buf, + size_t count, loff_t *ppos) +{ + struct dvb_device *dvbdev = file->private_data; + struct ddb_output *output = dvbdev->priv; + struct ddb_input *input = output->port->input[0]; + struct ddb *dev = output->port->dev; + size_t left = count; + int stat; + + if (!dev->has_dma) + return -EINVAL; + while (left) { + if (ddb_input_avail(input) < 188) { + if (file->f_flags & O_NONBLOCK) + break; + if (wait_event_interruptible( + input->dma->wq, + ddb_input_avail(input) >= 188) < 0) + break; + } + stat = ddb_input_read(input, buf, left); + if (stat < 0) + return stat; + left -= stat; + buf += stat; + } + return (count && (left == count)) ? -EAGAIN : (count - left); +} + +static __poll_t ts_poll(struct file *file, poll_table *wait) +{ + struct dvb_device *dvbdev = file->private_data; + struct ddb_output *output = dvbdev->priv; + struct ddb_input *input = output->port->input[0]; + + __poll_t mask = 0; + + poll_wait(file, &input->dma->wq, wait); + poll_wait(file, &output->dma->wq, wait); + if (ddb_input_avail(input) >= 188) + mask |= EPOLLIN | EPOLLRDNORM; + if (ddb_output_free(output) >= 188) + mask |= EPOLLOUT | EPOLLWRNORM; + return mask; +} + +static int ts_release(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct ddb_output *output = NULL; + struct ddb_input *input = NULL; + + if (dvbdev) { + output = dvbdev->priv; + input = output->port->input[0]; + } + + if ((file->f_flags & O_ACCMODE) == O_RDONLY) { + if (!input) + return -EINVAL; + ddb_input_stop(input); + } else if ((file->f_flags & O_ACCMODE) == O_WRONLY) { + if (!output) + return -EINVAL; + ddb_output_stop(output); + } + return dvb_generic_release(inode, file); +} + +static int ts_open(struct inode *inode, struct file *file) +{ + int err; + struct dvb_device *dvbdev = file->private_data; + struct ddb_output *output = NULL; + struct ddb_input *input = NULL; + + if (dvbdev) { + output = dvbdev->priv; + input = output->port->input[0]; + } + + if ((file->f_flags & O_ACCMODE) == O_RDONLY) { + if (!input) + return -EINVAL; + if (input->redo || input->redi) + return -EBUSY; + } else if ((file->f_flags & O_ACCMODE) == O_WRONLY) { + if (!output) + return -EINVAL; + } else { + return -EINVAL; + } + + err = dvb_generic_open(inode, file); + if (err < 0) + return err; + if ((file->f_flags & O_ACCMODE) == O_RDONLY) + ddb_input_start(input); + else if ((file->f_flags & O_ACCMODE) == O_WRONLY) + ddb_output_start(output); + return err; +} + +static const struct file_operations ci_fops = { + .owner = THIS_MODULE, + .read = ts_read, + .write = ts_write, + .open = ts_open, + .release = ts_release, + .poll = ts_poll, + .mmap = NULL, +}; + +static struct dvb_device dvbdev_ci = { + .priv = NULL, + .readers = 1, + .writers = 1, + .users = 2, + .fops = &ci_fops, +}; + +/****************************************************************************/ +/****************************************************************************/ + +static int locked_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct ddb_input *input = fe->sec_priv; + struct ddb_port *port = input->port; + struct ddb_dvb *dvb = &port->dvb[input->nr & 1]; + int status; + + if (enable) { + mutex_lock(&port->i2c_gate_lock); + status = dvb->i2c_gate_ctrl(fe, 1); + } else { + status = dvb->i2c_gate_ctrl(fe, 0); + mutex_unlock(&port->i2c_gate_lock); + } + return status; +} + +static int demod_attach_drxk(struct ddb_input *input) +{ + struct i2c_adapter *i2c = &input->port->i2c->adap; + struct ddb_dvb *dvb = &input->port->dvb[input->nr & 1]; + struct device *dev = input->port->dev->dev; + struct drxk_config config; + + memset(&config, 0, sizeof(config)); + config.adr = 0x29 + (input->nr & 1); + config.microcode_name = "drxk_a3.mc"; + + dvb->fe = dvb_attach(drxk_attach, &config, i2c); + if (!dvb->fe) { + dev_err(dev, "No DRXK found!\n"); + return -ENODEV; + } + dvb->fe->sec_priv = input; + dvb->i2c_gate_ctrl = dvb->fe->ops.i2c_gate_ctrl; + dvb->fe->ops.i2c_gate_ctrl = locked_gate_ctrl; + return 0; +} + +static int tuner_attach_tda18271(struct ddb_input *input) +{ + struct i2c_adapter *i2c = &input->port->i2c->adap; + struct ddb_dvb *dvb = &input->port->dvb[input->nr & 1]; + struct device *dev = input->port->dev->dev; + struct dvb_frontend *fe; + + if (dvb->fe->ops.i2c_gate_ctrl) + dvb->fe->ops.i2c_gate_ctrl(dvb->fe, 1); + fe = dvb_attach(tda18271c2dd_attach, dvb->fe, i2c, 0x60); + if (dvb->fe->ops.i2c_gate_ctrl) + dvb->fe->ops.i2c_gate_ctrl(dvb->fe, 0); + if (!fe) { + dev_err(dev, "No TDA18271 found!\n"); + return -ENODEV; + } + return 0; +} + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +static struct stv0367_config ddb_stv0367_config[] = { + { + .demod_address = 0x1f, + .xtal = 27000000, + .if_khz = 0, + .if_iq_mode = FE_TER_NORMAL_IF_TUNER, + .ts_mode = STV0367_SERIAL_PUNCT_CLOCK, + .clk_pol = STV0367_CLOCKPOLARITY_DEFAULT, + }, { + .demod_address = 0x1e, + .xtal = 27000000, + .if_khz = 0, + .if_iq_mode = FE_TER_NORMAL_IF_TUNER, + .ts_mode = STV0367_SERIAL_PUNCT_CLOCK, + .clk_pol = STV0367_CLOCKPOLARITY_DEFAULT, + }, +}; + +static int demod_attach_stv0367(struct ddb_input *input) +{ + struct i2c_adapter *i2c = &input->port->i2c->adap; + struct ddb_dvb *dvb = &input->port->dvb[input->nr & 1]; + struct device *dev = input->port->dev->dev; + + /* attach frontend */ + dvb->fe = dvb_attach(stv0367ddb_attach, + &ddb_stv0367_config[(input->nr & 1)], i2c); + + if (!dvb->fe) { + dev_err(dev, "No stv0367 found!\n"); + return -ENODEV; + } + dvb->fe->sec_priv = input; + dvb->i2c_gate_ctrl = dvb->fe->ops.i2c_gate_ctrl; + dvb->fe->ops.i2c_gate_ctrl = locked_gate_ctrl; + return 0; +} + +static int tuner_tda18212_ping(struct ddb_input *input, unsigned short adr) +{ + struct i2c_adapter *adapter = &input->port->i2c->adap; + struct ddb_dvb *dvb = &input->port->dvb[input->nr & 1]; + struct device *dev = input->port->dev->dev; + u8 tda_id[2]; + u8 subaddr = 0x00; + + dev_dbg(dev, "stv0367-tda18212 tuner ping\n"); + if (dvb->fe->ops.i2c_gate_ctrl) + dvb->fe->ops.i2c_gate_ctrl(dvb->fe, 1); + + if (i2c_read_regs(adapter, adr, subaddr, tda_id, sizeof(tda_id)) < 0) + dev_dbg(dev, "tda18212 ping 1 fail\n"); + if (i2c_read_regs(adapter, adr, subaddr, tda_id, sizeof(tda_id)) < 0) + dev_warn(dev, "tda18212 ping failed, expect problems\n"); + + if (dvb->fe->ops.i2c_gate_ctrl) + dvb->fe->ops.i2c_gate_ctrl(dvb->fe, 0); + + return 0; +} + +static int demod_attach_cxd28xx(struct ddb_input *input, int par, int osc24) +{ + struct i2c_adapter *i2c = &input->port->i2c->adap; + struct ddb_dvb *dvb = &input->port->dvb[input->nr & 1]; + struct device *dev = input->port->dev->dev; + struct cxd2841er_config cfg; + + /* the cxd2841er driver expects 8bit/shifted I2C addresses */ + cfg.i2c_addr = ((input->nr & 1) ? 0x6d : 0x6c) << 1; + + cfg.xtal = osc24 ? SONY_XTAL_24000 : SONY_XTAL_20500; + cfg.flags = CXD2841ER_AUTO_IFHZ | CXD2841ER_EARLY_TUNE | + CXD2841ER_NO_WAIT_LOCK | CXD2841ER_NO_AGCNEG | + CXD2841ER_TSBITS; + + if (!par) + cfg.flags |= CXD2841ER_TS_SERIAL; + + /* attach frontend */ + dvb->fe = dvb_attach(cxd2841er_attach_t_c, &cfg, i2c); + + if (!dvb->fe) { + dev_err(dev, "No cxd2837/38/43/54 found!\n"); + return -ENODEV; + } + dvb->fe->sec_priv = input; + dvb->i2c_gate_ctrl = dvb->fe->ops.i2c_gate_ctrl; + dvb->fe->ops.i2c_gate_ctrl = locked_gate_ctrl; + return 0; +} + +static int tuner_attach_tda18212(struct ddb_input *input, u32 porttype) +{ + struct i2c_adapter *adapter = &input->port->i2c->adap; + struct ddb_dvb *dvb = &input->port->dvb[input->nr & 1]; + struct device *dev = input->port->dev->dev; + struct i2c_client *client; + struct tda18212_config config = { + .fe = dvb->fe, + .if_dvbt_6 = 3550, + .if_dvbt_7 = 3700, + .if_dvbt_8 = 4150, + .if_dvbt2_6 = 3250, + .if_dvbt2_7 = 4000, + .if_dvbt2_8 = 4000, + .if_dvbc = 5000, + }; + u8 addr = (input->nr & 1) ? 0x63 : 0x60; + + /* due to a hardware quirk with the I2C gate on the stv0367+tda18212 + * combo, the tda18212 must be probed by reading it's id _twice_ when + * cold started, or it very likely will fail. + */ + if (porttype == DDB_TUNER_DVBCT_ST) + tuner_tda18212_ping(input, addr); + + /* perform tuner probe/init/attach */ + client = dvb_module_probe("tda18212", NULL, adapter, addr, &config); + if (!client) + goto err; + + dvb->i2c_client[0] = client; + return 0; +err: + dev_err(dev, "TDA18212 tuner not found. Device is not fully operational.\n"); + return -ENODEV; +} + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +static struct stv090x_config stv0900 = { + .device = STV0900, + .demod_mode = STV090x_DUAL, + .clk_mode = STV090x_CLK_EXT, + + .xtal = 27000000, + .address = 0x69, + + .ts1_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + .ts2_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + + .ts1_tei = 1, + .ts2_tei = 1, + + .repeater_level = STV090x_RPTLEVEL_16, + + .adc1_range = STV090x_ADC_1Vpp, + .adc2_range = STV090x_ADC_1Vpp, + + .diseqc_envelope_mode = true, +}; + +static struct stv090x_config stv0900_aa = { + .device = STV0900, + .demod_mode = STV090x_DUAL, + .clk_mode = STV090x_CLK_EXT, + + .xtal = 27000000, + .address = 0x68, + + .ts1_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + .ts2_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + + .ts1_tei = 1, + .ts2_tei = 1, + + .repeater_level = STV090x_RPTLEVEL_16, + + .adc1_range = STV090x_ADC_1Vpp, + .adc2_range = STV090x_ADC_1Vpp, + + .diseqc_envelope_mode = true, +}; + +static struct stv6110x_config stv6110a = { + .addr = 0x60, + .refclk = 27000000, + .clk_div = 1, +}; + +static struct stv6110x_config stv6110b = { + .addr = 0x63, + .refclk = 27000000, + .clk_div = 1, +}; + +static int demod_attach_stv0900(struct ddb_input *input, int type) +{ + struct i2c_adapter *i2c = &input->port->i2c->adap; + struct stv090x_config *feconf = type ? &stv0900_aa : &stv0900; + struct ddb_dvb *dvb = &input->port->dvb[input->nr & 1]; + struct device *dev = input->port->dev->dev; + + dvb->fe = dvb_attach(stv090x_attach, feconf, i2c, + (input->nr & 1) ? STV090x_DEMODULATOR_1 + : STV090x_DEMODULATOR_0); + if (!dvb->fe) { + dev_err(dev, "No STV0900 found!\n"); + return -ENODEV; + } + if (!dvb_attach(lnbh24_attach, dvb->fe, i2c, 0, + 0, (input->nr & 1) ? + (0x09 - type) : (0x0b - type))) { + dev_err(dev, "No LNBH24 found!\n"); + dvb_frontend_detach(dvb->fe); + return -ENODEV; + } + return 0; +} + +static int tuner_attach_stv6110(struct ddb_input *input, int type) +{ + struct i2c_adapter *i2c = &input->port->i2c->adap; + struct ddb_dvb *dvb = &input->port->dvb[input->nr & 1]; + struct device *dev = input->port->dev->dev; + struct stv090x_config *feconf = type ? &stv0900_aa : &stv0900; + struct stv6110x_config *tunerconf = (input->nr & 1) ? + &stv6110b : &stv6110a; + const struct stv6110x_devctl *ctl; + + ctl = dvb_attach(stv6110x_attach, dvb->fe, tunerconf, i2c); + if (!ctl) { + dev_err(dev, "No STV6110X found!\n"); + return -ENODEV; + } + dev_info(dev, "attach tuner input %d adr %02x\n", + input->nr, tunerconf->addr); + + feconf->tuner_init = ctl->tuner_init; + feconf->tuner_sleep = ctl->tuner_sleep; + feconf->tuner_set_mode = ctl->tuner_set_mode; + feconf->tuner_set_frequency = ctl->tuner_set_frequency; + feconf->tuner_get_frequency = ctl->tuner_get_frequency; + feconf->tuner_set_bandwidth = ctl->tuner_set_bandwidth; + feconf->tuner_get_bandwidth = ctl->tuner_get_bandwidth; + feconf->tuner_set_bbgain = ctl->tuner_set_bbgain; + feconf->tuner_get_bbgain = ctl->tuner_get_bbgain; + feconf->tuner_set_refclk = ctl->tuner_set_refclk; + feconf->tuner_get_status = ctl->tuner_get_status; + + return 0; +} + +static const struct stv0910_cfg stv0910_p = { + .adr = 0x68, + .parallel = 1, + .rptlvl = 4, + .clk = 30000000, + .tsspeed = 0x28, +}; + +static const struct lnbh25_config lnbh25_cfg = { + .i2c_address = 0x0c << 1, + .data2_config = LNBH25_TEN +}; + +static int has_lnbh25(struct i2c_adapter *i2c, u8 adr) +{ + u8 val; + + return i2c_read_reg(i2c, adr, 0, &val) ? 0 : 1; +} + +static int demod_attach_stv0910(struct ddb_input *input, int type, int tsfast) +{ + struct i2c_adapter *i2c = &input->port->i2c->adap; + struct ddb_dvb *dvb = &input->port->dvb[input->nr & 1]; + struct device *dev = input->port->dev->dev; + struct stv0910_cfg cfg = stv0910_p; + struct lnbh25_config lnbcfg = lnbh25_cfg; + + if (stv0910_single) + cfg.single = 1; + + if (type) + cfg.parallel = 2; + + if (tsfast) { + dev_info(dev, "Enabling stv0910 higher speed TS\n"); + cfg.tsspeed = 0x10; + } + + dvb->fe = dvb_attach(stv0910_attach, i2c, &cfg, (input->nr & 1)); + if (!dvb->fe) { + cfg.adr = 0x6c; + dvb->fe = dvb_attach(stv0910_attach, i2c, + &cfg, (input->nr & 1)); + } + if (!dvb->fe) { + dev_err(dev, "No STV0910 found!\n"); + return -ENODEV; + } + + /* attach lnbh25 - leftshift by one as the lnbh25 driver expects 8bit + * i2c addresses + */ + if (has_lnbh25(i2c, 0x0d)) + lnbcfg.i2c_address = (((input->nr & 1) ? 0x0d : 0x0c) << 1); + else + lnbcfg.i2c_address = (((input->nr & 1) ? 0x09 : 0x08) << 1); + + if (!dvb_attach(lnbh25_attach, dvb->fe, &lnbcfg, i2c)) { + dev_err(dev, "No LNBH25 found!\n"); + dvb_frontend_detach(dvb->fe); + return -ENODEV; + } + + return 0; +} + +static int tuner_attach_stv6111(struct ddb_input *input, int type) +{ + struct i2c_adapter *i2c = &input->port->i2c->adap; + struct ddb_dvb *dvb = &input->port->dvb[input->nr & 1]; + struct device *dev = input->port->dev->dev; + struct dvb_frontend *fe; + u8 adr = (type ? 0 : 4) + ((input->nr & 1) ? 0x63 : 0x60); + + fe = dvb_attach(stv6111_attach, dvb->fe, i2c, adr); + if (!fe) { + fe = dvb_attach(stv6111_attach, dvb->fe, i2c, adr & ~4); + if (!fe) { + dev_err(dev, "No STV6111 found at 0x%02x!\n", adr); + return -ENODEV; + } + } + return 0; +} + +static int demod_attach_dummy(struct ddb_input *input) +{ + struct ddb_dvb *dvb = &input->port->dvb[input->nr & 1]; + struct device *dev = input->port->dev->dev; + + dvb->fe = dvb_attach(ddbridge_dummy_fe_qam_attach); + if (!dvb->fe) { + dev_err(dev, "QAM dummy attach failed!\n"); + return -ENODEV; + } + + return 0; +} + +static int start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct ddb_input *input = dvbdmx->priv; + struct ddb_dvb *dvb = &input->port->dvb[input->nr & 1]; + + if (!dvb->users) + ddb_input_start_all(input); + + return ++dvb->users; +} + +static int stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct ddb_input *input = dvbdmx->priv; + struct ddb_dvb *dvb = &input->port->dvb[input->nr & 1]; + + if (--dvb->users) + return dvb->users; + + ddb_input_stop_all(input); + return 0; +} + +static void dvb_input_detach(struct ddb_input *input) +{ + struct ddb_dvb *dvb = &input->port->dvb[input->nr & 1]; + struct dvb_demux *dvbdemux = &dvb->demux; + + switch (dvb->attached) { + case 0x31: + if (dvb->fe2) + dvb_unregister_frontend(dvb->fe2); + if (dvb->fe) + dvb_unregister_frontend(dvb->fe); + fallthrough; + case 0x30: + dvb_module_release(dvb->i2c_client[0]); + dvb->i2c_client[0] = NULL; + + if (dvb->fe2) + dvb_frontend_detach(dvb->fe2); + if (dvb->fe) + dvb_frontend_detach(dvb->fe); + dvb->fe = NULL; + dvb->fe2 = NULL; + fallthrough; + case 0x20: + dvb_net_release(&dvb->dvbnet); + fallthrough; + case 0x12: + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, + &dvb->hw_frontend); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, + &dvb->mem_frontend); + fallthrough; + case 0x11: + dvb_dmxdev_release(&dvb->dmxdev); + fallthrough; + case 0x10: + dvb_dmx_release(&dvb->demux); + fallthrough; + case 0x01: + break; + } + dvb->attached = 0x00; +} + +static int dvb_register_adapters(struct ddb *dev) +{ + int i, ret = 0; + struct ddb_port *port; + struct dvb_adapter *adap; + + if (adapter_alloc == 3) { + port = &dev->port[0]; + adap = port->dvb[0].adap; + ret = dvb_register_adapter(adap, "DDBridge", THIS_MODULE, + port->dev->dev, + adapter_nr); + if (ret < 0) + return ret; + port->dvb[0].adap_registered = 1; + for (i = 0; i < dev->port_num; i++) { + port = &dev->port[i]; + port->dvb[0].adap = adap; + port->dvb[1].adap = adap; + } + return 0; + } + + for (i = 0; i < dev->port_num; i++) { + port = &dev->port[i]; + switch (port->class) { + case DDB_PORT_TUNER: + adap = port->dvb[0].adap; + ret = dvb_register_adapter(adap, "DDBridge", + THIS_MODULE, + port->dev->dev, + adapter_nr); + if (ret < 0) + return ret; + port->dvb[0].adap_registered = 1; + + if (adapter_alloc > 0) { + port->dvb[1].adap = port->dvb[0].adap; + break; + } + adap = port->dvb[1].adap; + ret = dvb_register_adapter(adap, "DDBridge", + THIS_MODULE, + port->dev->dev, + adapter_nr); + if (ret < 0) + return ret; + port->dvb[1].adap_registered = 1; + break; + + case DDB_PORT_CI: + case DDB_PORT_LOOP: + adap = port->dvb[0].adap; + ret = dvb_register_adapter(adap, "DDBridge", + THIS_MODULE, + port->dev->dev, + adapter_nr); + if (ret < 0) + return ret; + port->dvb[0].adap_registered = 1; + break; + default: + if (adapter_alloc < 2) + break; + adap = port->dvb[0].adap; + ret = dvb_register_adapter(adap, "DDBridge", + THIS_MODULE, + port->dev->dev, + adapter_nr); + if (ret < 0) + return ret; + port->dvb[0].adap_registered = 1; + break; + } + } + return ret; +} + +static void dvb_unregister_adapters(struct ddb *dev) +{ + int i; + struct ddb_port *port; + struct ddb_dvb *dvb; + + for (i = 0; i < dev->link[0].info->port_num; i++) { + port = &dev->port[i]; + + dvb = &port->dvb[0]; + if (dvb->adap_registered) + dvb_unregister_adapter(dvb->adap); + dvb->adap_registered = 0; + + dvb = &port->dvb[1]; + if (dvb->adap_registered) + dvb_unregister_adapter(dvb->adap); + dvb->adap_registered = 0; + } +} + +static int dvb_input_attach(struct ddb_input *input) +{ + int ret = 0; + struct ddb_dvb *dvb = &input->port->dvb[input->nr & 1]; + struct ddb_port *port = input->port; + struct dvb_adapter *adap = dvb->adap; + struct dvb_demux *dvbdemux = &dvb->demux; + struct ddb_ids *devids = &input->port->dev->link[input->port->lnr].ids; + int par = 0, osc24 = 0, tsfast = 0; + + /* + * Determine if bridges with stv0910 demods can run with fast TS and + * thus support high bandwidth transponders. + * STV0910_PR and STV0910_P tuner types covers all relevant bridges, + * namely the CineS2 V7(A) and the Octopus CI S2 Pro/Advanced. All + * DuoFlex S2 V4(A) have type=DDB_TUNER_DVBS_STV0910 without any suffix + * and are limited by the serial link to the bridge, thus won't work + * in fast TS mode. + */ + if (port->nr == 0 && + (port->type == DDB_TUNER_DVBS_STV0910_PR || + port->type == DDB_TUNER_DVBS_STV0910_P)) { + /* fast TS on port 0 requires FPGA version >= 1.7 */ + if ((devids->hwid & 0x00ffffff) >= 0x00010007) + tsfast = 1; + } + + dvb->attached = 0x01; + + dvbdemux->priv = input; + dvbdemux->dmx.capabilities = DMX_TS_FILTERING | + DMX_SECTION_FILTERING | DMX_MEMORY_BASED_FILTERING; + dvbdemux->start_feed = start_feed; + dvbdemux->stop_feed = stop_feed; + dvbdemux->filternum = 256; + dvbdemux->feednum = 256; + ret = dvb_dmx_init(dvbdemux); + if (ret < 0) + return ret; + dvb->attached = 0x10; + + dvb->dmxdev.filternum = 256; + dvb->dmxdev.demux = &dvbdemux->dmx; + ret = dvb_dmxdev_init(&dvb->dmxdev, adap); + if (ret < 0) + goto err_detach; + dvb->attached = 0x11; + + dvb->mem_frontend.source = DMX_MEMORY_FE; + dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->mem_frontend); + dvb->hw_frontend.source = DMX_FRONTEND_0; + dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->hw_frontend); + ret = dvbdemux->dmx.connect_frontend(&dvbdemux->dmx, &dvb->hw_frontend); + if (ret < 0) + goto err_detach; + dvb->attached = 0x12; + + ret = dvb_net_init(adap, &dvb->dvbnet, dvb->dmxdev.demux); + if (ret < 0) + goto err_detach; + dvb->attached = 0x20; + + dvb->fe = NULL; + dvb->fe2 = NULL; + switch (port->type) { + case DDB_TUNER_MXL5XX: + if (ddb_fe_attach_mxl5xx(input) < 0) + goto err_detach; + break; + case DDB_TUNER_DVBS_ST: + if (demod_attach_stv0900(input, 0) < 0) + goto err_detach; + if (tuner_attach_stv6110(input, 0) < 0) + goto err_tuner; + break; + case DDB_TUNER_DVBS_ST_AA: + if (demod_attach_stv0900(input, 1) < 0) + goto err_detach; + if (tuner_attach_stv6110(input, 1) < 0) + goto err_tuner; + break; + case DDB_TUNER_DVBS_STV0910: + if (demod_attach_stv0910(input, 0, tsfast) < 0) + goto err_detach; + if (tuner_attach_stv6111(input, 0) < 0) + goto err_tuner; + break; + case DDB_TUNER_DVBS_STV0910_PR: + if (demod_attach_stv0910(input, 1, tsfast) < 0) + goto err_detach; + if (tuner_attach_stv6111(input, 1) < 0) + goto err_tuner; + break; + case DDB_TUNER_DVBS_STV0910_P: + if (demod_attach_stv0910(input, 0, tsfast) < 0) + goto err_detach; + if (tuner_attach_stv6111(input, 1) < 0) + goto err_tuner; + break; + case DDB_TUNER_DVBCT_TR: + if (demod_attach_drxk(input) < 0) + goto err_detach; + if (tuner_attach_tda18271(input) < 0) + goto err_tuner; + break; + case DDB_TUNER_DVBCT_ST: + if (demod_attach_stv0367(input) < 0) + goto err_detach; + if (tuner_attach_tda18212(input, port->type) < 0) + goto err_tuner; + break; + case DDB_TUNER_DVBC2T2I_SONY_P: + if (input->port->dev->link[input->port->lnr].info->ts_quirks & + TS_QUIRK_ALT_OSC) + osc24 = 0; + else + osc24 = 1; + fallthrough; + case DDB_TUNER_DVBCT2_SONY_P: + case DDB_TUNER_DVBC2T2_SONY_P: + case DDB_TUNER_ISDBT_SONY_P: + if (input->port->dev->link[input->port->lnr].info->ts_quirks + & TS_QUIRK_SERIAL) + par = 0; + else + par = 1; + if (demod_attach_cxd28xx(input, par, osc24) < 0) + goto err_detach; + if (tuner_attach_tda18212(input, port->type) < 0) + goto err_tuner; + break; + case DDB_TUNER_DVBC2T2I_SONY: + osc24 = 1; + fallthrough; + case DDB_TUNER_DVBCT2_SONY: + case DDB_TUNER_DVBC2T2_SONY: + case DDB_TUNER_ISDBT_SONY: + if (demod_attach_cxd28xx(input, 0, osc24) < 0) + goto err_detach; + if (tuner_attach_tda18212(input, port->type) < 0) + goto err_tuner; + break; + case DDB_TUNER_DUMMY: + if (demod_attach_dummy(input) < 0) + goto err_detach; + break; + case DDB_TUNER_MCI_SX8: + if (ddb_fe_attach_mci(input, port->type) < 0) + goto err_detach; + break; + default: + return 0; + } + dvb->attached = 0x30; + + if (dvb->fe) { + if (dvb_register_frontend(adap, dvb->fe) < 0) + goto err_detach; + + if (dvb->fe2) { + if (dvb_register_frontend(adap, dvb->fe2) < 0) { + dvb_unregister_frontend(dvb->fe); + goto err_detach; + } + dvb->fe2->tuner_priv = dvb->fe->tuner_priv; + memcpy(&dvb->fe2->ops.tuner_ops, + &dvb->fe->ops.tuner_ops, + sizeof(struct dvb_tuner_ops)); + } + } + + dvb->attached = 0x31; + return 0; + +err_tuner: + dev_err(port->dev->dev, "tuner attach failed!\n"); + + if (dvb->fe2) + dvb_frontend_detach(dvb->fe2); + if (dvb->fe) + dvb_frontend_detach(dvb->fe); +err_detach: + dvb_input_detach(input); + + /* return error from ret if set */ + if (ret < 0) + return ret; + + return -ENODEV; +} + +static int port_has_encti(struct ddb_port *port) +{ + struct device *dev = port->dev->dev; + u8 val; + int ret = i2c_read_reg(&port->i2c->adap, 0x20, 0, &val); + + if (!ret) + dev_info(dev, "[0x20]=0x%02x\n", val); + return ret ? 0 : 1; +} + +static int port_has_cxd(struct ddb_port *port, u8 *type) +{ + u8 val; + u8 probe[4] = { 0xe0, 0x00, 0x00, 0x00 }, data[4]; + struct i2c_msg msgs[2] = {{ .addr = 0x40, .flags = 0, + .buf = probe, .len = 4 }, + { .addr = 0x40, .flags = I2C_M_RD, + .buf = data, .len = 4 } }; + val = i2c_transfer(&port->i2c->adap, msgs, 2); + if (val != 2) + return 0; + + if (data[0] == 0x02 && data[1] == 0x2b && data[3] == 0x43) + *type = 2; + else + *type = 1; + return 1; +} + +static int port_has_xo2(struct ddb_port *port, u8 *type, u8 *id) +{ + u8 probe[1] = { 0x00 }, data[4]; + + if (i2c_io(&port->i2c->adap, 0x10, probe, 1, data, 4)) + return 0; + if (data[0] == 'D' && data[1] == 'F') { + *id = data[2]; + *type = 1; + return 1; + } + if (data[0] == 'C' && data[1] == 'I') { + *id = data[2]; + *type = 2; + return 1; + } + return 0; +} + +static int port_has_stv0900(struct ddb_port *port) +{ + u8 val; + + if (i2c_read_reg16(&port->i2c->adap, 0x69, 0xf100, &val) < 0) + return 0; + return 1; +} + +static int port_has_stv0900_aa(struct ddb_port *port, u8 *id) +{ + if (i2c_read_reg16(&port->i2c->adap, 0x68, 0xf100, id) < 0) + return 0; + return 1; +} + +static int port_has_drxks(struct ddb_port *port) +{ + u8 val; + + if (i2c_read(&port->i2c->adap, 0x29, &val) < 0) + return 0; + if (i2c_read(&port->i2c->adap, 0x2a, &val) < 0) + return 0; + return 1; +} + +static int port_has_stv0367(struct ddb_port *port) +{ + u8 val; + + if (i2c_read_reg16(&port->i2c->adap, 0x1e, 0xf000, &val) < 0) + return 0; + if (val != 0x60) + return 0; + if (i2c_read_reg16(&port->i2c->adap, 0x1f, 0xf000, &val) < 0) + return 0; + if (val != 0x60) + return 0; + return 1; +} + +static int init_xo2(struct ddb_port *port) +{ + struct i2c_adapter *i2c = &port->i2c->adap; + struct ddb *dev = port->dev; + u8 val, data[2]; + int res; + + res = i2c_read_regs(i2c, 0x10, 0x04, data, 2); + if (res < 0) + return res; + + if (data[0] != 0x01) { + dev_info(dev->dev, "Port %d: invalid XO2\n", port->nr); + return -1; + } + + i2c_read_reg(i2c, 0x10, 0x08, &val); + if (val != 0) { + i2c_write_reg(i2c, 0x10, 0x08, 0x00); + msleep(100); + } + /* Enable tuner power, disable pll, reset demods */ + i2c_write_reg(i2c, 0x10, 0x08, 0x04); + usleep_range(2000, 3000); + /* Release demod resets */ + i2c_write_reg(i2c, 0x10, 0x08, 0x07); + + /* speed: 0=55,1=75,2=90,3=104 MBit/s */ + i2c_write_reg(i2c, 0x10, 0x09, xo2_speed); + + if (dev->link[port->lnr].info->con_clock) { + dev_info(dev->dev, "Setting continuous clock for XO2\n"); + i2c_write_reg(i2c, 0x10, 0x0a, 0x03); + i2c_write_reg(i2c, 0x10, 0x0b, 0x03); + } else { + i2c_write_reg(i2c, 0x10, 0x0a, 0x01); + i2c_write_reg(i2c, 0x10, 0x0b, 0x01); + } + + usleep_range(2000, 3000); + /* Start XO2 PLL */ + i2c_write_reg(i2c, 0x10, 0x08, 0x87); + + return 0; +} + +static int init_xo2_ci(struct ddb_port *port) +{ + struct i2c_adapter *i2c = &port->i2c->adap; + struct ddb *dev = port->dev; + u8 val, data[2]; + int res; + + res = i2c_read_regs(i2c, 0x10, 0x04, data, 2); + if (res < 0) + return res; + + if (data[0] > 1) { + dev_info(dev->dev, "Port %d: invalid XO2 CI %02x\n", + port->nr, data[0]); + return -1; + } + dev_info(dev->dev, "Port %d: DuoFlex CI %u.%u\n", + port->nr, data[0], data[1]); + + i2c_read_reg(i2c, 0x10, 0x08, &val); + if (val != 0) { + i2c_write_reg(i2c, 0x10, 0x08, 0x00); + msleep(100); + } + /* Enable both CI */ + i2c_write_reg(i2c, 0x10, 0x08, 3); + usleep_range(2000, 3000); + + /* speed: 0=55,1=75,2=90,3=104 MBit/s */ + i2c_write_reg(i2c, 0x10, 0x09, 1); + + i2c_write_reg(i2c, 0x10, 0x08, 0x83); + usleep_range(2000, 3000); + + if (dev->link[port->lnr].info->con_clock) { + dev_info(dev->dev, "Setting continuous clock for DuoFlex CI\n"); + i2c_write_reg(i2c, 0x10, 0x0a, 0x03); + i2c_write_reg(i2c, 0x10, 0x0b, 0x03); + } else { + i2c_write_reg(i2c, 0x10, 0x0a, 0x01); + i2c_write_reg(i2c, 0x10, 0x0b, 0x01); + } + return 0; +} + +static int port_has_cxd28xx(struct ddb_port *port, u8 *id) +{ + struct i2c_adapter *i2c = &port->i2c->adap; + int status; + + status = i2c_write_reg(&port->i2c->adap, 0x6e, 0, 0); + if (status) + return 0; + status = i2c_read_reg(i2c, 0x6e, 0xfd, id); + if (status) + return 0; + return 1; +} + +static char *xo2names[] = { + "DUAL DVB-S2", "DUAL DVB-C/T/T2", + "DUAL DVB-ISDBT", "DUAL DVB-C/C2/T/T2", + "DUAL ATSC", "DUAL DVB-C/C2/T/T2,ISDB-T", + "", "" +}; + +static char *xo2types[] = { + "DVBS_ST", "DVBCT2_SONY", + "ISDBT_SONY", "DVBC2T2_SONY", + "ATSC_ST", "DVBC2T2I_SONY" +}; + +static void ddb_port_probe(struct ddb_port *port) +{ + struct ddb *dev = port->dev; + u32 l = port->lnr; + struct ddb_link *link = &dev->link[l]; + u8 id, type; + + port->name = "NO MODULE"; + port->type_name = "NONE"; + port->class = DDB_PORT_NONE; + + /* Handle missing ports and ports without I2C */ + + if (dummy_tuner && !port->nr && + link->ids.device == 0x0005) { + port->name = "DUMMY"; + port->class = DDB_PORT_TUNER; + port->type = DDB_TUNER_DUMMY; + port->type_name = "DUMMY"; + return; + } + + if (port->nr == ts_loop) { + port->name = "TS LOOP"; + port->class = DDB_PORT_LOOP; + return; + } + + if (port->nr == 1 && link->info->type == DDB_OCTOPUS_CI && + link->info->i2c_mask == 1) { + port->name = "NO TAB"; + port->class = DDB_PORT_NONE; + return; + } + + if (link->info->type == DDB_OCTOPUS_MAX) { + port->name = "DUAL DVB-S2 MAX"; + port->type_name = "MXL5XX"; + port->class = DDB_PORT_TUNER; + port->type = DDB_TUNER_MXL5XX; + if (port->i2c) + ddbwritel(dev, I2C_SPEED_400, + port->i2c->regs + I2C_TIMING); + return; + } + + if (link->info->type == DDB_OCTOPUS_MCI) { + if (port->nr >= link->info->mci_ports) + return; + port->name = "DUAL MCI"; + port->type_name = "MCI"; + port->class = DDB_PORT_TUNER; + port->type = DDB_TUNER_MCI + link->info->mci_type; + return; + } + + if (port->nr > 1 && link->info->type == DDB_OCTOPUS_CI) { + port->name = "CI internal"; + port->type_name = "INTERNAL"; + port->class = DDB_PORT_CI; + port->type = DDB_CI_INTERNAL; + } + + if (!port->i2c) + return; + + /* Probe ports with I2C */ + + if (port_has_cxd(port, &id)) { + if (id == 1) { + port->name = "CI"; + port->type_name = "CXD2099"; + port->class = DDB_PORT_CI; + port->type = DDB_CI_EXTERNAL_SONY; + ddbwritel(dev, I2C_SPEED_400, + port->i2c->regs + I2C_TIMING); + } else { + dev_info(dev->dev, "Port %d: Uninitialized DuoFlex\n", + port->nr); + return; + } + } else if (port_has_xo2(port, &type, &id)) { + ddbwritel(dev, I2C_SPEED_400, port->i2c->regs + I2C_TIMING); + /*dev_info(dev->dev, "XO2 ID %02x\n", id);*/ + if (type == 2) { + port->name = "DuoFlex CI"; + port->class = DDB_PORT_CI; + port->type = DDB_CI_EXTERNAL_XO2; + port->type_name = "CI_XO2"; + init_xo2_ci(port); + return; + } + id >>= 2; + if (id > 5) { + port->name = "unknown XO2 DuoFlex"; + port->type_name = "UNKNOWN"; + } else { + port->name = xo2names[id]; + port->class = DDB_PORT_TUNER; + port->type = DDB_TUNER_XO2 + id; + port->type_name = xo2types[id]; + init_xo2(port); + } + } else if (port_has_cxd28xx(port, &id)) { + switch (id) { + case 0xa4: + port->name = "DUAL DVB-C2T2 CXD2843"; + port->type = DDB_TUNER_DVBC2T2_SONY_P; + port->type_name = "DVBC2T2_SONY"; + break; + case 0xb1: + port->name = "DUAL DVB-CT2 CXD2837"; + port->type = DDB_TUNER_DVBCT2_SONY_P; + port->type_name = "DVBCT2_SONY"; + break; + case 0xb0: + port->name = "DUAL ISDB-T CXD2838"; + port->type = DDB_TUNER_ISDBT_SONY_P; + port->type_name = "ISDBT_SONY"; + break; + case 0xc1: + port->name = "DUAL DVB-C2T2 ISDB-T CXD2854"; + port->type = DDB_TUNER_DVBC2T2I_SONY_P; + port->type_name = "DVBC2T2I_ISDBT_SONY"; + break; + default: + return; + } + port->class = DDB_PORT_TUNER; + ddbwritel(dev, I2C_SPEED_400, port->i2c->regs + I2C_TIMING); + } else if (port_has_stv0900(port)) { + port->name = "DUAL DVB-S2"; + port->class = DDB_PORT_TUNER; + port->type = DDB_TUNER_DVBS_ST; + port->type_name = "DVBS_ST"; + ddbwritel(dev, I2C_SPEED_100, port->i2c->regs + I2C_TIMING); + } else if (port_has_stv0900_aa(port, &id)) { + port->name = "DUAL DVB-S2"; + port->class = DDB_PORT_TUNER; + if (id == 0x51) { + if (port->nr == 0 && + link->info->ts_quirks & TS_QUIRK_REVERSED) + port->type = DDB_TUNER_DVBS_STV0910_PR; + else + port->type = DDB_TUNER_DVBS_STV0910_P; + port->type_name = "DVBS_ST_0910"; + } else { + port->type = DDB_TUNER_DVBS_ST_AA; + port->type_name = "DVBS_ST_AA"; + } + ddbwritel(dev, I2C_SPEED_100, port->i2c->regs + I2C_TIMING); + } else if (port_has_drxks(port)) { + port->name = "DUAL DVB-C/T"; + port->class = DDB_PORT_TUNER; + port->type = DDB_TUNER_DVBCT_TR; + port->type_name = "DVBCT_TR"; + ddbwritel(dev, I2C_SPEED_400, port->i2c->regs + I2C_TIMING); + } else if (port_has_stv0367(port)) { + port->name = "DUAL DVB-C/T"; + port->class = DDB_PORT_TUNER; + port->type = DDB_TUNER_DVBCT_ST; + port->type_name = "DVBCT_ST"; + ddbwritel(dev, I2C_SPEED_100, port->i2c->regs + I2C_TIMING); + } else if (port_has_encti(port)) { + port->name = "ENCTI"; + port->class = DDB_PORT_LOOP; + } +} + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +static int ddb_port_attach(struct ddb_port *port) +{ + int ret = 0; + + switch (port->class) { + case DDB_PORT_TUNER: + ret = dvb_input_attach(port->input[0]); + if (ret < 0) + break; + ret = dvb_input_attach(port->input[1]); + if (ret < 0) { + dvb_input_detach(port->input[0]); + break; + } + port->input[0]->redi = port->input[0]; + port->input[1]->redi = port->input[1]; + break; + case DDB_PORT_CI: + ret = ddb_ci_attach(port, ci_bitrate); + if (ret < 0) + break; + fallthrough; + case DDB_PORT_LOOP: + ret = dvb_register_device(port->dvb[0].adap, + &port->dvb[0].dev, + &dvbdev_ci, (void *)port->output, + DVB_DEVICE_SEC, 0); + break; + default: + break; + } + if (ret < 0) + dev_err(port->dev->dev, "port_attach on port %d failed\n", + port->nr); + return ret; +} + +int ddb_ports_attach(struct ddb *dev) +{ + int i, numports, err_ports = 0, ret = 0; + struct ddb_port *port; + + if (dev->port_num) { + ret = dvb_register_adapters(dev); + if (ret < 0) { + dev_err(dev->dev, "Registering adapters failed. Check DVB_MAX_ADAPTERS in config.\n"); + return ret; + } + } + + numports = dev->port_num; + + for (i = 0; i < dev->port_num; i++) { + port = &dev->port[i]; + if (port->class != DDB_PORT_NONE) { + ret = ddb_port_attach(port); + if (ret) + err_ports++; + } else { + numports--; + } + } + + if (err_ports) { + if (err_ports == numports) { + dev_err(dev->dev, "All connected ports failed to initialise!\n"); + return -ENODEV; + } + + dev_warn(dev->dev, "%d of %d connected ports failed to initialise!\n", + err_ports, numports); + } + + return 0; +} + +void ddb_ports_detach(struct ddb *dev) +{ + int i; + struct ddb_port *port; + + for (i = 0; i < dev->port_num; i++) { + port = &dev->port[i]; + + switch (port->class) { + case DDB_PORT_TUNER: + dvb_input_detach(port->input[1]); + dvb_input_detach(port->input[0]); + break; + case DDB_PORT_CI: + case DDB_PORT_LOOP: + ddb_ci_detach(port); + break; + } + } + dvb_unregister_adapters(dev); +} + +/* Copy input DMA pointers to output DMA and ACK. */ + +static void input_write_output(struct ddb_input *input, + struct ddb_output *output) +{ + ddbwritel(output->port->dev, + input->dma->stat, DMA_BUFFER_ACK(output->dma)); + output->dma->cbuf = (input->dma->stat >> 11) & 0x1f; + output->dma->coff = (input->dma->stat & 0x7ff) << 7; +} + +static void output_ack_input(struct ddb_output *output, + struct ddb_input *input) +{ + ddbwritel(input->port->dev, + output->dma->stat, DMA_BUFFER_ACK(input->dma)); +} + +static void input_write_dvb(struct ddb_input *input, + struct ddb_input *input2) +{ + struct ddb_dvb *dvb = &input2->port->dvb[input2->nr & 1]; + struct ddb_dma *dma, *dma2; + struct ddb *dev = input->port->dev; + int ack = 1; + + dma = input->dma; + dma2 = input->dma; + /* + * if there also is an output connected, do not ACK. + * input_write_output will ACK. + */ + if (input->redo) { + dma2 = input->redo->dma; + ack = 0; + } + while (dma->cbuf != ((dma->stat >> 11) & 0x1f) || + (4 & dma->ctrl)) { + if (4 & dma->ctrl) { + /* dev_err(dev->dev, "Overflow dma %d\n", dma->nr); */ + ack = 1; + } + if (alt_dma) + dma_sync_single_for_cpu(dev->dev, dma2->pbuf[dma->cbuf], + dma2->size, DMA_FROM_DEVICE); + dvb_dmx_swfilter_packets(&dvb->demux, + dma2->vbuf[dma->cbuf], + dma2->size / 188); + dma->cbuf = (dma->cbuf + 1) % dma2->num; + if (ack) + ddbwritel(dev, (dma->cbuf << 11), + DMA_BUFFER_ACK(dma)); + dma->stat = safe_ddbreadl(dev, DMA_BUFFER_CURRENT(dma)); + dma->ctrl = safe_ddbreadl(dev, DMA_BUFFER_CONTROL(dma)); + } +} + +static void input_work(struct work_struct *work) +{ + struct ddb_dma *dma = container_of(work, struct ddb_dma, work); + struct ddb_input *input = (struct ddb_input *)dma->io; + struct ddb *dev = input->port->dev; + unsigned long flags; + + spin_lock_irqsave(&dma->lock, flags); + if (!dma->running) { + spin_unlock_irqrestore(&dma->lock, flags); + return; + } + dma->stat = ddbreadl(dev, DMA_BUFFER_CURRENT(dma)); + dma->ctrl = ddbreadl(dev, DMA_BUFFER_CONTROL(dma)); + + if (input->redi) + input_write_dvb(input, input->redi); + if (input->redo) + input_write_output(input, input->redo); + wake_up(&dma->wq); + spin_unlock_irqrestore(&dma->lock, flags); +} + +static void input_handler(void *data) +{ + struct ddb_input *input = (struct ddb_input *)data; + struct ddb_dma *dma = input->dma; + + queue_work(ddb_wq, &dma->work); +} + +static void output_work(struct work_struct *work) +{ + struct ddb_dma *dma = container_of(work, struct ddb_dma, work); + struct ddb_output *output = (struct ddb_output *)dma->io; + struct ddb *dev = output->port->dev; + unsigned long flags; + + spin_lock_irqsave(&dma->lock, flags); + if (!dma->running) + goto unlock_exit; + dma->stat = ddbreadl(dev, DMA_BUFFER_CURRENT(dma)); + dma->ctrl = ddbreadl(dev, DMA_BUFFER_CONTROL(dma)); + if (output->redi) + output_ack_input(output, output->redi); + wake_up(&dma->wq); +unlock_exit: + spin_unlock_irqrestore(&dma->lock, flags); +} + +static void output_handler(void *data) +{ + struct ddb_output *output = (struct ddb_output *)data; + struct ddb_dma *dma = output->dma; + + queue_work(ddb_wq, &dma->work); +} + +/****************************************************************************/ +/****************************************************************************/ + +static const struct ddb_regmap *io_regmap(struct ddb_io *io, int link) +{ + const struct ddb_info *info; + + if (link) + info = io->port->dev->link[io->port->lnr].info; + else + info = io->port->dev->link[0].info; + + if (!info) + return NULL; + + return info->regmap; +} + +static void ddb_dma_init(struct ddb_io *io, int nr, int out) +{ + struct ddb_dma *dma; + const struct ddb_regmap *rm = io_regmap(io, 0); + + dma = out ? &io->port->dev->odma[nr] : &io->port->dev->idma[nr]; + io->dma = dma; + dma->io = io; + + spin_lock_init(&dma->lock); + init_waitqueue_head(&dma->wq); + if (out) { + INIT_WORK(&dma->work, output_work); + dma->regs = rm->odma->base + rm->odma->size * nr; + dma->bufregs = rm->odma_buf->base + rm->odma_buf->size * nr; + dma->num = dma_buf_num; + dma->size = dma_buf_size * 128 * 47; + dma->div = 1; + } else { + INIT_WORK(&dma->work, input_work); + dma->regs = rm->idma->base + rm->idma->size * nr; + dma->bufregs = rm->idma_buf->base + rm->idma_buf->size * nr; + dma->num = dma_buf_num; + dma->size = dma_buf_size * 128 * 47; + dma->div = 1; + } + ddbwritel(io->port->dev, 0, DMA_BUFFER_ACK(dma)); + dev_dbg(io->port->dev->dev, "init link %u, io %u, dma %u, dmaregs %08x bufregs %08x\n", + io->port->lnr, io->nr, nr, dma->regs, dma->bufregs); +} + +static void ddb_input_init(struct ddb_port *port, int nr, int pnr, int anr) +{ + struct ddb *dev = port->dev; + struct ddb_input *input = &dev->input[anr]; + const struct ddb_regmap *rm; + + port->input[pnr] = input; + input->nr = nr; + input->port = port; + rm = io_regmap(input, 1); + input->regs = DDB_LINK_TAG(port->lnr) | + (rm->input->base + rm->input->size * nr); + dev_dbg(dev->dev, "init link %u, input %u, regs %08x\n", + port->lnr, nr, input->regs); + + if (dev->has_dma) { + const struct ddb_regmap *rm0 = io_regmap(input, 0); + u32 base = rm0->irq_base_idma; + u32 dma_nr = nr; + + if (port->lnr) + dma_nr += 32 + (port->lnr - 1) * 8; + + dev_dbg(dev->dev, "init link %u, input %u, handler %u\n", + port->lnr, nr, dma_nr + base); + + ddb_irq_set(dev, 0, dma_nr + base, &input_handler, input); + ddb_dma_init(input, dma_nr, 0); + } +} + +static void ddb_output_init(struct ddb_port *port, int nr) +{ + struct ddb *dev = port->dev; + struct ddb_output *output = &dev->output[nr]; + const struct ddb_regmap *rm; + + port->output = output; + output->nr = nr; + output->port = port; + rm = io_regmap(output, 1); + output->regs = DDB_LINK_TAG(port->lnr) | + (rm->output->base + rm->output->size * nr); + + dev_dbg(dev->dev, "init link %u, output %u, regs %08x\n", + port->lnr, nr, output->regs); + + if (dev->has_dma) { + const struct ddb_regmap *rm0 = io_regmap(output, 0); + u32 base = rm0->irq_base_odma; + + ddb_irq_set(dev, 0, nr + base, &output_handler, output); + ddb_dma_init(output, nr, 1); + } +} + +static int ddb_port_match_i2c(struct ddb_port *port) +{ + struct ddb *dev = port->dev; + u32 i; + + for (i = 0; i < dev->i2c_num; i++) { + if (dev->i2c[i].link == port->lnr && + dev->i2c[i].nr == port->nr) { + port->i2c = &dev->i2c[i]; + return 1; + } + } + return 0; +} + +static int ddb_port_match_link_i2c(struct ddb_port *port) +{ + struct ddb *dev = port->dev; + u32 i; + + for (i = 0; i < dev->i2c_num; i++) { + if (dev->i2c[i].link == port->lnr) { + port->i2c = &dev->i2c[i]; + return 1; + } + } + return 0; +} + +void ddb_ports_init(struct ddb *dev) +{ + u32 i, l, p; + struct ddb_port *port; + const struct ddb_info *info; + const struct ddb_regmap *rm; + + for (p = l = 0; l < DDB_MAX_LINK; l++) { + info = dev->link[l].info; + if (!info) + continue; + rm = info->regmap; + if (!rm) + continue; + for (i = 0; i < info->port_num; i++, p++) { + port = &dev->port[p]; + port->dev = dev; + port->nr = i; + port->lnr = l; + port->pnr = p; + port->gap = 0xffffffff; + port->obr = ci_bitrate; + mutex_init(&port->i2c_gate_lock); + + if (!ddb_port_match_i2c(port)) { + if (info->type == DDB_OCTOPUS_MAX) + ddb_port_match_link_i2c(port); + } + + ddb_port_probe(port); + + port->dvb[0].adap = &dev->adap[2 * p]; + port->dvb[1].adap = &dev->adap[2 * p + 1]; + + if (port->class == DDB_PORT_NONE && i && p && + dev->port[p - 1].type == DDB_CI_EXTERNAL_XO2) { + port->class = DDB_PORT_CI; + port->type = DDB_CI_EXTERNAL_XO2_B; + port->name = "DuoFlex CI_B"; + port->i2c = dev->port[p - 1].i2c; + } + + dev_info(dev->dev, "Port %u: Link %u, Link Port %u (TAB %u): %s\n", + port->pnr, port->lnr, port->nr, port->nr + 1, + port->name); + + if (port->class == DDB_PORT_CI && + port->type == DDB_CI_EXTERNAL_XO2) { + ddb_input_init(port, 2 * i, 0, 2 * i); + ddb_output_init(port, i); + continue; + } + + if (port->class == DDB_PORT_CI && + port->type == DDB_CI_EXTERNAL_XO2_B) { + ddb_input_init(port, 2 * i - 1, 0, 2 * i - 1); + ddb_output_init(port, i); + continue; + } + + if (port->class == DDB_PORT_NONE) + continue; + + switch (dev->link[l].info->type) { + case DDB_OCTOPUS_CI: + if (i >= 2) { + ddb_input_init(port, 2 + i, 0, 2 + i); + ddb_input_init(port, 4 + i, 1, 4 + i); + ddb_output_init(port, i); + break; + } + fallthrough; + case DDB_OCTOPUS: + ddb_input_init(port, 2 * i, 0, 2 * i); + ddb_input_init(port, 2 * i + 1, 1, 2 * i + 1); + ddb_output_init(port, i); + break; + case DDB_OCTOPUS_MAX: + case DDB_OCTOPUS_MAX_CT: + case DDB_OCTOPUS_MCI: + ddb_input_init(port, 2 * i, 0, 2 * p); + ddb_input_init(port, 2 * i + 1, 1, 2 * p + 1); + break; + default: + break; + } + } + } + dev->port_num = p; +} + +void ddb_ports_release(struct ddb *dev) +{ + int i; + struct ddb_port *port; + + for (i = 0; i < dev->port_num; i++) { + port = &dev->port[i]; + if (port->input[0] && port->input[0]->dma) + cancel_work_sync(&port->input[0]->dma->work); + if (port->input[1] && port->input[1]->dma) + cancel_work_sync(&port->input[1]->dma->work); + if (port->output && port->output->dma) + cancel_work_sync(&port->output->dma->work); + } +} + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +#define IRQ_HANDLE(_nr) \ + do { if ((s & (1UL << ((_nr) & 0x1f))) && \ + dev->link[0].irq[_nr].handler) \ + dev->link[0].irq[_nr].handler(dev->link[0].irq[_nr].data); } \ + while (0) + +#define IRQ_HANDLE_NIBBLE(_shift) { \ + if (s & (0x0000000f << ((_shift) & 0x1f))) { \ + IRQ_HANDLE(0 + (_shift)); \ + IRQ_HANDLE(1 + (_shift)); \ + IRQ_HANDLE(2 + (_shift)); \ + IRQ_HANDLE(3 + (_shift)); \ + } \ +} + +#define IRQ_HANDLE_BYTE(_shift) { \ + if (s & (0x000000ff << ((_shift) & 0x1f))) { \ + IRQ_HANDLE(0 + (_shift)); \ + IRQ_HANDLE(1 + (_shift)); \ + IRQ_HANDLE(2 + (_shift)); \ + IRQ_HANDLE(3 + (_shift)); \ + IRQ_HANDLE(4 + (_shift)); \ + IRQ_HANDLE(5 + (_shift)); \ + IRQ_HANDLE(6 + (_shift)); \ + IRQ_HANDLE(7 + (_shift)); \ + } \ +} + +static void irq_handle_msg(struct ddb *dev, u32 s) +{ + dev->i2c_irq++; + IRQ_HANDLE_NIBBLE(0); +} + +static void irq_handle_io(struct ddb *dev, u32 s) +{ + dev->ts_irq++; + IRQ_HANDLE_NIBBLE(4); + IRQ_HANDLE_BYTE(8); + IRQ_HANDLE_BYTE(16); + IRQ_HANDLE_BYTE(24); +} + +irqreturn_t ddb_irq_handler0(int irq, void *dev_id) +{ + struct ddb *dev = (struct ddb *)dev_id; + u32 mask = 0x8fffff00; + u32 s = mask & ddbreadl(dev, INTERRUPT_STATUS); + + if (!s) + return IRQ_NONE; + do { + if (s & 0x80000000) + return IRQ_NONE; + ddbwritel(dev, s, INTERRUPT_ACK); + irq_handle_io(dev, s); + } while ((s = mask & ddbreadl(dev, INTERRUPT_STATUS))); + + return IRQ_HANDLED; +} + +irqreturn_t ddb_irq_handler1(int irq, void *dev_id) +{ + struct ddb *dev = (struct ddb *)dev_id; + u32 mask = 0x8000000f; + u32 s = mask & ddbreadl(dev, INTERRUPT_STATUS); + + if (!s) + return IRQ_NONE; + do { + if (s & 0x80000000) + return IRQ_NONE; + ddbwritel(dev, s, INTERRUPT_ACK); + irq_handle_msg(dev, s); + } while ((s = mask & ddbreadl(dev, INTERRUPT_STATUS))); + + return IRQ_HANDLED; +} + +irqreturn_t ddb_irq_handler(int irq, void *dev_id) +{ + struct ddb *dev = (struct ddb *)dev_id; + u32 s = ddbreadl(dev, INTERRUPT_STATUS); + int ret = IRQ_HANDLED; + + if (!s) + return IRQ_NONE; + do { + if (s & 0x80000000) + return IRQ_NONE; + ddbwritel(dev, s, INTERRUPT_ACK); + + if (s & 0x0000000f) + irq_handle_msg(dev, s); + if (s & 0x0fffff00) + irq_handle_io(dev, s); + } while ((s = ddbreadl(dev, INTERRUPT_STATUS))); + + return ret; +} + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +static int reg_wait(struct ddb *dev, u32 reg, u32 bit) +{ + u32 count = 0; + + while (safe_ddbreadl(dev, reg) & bit) { + ndelay(10); + if (++count == 100) + return -1; + } + return 0; +} + +static int flashio(struct ddb *dev, u32 lnr, u8 *wbuf, u32 wlen, u8 *rbuf, + u32 rlen) +{ + u32 data, shift; + u32 tag = DDB_LINK_TAG(lnr); + struct ddb_link *link = &dev->link[lnr]; + + mutex_lock(&link->flash_mutex); + if (wlen > 4) + ddbwritel(dev, 1, tag | SPI_CONTROL); + while (wlen > 4) { + /* FIXME: check for big-endian */ + data = swab32(*(u32 *)wbuf); + wbuf += 4; + wlen -= 4; + ddbwritel(dev, data, tag | SPI_DATA); + if (reg_wait(dev, tag | SPI_CONTROL, 4)) + goto fail; + } + if (rlen) + ddbwritel(dev, 0x0001 | ((wlen << (8 + 3)) & 0x1f00), + tag | SPI_CONTROL); + else + ddbwritel(dev, 0x0003 | ((wlen << (8 + 3)) & 0x1f00), + tag | SPI_CONTROL); + + data = 0; + shift = ((4 - wlen) * 8); + while (wlen) { + data <<= 8; + data |= *wbuf; + wlen--; + wbuf++; + } + if (shift) + data <<= shift; + ddbwritel(dev, data, tag | SPI_DATA); + if (reg_wait(dev, tag | SPI_CONTROL, 4)) + goto fail; + + if (!rlen) { + ddbwritel(dev, 0, tag | SPI_CONTROL); + goto exit; + } + if (rlen > 4) + ddbwritel(dev, 1, tag | SPI_CONTROL); + + while (rlen > 4) { + ddbwritel(dev, 0xffffffff, tag | SPI_DATA); + if (reg_wait(dev, tag | SPI_CONTROL, 4)) + goto fail; + data = ddbreadl(dev, tag | SPI_DATA); + *(u32 *)rbuf = swab32(data); + rbuf += 4; + rlen -= 4; + } + ddbwritel(dev, 0x0003 | ((rlen << (8 + 3)) & 0x1F00), + tag | SPI_CONTROL); + ddbwritel(dev, 0xffffffff, tag | SPI_DATA); + if (reg_wait(dev, tag | SPI_CONTROL, 4)) + goto fail; + + data = ddbreadl(dev, tag | SPI_DATA); + ddbwritel(dev, 0, tag | SPI_CONTROL); + + if (rlen < 4) + data <<= ((4 - rlen) * 8); + + while (rlen > 0) { + *rbuf = ((data >> 24) & 0xff); + data <<= 8; + rbuf++; + rlen--; + } +exit: + mutex_unlock(&link->flash_mutex); + return 0; +fail: + mutex_unlock(&link->flash_mutex); + return -1; +} + +int ddbridge_flashread(struct ddb *dev, u32 link, u8 *buf, u32 addr, u32 len) +{ + u8 cmd[4] = {0x03, (addr >> 16) & 0xff, + (addr >> 8) & 0xff, addr & 0xff}; + + return flashio(dev, link, cmd, 4, buf, len); +} + +/* + * TODO/FIXME: add/implement IOCTLs from upstream driver + */ + +#define DDB_NAME "ddbridge" + +static u32 ddb_num; +static int ddb_major; +static DEFINE_MUTEX(ddb_mutex); + +static int ddb_release(struct inode *inode, struct file *file) +{ + struct ddb *dev = file->private_data; + + dev->ddb_dev_users--; + return 0; +} + +static int ddb_open(struct inode *inode, struct file *file) +{ + struct ddb *dev = ddbs[iminor(inode)]; + + if (dev->ddb_dev_users) + return -EBUSY; + dev->ddb_dev_users++; + file->private_data = dev; + return 0; +} + +static long ddb_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct ddb *dev = file->private_data; + + dev_warn(dev->dev, "DDB IOCTLs unsupported (cmd: %d, arg: %lu)\n", + cmd, arg); + + return -ENOTTY; +} + +static const struct file_operations ddb_fops = { + .unlocked_ioctl = ddb_ioctl, + .open = ddb_open, + .release = ddb_release, +}; + +static char *ddb_devnode(const struct device *device, umode_t *mode) +{ + const struct ddb *dev = dev_get_drvdata(device); + + return kasprintf(GFP_KERNEL, "ddbridge/card%d", dev->nr); +} + +#define __ATTR_MRO(_name, _show) { \ + .attr = { .name = __stringify(_name), .mode = 0444 }, \ + .show = _show, \ +} + +#define __ATTR_MWO(_name, _store) { \ + .attr = { .name = __stringify(_name), .mode = 0222 }, \ + .store = _store, \ +} + +static ssize_t ports_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct ddb *dev = dev_get_drvdata(device); + + return sprintf(buf, "%d\n", dev->port_num); +} + +static ssize_t ts_irq_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct ddb *dev = dev_get_drvdata(device); + + return sprintf(buf, "%d\n", dev->ts_irq); +} + +static ssize_t i2c_irq_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct ddb *dev = dev_get_drvdata(device); + + return sprintf(buf, "%d\n", dev->i2c_irq); +} + +static ssize_t fan_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct ddb *dev = dev_get_drvdata(device); + u32 val; + + val = ddbreadl(dev, GPIO_OUTPUT) & 1; + return sprintf(buf, "%d\n", val); +} + +static ssize_t fan_store(struct device *device, struct device_attribute *d, + const char *buf, size_t count) +{ + struct ddb *dev = dev_get_drvdata(device); + u32 val; + + if (sscanf(buf, "%u\n", &val) != 1) + return -EINVAL; + ddbwritel(dev, 1, GPIO_DIRECTION); + ddbwritel(dev, val & 1, GPIO_OUTPUT); + return count; +} + +static ssize_t fanspeed_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct ddb *dev = dev_get_drvdata(device); + int num = attr->attr.name[8] - 0x30; + struct ddb_link *link = &dev->link[num]; + u32 spd; + + spd = ddblreadl(link, TEMPMON_FANCONTROL) & 0xff; + return sprintf(buf, "%u\n", spd * 100); +} + +static ssize_t temp_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct ddb *dev = dev_get_drvdata(device); + struct ddb_link *link = &dev->link[0]; + struct i2c_adapter *adap; + int temp, temp2; + u8 tmp[2]; + + if (!link->info->temp_num) + return sprintf(buf, "no sensor\n"); + adap = &dev->i2c[link->info->temp_bus].adap; + if (i2c_read_regs(adap, 0x48, 0, tmp, 2) < 0) + return sprintf(buf, "read_error\n"); + temp = (tmp[0] << 3) | (tmp[1] >> 5); + temp *= 125; + if (link->info->temp_num == 2) { + if (i2c_read_regs(adap, 0x49, 0, tmp, 2) < 0) + return sprintf(buf, "read_error\n"); + temp2 = (tmp[0] << 3) | (tmp[1] >> 5); + temp2 *= 125; + return sprintf(buf, "%d %d\n", temp, temp2); + } + return sprintf(buf, "%d\n", temp); +} + +static ssize_t ctemp_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct ddb *dev = dev_get_drvdata(device); + struct i2c_adapter *adap; + int temp; + u8 tmp[2]; + int num = attr->attr.name[4] - 0x30; + + adap = &dev->i2c[num].adap; + if (!adap) + return 0; + if (i2c_read_regs(adap, 0x49, 0, tmp, 2) < 0) + if (i2c_read_regs(adap, 0x4d, 0, tmp, 2) < 0) + return sprintf(buf, "no sensor\n"); + temp = tmp[0] * 1000; + return sprintf(buf, "%d\n", temp); +} + +static ssize_t led_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct ddb *dev = dev_get_drvdata(device); + int num = attr->attr.name[3] - 0x30; + + return sprintf(buf, "%d\n", dev->leds & (1 << num) ? 1 : 0); +} + +static void ddb_set_led(struct ddb *dev, int num, int val) +{ + if (!dev->link[0].info->led_num) + return; + switch (dev->port[num].class) { + case DDB_PORT_TUNER: + switch (dev->port[num].type) { + case DDB_TUNER_DVBS_ST: + i2c_write_reg16(&dev->i2c[num].adap, + 0x69, 0xf14c, val ? 2 : 0); + break; + case DDB_TUNER_DVBCT_ST: + i2c_write_reg16(&dev->i2c[num].adap, + 0x1f, 0xf00e, 0); + i2c_write_reg16(&dev->i2c[num].adap, + 0x1f, 0xf00f, val ? 1 : 0); + break; + case DDB_TUNER_XO2 ... DDB_TUNER_DVBC2T2I_SONY: + { + u8 v; + + i2c_read_reg(&dev->i2c[num].adap, 0x10, 0x08, &v); + v = (v & ~0x10) | (val ? 0x10 : 0); + i2c_write_reg(&dev->i2c[num].adap, 0x10, 0x08, v); + break; + } + default: + break; + } + break; + } +} + +static ssize_t led_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ddb *dev = dev_get_drvdata(device); + int num = attr->attr.name[3] - 0x30; + u32 val; + + if (sscanf(buf, "%u\n", &val) != 1) + return -EINVAL; + if (val) + dev->leds |= (1 << num); + else + dev->leds &= ~(1 << num); + ddb_set_led(dev, num, val); + return count; +} + +static ssize_t snr_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct ddb *dev = dev_get_drvdata(device); + char snr[32]; + int num = attr->attr.name[3] - 0x30; + + if (dev->port[num].type >= DDB_TUNER_XO2) { + if (i2c_read_regs(&dev->i2c[num].adap, 0x10, 0x10, snr, 16) < 0) + return sprintf(buf, "NO SNR\n"); + snr[16] = 0; + } else { + /* serial number at 0x100-0x11f */ + if (i2c_read_regs16(&dev->i2c[num].adap, + 0x57, 0x100, snr, 32) < 0) + if (i2c_read_regs16(&dev->i2c[num].adap, + 0x50, 0x100, snr, 32) < 0) + return sprintf(buf, "NO SNR\n"); + snr[31] = 0; /* in case it is not terminated on EEPROM */ + } + return sprintf(buf, "%s\n", snr); +} + +static ssize_t bsnr_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct ddb *dev = dev_get_drvdata(device); + char snr[16]; + + ddbridge_flashread(dev, 0, snr, 0x10, 15); + snr[15] = 0; /* in case it is not terminated on EEPROM */ + return sprintf(buf, "%s\n", snr); +} + +static ssize_t bpsnr_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct ddb *dev = dev_get_drvdata(device); + unsigned char snr[32]; + + if (!dev->i2c_num) + return 0; + + if (i2c_read_regs16(&dev->i2c[0].adap, + 0x50, 0x0000, snr, 32) < 0 || + snr[0] == 0xff) + return sprintf(buf, "NO SNR\n"); + snr[31] = 0; /* in case it is not terminated on EEPROM */ + return sprintf(buf, "%s\n", snr); +} + +static ssize_t redirect_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + return 0; +} + +static ssize_t redirect_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int i, p; + int res; + + if (sscanf(buf, "%x %x\n", &i, &p) != 2) + return -EINVAL; + res = ddb_redirect(i, p); + if (res < 0) + return res; + dev_info(device, "redirect: %02x, %02x\n", i, p); + return count; +} + +static ssize_t gap_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct ddb *dev = dev_get_drvdata(device); + int num = attr->attr.name[3] - 0x30; + + return sprintf(buf, "%d\n", dev->port[num].gap); +} + +static ssize_t gap_store(struct device *device, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ddb *dev = dev_get_drvdata(device); + int num = attr->attr.name[3] - 0x30; + unsigned int val; + + if (sscanf(buf, "%u\n", &val) != 1) + return -EINVAL; + if (val > 128) + return -EINVAL; + if (val == 128) + val = 0xffffffff; + dev->port[num].gap = val; + return count; +} + +static ssize_t version_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct ddb *dev = dev_get_drvdata(device); + + return sprintf(buf, "%08x %08x\n", + dev->link[0].ids.hwid, dev->link[0].ids.regmapid); +} + +static ssize_t hwid_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct ddb *dev = dev_get_drvdata(device); + + return sprintf(buf, "0x%08X\n", dev->link[0].ids.hwid); +} + +static ssize_t regmap_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct ddb *dev = dev_get_drvdata(device); + + return sprintf(buf, "0x%08X\n", dev->link[0].ids.regmapid); +} + +static ssize_t fmode_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + int num = attr->attr.name[5] - 0x30; + struct ddb *dev = dev_get_drvdata(device); + + return sprintf(buf, "%u\n", dev->link[num].lnb.fmode); +} + +static ssize_t devid_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + int num = attr->attr.name[5] - 0x30; + struct ddb *dev = dev_get_drvdata(device); + + return sprintf(buf, "%08x\n", dev->link[num].ids.devid); +} + +static ssize_t fmode_store(struct device *device, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ddb *dev = dev_get_drvdata(device); + int num = attr->attr.name[5] - 0x30; + unsigned int val; + + if (sscanf(buf, "%u\n", &val) != 1) + return -EINVAL; + if (val > 3) + return -EINVAL; + ddb_lnb_init_fmode(dev, &dev->link[num], val); + return count; +} + +static struct device_attribute ddb_attrs[] = { + __ATTR_RO(version), + __ATTR_RO(ports), + __ATTR_RO(ts_irq), + __ATTR_RO(i2c_irq), + __ATTR(gap0, 0664, gap_show, gap_store), + __ATTR(gap1, 0664, gap_show, gap_store), + __ATTR(gap2, 0664, gap_show, gap_store), + __ATTR(gap3, 0664, gap_show, gap_store), + __ATTR(fmode0, 0664, fmode_show, fmode_store), + __ATTR(fmode1, 0664, fmode_show, fmode_store), + __ATTR(fmode2, 0664, fmode_show, fmode_store), + __ATTR(fmode3, 0664, fmode_show, fmode_store), + __ATTR_MRO(devid0, devid_show), + __ATTR_MRO(devid1, devid_show), + __ATTR_MRO(devid2, devid_show), + __ATTR_MRO(devid3, devid_show), + __ATTR_RO(hwid), + __ATTR_RO(regmap), + __ATTR(redirect, 0664, redirect_show, redirect_store), + __ATTR_MRO(snr, bsnr_show), + __ATTR_RO(bpsnr), + __ATTR_NULL, +}; + +static struct device_attribute ddb_attrs_temp[] = { + __ATTR_RO(temp), +}; + +static struct device_attribute ddb_attrs_fan[] = { + __ATTR(fan, 0664, fan_show, fan_store), +}; + +static struct device_attribute ddb_attrs_snr[] = { + __ATTR_MRO(snr0, snr_show), + __ATTR_MRO(snr1, snr_show), + __ATTR_MRO(snr2, snr_show), + __ATTR_MRO(snr3, snr_show), +}; + +static struct device_attribute ddb_attrs_ctemp[] = { + __ATTR_MRO(temp0, ctemp_show), + __ATTR_MRO(temp1, ctemp_show), + __ATTR_MRO(temp2, ctemp_show), + __ATTR_MRO(temp3, ctemp_show), +}; + +static struct device_attribute ddb_attrs_led[] = { + __ATTR(led0, 0664, led_show, led_store), + __ATTR(led1, 0664, led_show, led_store), + __ATTR(led2, 0664, led_show, led_store), + __ATTR(led3, 0664, led_show, led_store), +}; + +static struct device_attribute ddb_attrs_fanspeed[] = { + __ATTR_MRO(fanspeed0, fanspeed_show), + __ATTR_MRO(fanspeed1, fanspeed_show), + __ATTR_MRO(fanspeed2, fanspeed_show), + __ATTR_MRO(fanspeed3, fanspeed_show), +}; + +static struct class ddb_class = { + .name = "ddbridge", + .devnode = ddb_devnode, +}; + +static int ddb_class_create(void) +{ + ddb_major = register_chrdev(0, DDB_NAME, &ddb_fops); + if (ddb_major < 0) + return ddb_major; + if (class_register(&ddb_class) < 0) + return -1; + return 0; +} + +static void ddb_class_destroy(void) +{ + class_unregister(&ddb_class); + unregister_chrdev(ddb_major, DDB_NAME); +} + +static void ddb_device_attrs_del(struct ddb *dev) +{ + int i; + + for (i = 0; i < 4; i++) + if (dev->link[i].info && dev->link[i].info->tempmon_irq) + device_remove_file(dev->ddb_dev, + &ddb_attrs_fanspeed[i]); + for (i = 0; i < dev->link[0].info->temp_num; i++) + device_remove_file(dev->ddb_dev, &ddb_attrs_temp[i]); + for (i = 0; i < dev->link[0].info->fan_num; i++) + device_remove_file(dev->ddb_dev, &ddb_attrs_fan[i]); + for (i = 0; i < dev->i2c_num && i < 4; i++) { + if (dev->link[0].info->led_num) + device_remove_file(dev->ddb_dev, &ddb_attrs_led[i]); + device_remove_file(dev->ddb_dev, &ddb_attrs_snr[i]); + device_remove_file(dev->ddb_dev, &ddb_attrs_ctemp[i]); + } + for (i = 0; ddb_attrs[i].attr.name; i++) + device_remove_file(dev->ddb_dev, &ddb_attrs[i]); +} + +static int ddb_device_attrs_add(struct ddb *dev) +{ + int i; + + for (i = 0; ddb_attrs[i].attr.name; i++) + if (device_create_file(dev->ddb_dev, &ddb_attrs[i])) + goto fail; + for (i = 0; i < dev->link[0].info->temp_num; i++) + if (device_create_file(dev->ddb_dev, &ddb_attrs_temp[i])) + goto fail; + for (i = 0; i < dev->link[0].info->fan_num; i++) + if (device_create_file(dev->ddb_dev, &ddb_attrs_fan[i])) + goto fail; + for (i = 0; (i < dev->i2c_num) && (i < 4); i++) { + if (device_create_file(dev->ddb_dev, &ddb_attrs_snr[i])) + goto fail; + if (device_create_file(dev->ddb_dev, &ddb_attrs_ctemp[i])) + goto fail; + if (dev->link[0].info->led_num) + if (device_create_file(dev->ddb_dev, + &ddb_attrs_led[i])) + goto fail; + } + for (i = 0; i < 4; i++) + if (dev->link[i].info && dev->link[i].info->tempmon_irq) + if (device_create_file(dev->ddb_dev, + &ddb_attrs_fanspeed[i])) + goto fail; + return 0; +fail: + return -1; +} + +int ddb_device_create(struct ddb *dev) +{ + int res = 0; + + if (ddb_num == DDB_MAX_ADAPTER) + return -ENOMEM; + mutex_lock(&ddb_mutex); + dev->nr = ddb_num; + ddbs[dev->nr] = dev; + dev->ddb_dev = device_create(&ddb_class, dev->dev, + MKDEV(ddb_major, dev->nr), + dev, "ddbridge%d", dev->nr); + if (IS_ERR(dev->ddb_dev)) { + res = PTR_ERR(dev->ddb_dev); + dev_info(dev->dev, "Could not create ddbridge%d\n", dev->nr); + goto fail; + } + res = ddb_device_attrs_add(dev); + if (res) { + ddb_device_attrs_del(dev); + device_destroy(&ddb_class, MKDEV(ddb_major, dev->nr)); + ddbs[dev->nr] = NULL; + dev->ddb_dev = ERR_PTR(-ENODEV); + } else { + ddb_num++; + } +fail: + mutex_unlock(&ddb_mutex); + return res; +} + +void ddb_device_destroy(struct ddb *dev) +{ + if (IS_ERR(dev->ddb_dev)) + return; + ddb_device_attrs_del(dev); + device_destroy(&ddb_class, MKDEV(ddb_major, dev->nr)); +} + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +static void tempmon_setfan(struct ddb_link *link) +{ + u32 temp, temp2, pwm; + + if ((ddblreadl(link, TEMPMON_CONTROL) & + TEMPMON_CONTROL_OVERTEMP) != 0) { + dev_info(link->dev->dev, "Over temperature condition\n"); + link->overtemperature_error = 1; + } + temp = (ddblreadl(link, TEMPMON_SENSOR0) >> 8) & 0xFF; + if (temp & 0x80) + temp = 0; + temp2 = (ddblreadl(link, TEMPMON_SENSOR1) >> 8) & 0xFF; + if (temp2 & 0x80) + temp2 = 0; + if (temp2 > temp) + temp = temp2; + + pwm = (ddblreadl(link, TEMPMON_FANCONTROL) >> 8) & 0x0F; + if (pwm > 10) + pwm = 10; + + if (temp >= link->temp_tab[pwm]) { + while (pwm < 10 && temp >= link->temp_tab[pwm + 1]) + pwm += 1; + } else { + while (pwm > 1 && temp < link->temp_tab[pwm - 2]) + pwm -= 1; + } + ddblwritel(link, (pwm << 8), TEMPMON_FANCONTROL); +} + +static void temp_handler(void *data) +{ + struct ddb_link *link = (struct ddb_link *)data; + + spin_lock(&link->temp_lock); + tempmon_setfan(link); + spin_unlock(&link->temp_lock); +} + +static int tempmon_init(struct ddb_link *link, int first_time) +{ + struct ddb *dev = link->dev; + int status = 0; + u32 l = link->nr; + + spin_lock_irq(&link->temp_lock); + if (first_time) { + static u8 temperature_table[11] = { + 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80 }; + + memcpy(link->temp_tab, temperature_table, + sizeof(temperature_table)); + } + ddb_irq_set(dev, l, link->info->tempmon_irq, temp_handler, link); + ddblwritel(link, (TEMPMON_CONTROL_OVERTEMP | TEMPMON_CONTROL_AUTOSCAN | + TEMPMON_CONTROL_INTENABLE), + TEMPMON_CONTROL); + ddblwritel(link, (3 << 8), TEMPMON_FANCONTROL); + + link->overtemperature_error = + ((ddblreadl(link, TEMPMON_CONTROL) & + TEMPMON_CONTROL_OVERTEMP) != 0); + if (link->overtemperature_error) { + dev_info(link->dev->dev, "Over temperature condition\n"); + status = -1; + } + tempmon_setfan(link); + spin_unlock_irq(&link->temp_lock); + return status; +} + +static int ddb_init_tempmon(struct ddb_link *link) +{ + const struct ddb_info *info = link->info; + + if (!info->tempmon_irq) + return 0; + if (info->type == DDB_OCTOPUS_MAX_CT) + if (link->ids.regmapid < 0x00010002) + return 0; + spin_lock_init(&link->temp_lock); + dev_dbg(link->dev->dev, "init_tempmon\n"); + return tempmon_init(link, 1); +} + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +static int ddb_init_boards(struct ddb *dev) +{ + const struct ddb_info *info; + struct ddb_link *link; + u32 l; + + for (l = 0; l < DDB_MAX_LINK; l++) { + link = &dev->link[l]; + info = link->info; + + if (!info) + continue; + if (info->board_control) { + ddbwritel(dev, 0, DDB_LINK_TAG(l) | BOARD_CONTROL); + msleep(100); + ddbwritel(dev, info->board_control_2, + DDB_LINK_TAG(l) | BOARD_CONTROL); + usleep_range(2000, 3000); + ddbwritel(dev, + info->board_control_2 | info->board_control, + DDB_LINK_TAG(l) | BOARD_CONTROL); + usleep_range(2000, 3000); + } + ddb_init_tempmon(link); + } + return 0; +} + +int ddb_init(struct ddb *dev) +{ + mutex_init(&dev->link[0].lnb.lock); + mutex_init(&dev->link[0].flash_mutex); + if (no_init) { + ddb_device_create(dev); + return 0; + } + + ddb_init_boards(dev); + + if (ddb_i2c_init(dev) < 0) + goto fail1; + ddb_ports_init(dev); + if (ddb_buffers_alloc(dev) < 0) { + dev_info(dev->dev, "Could not allocate buffer memory\n"); + goto fail2; + } + if (ddb_ports_attach(dev) < 0) + goto fail3; + + ddb_device_create(dev); + + if (dev->link[0].info->fan_num) { + ddbwritel(dev, 1, GPIO_DIRECTION); + ddbwritel(dev, 1, GPIO_OUTPUT); + } + return 0; + +fail3: + dev_err(dev->dev, "fail3\n"); + ddb_ports_detach(dev); + ddb_buffers_free(dev); +fail2: + dev_err(dev->dev, "fail2\n"); + ddb_ports_release(dev); + ddb_i2c_release(dev); +fail1: + dev_err(dev->dev, "fail1\n"); + return -1; +} + +void ddb_unmap(struct ddb *dev) +{ + if (dev->regs) + iounmap(dev->regs); + vfree(dev); +} + +int ddb_exit_ddbridge(int stage, int error) +{ + switch (stage) { + default: + case 2: + destroy_workqueue(ddb_wq); + fallthrough; + case 1: + ddb_class_destroy(); + break; + } + + return error; +} + +int ddb_init_ddbridge(void) +{ + if (dma_buf_num < 8) + dma_buf_num = 8; + if (dma_buf_num > 32) + dma_buf_num = 32; + if (dma_buf_size < 1) + dma_buf_size = 1; + if (dma_buf_size > 43) + dma_buf_size = 43; + + if (ddb_class_create() < 0) + return -1; + ddb_wq = alloc_workqueue("ddbridge", WQ_PERCPU, 0); + if (!ddb_wq) + return ddb_exit_ddbridge(1, -1); + + return 0; +} diff --git a/drivers/media/pci/ddbridge/ddbridge-dummy-fe.c b/drivers/media/pci/ddbridge/ddbridge-dummy-fe.c new file mode 100644 index 000000000..d2e876a2c --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-dummy-fe.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Dummy Frontend + * + * Written by Emard + */ + +#include +#include +#include +#include + +#include +#include "ddbridge-dummy-fe.h" + +struct ddbridge_dummy_fe_state { + struct dvb_frontend frontend; +}; + +static int ddbridge_dummy_fe_read_status(struct dvb_frontend *fe, + enum fe_status *status) +{ + *status = FE_HAS_SIGNAL + | FE_HAS_CARRIER + | FE_HAS_VITERBI + | FE_HAS_SYNC + | FE_HAS_LOCK; + + return 0; +} + +static int ddbridge_dummy_fe_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + *ber = 0; + return 0; +} + +static int ddbridge_dummy_fe_read_signal_strength(struct dvb_frontend *fe, + u16 *strength) +{ + *strength = 0; + return 0; +} + +static int ddbridge_dummy_fe_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + *snr = 0; + return 0; +} + +static int ddbridge_dummy_fe_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + *ucblocks = 0; + return 0; +} + +/* + * Should only be implemented if it actually reads something from the hardware. + * Also, it should check for the locks, in order to avoid report wrong data + * to userspace. + */ +static int ddbridge_dummy_fe_get_frontend(struct dvb_frontend *fe, + struct dtv_frontend_properties *p) +{ + return 0; +} + +static int ddbridge_dummy_fe_set_frontend(struct dvb_frontend *fe) +{ + if (fe->ops.tuner_ops.set_params) { + fe->ops.tuner_ops.set_params(fe); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + } + + return 0; +} + +static int ddbridge_dummy_fe_sleep(struct dvb_frontend *fe) +{ + return 0; +} + +static int ddbridge_dummy_fe_init(struct dvb_frontend *fe) +{ + return 0; +} + +static void ddbridge_dummy_fe_release(struct dvb_frontend *fe) +{ + struct ddbridge_dummy_fe_state *state = fe->demodulator_priv; + + kfree(state); +} + +static const struct dvb_frontend_ops ddbridge_dummy_fe_qam_ops; + +struct dvb_frontend *ddbridge_dummy_fe_qam_attach(void) +{ + struct ddbridge_dummy_fe_state *state = NULL; + + /* allocate memory for the internal state */ + state = kzalloc_obj(struct ddbridge_dummy_fe_state); + if (!state) + return NULL; + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, + &ddbridge_dummy_fe_qam_ops, + sizeof(struct dvb_frontend_ops)); + + state->frontend.demodulator_priv = state; + return &state->frontend; +} +EXPORT_SYMBOL_GPL(ddbridge_dummy_fe_qam_attach); + +static const struct dvb_frontend_ops ddbridge_dummy_fe_qam_ops = { + .delsys = { SYS_DVBC_ANNEX_A }, + .info = { + .name = "ddbridge dummy DVB-C", + .frequency_min_hz = 51 * MHz, + .frequency_max_hz = 858 * MHz, + .frequency_stepsize_hz = 62500, + /* symbol_rate_min: SACLK/64 == (XIN/2)/64 */ + .symbol_rate_min = (57840000 / 2) / 64, + .symbol_rate_max = (57840000 / 2) / 4, /* SACLK/4 */ + .caps = FE_CAN_QAM_16 | + FE_CAN_QAM_32 | + FE_CAN_QAM_64 | + FE_CAN_QAM_128 | + FE_CAN_QAM_256 | + FE_CAN_FEC_AUTO | + FE_CAN_INVERSION_AUTO + }, + + .release = ddbridge_dummy_fe_release, + + .init = ddbridge_dummy_fe_init, + .sleep = ddbridge_dummy_fe_sleep, + + .set_frontend = ddbridge_dummy_fe_set_frontend, + .get_frontend = ddbridge_dummy_fe_get_frontend, + + .read_status = ddbridge_dummy_fe_read_status, + .read_ber = ddbridge_dummy_fe_read_ber, + .read_signal_strength = ddbridge_dummy_fe_read_signal_strength, + .read_snr = ddbridge_dummy_fe_read_snr, + .read_ucblocks = ddbridge_dummy_fe_read_ucblocks, +}; + +MODULE_DESCRIPTION("ddbridge dummy Frontend"); +MODULE_AUTHOR("Emard"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/ddbridge/ddbridge-dummy-fe.h b/drivers/media/pci/ddbridge/ddbridge-dummy-fe.h new file mode 100644 index 000000000..ddf189c09 --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-dummy-fe.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for Dummy Frontend + * + * Written by Emard + */ + +#ifndef DDBRIDGE_DUMMY_FE_H +#define DDBRIDGE_DUMMY_FE_H + +#include +#include + +struct dvb_frontend *ddbridge_dummy_fe_qam_attach(void); + +#endif // DDBRIDGE_DUMMY_FE_H diff --git a/drivers/media/pci/ddbridge/ddbridge-hw.c b/drivers/media/pci/ddbridge/ddbridge-hw.c new file mode 100644 index 000000000..d7d9cd0da --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-hw.c @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ddbridge-hw.c: Digital Devices bridge hardware maps + * + * Copyright (C) 2010-2017 Digital Devices GmbH + * Ralph Metzler + * Marcus Metzler + */ + +#include "ddbridge.h" +#include "ddbridge-hw.h" + +/******************************************************************************/ + +static const struct ddb_regset octopus_input = { + .base = 0x200, + .num = 0x08, + .size = 0x10, +}; + +static const struct ddb_regset octopus_output = { + .base = 0x280, + .num = 0x08, + .size = 0x10, +}; + +static const struct ddb_regset octopus_idma = { + .base = 0x300, + .num = 0x08, + .size = 0x10, +}; + +static const struct ddb_regset octopus_idma_buf = { + .base = 0x2000, + .num = 0x08, + .size = 0x100, +}; + +static const struct ddb_regset octopus_odma = { + .base = 0x380, + .num = 0x04, + .size = 0x10, +}; + +static const struct ddb_regset octopus_odma_buf = { + .base = 0x2800, + .num = 0x04, + .size = 0x100, +}; + +static const struct ddb_regset octopus_i2c = { + .base = 0x80, + .num = 0x04, + .size = 0x20, +}; + +static const struct ddb_regset octopus_i2c_buf = { + .base = 0x1000, + .num = 0x04, + .size = 0x200, +}; + +/****************************************************************************/ + +static const struct ddb_regmap octopus_map = { + .irq_base_i2c = 0, + .irq_base_idma = 8, + .irq_base_odma = 16, + .i2c = &octopus_i2c, + .i2c_buf = &octopus_i2c_buf, + .idma = &octopus_idma, + .idma_buf = &octopus_idma_buf, + .odma = &octopus_odma, + .odma_buf = &octopus_odma_buf, + .input = &octopus_input, + .output = &octopus_output, +}; + +/****************************************************************************/ + +static const struct ddb_info ddb_none = { + .type = DDB_NONE, + .name = "unknown Digital Devices PCIe card, install newer driver", + .regmap = &octopus_map, +}; + +static const struct ddb_info ddb_octopus = { + .type = DDB_OCTOPUS, + .name = "Digital Devices Octopus DVB adapter", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x0f, +}; + +static const struct ddb_info ddb_octopusv3 = { + .type = DDB_OCTOPUS, + .name = "Digital Devices Octopus V3 DVB adapter", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x0f, +}; + +static const struct ddb_info ddb_octopus_le = { + .type = DDB_OCTOPUS, + .name = "Digital Devices Octopus LE DVB adapter", + .regmap = &octopus_map, + .port_num = 2, + .i2c_mask = 0x03, +}; + +static const struct ddb_info ddb_octopus_oem = { + .type = DDB_OCTOPUS, + .name = "Digital Devices Octopus OEM", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x0f, + .led_num = 1, + .fan_num = 1, + .temp_num = 1, + .temp_bus = 0, +}; + +static const struct ddb_info ddb_octopus_mini = { + .type = DDB_OCTOPUS, + .name = "Digital Devices Octopus Mini", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x0f, +}; + +static const struct ddb_info ddb_v6 = { + .type = DDB_OCTOPUS, + .name = "Digital Devices Cine S2 V6 DVB adapter", + .regmap = &octopus_map, + .port_num = 3, + .i2c_mask = 0x07, +}; + +static const struct ddb_info ddb_v6_5 = { + .type = DDB_OCTOPUS, + .name = "Digital Devices Cine S2 V6.5 DVB adapter", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x0f, +}; + +static const struct ddb_info ddb_v7 = { + .type = DDB_OCTOPUS, + .name = "Digital Devices Cine S2 V7 DVB adapter", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x0f, + .board_control = 2, + .board_control_2 = 4, + .ts_quirks = TS_QUIRK_REVERSED, +}; + +static const struct ddb_info ddb_v7a = { + .type = DDB_OCTOPUS, + .name = "Digital Devices Cine S2 V7 Advanced DVB adapter", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x0f, + .board_control = 2, + .board_control_2 = 4, + .ts_quirks = TS_QUIRK_REVERSED, +}; + +static const struct ddb_info ddb_ctv7 = { + .type = DDB_OCTOPUS, + .name = "Digital Devices Cine CT V7 DVB adapter", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x0f, + .board_control = 3, + .board_control_2 = 4, +}; + +static const struct ddb_info ddb_satixs2v3 = { + .type = DDB_OCTOPUS, + .name = "Mystique SaTiX-S2 V3 DVB adapter", + .regmap = &octopus_map, + .port_num = 3, + .i2c_mask = 0x07, +}; + +static const struct ddb_info ddb_ci = { + .type = DDB_OCTOPUS_CI, + .name = "Digital Devices Octopus CI", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x03, +}; + +static const struct ddb_info ddb_cis = { + .type = DDB_OCTOPUS_CI, + .name = "Digital Devices Octopus CI single", + .regmap = &octopus_map, + .port_num = 3, + .i2c_mask = 0x03, +}; + +static const struct ddb_info ddb_ci_s2_pro = { + .type = DDB_OCTOPUS_CI, + .name = "Digital Devices Octopus CI S2 Pro", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x01, + .board_control = 2, + .board_control_2 = 4, +}; + +static const struct ddb_info ddb_ci_s2_pro_a = { + .type = DDB_OCTOPUS_CI, + .name = "Digital Devices Octopus CI S2 Pro Advanced", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x01, + .board_control = 2, + .board_control_2 = 4, +}; + +static const struct ddb_info ddb_dvbct = { + .type = DDB_OCTOPUS, + .name = "Digital Devices DVBCT V6.1 DVB adapter", + .regmap = &octopus_map, + .port_num = 3, + .i2c_mask = 0x07, +}; + +/****************************************************************************/ + +static const struct ddb_info ddb_ct2_8 = { + .type = DDB_OCTOPUS_MAX_CT, + .name = "Digital Devices MAX A8 CT2", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x0f, + .board_control = 0x0ff, + .board_control_2 = 0xf00, + .ts_quirks = TS_QUIRK_SERIAL, + .tempmon_irq = 24, +}; + +static const struct ddb_info ddb_c2t2_8 = { + .type = DDB_OCTOPUS_MAX_CT, + .name = "Digital Devices MAX A8 C2T2", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x0f, + .board_control = 0x0ff, + .board_control_2 = 0xf00, + .ts_quirks = TS_QUIRK_SERIAL, + .tempmon_irq = 24, +}; + +static const struct ddb_info ddb_isdbt_8 = { + .type = DDB_OCTOPUS_MAX_CT, + .name = "Digital Devices MAX A8 ISDBT", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x0f, + .board_control = 0x0ff, + .board_control_2 = 0xf00, + .ts_quirks = TS_QUIRK_SERIAL, + .tempmon_irq = 24, +}; + +static const struct ddb_info ddb_c2t2i_v0_8 = { + .type = DDB_OCTOPUS_MAX_CT, + .name = "Digital Devices MAX A8 C2T2I V0", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x0f, + .board_control = 0x0ff, + .board_control_2 = 0xf00, + .ts_quirks = TS_QUIRK_SERIAL | TS_QUIRK_ALT_OSC, + .tempmon_irq = 24, +}; + +static const struct ddb_info ddb_c2t2i_8 = { + .type = DDB_OCTOPUS_MAX_CT, + .name = "Digital Devices MAX A8 C2T2I", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x0f, + .board_control = 0x0ff, + .board_control_2 = 0xf00, + .ts_quirks = TS_QUIRK_SERIAL, + .tempmon_irq = 24, +}; + +/****************************************************************************/ + +static const struct ddb_info ddb_s2_48 = { + .type = DDB_OCTOPUS_MAX, + .name = "Digital Devices MAX S8 4/8", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x01, + .board_control = 1, + .tempmon_irq = 24, +}; + +static const struct ddb_info ddb_s2x_48 = { + .type = DDB_OCTOPUS_MCI, + .name = "Digital Devices MAX SX8", + .regmap = &octopus_map, + .port_num = 4, + .i2c_mask = 0x00, + .tempmon_irq = 24, + .mci_ports = 4, + .mci_type = 0, +}; + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +#define DDB_DEVID(_device, _subdevice, _info) { \ + .vendor = DDVID, \ + .device = _device, \ + .subvendor = DDVID, \ + .subdevice = _subdevice, \ + .info = &_info } + +static const struct ddb_device_id ddb_device_ids[] = { + /* PCIe devices */ + DDB_DEVID(0x0002, 0x0001, ddb_octopus), + DDB_DEVID(0x0003, 0x0001, ddb_octopus), + DDB_DEVID(0x0005, 0x0004, ddb_octopusv3), + DDB_DEVID(0x0003, 0x0002, ddb_octopus_le), + DDB_DEVID(0x0003, 0x0003, ddb_octopus_oem), + DDB_DEVID(0x0003, 0x0010, ddb_octopus_mini), + DDB_DEVID(0x0005, 0x0011, ddb_octopus_mini), + DDB_DEVID(0x0003, 0x0020, ddb_v6), + DDB_DEVID(0x0003, 0x0021, ddb_v6_5), + DDB_DEVID(0x0006, 0x0022, ddb_v7), + DDB_DEVID(0x0006, 0x0024, ddb_v7a), + DDB_DEVID(0x0003, 0x0030, ddb_dvbct), + DDB_DEVID(0x0003, 0xdb03, ddb_satixs2v3), + DDB_DEVID(0x0006, 0x0031, ddb_ctv7), + DDB_DEVID(0x0006, 0x0032, ddb_ctv7), + DDB_DEVID(0x0006, 0x0033, ddb_ctv7), + DDB_DEVID(0x0007, 0x0023, ddb_s2_48), + DDB_DEVID(0x0008, 0x0034, ddb_ct2_8), + DDB_DEVID(0x0008, 0x0035, ddb_c2t2_8), + DDB_DEVID(0x0008, 0x0036, ddb_isdbt_8), + DDB_DEVID(0x0008, 0x0037, ddb_c2t2i_v0_8), + DDB_DEVID(0x0008, 0x0038, ddb_c2t2i_8), + DDB_DEVID(0x0009, 0x0025, ddb_s2x_48), + DDB_DEVID(0x0006, 0x0039, ddb_ctv7), + DDB_DEVID(0x0011, 0x0040, ddb_ci), + DDB_DEVID(0x0011, 0x0041, ddb_cis), + DDB_DEVID(0x0012, 0x0042, ddb_ci), + DDB_DEVID(0x0013, 0x0043, ddb_ci_s2_pro), + DDB_DEVID(0x0013, 0x0044, ddb_ci_s2_pro_a), +}; + +/****************************************************************************/ + +const struct ddb_info *get_ddb_info(u16 vendor, u16 device, + u16 subvendor, u16 subdevice) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ddb_device_ids); i++) { + const struct ddb_device_id *id = &ddb_device_ids[i]; + + if (vendor == id->vendor && + device == id->device && + subvendor == id->subvendor && + (subdevice == id->subdevice || + id->subdevice == 0xffff)) + return id->info; + } + + return &ddb_none; +} diff --git a/drivers/media/pci/ddbridge/ddbridge-hw.h b/drivers/media/pci/ddbridge/ddbridge-hw.h new file mode 100644 index 000000000..934f296f4 --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-hw.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ddbridge-hw.h: Digital Devices bridge hardware maps + * + * Copyright (C) 2010-2017 Digital Devices GmbH + * Ralph Metzler + * Marcus Metzler + */ + +#ifndef _DDBRIDGE_HW_H_ +#define _DDBRIDGE_HW_H_ + +#include "ddbridge.h" + +/******************************************************************************/ + +#define DDVID 0xdd01 /* Digital Devices Vendor ID */ + +/******************************************************************************/ + +struct ddb_device_id { + u16 vendor; + u16 device; + u16 subvendor; + u16 subdevice; + const struct ddb_info *info; +}; + +/******************************************************************************/ + +const struct ddb_info *get_ddb_info(u16 vendor, u16 device, + u16 subvendor, u16 subdevice); + +#endif /* _DDBRIDGE_HW_H_ */ diff --git a/drivers/media/pci/ddbridge/ddbridge-i2c.c b/drivers/media/pci/ddbridge/ddbridge-i2c.c new file mode 100644 index 000000000..c894be180 --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-i2c.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ddbridge-i2c.c: Digital Devices bridge i2c driver + * + * Copyright (C) 2010-2017 Digital Devices GmbH + * Ralph Metzler + * Marcus Metzler + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ddbridge.h" +#include "ddbridge-i2c.h" +#include "ddbridge-regs.h" +#include "ddbridge-io.h" + +/******************************************************************************/ + +static int ddb_i2c_cmd(struct ddb_i2c *i2c, u32 adr, u32 cmd) +{ + struct ddb *dev = i2c->dev; + unsigned long stat; + u32 val; + + ddbwritel(dev, (adr << 9) | cmd, i2c->regs + I2C_COMMAND); + stat = wait_for_completion_timeout(&i2c->completion, HZ); + val = ddbreadl(dev, i2c->regs + I2C_COMMAND); + if (stat == 0) { + dev_err(dev->dev, "I2C timeout, card %d, port %d, link %u\n", + dev->nr, i2c->nr, i2c->link); + { + u32 istat = ddbreadl(dev, INTERRUPT_STATUS); + + dev_err(dev->dev, "DDBridge IRS %08x\n", istat); + if (i2c->link) { + u32 listat = ddbreadl(dev, + DDB_LINK_TAG(i2c->link) | + INTERRUPT_STATUS); + + dev_err(dev->dev, "DDBridge link %u IRS %08x\n", + i2c->link, listat); + } + if (istat & 1) { + ddbwritel(dev, istat & 1, INTERRUPT_ACK); + } else { + u32 mon = ddbreadl(dev, + i2c->regs + I2C_MONITOR); + + dev_err(dev->dev, "I2C cmd=%08x mon=%08x\n", + val, mon); + } + } + return -EIO; + } + val &= 0x70000; + if (val == 0x20000) + dev_err(dev->dev, "I2C bus error\n"); + if (val) + return -EIO; + return 0; +} + +static int ddb_i2c_master_xfer(struct i2c_adapter *adapter, + struct i2c_msg msg[], int num) +{ + struct ddb_i2c *i2c = (struct ddb_i2c *)i2c_get_adapdata(adapter); + struct ddb *dev = i2c->dev; + u8 addr = 0; + + addr = msg[0].addr; + if (msg[0].len > i2c->bsize) + return -EIO; + switch (num) { + case 1: + if (msg[0].flags & I2C_M_RD) { + ddbwritel(dev, msg[0].len << 16, + i2c->regs + I2C_TASKLENGTH); + if (ddb_i2c_cmd(i2c, addr, 3)) + break; + ddbcpyfrom(dev, msg[0].buf, + i2c->rbuf, msg[0].len); + return num; + } + ddbcpyto(dev, i2c->wbuf, msg[0].buf, msg[0].len); + ddbwritel(dev, msg[0].len, i2c->regs + I2C_TASKLENGTH); + if (ddb_i2c_cmd(i2c, addr, 2)) + break; + return num; + case 2: + if ((msg[0].flags & I2C_M_RD) == I2C_M_RD) + break; + if ((msg[1].flags & I2C_M_RD) != I2C_M_RD) + break; + if (msg[1].len > i2c->bsize) + break; + ddbcpyto(dev, i2c->wbuf, msg[0].buf, msg[0].len); + ddbwritel(dev, msg[0].len | (msg[1].len << 16), + i2c->regs + I2C_TASKLENGTH); + if (ddb_i2c_cmd(i2c, addr, 1)) + break; + ddbcpyfrom(dev, msg[1].buf, + i2c->rbuf, + msg[1].len); + return num; + default: + break; + } + return -EIO; +} + +static u32 ddb_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm ddb_i2c_algo = { + .master_xfer = ddb_i2c_master_xfer, + .functionality = ddb_i2c_functionality, +}; + +void ddb_i2c_release(struct ddb *dev) +{ + int i; + struct ddb_i2c *i2c; + + for (i = 0; i < dev->i2c_num; i++) { + i2c = &dev->i2c[i]; + i2c_del_adapter(&i2c->adap); + } +} + +static void i2c_handler(void *priv) +{ + struct ddb_i2c *i2c = (struct ddb_i2c *)priv; + + complete(&i2c->completion); +} + +static int ddb_i2c_add(struct ddb *dev, struct ddb_i2c *i2c, + const struct ddb_regmap *regmap, int link, + int i, int num) +{ + struct i2c_adapter *adap; + + i2c->nr = i; + i2c->dev = dev; + i2c->link = link; + i2c->bsize = regmap->i2c_buf->size; + i2c->wbuf = DDB_LINK_TAG(link) | + (regmap->i2c_buf->base + i2c->bsize * i); + i2c->rbuf = i2c->wbuf; /* + i2c->bsize / 2 */ + i2c->regs = DDB_LINK_TAG(link) | + (regmap->i2c->base + regmap->i2c->size * i); + ddbwritel(dev, I2C_SPEED_100, i2c->regs + I2C_TIMING); + ddbwritel(dev, ((i2c->rbuf & 0xffff) << 16) | (i2c->wbuf & 0xffff), + i2c->regs + I2C_TASKADDRESS); + init_completion(&i2c->completion); + + adap = &i2c->adap; + i2c_set_adapdata(adap, i2c); +#ifdef I2C_ADAP_CLASS_TV_DIGITAL + adap->class = I2C_ADAP_CLASS_TV_DIGITAL | I2C_CLASS_TV_ANALOG; +#else +#ifdef I2C_CLASS_TV_ANALOG + adap->class = I2C_CLASS_TV_ANALOG; +#endif +#endif + snprintf(adap->name, I2C_NAME_SIZE, "ddbridge_%02x.%x.%x", + dev->nr, i2c->link, i); + adap->algo = &ddb_i2c_algo; + adap->algo_data = (void *)i2c; + adap->dev.parent = dev->dev; + return i2c_add_adapter(adap); +} + +int ddb_i2c_init(struct ddb *dev) +{ + int stat = 0; + u32 i, j, num = 0, l, base; + struct ddb_i2c *i2c; + struct i2c_adapter *adap; + const struct ddb_regmap *regmap; + + for (l = 0; l < DDB_MAX_LINK; l++) { + if (!dev->link[l].info) + continue; + regmap = dev->link[l].info->regmap; + if (!regmap || !regmap->i2c) + continue; + base = regmap->irq_base_i2c; + for (i = 0; i < regmap->i2c->num; i++) { + if (!(dev->link[l].info->i2c_mask & (1 << i))) + continue; + i2c = &dev->i2c[num]; + ddb_irq_set(dev, l, i + base, i2c_handler, i2c); + stat = ddb_i2c_add(dev, i2c, regmap, l, i, num); + if (stat) + break; + num++; + } + } + if (stat) { + for (j = 0; j < num; j++) { + i2c = &dev->i2c[j]; + adap = &i2c->adap; + i2c_del_adapter(adap); + } + } else { + dev->i2c_num = num; + } + + return stat; +} diff --git a/drivers/media/pci/ddbridge/ddbridge-i2c.h b/drivers/media/pci/ddbridge/ddbridge-i2c.h new file mode 100644 index 000000000..48555d41a --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-i2c.h @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ddbridge-i2c.h: Digital Devices bridge i2c driver + * + * Copyright (C) 2010-2017 Digital Devices GmbH + * Ralph Metzler + * Marcus Metzler + */ + +#ifndef __DDBRIDGE_I2C_H__ +#define __DDBRIDGE_I2C_H__ + +#include + +#include "ddbridge.h" + +/******************************************************************************/ + +void ddb_i2c_release(struct ddb *dev); +int ddb_i2c_init(struct ddb *dev); + +/******************************************************************************/ + +static int __maybe_unused i2c_io(struct i2c_adapter *adapter, u8 adr, + u8 *wbuf, u32 wlen, u8 *rbuf, u32 rlen) +{ + struct i2c_msg msgs[2] = { { .addr = adr, .flags = 0, + .buf = wbuf, .len = wlen }, + { .addr = adr, .flags = I2C_M_RD, + .buf = rbuf, .len = rlen } }; + + return (i2c_transfer(adapter, msgs, 2) == 2) ? 0 : -1; +} + +static int __maybe_unused i2c_write(struct i2c_adapter *adap, u8 adr, + u8 *data, int len) +{ + struct i2c_msg msg = { .addr = adr, .flags = 0, + .buf = data, .len = len }; + + return (i2c_transfer(adap, &msg, 1) == 1) ? 0 : -1; +} + +static int __maybe_unused i2c_read(struct i2c_adapter *adapter, u8 adr, u8 *val) +{ + struct i2c_msg msgs[1] = { { .addr = adr, .flags = I2C_M_RD, + .buf = val, .len = 1 } }; + + return (i2c_transfer(adapter, msgs, 1) == 1) ? 0 : -1; +} + +static int __maybe_unused i2c_read_regs(struct i2c_adapter *adapter, + u8 adr, u8 reg, u8 *val, u8 len) +{ + struct i2c_msg msgs[2] = { { .addr = adr, .flags = 0, + .buf = ®, .len = 1 }, + { .addr = adr, .flags = I2C_M_RD, + .buf = val, .len = len } }; + + return (i2c_transfer(adapter, msgs, 2) == 2) ? 0 : -1; +} + +static int __maybe_unused i2c_read_regs16(struct i2c_adapter *adapter, + u8 adr, u16 reg, u8 *val, u8 len) +{ + u8 msg[2] = { reg >> 8, reg & 0xff }; + struct i2c_msg msgs[2] = { { .addr = adr, .flags = 0, + .buf = msg, .len = 2 }, + { .addr = adr, .flags = I2C_M_RD, + .buf = val, .len = len } }; + + return (i2c_transfer(adapter, msgs, 2) == 2) ? 0 : -1; +} + +static int __maybe_unused i2c_write_reg16(struct i2c_adapter *adap, + u8 adr, u16 reg, u8 val) +{ + u8 msg[3] = { reg >> 8, reg & 0xff, val }; + + return i2c_write(adap, adr, msg, 3); +} + +static int __maybe_unused i2c_write_reg(struct i2c_adapter *adap, + u8 adr, u8 reg, u8 val) +{ + u8 msg[2] = { reg, val }; + + return i2c_write(adap, adr, msg, 2); +} + +static int __maybe_unused i2c_read_reg16(struct i2c_adapter *adapter, + u8 adr, u16 reg, u8 *val) +{ + return i2c_read_regs16(adapter, adr, reg, val, 1); +} + +static int __maybe_unused i2c_read_reg(struct i2c_adapter *adapter, + u8 adr, u8 reg, u8 *val) +{ + return i2c_read_regs(adapter, adr, reg, val, 1); +} + +#endif /* __DDBRIDGE_I2C_H__ */ diff --git a/drivers/media/pci/ddbridge/ddbridge-io.h b/drivers/media/pci/ddbridge/ddbridge-io.h new file mode 100644 index 000000000..991246cec --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-io.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ddbridge-io.h: Digital Devices bridge I/O inline functions + * + * Copyright (C) 2010-2017 Digital Devices GmbH + * Ralph Metzler + * Marcus Metzler + */ + +#ifndef __DDBRIDGE_IO_H__ +#define __DDBRIDGE_IO_H__ + +#include + +#include "ddbridge.h" + +/******************************************************************************/ + +static inline u32 ddblreadl(struct ddb_link *link, u32 adr) +{ + return readl(link->dev->regs + adr); +} + +static inline void ddblwritel(struct ddb_link *link, u32 val, u32 adr) +{ + writel(val, link->dev->regs + adr); +} + +static inline u32 ddbreadl(struct ddb *dev, u32 adr) +{ + return readl(dev->regs + adr); +} + +static inline void ddbwritel(struct ddb *dev, u32 val, u32 adr) +{ + writel(val, dev->regs + adr); +} + +static inline void ddbcpyto(struct ddb *dev, u32 adr, void *src, long count) +{ + memcpy_toio(dev->regs + adr, src, count); +} + +static inline void ddbcpyfrom(struct ddb *dev, void *dst, u32 adr, long count) +{ + memcpy_fromio(dst, dev->regs + adr, count); +} + +static inline u32 safe_ddbreadl(struct ddb *dev, u32 adr) +{ + u32 val = ddbreadl(dev, adr); + + /* (ddb)readl returns (uint)-1 (all bits set) on failure, catch that */ + if (val == ~0) { + dev_err(&dev->pdev->dev, "ddbreadl failure, adr=%08x\n", adr); + return 0; + } + + return val; +} + +#endif /* __DDBRIDGE_IO_H__ */ diff --git a/drivers/media/pci/ddbridge/ddbridge-main.c b/drivers/media/pci/ddbridge/ddbridge-main.c new file mode 100644 index 000000000..248ace29e --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-main.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ddbridge.c: Digital Devices PCIe bridge driver + * + * Copyright (C) 2010-2017 Digital Devices GmbH + * Ralph Metzler + * Marcus Metzler + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ddbridge.h" +#include "ddbridge-i2c.h" +#include "ddbridge-regs.h" +#include "ddbridge-hw.h" +#include "ddbridge-io.h" + +/****************************************************************************/ +/* module parameters */ + +#ifdef CONFIG_PCI_MSI +#ifdef CONFIG_DVB_DDBRIDGE_MSIENABLE +static int msi = 1; +#else +static int msi; +#endif +module_param(msi, int, 0444); +#ifdef CONFIG_DVB_DDBRIDGE_MSIENABLE +MODULE_PARM_DESC(msi, "Control MSI interrupts: 0-disable, 1-enable (default)"); +#else +MODULE_PARM_DESC(msi, "Control MSI interrupts: 0-disable (default), 1-enable"); +#endif +#endif + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +static void ddb_irq_disable(struct ddb *dev) +{ + ddbwritel(dev, 0, INTERRUPT_ENABLE); + ddbwritel(dev, 0, MSI1_ENABLE); +} + +static void ddb_msi_exit(struct ddb *dev) +{ +#ifdef CONFIG_PCI_MSI + if (dev->msi) + pci_free_irq_vectors(dev->pdev); +#endif +} + +static void ddb_irq_exit(struct ddb *dev) +{ + ddb_irq_disable(dev); + if (dev->msi == 2) + free_irq(pci_irq_vector(dev->pdev, 1), dev); + free_irq(pci_irq_vector(dev->pdev, 0), dev); +} + +static void ddb_remove(struct pci_dev *pdev) +{ + struct ddb *dev = (struct ddb *)pci_get_drvdata(pdev); + + ddb_device_destroy(dev); + ddb_ports_detach(dev); + ddb_i2c_release(dev); + + ddb_irq_exit(dev); + ddb_msi_exit(dev); + ddb_ports_release(dev); + ddb_buffers_free(dev); + + ddb_unmap(dev); + pci_set_drvdata(pdev, NULL); + pci_disable_device(pdev); +} + +#ifdef CONFIG_PCI_MSI +static void ddb_irq_msi(struct ddb *dev, int nr) +{ + int stat; + + if (msi && pci_msi_enabled()) { + stat = pci_alloc_irq_vectors(dev->pdev, 1, nr, + PCI_IRQ_MSI | PCI_IRQ_MSIX); + if (stat >= 1) { + dev->msi = stat; + dev_info(dev->dev, "using %d MSI interrupt(s)\n", + dev->msi); + } else { + dev_info(dev->dev, "MSI not available.\n"); + } + } +} +#endif + +static int ddb_irq_init(struct ddb *dev) +{ + int stat; + int irq_flag = IRQF_SHARED; + + ddbwritel(dev, 0x00000000, INTERRUPT_ENABLE); + ddbwritel(dev, 0x00000000, MSI1_ENABLE); + ddbwritel(dev, 0x00000000, MSI2_ENABLE); + ddbwritel(dev, 0x00000000, MSI3_ENABLE); + ddbwritel(dev, 0x00000000, MSI4_ENABLE); + ddbwritel(dev, 0x00000000, MSI5_ENABLE); + ddbwritel(dev, 0x00000000, MSI6_ENABLE); + ddbwritel(dev, 0x00000000, MSI7_ENABLE); + +#ifdef CONFIG_PCI_MSI + ddb_irq_msi(dev, 2); + + if (dev->msi) + irq_flag = 0; + if (dev->msi == 2) { + stat = request_irq(pci_irq_vector(dev->pdev, 0), + ddb_irq_handler0, irq_flag, "ddbridge", + (void *)dev); + if (stat < 0) + return stat; + stat = request_irq(pci_irq_vector(dev->pdev, 1), + ddb_irq_handler1, irq_flag, "ddbridge", + (void *)dev); + if (stat < 0) { + free_irq(pci_irq_vector(dev->pdev, 0), dev); + return stat; + } + } else +#endif + { + stat = request_irq(pci_irq_vector(dev->pdev, 0), + ddb_irq_handler, irq_flag, "ddbridge", + (void *)dev); + if (stat < 0) + return stat; + } + if (dev->msi == 2) { + ddbwritel(dev, 0x0fffff00, INTERRUPT_ENABLE); + ddbwritel(dev, 0x0000000f, MSI1_ENABLE); + } else { + ddbwritel(dev, 0x0fffff0f, INTERRUPT_ENABLE); + ddbwritel(dev, 0x00000000, MSI1_ENABLE); + } + return stat; +} + +static int ddb_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct ddb *dev; + int stat = 0; + + if (pci_enable_device(pdev) < 0) + return -ENODEV; + + pci_set_master(pdev); + + if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(64))) + if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(32))) + return -ENODEV; + + dev = vzalloc(sizeof(*dev)); + if (!dev) + return -ENOMEM; + + mutex_init(&dev->mutex); + dev->has_dma = 1; + dev->pdev = pdev; + dev->dev = &pdev->dev; + pci_set_drvdata(pdev, dev); + + dev->link[0].ids.vendor = id->vendor; + dev->link[0].ids.device = id->device; + dev->link[0].ids.subvendor = id->subvendor; + dev->link[0].ids.subdevice = pdev->subsystem_device; + dev->link[0].ids.devid = (id->device << 16) | id->vendor; + + dev->link[0].dev = dev; + dev->link[0].info = get_ddb_info(id->vendor, id->device, + id->subvendor, pdev->subsystem_device); + + dev_info(&pdev->dev, "detected %s\n", dev->link[0].info->name); + + dev->regs_len = pci_resource_len(dev->pdev, 0); + dev->regs = ioremap(pci_resource_start(dev->pdev, 0), + pci_resource_len(dev->pdev, 0)); + + if (!dev->regs) { + dev_err(&pdev->dev, "not enough memory for register map\n"); + stat = -ENOMEM; + goto fail; + } + if (ddbreadl(dev, 0) == 0xffffffff) { + dev_err(&pdev->dev, "cannot read registers\n"); + stat = -ENODEV; + goto fail; + } + + dev->link[0].ids.hwid = ddbreadl(dev, 0); + dev->link[0].ids.regmapid = ddbreadl(dev, 4); + + dev_info(&pdev->dev, "HW %08x REGMAP %08x\n", + dev->link[0].ids.hwid, dev->link[0].ids.regmapid); + + ddbwritel(dev, 0, DMA_BASE_READ); + ddbwritel(dev, 0, DMA_BASE_WRITE); + + stat = ddb_irq_init(dev); + if (stat < 0) + goto fail0; + + if (ddb_init(dev) == 0) + return 0; + + ddb_irq_exit(dev); +fail0: + dev_err(&pdev->dev, "fail0\n"); + ddb_msi_exit(dev); +fail: + dev_err(&pdev->dev, "fail\n"); + + ddb_unmap(dev); + pci_set_drvdata(pdev, NULL); + pci_disable_device(pdev); + return stat; +} + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +#define DDB_DEVICE_ANY(_device) \ + { PCI_DEVICE_SUB(DDVID, _device, DDVID, PCI_ANY_ID) } + +static const struct pci_device_id ddb_id_table[] = { + DDB_DEVICE_ANY(0x0002), + DDB_DEVICE_ANY(0x0003), + DDB_DEVICE_ANY(0x0005), + DDB_DEVICE_ANY(0x0006), + DDB_DEVICE_ANY(0x0007), + DDB_DEVICE_ANY(0x0008), + DDB_DEVICE_ANY(0x0009), + DDB_DEVICE_ANY(0x0011), + DDB_DEVICE_ANY(0x0012), + DDB_DEVICE_ANY(0x0013), + DDB_DEVICE_ANY(0x0201), + DDB_DEVICE_ANY(0x0203), + DDB_DEVICE_ANY(0x0210), + DDB_DEVICE_ANY(0x0220), + DDB_DEVICE_ANY(0x0320), + DDB_DEVICE_ANY(0x0321), + DDB_DEVICE_ANY(0x0322), + DDB_DEVICE_ANY(0x0323), + DDB_DEVICE_ANY(0x0328), + DDB_DEVICE_ANY(0x0329), + { } +}; + +MODULE_DEVICE_TABLE(pci, ddb_id_table); + +static struct pci_driver ddb_pci_driver = { + .name = "ddbridge", + .id_table = ddb_id_table, + .probe = ddb_probe, + .remove = ddb_remove, +}; + +static __init int module_init_ddbridge(void) +{ + int stat; + + pr_info("Digital Devices PCIE bridge driver " + DDBRIDGE_VERSION + ", Copyright (C) 2010-17 Digital Devices GmbH\n"); + stat = ddb_init_ddbridge(); + if (stat < 0) + return stat; + stat = pci_register_driver(&ddb_pci_driver); + if (stat < 0) + ddb_exit_ddbridge(0, stat); + + return stat; +} + +static __exit void module_exit_ddbridge(void) +{ + pci_unregister_driver(&ddb_pci_driver); + ddb_exit_ddbridge(0, 0); +} + +module_init(module_init_ddbridge); +module_exit(module_exit_ddbridge); + +MODULE_DESCRIPTION("Digital Devices PCIe Bridge"); +MODULE_AUTHOR("Ralph and Marcus Metzler, Metzler Brothers Systementwicklung GbR"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(DDBRIDGE_VERSION); diff --git a/drivers/media/pci/ddbridge/ddbridge-max.c b/drivers/media/pci/ddbridge/ddbridge-max.c new file mode 100644 index 000000000..0582b86bb --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-max.c @@ -0,0 +1,495 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ddbridge-max.c: Digital Devices bridge MAX card support + * + * Copyright (C) 2010-2017 Digital Devices GmbH + * Ralph Metzler + * Marcus Metzler + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ddbridge.h" +#include "ddbridge-regs.h" +#include "ddbridge-io.h" +#include "ddbridge-mci.h" + +#include "ddbridge-max.h" +#include "mxl5xx.h" + +/******************************************************************************/ + +/* MaxS4/8 related modparams */ +static int fmode; +module_param(fmode, int, 0444); +MODULE_PARM_DESC(fmode, "frontend emulation mode"); + +static int fmode_sat = -1; +module_param(fmode_sat, int, 0444); +MODULE_PARM_DESC(fmode_sat, "set frontend emulation mode sat"); + +static int old_quattro; +module_param(old_quattro, int, 0444); +MODULE_PARM_DESC(old_quattro, "old quattro LNB input order "); + +/******************************************************************************/ + +static int lnb_command(struct ddb *dev, u32 link, u32 lnb, u32 cmd) +{ + u32 c, v = 0, tag = DDB_LINK_TAG(link); + + v = LNB_TONE & (dev->link[link].lnb.tone << (15 - lnb)); + ddbwritel(dev, cmd | v, tag | LNB_CONTROL(lnb)); + for (c = 0; c < 10; c++) { + v = ddbreadl(dev, tag | LNB_CONTROL(lnb)); + if ((v & LNB_BUSY) == 0) + break; + msleep(20); + } + if (c == 10) + dev_info(dev->dev, "%s lnb = %08x cmd = %08x\n", + __func__, lnb, cmd); + return 0; +} + +static int max_send_master_cmd(struct dvb_frontend *fe, + struct dvb_diseqc_master_cmd *cmd) +{ + struct ddb_input *input = fe->sec_priv; + struct ddb_port *port = input->port; + struct ddb *dev = port->dev; + struct ddb_dvb *dvb = &port->dvb[input->nr & 1]; + u32 tag = DDB_LINK_TAG(port->lnr); + int i; + u32 fmode = dev->link[port->lnr].lnb.fmode; + + if (fmode == 2 || fmode == 1) + return 0; + if (dvb->diseqc_send_master_cmd) + dvb->diseqc_send_master_cmd(fe, cmd); + + mutex_lock(&dev->link[port->lnr].lnb.lock); + ddbwritel(dev, 0, tag | LNB_BUF_LEVEL(dvb->input)); + for (i = 0; i < cmd->msg_len; i++) + ddbwritel(dev, cmd->msg[i], tag | LNB_BUF_WRITE(dvb->input)); + lnb_command(dev, port->lnr, dvb->input, LNB_CMD_DISEQC); + mutex_unlock(&dev->link[port->lnr].lnb.lock); + return 0; +} + +static int lnb_send_diseqc(struct ddb *dev, u32 link, u32 input, + struct dvb_diseqc_master_cmd *cmd) +{ + u32 tag = DDB_LINK_TAG(link); + int i; + + ddbwritel(dev, 0, tag | LNB_BUF_LEVEL(input)); + for (i = 0; i < cmd->msg_len; i++) + ddbwritel(dev, cmd->msg[i], tag | LNB_BUF_WRITE(input)); + lnb_command(dev, link, input, LNB_CMD_DISEQC); + return 0; +} + +static int lnb_set_sat(struct ddb *dev, u32 link, u32 input, u32 sat, u32 band, + u32 hor) +{ + struct dvb_diseqc_master_cmd cmd = { + .msg = {0xe0, 0x10, 0x38, 0xf0, 0x00, 0x00}, + .msg_len = 4 + }; + cmd.msg[3] = 0xf0 | (((sat << 2) & 0x0c) | (band ? 1 : 0) | + (hor ? 2 : 0)); + return lnb_send_diseqc(dev, link, input, &cmd); +} + +static int lnb_set_tone(struct ddb *dev, u32 link, u32 input, + enum fe_sec_tone_mode tone) +{ + int s = 0; + u32 mask = (1ULL << input); + + switch (tone) { + case SEC_TONE_OFF: + if (!(dev->link[link].lnb.tone & mask)) + return 0; + dev->link[link].lnb.tone &= ~(1ULL << input); + break; + case SEC_TONE_ON: + if (dev->link[link].lnb.tone & mask) + return 0; + dev->link[link].lnb.tone |= (1ULL << input); + break; + default: + s = -EINVAL; + break; + } + if (!s) + s = lnb_command(dev, link, input, LNB_CMD_NOP); + return s; +} + +static int lnb_set_voltage(struct ddb *dev, u32 link, u32 input, + enum fe_sec_voltage voltage) +{ + int s = 0; + + if (dev->link[link].lnb.oldvoltage[input] == voltage) + return 0; + switch (voltage) { + case SEC_VOLTAGE_OFF: + if (dev->link[link].lnb.voltage[input]) + return 0; + lnb_command(dev, link, input, LNB_CMD_OFF); + break; + case SEC_VOLTAGE_13: + lnb_command(dev, link, input, LNB_CMD_LOW); + break; + case SEC_VOLTAGE_18: + lnb_command(dev, link, input, LNB_CMD_HIGH); + break; + default: + s = -EINVAL; + break; + } + dev->link[link].lnb.oldvoltage[input] = voltage; + return s; +} + +static int max_set_input_unlocked(struct dvb_frontend *fe, int in) +{ + struct ddb_input *input = fe->sec_priv; + struct ddb_port *port = input->port; + struct ddb *dev = port->dev; + struct ddb_dvb *dvb = &port->dvb[input->nr & 1]; + int res = 0; + + if (in > 3) + return -EINVAL; + if (dvb->input != in) { + u32 bit = (1ULL << input->nr); + u32 obit = + dev->link[port->lnr].lnb.voltage[dvb->input & 3] & bit; + + dev->link[port->lnr].lnb.voltage[dvb->input & 3] &= ~bit; + dvb->input = in; + dev->link[port->lnr].lnb.voltage[dvb->input & 3] |= obit; + } + res = dvb->set_input(fe, in); + return res; +} + +static int max_set_tone(struct dvb_frontend *fe, enum fe_sec_tone_mode tone) +{ + struct ddb_input *input = fe->sec_priv; + struct ddb_port *port = input->port; + struct ddb *dev = port->dev; + struct ddb_dvb *dvb = &port->dvb[input->nr & 1]; + int tuner = 0; + int res = 0; + u32 fmode = dev->link[port->lnr].lnb.fmode; + + mutex_lock(&dev->link[port->lnr].lnb.lock); + dvb->tone = tone; + switch (fmode) { + default: + case 0: + case 3: + res = lnb_set_tone(dev, port->lnr, dvb->input, tone); + break; + case 1: + case 2: + if (old_quattro) { + if (dvb->tone == SEC_TONE_ON) + tuner |= 2; + if (dvb->voltage == SEC_VOLTAGE_18) + tuner |= 1; + } else { + if (dvb->tone == SEC_TONE_ON) + tuner |= 1; + if (dvb->voltage == SEC_VOLTAGE_18) + tuner |= 2; + } + res = max_set_input_unlocked(fe, tuner); + break; + } + mutex_unlock(&dev->link[port->lnr].lnb.lock); + return res; +} + +static int max_set_voltage(struct dvb_frontend *fe, enum fe_sec_voltage voltage) +{ + struct ddb_input *input = fe->sec_priv; + struct ddb_port *port = input->port; + struct ddb *dev = port->dev; + struct ddb_dvb *dvb = &port->dvb[input->nr & 1]; + int tuner = 0; + u32 nv, ov = dev->link[port->lnr].lnb.voltages; + int res = 0; + u32 fmode = dev->link[port->lnr].lnb.fmode; + + mutex_lock(&dev->link[port->lnr].lnb.lock); + dvb->voltage = voltage; + + switch (fmode) { + case 3: + default: + case 0: + if (fmode == 3) + max_set_input_unlocked(fe, 0); + if (voltage == SEC_VOLTAGE_OFF) + dev->link[port->lnr].lnb.voltage[dvb->input] &= + ~(1ULL << input->nr); + else + dev->link[port->lnr].lnb.voltage[dvb->input] |= + (1ULL << input->nr); + + res = lnb_set_voltage(dev, port->lnr, dvb->input, voltage); + break; + case 1: + case 2: + if (voltage == SEC_VOLTAGE_OFF) + dev->link[port->lnr].lnb.voltages &= + ~(1ULL << input->nr); + else + dev->link[port->lnr].lnb.voltages |= + (1ULL << input->nr); + + nv = dev->link[port->lnr].lnb.voltages; + + if (old_quattro) { + if (dvb->tone == SEC_TONE_ON) + tuner |= 2; + if (dvb->voltage == SEC_VOLTAGE_18) + tuner |= 1; + } else { + if (dvb->tone == SEC_TONE_ON) + tuner |= 1; + if (dvb->voltage == SEC_VOLTAGE_18) + tuner |= 2; + } + res = max_set_input_unlocked(fe, tuner); + + if (nv != ov) { + if (nv) { + lnb_set_voltage( + dev, port->lnr, + 0, SEC_VOLTAGE_13); + if (fmode == 1) { + lnb_set_voltage( + dev, port->lnr, + 0, SEC_VOLTAGE_13); + if (old_quattro) { + lnb_set_voltage( + dev, port->lnr, + 1, SEC_VOLTAGE_18); + lnb_set_voltage( + dev, port->lnr, + 2, SEC_VOLTAGE_13); + } else { + lnb_set_voltage( + dev, port->lnr, + 1, SEC_VOLTAGE_13); + lnb_set_voltage( + dev, port->lnr, + 2, SEC_VOLTAGE_18); + } + lnb_set_voltage( + dev, port->lnr, + 3, SEC_VOLTAGE_18); + } + } else { + lnb_set_voltage( + dev, port->lnr, + 0, SEC_VOLTAGE_OFF); + if (fmode == 1) { + lnb_set_voltage( + dev, port->lnr, + 1, SEC_VOLTAGE_OFF); + lnb_set_voltage( + dev, port->lnr, + 2, SEC_VOLTAGE_OFF); + lnb_set_voltage( + dev, port->lnr, + 3, SEC_VOLTAGE_OFF); + } + } + } + break; + } + mutex_unlock(&dev->link[port->lnr].lnb.lock); + return res; +} + +static int max_enable_high_lnb_voltage(struct dvb_frontend *fe, long arg) +{ + return 0; +} + +static int max_send_burst(struct dvb_frontend *fe, enum fe_sec_mini_cmd burst) +{ + return 0; +} + +static int mxl_fw_read(void *priv, u8 *buf, u32 len) +{ + struct ddb_link *link = priv; + struct ddb *dev = link->dev; + + dev_info(dev->dev, "Read mxl_fw from link %u\n", link->nr); + + return ddbridge_flashread(dev, link->nr, buf, 0xc0000, len); +} + +int ddb_lnb_init_fmode(struct ddb *dev, struct ddb_link *link, u32 fm) +{ + u32 l = link->nr; + + if (link->lnb.fmode == fm) + return 0; + dev_info(dev->dev, "Set fmode link %u = %u\n", l, fm); + mutex_lock(&link->lnb.lock); + if (fm == 2 || fm == 1) { + if (fmode_sat >= 0) { + lnb_set_sat(dev, l, 0, fmode_sat, 0, 0); + if (old_quattro) { + lnb_set_sat(dev, l, 1, fmode_sat, 0, 1); + lnb_set_sat(dev, l, 2, fmode_sat, 1, 0); + } else { + lnb_set_sat(dev, l, 1, fmode_sat, 1, 0); + lnb_set_sat(dev, l, 2, fmode_sat, 0, 1); + } + lnb_set_sat(dev, l, 3, fmode_sat, 1, 1); + } + lnb_set_tone(dev, l, 0, SEC_TONE_OFF); + if (old_quattro) { + lnb_set_tone(dev, l, 1, SEC_TONE_OFF); + lnb_set_tone(dev, l, 2, SEC_TONE_ON); + } else { + lnb_set_tone(dev, l, 1, SEC_TONE_ON); + lnb_set_tone(dev, l, 2, SEC_TONE_OFF); + } + lnb_set_tone(dev, l, 3, SEC_TONE_ON); + } + link->lnb.fmode = fm; + mutex_unlock(&link->lnb.lock); + return 0; +} + +static struct mxl5xx_cfg mxl5xx = { + .adr = 0x60, + .type = 0x01, + .clk = 27000000, + .ts_clk = 139, + .cap = 12, + .fw_read = mxl_fw_read, +}; + +int ddb_fe_attach_mxl5xx(struct ddb_input *input) +{ + struct ddb *dev = input->port->dev; + struct i2c_adapter *i2c = &input->port->i2c->adap; + struct ddb_dvb *dvb = &input->port->dvb[input->nr & 1]; + struct ddb_port *port = input->port; + struct ddb_link *link = &dev->link[port->lnr]; + struct mxl5xx_cfg cfg; + int demod, tuner; + + cfg = mxl5xx; + cfg.fw_priv = link; + dvb->set_input = NULL; + + demod = input->nr; + tuner = demod & 3; + if (fmode == 3) + tuner = 0; + + dvb->fe = dvb_attach(mxl5xx_attach, i2c, &cfg, + demod, tuner, &dvb->set_input); + + if (!dvb->fe) { + dev_err(dev->dev, "No MXL5XX found!\n"); + return -ENODEV; + } + + if (!dvb->set_input) { + dev_err(dev->dev, "No mxl5xx_set_input function pointer!\n"); + return -ENODEV; + } + + if (input->nr < 4) { + lnb_command(dev, port->lnr, input->nr, LNB_CMD_INIT); + lnb_set_voltage(dev, port->lnr, input->nr, SEC_VOLTAGE_OFF); + } + ddb_lnb_init_fmode(dev, link, fmode); + + dvb->fe->ops.set_voltage = max_set_voltage; + dvb->fe->ops.enable_high_lnb_voltage = max_enable_high_lnb_voltage; + dvb->fe->ops.set_tone = max_set_tone; + dvb->diseqc_send_master_cmd = dvb->fe->ops.diseqc_send_master_cmd; + dvb->fe->ops.diseqc_send_master_cmd = max_send_master_cmd; + dvb->fe->ops.diseqc_send_burst = max_send_burst; + dvb->fe->sec_priv = input; + dvb->input = tuner; + return 0; +} + +/******************************************************************************/ +/* MAX MCI related functions */ + +int ddb_fe_attach_mci(struct ddb_input *input, u32 type) +{ + struct ddb *dev = input->port->dev; + struct ddb_dvb *dvb = &input->port->dvb[input->nr & 1]; + struct ddb_port *port = input->port; + struct ddb_link *link = &dev->link[port->lnr]; + int demod, tuner; + struct mci_cfg cfg; + + demod = input->nr; + tuner = demod & 3; + switch (type) { + case DDB_TUNER_MCI_SX8: + cfg = ddb_max_sx8_cfg; + if (fmode == 3) + tuner = 0; + break; + default: + return -EINVAL; + } + dvb->fe = ddb_mci_attach(input, &cfg, demod, &dvb->set_input); + if (!dvb->fe) { + dev_err(dev->dev, "No MCI card found!\n"); + return -ENODEV; + } + if (!dvb->set_input) { + dev_err(dev->dev, "No MCI set_input function pointer!\n"); + return -ENODEV; + } + if (input->nr < 4) { + lnb_command(dev, port->lnr, input->nr, LNB_CMD_INIT); + lnb_set_voltage(dev, port->lnr, input->nr, SEC_VOLTAGE_OFF); + } + ddb_lnb_init_fmode(dev, link, fmode); + + dvb->fe->ops.set_voltage = max_set_voltage; + dvb->fe->ops.enable_high_lnb_voltage = max_enable_high_lnb_voltage; + dvb->fe->ops.set_tone = max_set_tone; + dvb->diseqc_send_master_cmd = dvb->fe->ops.diseqc_send_master_cmd; + dvb->fe->ops.diseqc_send_master_cmd = max_send_master_cmd; + dvb->fe->ops.diseqc_send_burst = max_send_burst; + dvb->fe->sec_priv = input; + dvb->input = tuner; + return 0; +} diff --git a/drivers/media/pci/ddbridge/ddbridge-max.h b/drivers/media/pci/ddbridge/ddbridge-max.h new file mode 100644 index 000000000..da1553fe8 --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-max.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ddbridge-max.h: Digital Devices bridge MAX card support + * + * Copyright (C) 2010-2017 Digital Devices GmbH + * Ralph Metzler + * Marcus Metzler + */ + +#ifndef _DDBRIDGE_MAX_H_ +#define _DDBRIDGE_MAX_H_ + +#include "ddbridge.h" + +/******************************************************************************/ + +int ddb_lnb_init_fmode(struct ddb *dev, struct ddb_link *link, u32 fm); +int ddb_fe_attach_mxl5xx(struct ddb_input *input); +int ddb_fe_attach_mci(struct ddb_input *input, u32 type); + +#endif /* _DDBRIDGE_MAX_H_ */ diff --git a/drivers/media/pci/ddbridge/ddbridge-mci.c b/drivers/media/pci/ddbridge/ddbridge-mci.c new file mode 100644 index 000000000..a006cb0fa --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-mci.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ddbridge-mci.c: Digital Devices microcode interface + * + * Copyright (C) 2017-2018 Digital Devices GmbH + * Ralph Metzler + * Marcus Metzler + */ + +#include "ddbridge.h" +#include "ddbridge-io.h" +#include "ddbridge-mci.h" + +static LIST_HEAD(mci_list); + +static int mci_reset(struct mci *state) +{ + struct ddb_link *link = state->base->link; + u32 status = 0; + u32 timeout = 40; + + ddblwritel(link, MCI_CONTROL_RESET, MCI_CONTROL); + ddblwritel(link, 0, MCI_CONTROL + 4); /* 1= no internal init */ + msleep(300); + ddblwritel(link, 0, MCI_CONTROL); + + while (1) { + status = ddblreadl(link, MCI_CONTROL); + if ((status & MCI_CONTROL_READY) == MCI_CONTROL_READY) + break; + if (--timeout == 0) + break; + msleep(50); + } + if ((status & MCI_CONTROL_READY) == 0) + return -1; + if (link->ids.device == 0x0009) + ddblwritel(link, SX8_TSCONFIG_MODE_NORMAL, SX8_TSCONFIG); + return 0; +} + +int ddb_mci_config(struct mci *state, u32 config) +{ + struct ddb_link *link = state->base->link; + + if (link->ids.device != 0x0009) + return -EINVAL; + ddblwritel(link, config, SX8_TSCONFIG); + return 0; +} + +static int _mci_cmd_unlocked(struct mci *state, + u32 *cmd, u32 cmd_len, + u32 *res, u32 res_len) +{ + struct ddb_link *link = state->base->link; + u32 i, val; + unsigned long stat; + + val = ddblreadl(link, MCI_CONTROL); + if (val & (MCI_CONTROL_RESET | MCI_CONTROL_START_COMMAND)) + return -EIO; + if (cmd && cmd_len) + for (i = 0; i < cmd_len; i++) + ddblwritel(link, cmd[i], MCI_COMMAND + i * 4); + val |= (MCI_CONTROL_START_COMMAND | MCI_CONTROL_ENABLE_DONE_INTERRUPT); + ddblwritel(link, val, MCI_CONTROL); + + stat = wait_for_completion_timeout(&state->base->completion, HZ); + if (stat == 0) { + dev_warn(state->base->dev, "MCI-%d: MCI timeout\n", state->nr); + return -EIO; + } + if (res && res_len) + for (i = 0; i < res_len; i++) + res[i] = ddblreadl(link, MCI_RESULT + i * 4); + return 0; +} + +int ddb_mci_cmd(struct mci *state, + struct mci_command *command, + struct mci_result *result) +{ + int stat; + + mutex_lock(&state->base->mci_lock); + stat = _mci_cmd_unlocked(state, + (u32 *)command, sizeof(*command) / sizeof(u32), + (u32 *)result, sizeof(*result) / sizeof(u32)); + mutex_unlock(&state->base->mci_lock); + return stat; +} + +static void mci_handler(void *priv) +{ + struct mci_base *base = (struct mci_base *)priv; + + complete(&base->completion); +} + +static struct mci_base *match_base(void *key) +{ + struct mci_base *p; + + list_for_each_entry(p, &mci_list, mci_list) + if (p->key == key) + return p; + return NULL; +} + +static int probe(struct mci *state) +{ + mci_reset(state); + return 0; +} + +struct dvb_frontend +*ddb_mci_attach(struct ddb_input *input, struct mci_cfg *cfg, int nr, + int (**fn_set_input)(struct dvb_frontend *fe, int input)) +{ + struct ddb_port *port = input->port; + struct ddb *dev = port->dev; + struct ddb_link *link = &dev->link[port->lnr]; + struct mci_base *base; + struct mci *state; + void *key = cfg->type ? (void *)port : (void *)link; + + state = kzalloc(cfg->state_size, GFP_KERNEL); + if (!state) + return NULL; + + base = match_base(key); + if (base) { + base->count++; + state->base = base; + } else { + base = kzalloc(cfg->base_size, GFP_KERNEL); + if (!base) + goto fail; + base->key = key; + base->count = 1; + base->link = link; + base->dev = dev->dev; + mutex_init(&base->mci_lock); + mutex_init(&base->tuner_lock); + ddb_irq_set(dev, link->nr, 0, mci_handler, base); + init_completion(&base->completion); + state->base = base; + if (probe(state) < 0) { + kfree(base); + goto fail; + } + list_add(&base->mci_list, &mci_list); + if (cfg->base_init) + cfg->base_init(base); + } + memcpy(&state->fe.ops, cfg->fe_ops, sizeof(struct dvb_frontend_ops)); + state->fe.demodulator_priv = state; + state->nr = nr; + *fn_set_input = cfg->set_input; + state->tuner = nr; + state->demod = nr; + if (cfg->init) + cfg->init(state); + return &state->fe; +fail: + kfree(state); + return NULL; +} diff --git a/drivers/media/pci/ddbridge/ddbridge-mci.h b/drivers/media/pci/ddbridge/ddbridge-mci.h new file mode 100644 index 000000000..d9799fbf5 --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-mci.h @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ddbridge-mci.h: Digital Devices micro code interface + * + * Copyright (C) 2017-2018 Digital Devices GmbH + * Marcus Metzler + * Ralph Metzler + */ + +#ifndef _DDBRIDGE_MCI_H_ +#define _DDBRIDGE_MCI_H_ + +#define MCI_DEMOD_MAX 8 +#define MCI_TUNER_MAX 4 +#define DEMOD_UNUSED (0xFF) + +#define MCI_CONTROL (0x500) +#define MCI_COMMAND (0x600) +#define MCI_RESULT (0x680) + +#define MCI_COMMAND_SIZE (0x80) +#define MCI_RESULT_SIZE (0x80) + +#define MCI_CONTROL_START_COMMAND (0x00000001) +#define MCI_CONTROL_ENABLE_DONE_INTERRUPT (0x00000002) +#define MCI_CONTROL_RESET (0x00008000) +#define MCI_CONTROL_READY (0x00010000) + +#define SX8_TSCONFIG (0x280) + +#define SX8_TSCONFIG_MODE_MASK (0x00000003) +#define SX8_TSCONFIG_MODE_OFF (0x00000000) +#define SX8_TSCONFIG_MODE_NORMAL (0x00000001) +#define SX8_TSCONFIG_MODE_IQ (0x00000003) + +/* + * IQMode is only available on MaxSX8 on a single tuner + * + * IQ_MODE_SAMPLES + * sampling rate is 1550/24 MHz (64.583 MHz) + * channel agc is frozen, to allow stitching the FFT results together + * + * IQ_MODE_VTM + * sampling rate is the supplied symbolrate + * channel agc is active + * + * in both cases down sampling is done with a RRC Filter (currently fixed to + * alpha = 0.05) which causes some (ca 5%) aliasing at the edges from + * outside the spectrum + */ + +#define SX8_TSCONFIG_TSHEADER (0x00000004) +#define SX8_TSCONFIG_BURST (0x00000008) + +#define SX8_TSCONFIG_BURSTSIZE_MASK (0x00000030) +#define SX8_TSCONFIG_BURSTSIZE_2K (0x00000000) +#define SX8_TSCONFIG_BURSTSIZE_4K (0x00000010) +#define SX8_TSCONFIG_BURSTSIZE_8K (0x00000020) +#define SX8_TSCONFIG_BURSTSIZE_16K (0x00000030) + +#define SX8_DEMOD_STOPPED (0) +#define SX8_DEMOD_IQ_MODE (1) +#define SX8_DEMOD_WAIT_SIGNAL (2) +#define SX8_DEMOD_WAIT_MATYPE (3) +#define SX8_DEMOD_TIMEOUT (14) +#define SX8_DEMOD_LOCKED (15) + +#define MCI_CMD_STOP (0x01) +#define MCI_CMD_GETSTATUS (0x02) +#define MCI_CMD_GETSIGNALINFO (0x03) +#define MCI_CMD_RFPOWER (0x04) + +#define MCI_CMD_SEARCH_DVBS (0x10) + +#define MCI_CMD_GET_IQSYMBOL (0x30) + +#define SX8_CMD_INPUT_ENABLE (0x40) +#define SX8_CMD_INPUT_DISABLE (0x41) +#define SX8_CMD_START_IQ (0x42) +#define SX8_CMD_STOP_IQ (0x43) +#define SX8_CMD_ENABLE_IQOUTPUT (0x44) +#define SX8_CMD_DISABLE_IQOUTPUT (0x45) + +#define MCI_STATUS_OK (0x00) +#define MCI_STATUS_UNSUPPORTED (0x80) +#define MCI_STATUS_RETRY (0xFD) +#define MCI_STATUS_NOT_READY (0xFE) +#define MCI_STATUS_ERROR (0xFF) + +#define MCI_SUCCESS(status) ((status & MCI_STATUS_UNSUPPORTED) == 0) + +struct mci_command { + union { + u32 command_word; + struct { + u8 command; + u8 tuner; + u8 demod; + u8 output; + }; + }; + union { + u32 params[31]; + struct { + /* + * Bit 0: DVB-S Enabled + * Bit 1: DVB-S2 Enabled + * Bit 7: InputStreamID + */ + u8 flags; + /* + * Bit 0: QPSK, + * Bit 1: 8PSK/8APSK + * Bit 2: 16APSK + * Bit 3: 32APSK + * Bit 4: 64APSK + * Bit 5: 128APSK + * Bit 6: 256APSK + */ + u8 s2_modulation_mask; + u8 rsvd1; + u8 retry; + u32 frequency; + u32 symbol_rate; + u8 input_stream_id; + u8 rsvd2[3]; + u32 scrambling_sequence_index; + u32 frequency_range; + } dvbs2_search; + + struct { + u8 tap; + u8 rsvd; + u16 point; + } get_iq_symbol; + + struct { + /* + * Bit 0: 0=VTM/1=SCAN + * Bit 1: Set Gain + */ + u8 flags; + u8 roll_off; + u8 rsvd1; + u8 rsvd2; + u32 frequency; + u32 symbol_rate; /* Only in VTM mode */ + u16 gain; + } sx8_start_iq; + + struct { + /* + * Bit 1:0 = STVVGLNA Gain. + * 0 = AGC, 1 = 0dB, 2 = Minimum, 3 = Maximum + */ + u8 flags; + } sx8_input_enable; + }; +}; + +struct mci_result { + union { + u32 status_word; + struct { + u8 status; + u8 mode; + u16 time; + }; + }; + union { + u32 result[27]; + struct { + /* 1 = DVB-S, 2 = DVB-S2X */ + u8 standard; + /* puncture rate for DVB-S */ + u8 pls_code; + /* 2-0: rolloff */ + u8 roll_off; + u8 rsvd; + /* actual frequency in Hz */ + u32 frequency; + /* actual symbolrate in Hz */ + u32 symbol_rate; + /* channel power in dBm x 100 */ + s16 channel_power; + /* band power in dBm x 100 */ + s16 band_power; + /* + * SNR in dB x 100 + * Note: negative values are valid in DVB-S2 + */ + s16 signal_to_noise; + s16 rsvd2; + /* + * Counter for packet errors + * (set to 0 on start command) + */ + u32 packet_errors; + /* Bit error rate: PreRS in DVB-S, PreBCH in DVB-S2X */ + u32 ber_numerator; + u32 ber_denominator; + } dvbs2_signal_info; + + struct { + s16 i; + s16 q; + } iq_symbol; + }; + u32 version[4]; +}; + +struct mci_base { + struct list_head mci_list; + void *key; + struct ddb_link *link; + struct completion completion; + struct device *dev; + struct mutex tuner_lock; /* concurrent tuner access lock */ + struct mutex mci_lock; /* concurrent MCI access lock */ + int count; + int type; +}; + +struct mci { + struct mci_base *base; + struct dvb_frontend fe; + int nr; + int demod; + int tuner; +}; + +struct mci_cfg { + int type; + struct dvb_frontend_ops *fe_ops; + u32 base_size; + u32 state_size; + int (*init)(struct mci *mci); + int (*base_init)(struct mci_base *mci_base); + int (*set_input)(struct dvb_frontend *fe, int input); +}; + +/* defined in ddbridge-sx8.c */ +extern const struct mci_cfg ddb_max_sx8_cfg; + +int ddb_mci_cmd(struct mci *state, struct mci_command *command, + struct mci_result *result); +int ddb_mci_config(struct mci *state, u32 config); + +struct dvb_frontend +*ddb_mci_attach(struct ddb_input *input, struct mci_cfg *cfg, int nr, + int (**fn_set_input)(struct dvb_frontend *fe, int input)); + +#endif /* _DDBRIDGE_MCI_H_ */ diff --git a/drivers/media/pci/ddbridge/ddbridge-regs.h b/drivers/media/pci/ddbridge/ddbridge-regs.h new file mode 100644 index 000000000..42256fc96 --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-regs.h @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ddbridge-regs.h: Digital Devices PCIe bridge driver + * + * Copyright (C) 2010-2017 Digital Devices GmbH + */ + +#ifndef __DDBRIDGE_REGS_H__ +#define __DDBRIDGE_REGS_H__ + +/* ------------------------------------------------------------------------- */ +/* SPI Controller */ + +#define SPI_CONTROL 0x10 +#define SPI_DATA 0x14 + +/* ------------------------------------------------------------------------- */ +/* GPIO */ + +#define GPIO_OUTPUT 0x20 +#define GPIO_INPUT 0x24 +#define GPIO_DIRECTION 0x28 + +/* ------------------------------------------------------------------------- */ + +#define BOARD_CONTROL 0x30 + +/* ------------------------------------------------------------------------- */ + +/* Interrupt controller + * How many MSI's are available depends on HW (Min 2 max 8) + * How many are usable also depends on Host platform + */ + +#define INTERRUPT_BASE (0x40) + +#define INTERRUPT_ENABLE (INTERRUPT_BASE + 0x00) +#define MSI1_ENABLE (INTERRUPT_BASE + 0x04) +#define MSI2_ENABLE (INTERRUPT_BASE + 0x08) +#define MSI3_ENABLE (INTERRUPT_BASE + 0x0C) +#define MSI4_ENABLE (INTERRUPT_BASE + 0x10) +#define MSI5_ENABLE (INTERRUPT_BASE + 0x14) +#define MSI6_ENABLE (INTERRUPT_BASE + 0x18) +#define MSI7_ENABLE (INTERRUPT_BASE + 0x1C) + +#define INTERRUPT_STATUS (INTERRUPT_BASE + 0x20) +#define INTERRUPT_ACK (INTERRUPT_BASE + 0x20) + +/* Temperature Monitor ( 2x LM75A @ 0x90,0x92 I2c ) */ +#define TEMPMON_BASE (0x1c0) +#define TEMPMON_CONTROL (TEMPMON_BASE + 0x00) + +#define TEMPMON_CONTROL_AUTOSCAN (0x00000002) +#define TEMPMON_CONTROL_INTENABLE (0x00000004) +#define TEMPMON_CONTROL_OVERTEMP (0x00008000) + +/* SHORT Temperature in Celsius x 256 */ +#define TEMPMON_SENSOR0 (TEMPMON_BASE + 0x04) +#define TEMPMON_SENSOR1 (TEMPMON_BASE + 0x08) + +#define TEMPMON_FANCONTROL (TEMPMON_BASE + 0x10) + +/* ------------------------------------------------------------------------- */ +/* I2C Master Controller */ + +#define I2C_COMMAND (0x00) +#define I2C_TIMING (0x04) +#define I2C_TASKLENGTH (0x08) /* High read, low write */ +#define I2C_TASKADDRESS (0x0C) /* High read, low write */ +#define I2C_MONITOR (0x1C) + +#define I2C_SPEED_400 (0x04030404) +#define I2C_SPEED_100 (0x13121313) + +/* ------------------------------------------------------------------------- */ +/* DMA Controller */ + +#define DMA_BASE_WRITE (0x100) +#define DMA_BASE_READ (0x140) + +#define TS_CONTROL(_io) ((_io)->regs + 0x00) +#define TS_CONTROL2(_io) ((_io)->regs + 0x04) + +/* ------------------------------------------------------------------------- */ +/* DMA Buffer */ + +#define DMA_BUFFER_CONTROL(_dma) ((_dma)->regs + 0x00) +#define DMA_BUFFER_ACK(_dma) ((_dma)->regs + 0x04) +#define DMA_BUFFER_CURRENT(_dma) ((_dma)->regs + 0x08) +#define DMA_BUFFER_SIZE(_dma) ((_dma)->regs + 0x0c) + +/* ------------------------------------------------------------------------- */ +/* CI Interface (only CI-Bridge) */ + +#define CI_BASE (0x400) +#define CI_CONTROL(i) (CI_BASE + (i) * 32 + 0x00) + +#define CI_DO_ATTRIBUTE_RW(i) (CI_BASE + (i) * 32 + 0x04) +#define CI_DO_IO_RW(i) (CI_BASE + (i) * 32 + 0x08) +#define CI_READDATA(i) (CI_BASE + (i) * 32 + 0x0c) +#define CI_DO_READ_ATTRIBUTES(i) (CI_BASE + (i) * 32 + 0x10) + +#define CI_RESET_CAM (0x00000001) +#define CI_POWER_ON (0x00000002) +#define CI_ENABLE (0x00000004) +#define CI_BYPASS_DISABLE (0x00000010) + +#define CI_CAM_READY (0x00010000) +#define CI_CAM_DETECT (0x00020000) +#define CI_READY (0x80000000) + +#define CI_READ_CMD (0x40000000) +#define CI_WRITE_CMD (0x80000000) + +#define CI_BUFFER_BASE (0x3000) +#define CI_BUFFER_SIZE (0x0800) + +#define CI_BUFFER(i) (CI_BUFFER_BASE + (i) * CI_BUFFER_SIZE) + +/* ------------------------------------------------------------------------- */ +/* LNB commands (mxl5xx / Max S8) */ + +#define LNB_BASE (0x400) +#define LNB_CONTROL(i) (LNB_BASE + (i) * 0x20 + 0x00) + +#define LNB_CMD (7ULL << 0) +#define LNB_CMD_NOP 0 +#define LNB_CMD_INIT 1 +#define LNB_CMD_LOW 3 +#define LNB_CMD_HIGH 4 +#define LNB_CMD_OFF 5 +#define LNB_CMD_DISEQC 6 + +#define LNB_BUSY BIT_ULL(4) +#define LNB_TONE BIT_ULL(15) + +#define LNB_BUF_LEVEL(i) (LNB_BASE + (i) * 0x20 + 0x10) +#define LNB_BUF_WRITE(i) (LNB_BASE + (i) * 0x20 + 0x14) + +#endif /* __DDBRIDGE_REGS_H__ */ diff --git a/drivers/media/pci/ddbridge/ddbridge-sx8.c b/drivers/media/pci/ddbridge/ddbridge-sx8.c new file mode 100644 index 000000000..c8de8d283 --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-sx8.c @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ddbridge-sx8.c: Digital Devices MAX SX8 driver + * + * Copyright (C) 2018 Digital Devices GmbH + * Marcus Metzler + * Ralph Metzler + */ + +#include "ddbridge.h" +#include "ddbridge-io.h" +#include "ddbridge-mci.h" + +static const u32 MCLK = (1550000000 / 12); +static const u32 MAX_LDPC_BITRATE = (720000000); +static const u32 MAX_DEMOD_LDPC_BITRATE = (1550000000 / 6); + +#define SX8_TUNER_NUM 4 +#define SX8_DEMOD_NUM 8 +#define SX8_DEMOD_NONE 0xff + +struct sx8_base { + struct mci_base mci_base; + + u8 tuner_use_count[SX8_TUNER_NUM]; + u32 gain_mode[SX8_TUNER_NUM]; + + u32 used_ldpc_bitrate[SX8_DEMOD_NUM]; + u8 demod_in_use[SX8_DEMOD_NUM]; + u32 iq_mode; + u32 burst_size; + u32 direct_mode; +}; + +struct sx8 { + struct mci mci; + + int first_time_lock; + int started; + struct mci_result signal_info; + + u32 bb_mode; + u32 local_frequency; +}; + +static void release(struct dvb_frontend *fe) +{ + struct sx8 *state = fe->demodulator_priv; + struct mci_base *mci_base = state->mci.base; + + mci_base->count--; + if (mci_base->count == 0) { + list_del(&mci_base->mci_list); + kfree(mci_base); + } + kfree(state); +} + +static int get_info(struct dvb_frontend *fe) +{ + int stat; + struct sx8 *state = fe->demodulator_priv; + struct mci_command cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.command = MCI_CMD_GETSIGNALINFO; + cmd.demod = state->mci.demod; + stat = ddb_mci_cmd(&state->mci, &cmd, &state->signal_info); + return stat; +} + +static int get_snr(struct dvb_frontend *fe) +{ + struct sx8 *state = fe->demodulator_priv; + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + + p->cnr.len = 1; + p->cnr.stat[0].scale = FE_SCALE_DECIBEL; + p->cnr.stat[0].svalue = + (s64)state->signal_info.dvbs2_signal_info.signal_to_noise + * 10; + return 0; +} + +static int get_strength(struct dvb_frontend *fe) +{ + struct sx8 *state = fe->demodulator_priv; + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + s32 str; + + str = 100000 - + (state->signal_info.dvbs2_signal_info.channel_power + * 10 + 108750); + p->strength.len = 1; + p->strength.stat[0].scale = FE_SCALE_DECIBEL; + p->strength.stat[0].svalue = str; + return 0; +} + +static int read_status(struct dvb_frontend *fe, enum fe_status *status) +{ + int stat; + struct sx8 *state = fe->demodulator_priv; + struct mci_command cmd; + struct mci_result res; + + cmd.command = MCI_CMD_GETSTATUS; + cmd.demod = state->mci.demod; + stat = ddb_mci_cmd(&state->mci, &cmd, &res); + if (stat) + return stat; + *status = 0x00; + get_info(fe); + get_strength(fe); + if (res.status == SX8_DEMOD_WAIT_MATYPE) + *status = 0x0f; + if (res.status == SX8_DEMOD_LOCKED) { + *status = 0x1f; + get_snr(fe); + } + return stat; +} + +static int mci_set_tuner(struct dvb_frontend *fe, u32 tuner, u32 on) +{ + struct sx8 *state = fe->demodulator_priv; + struct mci_base *mci_base = state->mci.base; + struct sx8_base *sx8_base = (struct sx8_base *)mci_base; + struct mci_command cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.tuner = state->mci.tuner; + cmd.command = on ? SX8_CMD_INPUT_ENABLE : SX8_CMD_INPUT_DISABLE; + cmd.sx8_input_enable.flags = sx8_base->gain_mode[state->mci.tuner]; + return ddb_mci_cmd(&state->mci, &cmd, NULL); +} + +static int stop(struct dvb_frontend *fe) +{ + struct sx8 *state = fe->demodulator_priv; + struct mci_base *mci_base = state->mci.base; + struct sx8_base *sx8_base = (struct sx8_base *)mci_base; + struct mci_command cmd; + u32 input = state->mci.tuner; + + memset(&cmd, 0, sizeof(cmd)); + if (state->mci.demod != SX8_DEMOD_NONE) { + cmd.command = MCI_CMD_STOP; + cmd.demod = state->mci.demod; + ddb_mci_cmd(&state->mci, &cmd, NULL); + if (sx8_base->iq_mode) { + cmd.command = SX8_CMD_DISABLE_IQOUTPUT; + cmd.demod = state->mci.demod; + cmd.output = 0; + ddb_mci_cmd(&state->mci, &cmd, NULL); + ddb_mci_config(&state->mci, SX8_TSCONFIG_MODE_NORMAL); + } + } + mutex_lock(&mci_base->tuner_lock); + sx8_base->tuner_use_count[input]--; + if (!sx8_base->tuner_use_count[input]) + mci_set_tuner(fe, input, 0); + if (state->mci.demod < SX8_DEMOD_NUM) { + sx8_base->demod_in_use[state->mci.demod] = 0; + state->mci.demod = SX8_DEMOD_NONE; + } + sx8_base->used_ldpc_bitrate[state->mci.nr] = 0; + sx8_base->iq_mode = 0; + mutex_unlock(&mci_base->tuner_lock); + state->started = 0; + return 0; +} + +static int start(struct dvb_frontend *fe, u32 flags, u32 modmask, u32 ts_config) +{ + struct sx8 *state = fe->demodulator_priv; + struct mci_base *mci_base = state->mci.base; + struct sx8_base *sx8_base = (struct sx8_base *)mci_base; + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + u32 used_ldpc_bitrate = 0, free_ldpc_bitrate; + u32 used_demods = 0; + struct mci_command cmd; + u32 input = state->mci.tuner; + u32 bits_per_symbol = 0; + int i = -1, stat = 0; + + if (p->symbol_rate >= (MCLK / 2)) + flags &= ~1; + if ((flags & 3) == 0) + return -EINVAL; + + if (flags & 2) { + u32 tmp = modmask; + + bits_per_symbol = 1; + while (tmp & 1) { + tmp >>= 1; + bits_per_symbol++; + } + } + + mutex_lock(&mci_base->tuner_lock); + if (sx8_base->iq_mode) { + stat = -EBUSY; + goto unlock; + } + + if (sx8_base->direct_mode) { + if (p->symbol_rate >= MCLK / 2) { + if (state->mci.nr < 4) + i = state->mci.nr; + } else { + i = state->mci.nr; + } + } else { + for (i = 0; i < SX8_DEMOD_NUM; i++) { + used_ldpc_bitrate += sx8_base->used_ldpc_bitrate[i]; + if (sx8_base->demod_in_use[i]) + used_demods++; + } + if (used_ldpc_bitrate >= MAX_LDPC_BITRATE || + ((ts_config & SX8_TSCONFIG_MODE_MASK) > + SX8_TSCONFIG_MODE_NORMAL && used_demods > 0)) { + stat = -EBUSY; + goto unlock; + } + free_ldpc_bitrate = MAX_LDPC_BITRATE - used_ldpc_bitrate; + if (free_ldpc_bitrate > MAX_DEMOD_LDPC_BITRATE) + free_ldpc_bitrate = MAX_DEMOD_LDPC_BITRATE; + + while (p->symbol_rate * bits_per_symbol > free_ldpc_bitrate) + bits_per_symbol--; + if (bits_per_symbol < 2) { + stat = -EBUSY; + goto unlock; + } + + modmask &= ((1 << (bits_per_symbol - 1)) - 1); + if (((flags & 0x02) != 0) && modmask == 0) { + stat = -EBUSY; + goto unlock; + } + + i = (p->symbol_rate > (MCLK / 2)) ? 3 : 7; + while (i >= 0 && sx8_base->demod_in_use[i]) + i--; + } + + if (i < 0) { + stat = -EBUSY; + goto unlock; + } + sx8_base->demod_in_use[i] = 1; + sx8_base->used_ldpc_bitrate[state->mci.nr] = p->symbol_rate + * bits_per_symbol; + state->mci.demod = i; + + if (!sx8_base->tuner_use_count[input]) + mci_set_tuner(fe, input, 1); + sx8_base->tuner_use_count[input]++; + sx8_base->iq_mode = (ts_config > 1); +unlock: + mutex_unlock(&mci_base->tuner_lock); + if (stat) + return stat; + memset(&cmd, 0, sizeof(cmd)); + + if (sx8_base->iq_mode) { + cmd.command = SX8_CMD_ENABLE_IQOUTPUT; + cmd.demod = state->mci.demod; + cmd.output = 0; + ddb_mci_cmd(&state->mci, &cmd, NULL); + ddb_mci_config(&state->mci, ts_config); + } + if (p->stream_id != NO_STREAM_ID_FILTER && p->stream_id != 0x80000000) + flags |= 0x80; + dev_dbg(mci_base->dev, "MCI-%d: tuner=%d demod=%d\n", + state->mci.nr, state->mci.tuner, state->mci.demod); + cmd.command = MCI_CMD_SEARCH_DVBS; + cmd.dvbs2_search.flags = flags; + cmd.dvbs2_search.s2_modulation_mask = modmask; + cmd.dvbs2_search.retry = 2; + cmd.dvbs2_search.frequency = p->frequency * 1000; + cmd.dvbs2_search.symbol_rate = p->symbol_rate; + cmd.dvbs2_search.scrambling_sequence_index = + p->scrambling_sequence_index | 0x80000000; + cmd.dvbs2_search.input_stream_id = + (p->stream_id != NO_STREAM_ID_FILTER) ? p->stream_id : 0; + cmd.tuner = state->mci.tuner; + cmd.demod = state->mci.demod; + cmd.output = state->mci.nr; + if (p->stream_id == 0x80000000) + cmd.output |= 0x80; + stat = ddb_mci_cmd(&state->mci, &cmd, NULL); + if (stat) + stop(fe); + return stat; +} + +static int start_iq(struct dvb_frontend *fe, u32 flags, u32 roll_off, + u32 ts_config) +{ + struct sx8 *state = fe->demodulator_priv; + struct mci_base *mci_base = state->mci.base; + struct sx8_base *sx8_base = (struct sx8_base *)mci_base; + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + u32 used_demods = 0; + struct mci_command cmd; + u32 input = state->mci.tuner; + int i, stat = 0; + + mutex_lock(&mci_base->tuner_lock); + if (sx8_base->iq_mode) { + stat = -EBUSY; + goto unlock; + } + for (i = 0; i < SX8_DEMOD_NUM; i++) + if (sx8_base->demod_in_use[i]) + used_demods++; + if (used_demods > 0) { + stat = -EBUSY; + goto unlock; + } + state->mci.demod = 0; + if (!sx8_base->tuner_use_count[input]) + mci_set_tuner(fe, input, 1); + sx8_base->tuner_use_count[input]++; + sx8_base->iq_mode = (ts_config > 1); +unlock: + mutex_unlock(&mci_base->tuner_lock); + if (stat) + return stat; + + memset(&cmd, 0, sizeof(cmd)); + cmd.command = SX8_CMD_START_IQ; + cmd.sx8_start_iq.flags = flags; + cmd.sx8_start_iq.roll_off = roll_off; + cmd.sx8_start_iq.frequency = p->frequency * 1000; + cmd.sx8_start_iq.symbol_rate = p->symbol_rate; + cmd.tuner = state->mci.tuner; + cmd.demod = state->mci.demod; + stat = ddb_mci_cmd(&state->mci, &cmd, NULL); + if (stat) + stop(fe); + ddb_mci_config(&state->mci, ts_config); + return stat; +} + +static int set_parameters(struct dvb_frontend *fe) +{ + int stat = 0; + struct sx8 *state = fe->demodulator_priv; + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + u32 ts_config = SX8_TSCONFIG_MODE_NORMAL, iq_mode = 0, isi; + + if (state->started) + stop(fe); + + isi = p->stream_id; + if (isi != NO_STREAM_ID_FILTER) + iq_mode = (isi & 0x30000000) >> 28; + + if (iq_mode) + ts_config = (SX8_TSCONFIG_TSHEADER | SX8_TSCONFIG_MODE_IQ); + if (iq_mode < 3) { + u32 mask; + + switch (p->modulation) { + /* uncomment whenever these modulations hit the DVB API + * case APSK_256: + * mask = 0x7f; + * break; + * case APSK_128: + * mask = 0x3f; + * break; + * case APSK_64: + * mask = 0x1f; + * break; + */ + case APSK_32: + mask = 0x0f; + break; + case APSK_16: + mask = 0x07; + break; + default: + mask = 0x03; + break; + } + stat = start(fe, 3, mask, ts_config); + } else { + stat = start_iq(fe, 0, 4, ts_config); + } + if (!stat) { + state->started = 1; + state->first_time_lock = 1; + state->signal_info.status = SX8_DEMOD_WAIT_SIGNAL; + } + + return stat; +} + +static int tune(struct dvb_frontend *fe, bool re_tune, + unsigned int mode_flags, + unsigned int *delay, enum fe_status *status) +{ + int r; + + if (re_tune) { + r = set_parameters(fe); + if (r) + return r; + } + r = read_status(fe, status); + if (r) + return r; + + if (*status & FE_HAS_LOCK) + return 0; + *delay = HZ / 10; + return 0; +} + +static enum dvbfe_algo get_algo(struct dvb_frontend *fe) +{ + return DVBFE_ALGO_HW; +} + +static int set_input(struct dvb_frontend *fe, int input) +{ + struct sx8 *state = fe->demodulator_priv; + struct mci_base *mci_base = state->mci.base; + + if (input >= SX8_TUNER_NUM) + return -EINVAL; + + state->mci.tuner = input; + dev_dbg(mci_base->dev, "MCI-%d: input=%d\n", state->mci.nr, input); + return 0; +} + +static struct dvb_frontend_ops sx8_ops = { + .delsys = { SYS_DVBS, SYS_DVBS2 }, + .info = { + .name = "Digital Devices MaxSX8 MCI DVB-S/S2/S2X", + .frequency_min_hz = 950 * MHz, + .frequency_max_hz = 2150 * MHz, + .symbol_rate_min = 100000, + .symbol_rate_max = 100000000, + .caps = FE_CAN_INVERSION_AUTO | + FE_CAN_FEC_AUTO | + FE_CAN_QPSK | + FE_CAN_2G_MODULATION | + FE_CAN_MULTISTREAM, + }, + .get_frontend_algo = get_algo, + .tune = tune, + .release = release, + .read_status = read_status, +}; + +static int init(struct mci *mci) +{ + struct sx8 *state = (struct sx8 *)mci; + + state->mci.demod = SX8_DEMOD_NONE; + return 0; +} + +const struct mci_cfg ddb_max_sx8_cfg = { + .type = 0, + .fe_ops = &sx8_ops, + .base_size = sizeof(struct sx8_base), + .state_size = sizeof(struct sx8), + .init = init, + .set_input = set_input, +}; diff --git a/drivers/media/pci/ddbridge/ddbridge.h b/drivers/media/pci/ddbridge/ddbridge.h new file mode 100644 index 000000000..cf50898f9 --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge.h @@ -0,0 +1,373 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ddbridge.h: Digital Devices PCIe bridge driver + * + * Copyright (C) 2010-2017 Digital Devices GmbH + * Ralph Metzler + */ + +#ifndef _DDBRIDGE_H_ +#define _DDBRIDGE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define DDBRIDGE_VERSION "0.9.33-integrated" + +#define DDB_MAX_I2C 32 +#define DDB_MAX_PORT 32 +#define DDB_MAX_INPUT 64 +#define DDB_MAX_OUTPUT 32 +#define DDB_MAX_LINK 4 +#define DDB_LINK_SHIFT 28 + +#define DDB_LINK_TAG(_x) (_x << DDB_LINK_SHIFT) + +struct ddb_regset { + u32 base; + u32 num; + u32 size; +}; + +struct ddb_regmap { + u32 irq_base_i2c; + u32 irq_base_idma; + u32 irq_base_odma; + + const struct ddb_regset *i2c; + const struct ddb_regset *i2c_buf; + const struct ddb_regset *idma; + const struct ddb_regset *idma_buf; + const struct ddb_regset *odma; + const struct ddb_regset *odma_buf; + + const struct ddb_regset *input; + const struct ddb_regset *output; + + const struct ddb_regset *channel; +}; + +struct ddb_ids { + u16 vendor; + u16 device; + u16 subvendor; + u16 subdevice; + + u32 hwid; + u32 regmapid; + u32 devid; + u32 mac; +}; + +struct ddb_info { + int type; +#define DDB_NONE 0 +#define DDB_OCTOPUS 1 +#define DDB_OCTOPUS_CI 2 +#define DDB_OCTOPUS_MAX 5 +#define DDB_OCTOPUS_MAX_CT 6 +#define DDB_OCTOPUS_MCI 9 + char *name; + u32 i2c_mask; + u32 board_control; + u32 board_control_2; + + u8 port_num; + u8 led_num; + u8 fan_num; + u8 temp_num; + u8 temp_bus; + u8 con_clock; /* use a continuous clock */ + u8 ts_quirks; +#define TS_QUIRK_SERIAL 1 +#define TS_QUIRK_REVERSED 2 +#define TS_QUIRK_ALT_OSC 8 + u8 mci_ports; + u8 mci_type; + + u32 tempmon_irq; + const struct ddb_regmap *regmap; +}; + +#define DMA_MAX_BUFS 32 /* hardware table limit */ + +struct ddb; +struct ddb_port; + +struct ddb_dma { + void *io; + u32 regs; + u32 bufregs; + + dma_addr_t pbuf[DMA_MAX_BUFS]; + u8 *vbuf[DMA_MAX_BUFS]; + u32 num; + u32 size; + u32 div; + u32 bufval; + + struct work_struct work; + spinlock_t lock; /* DMA lock */ + wait_queue_head_t wq; + int running; + u32 stat; + u32 ctrl; + u32 cbuf; + u32 coff; +}; + +struct ddb_dvb { + struct dvb_adapter *adap; + int adap_registered; + struct dvb_device *dev; + struct i2c_client *i2c_client[1]; + struct dvb_frontend *fe; + struct dvb_frontend *fe2; + struct dmxdev dmxdev; + struct dvb_demux demux; + struct dvb_net dvbnet; + struct dmx_frontend hw_frontend; + struct dmx_frontend mem_frontend; + int users; + u32 attached; + u8 input; + + enum fe_sec_tone_mode tone; + enum fe_sec_voltage voltage; + + int (*i2c_gate_ctrl)(struct dvb_frontend *, int); + int (*set_voltage)(struct dvb_frontend *fe, + enum fe_sec_voltage voltage); + int (*set_input)(struct dvb_frontend *fe, int input); + int (*diseqc_send_master_cmd)(struct dvb_frontend *fe, + struct dvb_diseqc_master_cmd *cmd); +}; + +struct ddb_ci { + struct dvb_ca_en50221 en; + struct ddb_port *port; + u32 nr; +}; + +struct ddb_io { + struct ddb_port *port; + u32 nr; + u32 regs; + struct ddb_dma *dma; + struct ddb_io *redo; + struct ddb_io *redi; +}; + +#define ddb_output ddb_io +#define ddb_input ddb_io + +struct ddb_i2c { + struct ddb *dev; + u32 nr; + u32 regs; + u32 link; + struct i2c_adapter adap; + u32 rbuf; + u32 wbuf; + u32 bsize; + struct completion completion; +}; + +struct ddb_port { + struct ddb *dev; + u32 nr; + u32 pnr; + u32 regs; + u32 lnr; + struct ddb_i2c *i2c; + struct mutex i2c_gate_lock; /* I2C access lock */ + u32 class; +#define DDB_PORT_NONE 0 +#define DDB_PORT_CI 1 +#define DDB_PORT_TUNER 2 +#define DDB_PORT_LOOP 3 + char *name; + char *type_name; + u32 type; +#define DDB_TUNER_DUMMY 0xffffffff +#define DDB_TUNER_NONE 0 +#define DDB_TUNER_DVBS_ST 1 +#define DDB_TUNER_DVBS_ST_AA 2 +#define DDB_TUNER_DVBCT_TR 3 +#define DDB_TUNER_DVBCT_ST 4 +#define DDB_CI_INTERNAL 5 +#define DDB_CI_EXTERNAL_SONY 6 +#define DDB_TUNER_DVBCT2_SONY_P 7 +#define DDB_TUNER_DVBC2T2_SONY_P 8 +#define DDB_TUNER_ISDBT_SONY_P 9 +#define DDB_TUNER_DVBS_STV0910_P 10 +#define DDB_TUNER_MXL5XX 11 +#define DDB_CI_EXTERNAL_XO2 12 +#define DDB_CI_EXTERNAL_XO2_B 13 +#define DDB_TUNER_DVBS_STV0910_PR 14 +#define DDB_TUNER_DVBC2T2I_SONY_P 15 + +#define DDB_TUNER_XO2 32 +#define DDB_TUNER_DVBS_STV0910 (DDB_TUNER_XO2 + 0) +#define DDB_TUNER_DVBCT2_SONY (DDB_TUNER_XO2 + 1) +#define DDB_TUNER_ISDBT_SONY (DDB_TUNER_XO2 + 2) +#define DDB_TUNER_DVBC2T2_SONY (DDB_TUNER_XO2 + 3) +#define DDB_TUNER_ATSC_ST (DDB_TUNER_XO2 + 4) +#define DDB_TUNER_DVBC2T2I_SONY (DDB_TUNER_XO2 + 5) + +#define DDB_TUNER_MCI 48 +#define DDB_TUNER_MCI_SX8 (DDB_TUNER_MCI + 0) + + struct ddb_input *input[2]; + struct ddb_output *output; + struct dvb_ca_en50221 *en; + u8 en_freedata; + struct ddb_dvb dvb[2]; + u32 gap; + u32 obr; + u8 creg; +}; + +#define CM_STARTUP_DELAY 2 +#define CM_AVERAGE 20 +#define CM_GAIN 10 + +#define HW_LSB_SHIFT 12 +#define HW_LSB_MASK 0x1000 + +#define CM_IDLE 0 +#define CM_STARTUP 1 +#define CM_ADJUST 2 + +#define TS_CAPTURE_LEN (4096) + +struct ddb_lnb { + struct mutex lock; /* lock lnb access */ + u32 tone; + enum fe_sec_voltage oldvoltage[4]; + u32 voltage[4]; + u32 voltages; + u32 fmode; +}; + +struct ddb_irq { + void (*handler)(void *); + void *data; +}; + +struct ddb_link { + struct ddb *dev; + const struct ddb_info *info; + u32 nr; + u32 regs; + spinlock_t lock; /* lock link access */ + struct mutex flash_mutex; /* lock flash access */ + struct ddb_lnb lnb; + struct work_struct bh_work; + struct ddb_ids ids; + + spinlock_t temp_lock; /* lock temp chip access */ + int overtemperature_error; + u8 temp_tab[11]; + struct ddb_irq irq[256]; +}; + +struct ddb { + struct pci_dev *pdev; + struct platform_device *pfdev; + struct device *dev; + + int msi; + struct workqueue_struct *wq; + u32 has_dma; + + struct ddb_link link[DDB_MAX_LINK]; + unsigned char __iomem *regs; + u32 regs_len; + u32 port_num; + struct ddb_port port[DDB_MAX_PORT]; + u32 i2c_num; + struct ddb_i2c i2c[DDB_MAX_I2C]; + struct ddb_input input[DDB_MAX_INPUT]; + struct ddb_output output[DDB_MAX_OUTPUT]; + struct dvb_adapter adap[DDB_MAX_INPUT]; + struct ddb_dma idma[DDB_MAX_INPUT]; + struct ddb_dma odma[DDB_MAX_OUTPUT]; + + struct device *ddb_dev; + u32 ddb_dev_users; + u32 nr; + u8 iobuf[1028]; + + u8 leds; + u32 ts_irq; + u32 i2c_irq; + + struct mutex mutex; /* lock access to global ddb array */ + + u8 tsbuf[TS_CAPTURE_LEN]; +}; + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +int ddbridge_flashread(struct ddb *dev, u32 link, u8 *buf, u32 addr, u32 len); + +/****************************************************************************/ + +/* ddbridge-core.c */ +struct ddb_irq *ddb_irq_set(struct ddb *dev, u32 link, u32 nr, + void (*handler)(void *), void *data); +void ddb_ports_detach(struct ddb *dev); +void ddb_ports_release(struct ddb *dev); +void ddb_buffers_free(struct ddb *dev); +void ddb_device_destroy(struct ddb *dev); +irqreturn_t ddb_irq_handler0(int irq, void *dev_id); +irqreturn_t ddb_irq_handler1(int irq, void *dev_id); +irqreturn_t ddb_irq_handler(int irq, void *dev_id); +void ddb_ports_init(struct ddb *dev); +int ddb_buffers_alloc(struct ddb *dev); +int ddb_ports_attach(struct ddb *dev); +int ddb_device_create(struct ddb *dev); +int ddb_init(struct ddb *dev); +void ddb_unmap(struct ddb *dev); +int ddb_exit_ddbridge(int stage, int error); +int ddb_init_ddbridge(void); + +#endif /* _DDBRIDGE_H_ */ diff --git a/drivers/media/pci/dm1105/Kconfig b/drivers/media/pci/dm1105/Kconfig new file mode 100644 index 000000000..4498c37f4 --- /dev/null +++ b/drivers/media/pci/dm1105/Kconfig @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DVB_DM1105 + tristate "SDMC DM1105 based PCI cards" + depends on DVB_CORE && PCI && I2C && I2C_ALGOBIT && HAS_IOPORT + select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0288 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6000 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CX24116 if MEDIA_SUBDRV_AUTOSELECT + select DVB_SI21XX if MEDIA_SUBDRV_AUTOSELECT + select DVB_DS3000 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TS2020 if MEDIA_SUBDRV_AUTOSELECT + depends on RC_CORE + help + Support for cards based on the SDMC DM1105 PCI chip like + DvbWorld 2002 + + Since these cards have no MPEG decoder onboard, they transmit + only compressed MPEG data over the PCI bus, so you need + an external software decoder to watch TV on your computer. + + Say Y or M if you own such a device and want to use it. diff --git a/drivers/media/pci/dm1105/Makefile b/drivers/media/pci/dm1105/Makefile new file mode 100644 index 000000000..bf804098e --- /dev/null +++ b/drivers/media/pci/dm1105/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_DVB_DM1105) += dm1105.o + +ccflags-y += -I $(srctree)/drivers/media/dvb-frontends diff --git a/drivers/media/pci/dm1105/dm1105.c b/drivers/media/pci/dm1105/dm1105.c new file mode 100644 index 000000000..ae4544042 --- /dev/null +++ b/drivers/media/pci/dm1105/dm1105.c @@ -0,0 +1,1233 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * dm1105.c - driver for DVB cards based on SDMC DM1105 PCI chip + * + * Copyright (C) 2008 Igor M. Liplianin + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "dvb-pll.h" + +#include "stv0299.h" +#include "stv0288.h" +#include "stb6000.h" +#include "si21xx.h" +#include "cx24116.h" +#include "z0194a.h" +#include "ts2020.h" +#include "ds3000.h" + +#define MODULE_NAME "dm1105" + +#define UNSET (-1U) + +#define DM1105_BOARD_NOAUTO UNSET +#define DM1105_BOARD_UNKNOWN 0 +#define DM1105_BOARD_DVBWORLD_2002 1 +#define DM1105_BOARD_DVBWORLD_2004 2 +#define DM1105_BOARD_AXESS_DM05 3 +#define DM1105_BOARD_UNBRANDED_I2C_ON_GPIO 4 + +/* ----------------------------------------------- */ +/* + * PCI ID's + */ +#ifndef PCI_VENDOR_ID_TRIGEM +#define PCI_VENDOR_ID_TRIGEM 0x109f +#endif +#ifndef PCI_VENDOR_ID_AXESS +#define PCI_VENDOR_ID_AXESS 0x195d +#endif +#ifndef PCI_DEVICE_ID_DM1105 +#define PCI_DEVICE_ID_DM1105 0x036f +#endif +#ifndef PCI_DEVICE_ID_DW2002 +#define PCI_DEVICE_ID_DW2002 0x2002 +#endif +#ifndef PCI_DEVICE_ID_DW2004 +#define PCI_DEVICE_ID_DW2004 0x2004 +#endif +#ifndef PCI_DEVICE_ID_DM05 +#define PCI_DEVICE_ID_DM05 0x1105 +#endif +/* ----------------------------------------------- */ +/* sdmc dm1105 registers */ + +/* TS Control */ +#define DM1105_TSCTR 0x00 +#define DM1105_DTALENTH 0x04 + +/* GPIO Interface */ +#define DM1105_GPIOVAL 0x08 +#define DM1105_GPIOCTR 0x0c + +/* PID serial number */ +#define DM1105_PIDN 0x10 + +/* Odd-even secret key select */ +#define DM1105_CWSEL 0x14 + +/* Host Command Interface */ +#define DM1105_HOST_CTR 0x18 +#define DM1105_HOST_AD 0x1c + +/* PCI Interface */ +#define DM1105_CR 0x30 +#define DM1105_RST 0x34 +#define DM1105_STADR 0x38 +#define DM1105_RLEN 0x3c +#define DM1105_WRP 0x40 +#define DM1105_INTCNT 0x44 +#define DM1105_INTMAK 0x48 +#define DM1105_INTSTS 0x4c + +/* CW Value */ +#define DM1105_ODD 0x50 +#define DM1105_EVEN 0x58 + +/* PID Value */ +#define DM1105_PID 0x60 + +/* IR Control */ +#define DM1105_IRCTR 0x64 +#define DM1105_IRMODE 0x68 +#define DM1105_SYSTEMCODE 0x6c +#define DM1105_IRCODE 0x70 + +/* Unknown Values */ +#define DM1105_ENCRYPT 0x74 +#define DM1105_VER 0x7c + +/* I2C Interface */ +#define DM1105_I2CCTR 0x80 +#define DM1105_I2CSTS 0x81 +#define DM1105_I2CDAT 0x82 +#define DM1105_I2C_RA 0x83 +/* ----------------------------------------------- */ +/* Interrupt Mask Bits */ + +#define INTMAK_TSIRQM 0x01 +#define INTMAK_HIRQM 0x04 +#define INTMAK_IRM 0x08 +#define INTMAK_ALLMASK (INTMAK_TSIRQM | \ + INTMAK_HIRQM | \ + INTMAK_IRM) +#define INTMAK_NONEMASK 0x00 + +/* Interrupt Status Bits */ +#define INTSTS_TSIRQ 0x01 +#define INTSTS_HIRQ 0x04 +#define INTSTS_IR 0x08 + +/* IR Control Bits */ +#define DM1105_IR_EN 0x01 +#define DM1105_SYS_CHK 0x02 +#define DM1105_REP_FLG 0x08 + +/* EEPROM addr */ +#define IIC_24C01_addr 0xa0 +/* Max board count */ +#define DM1105_MAX 0x04 + +#define DRIVER_NAME "dm1105" +#define DM1105_I2C_GPIO_NAME "dm1105-gpio" + +#define DM1105_DMA_PACKETS 47 +#define DM1105_DMA_PACKET_LENGTH (128*4) +#define DM1105_DMA_BYTES (128 * 4 * DM1105_DMA_PACKETS) + +/* */ +#define GPIO08 (1 << 8) +#define GPIO13 (1 << 13) +#define GPIO14 (1 << 14) +#define GPIO15 (1 << 15) +#define GPIO16 (1 << 16) +#define GPIO17 (1 << 17) +#define GPIO_ALL 0x03ffff + +/* GPIO's for LNB power control */ +#define DM1105_LNB_MASK (GPIO_ALL & ~(GPIO14 | GPIO13)) +#define DM1105_LNB_OFF GPIO17 +#define DM1105_LNB_13V (GPIO16 | GPIO08) +#define DM1105_LNB_18V GPIO08 + +/* GPIO's for LNB power control for Axess DM05 */ +#define DM05_LNB_MASK (GPIO_ALL & ~(GPIO14 | GPIO13)) +#define DM05_LNB_OFF GPIO17/* actually 13v */ +#define DM05_LNB_13V GPIO17 +#define DM05_LNB_18V (GPIO17 | GPIO16) + +/* GPIO's for LNB power control for unbranded with I2C on GPIO */ +#define UNBR_LNB_MASK (GPIO17 | GPIO16) +#define UNBR_LNB_OFF 0 +#define UNBR_LNB_13V GPIO17 +#define UNBR_LNB_18V (GPIO17 | GPIO16) + +static unsigned int card[] = {[0 ... 3] = UNSET }; +module_param_array(card, int, NULL, 0444); +MODULE_PARM_DESC(card, "card type"); + +static int ir_debug; +module_param(ir_debug, int, 0644); +MODULE_PARM_DESC(ir_debug, "enable debugging information for IR decoding"); + +static unsigned int dm1105_devcount; + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +struct dm1105_board { + char *name; + struct { + u32 mask, off, v13, v18; + } lnb; + u32 gpio_scl, gpio_sda; +}; + +struct dm1105_subid { + u16 subvendor; + u16 subdevice; + u32 card; +}; + +static const struct dm1105_board dm1105_boards[] = { + [DM1105_BOARD_UNKNOWN] = { + .name = "UNKNOWN/GENERIC", + .lnb = { + .mask = DM1105_LNB_MASK, + .off = DM1105_LNB_OFF, + .v13 = DM1105_LNB_13V, + .v18 = DM1105_LNB_18V, + }, + }, + [DM1105_BOARD_DVBWORLD_2002] = { + .name = "DVBWorld PCI 2002", + .lnb = { + .mask = DM1105_LNB_MASK, + .off = DM1105_LNB_OFF, + .v13 = DM1105_LNB_13V, + .v18 = DM1105_LNB_18V, + }, + }, + [DM1105_BOARD_DVBWORLD_2004] = { + .name = "DVBWorld PCI 2004", + .lnb = { + .mask = DM1105_LNB_MASK, + .off = DM1105_LNB_OFF, + .v13 = DM1105_LNB_13V, + .v18 = DM1105_LNB_18V, + }, + }, + [DM1105_BOARD_AXESS_DM05] = { + .name = "Axess/EasyTv DM05", + .lnb = { + .mask = DM05_LNB_MASK, + .off = DM05_LNB_OFF, + .v13 = DM05_LNB_13V, + .v18 = DM05_LNB_18V, + }, + }, + [DM1105_BOARD_UNBRANDED_I2C_ON_GPIO] = { + .name = "Unbranded DM1105 with i2c on GPIOs", + .lnb = { + .mask = UNBR_LNB_MASK, + .off = UNBR_LNB_OFF, + .v13 = UNBR_LNB_13V, + .v18 = UNBR_LNB_18V, + }, + .gpio_scl = GPIO14, + .gpio_sda = GPIO13, + }, +}; + +static const struct dm1105_subid dm1105_subids[] = { + { + .subvendor = 0x0000, + .subdevice = 0x2002, + .card = DM1105_BOARD_DVBWORLD_2002, + }, { + .subvendor = 0x0001, + .subdevice = 0x2002, + .card = DM1105_BOARD_DVBWORLD_2002, + }, { + .subvendor = 0x0000, + .subdevice = 0x2004, + .card = DM1105_BOARD_DVBWORLD_2004, + }, { + .subvendor = 0x0001, + .subdevice = 0x2004, + .card = DM1105_BOARD_DVBWORLD_2004, + }, { + .subvendor = 0x195d, + .subdevice = 0x1105, + .card = DM1105_BOARD_AXESS_DM05, + }, +}; + +static void dm1105_card_list(struct pci_dev *pci) +{ + int i; + + if (0 == pci->subsystem_vendor && + 0 == pci->subsystem_device) { + printk(KERN_ERR + "dm1105: Your board has no valid PCI Subsystem ID\n" + "dm1105: and thus can't be autodetected\n" + "dm1105: Please pass card= insmod option to\n" + "dm1105: workaround that. Redirect complaints to\n" + "dm1105: the vendor of the TV card. Best regards,\n" + "dm1105: -- tux\n"); + } else { + printk(KERN_ERR + "dm1105: Your board isn't known (yet) to the driver.\n" + "dm1105: You can try to pick one of the existing\n" + "dm1105: card configs via card= insmod option.\n" + "dm1105: Updating to the latest version might help\n" + "dm1105: as well.\n"); + } + printk(KERN_ERR "Here is a list of valid choices for the card= insmod option:\n"); + for (i = 0; i < ARRAY_SIZE(dm1105_boards); i++) + printk(KERN_ERR "dm1105: card=%d -> %s\n", + i, dm1105_boards[i].name); +} + +/* infrared remote control */ +struct infrared { + struct rc_dev *dev; + char input_phys[32]; + struct work_struct work; + u32 ir_command; +}; + +struct dm1105_dev { + /* pci */ + struct pci_dev *pdev; + u8 __iomem *io_mem; + + /* ir */ + struct infrared ir; + + /* dvb */ + struct dmx_frontend hw_frontend; + struct dmx_frontend mem_frontend; + struct dmxdev dmxdev; + struct dvb_adapter dvb_adapter; + struct dvb_demux demux; + struct dvb_frontend *fe; + struct dvb_net dvbnet; + unsigned int full_ts_users; + unsigned int boardnr; + int nr; + + /* i2c */ + struct i2c_adapter i2c_adap; + struct i2c_adapter i2c_bb_adap; + struct i2c_algo_bit_data i2c_bit; + + /* irq */ + struct work_struct work; + struct workqueue_struct *wq; + char wqn[16]; + + /* dma */ + dma_addr_t dma_addr; + unsigned char *ts_buf; + u32 wrp; + u32 nextwrp; + u32 buffer_size; + unsigned int PacketErrorCount; + unsigned int dmarst; + spinlock_t lock; +}; + +#define dm_io_mem(reg) ((unsigned long)(&dev->io_mem[reg])) + +#define dm_readb(reg) inb(dm_io_mem(reg)) +#define dm_writeb(reg, value) outb((value), (dm_io_mem(reg))) + +#define dm_readw(reg) inw(dm_io_mem(reg)) +#define dm_writew(reg, value) outw((value), (dm_io_mem(reg))) + +#define dm_readl(reg) inl(dm_io_mem(reg)) +#define dm_writel(reg, value) outl((value), (dm_io_mem(reg))) + +#define dm_andorl(reg, mask, value) \ + outl((inl(dm_io_mem(reg)) & ~(mask)) |\ + ((value) & (mask)), (dm_io_mem(reg))) + +#define dm_setl(reg, bit) dm_andorl((reg), (bit), (bit)) +#define dm_clearl(reg, bit) dm_andorl((reg), (bit), 0) + +/* The chip has 18 GPIOs. In HOST mode GPIO's used as 15 bit address lines, + so we can use only 3 GPIO's from GPIO15 to GPIO17. + Here I don't check whether HOST is enebled as it is not implemented yet. + */ +static void dm1105_gpio_set(struct dm1105_dev *dev, u32 mask) +{ + if (mask & 0xfffc0000) + printk(KERN_ERR "%s: Only 18 GPIO's are allowed\n", __func__); + + if (mask & 0x0003ffff) + dm_setl(DM1105_GPIOVAL, mask & 0x0003ffff); + +} + +static void dm1105_gpio_clear(struct dm1105_dev *dev, u32 mask) +{ + if (mask & 0xfffc0000) + printk(KERN_ERR "%s: Only 18 GPIO's are allowed\n", __func__); + + if (mask & 0x0003ffff) + dm_clearl(DM1105_GPIOVAL, mask & 0x0003ffff); + +} + +static void dm1105_gpio_andor(struct dm1105_dev *dev, u32 mask, u32 val) +{ + if (mask & 0xfffc0000) + printk(KERN_ERR "%s: Only 18 GPIO's are allowed\n", __func__); + + if (mask & 0x0003ffff) + dm_andorl(DM1105_GPIOVAL, mask & 0x0003ffff, val); + +} + +static u32 dm1105_gpio_get(struct dm1105_dev *dev, u32 mask) +{ + if (mask & 0xfffc0000) + printk(KERN_ERR "%s: Only 18 GPIO's are allowed\n", __func__); + + if (mask & 0x0003ffff) + return dm_readl(DM1105_GPIOVAL) & mask & 0x0003ffff; + + return 0; +} + +static void dm1105_gpio_enable(struct dm1105_dev *dev, u32 mask, int asoutput) +{ + if (mask & 0xfffc0000) + printk(KERN_ERR "%s: Only 18 GPIO's are allowed\n", __func__); + + if ((mask & 0x0003ffff) && asoutput) + dm_clearl(DM1105_GPIOCTR, mask & 0x0003ffff); + else if ((mask & 0x0003ffff) && !asoutput) + dm_setl(DM1105_GPIOCTR, mask & 0x0003ffff); + +} + +static void dm1105_setline(struct dm1105_dev *dev, u32 line, int state) +{ + if (state) + dm1105_gpio_enable(dev, line, 0); + else { + dm1105_gpio_enable(dev, line, 1); + dm1105_gpio_clear(dev, line); + } +} + +static void dm1105_setsda(void *data, int state) +{ + struct dm1105_dev *dev = data; + + dm1105_setline(dev, dm1105_boards[dev->boardnr].gpio_sda, state); +} + +static void dm1105_setscl(void *data, int state) +{ + struct dm1105_dev *dev = data; + + dm1105_setline(dev, dm1105_boards[dev->boardnr].gpio_scl, state); +} + +static int dm1105_getsda(void *data) +{ + struct dm1105_dev *dev = data; + + return dm1105_gpio_get(dev, dm1105_boards[dev->boardnr].gpio_sda) + ? 1 : 0; +} + +static int dm1105_getscl(void *data) +{ + struct dm1105_dev *dev = data; + + return dm1105_gpio_get(dev, dm1105_boards[dev->boardnr].gpio_scl) + ? 1 : 0; +} + +static int dm1105_i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg *msgs, int num) +{ + struct dm1105_dev *dev ; + + int addr, rc, i, j, k, len, byte, data; + u8 status; + + dev = i2c_adap->algo_data; + for (i = 0; i < num; i++) { + dm_writeb(DM1105_I2CCTR, 0x00); + if (msgs[i].flags & I2C_M_RD) { + /* read bytes */ + addr = msgs[i].addr << 1; + addr |= 1; + dm_writeb(DM1105_I2CDAT, addr); + for (byte = 0; byte < msgs[i].len; byte++) + dm_writeb(DM1105_I2CDAT + byte + 1, 0); + + dm_writeb(DM1105_I2CCTR, 0x81 + msgs[i].len); + for (j = 0; j < 55; j++) { + mdelay(10); + status = dm_readb(DM1105_I2CSTS); + if ((status & 0xc0) == 0x40) + break; + } + if (j >= 55) + return -1; + + for (byte = 0; byte < msgs[i].len; byte++) { + rc = dm_readb(DM1105_I2CDAT + byte + 1); + if (rc < 0) + goto err; + msgs[i].buf[byte] = rc; + } + } else if ((msgs[i].buf[0] == 0xf7) && (msgs[i].addr == 0x55)) { + /* prepared for cx24116 firmware */ + /* Write in small blocks */ + len = msgs[i].len - 1; + k = 1; + do { + dm_writeb(DM1105_I2CDAT, msgs[i].addr << 1); + dm_writeb(DM1105_I2CDAT + 1, 0xf7); + for (byte = 0; byte < (len > 48 ? 48 : len); byte++) { + data = msgs[i].buf[k + byte]; + dm_writeb(DM1105_I2CDAT + byte + 2, data); + } + dm_writeb(DM1105_I2CCTR, 0x82 + (len > 48 ? 48 : len)); + for (j = 0; j < 25; j++) { + mdelay(10); + status = dm_readb(DM1105_I2CSTS); + if ((status & 0xc0) == 0x40) + break; + } + + if (j >= 25) + return -1; + + k += 48; + len -= 48; + } while (len > 0); + } else { + /* write bytes */ + dm_writeb(DM1105_I2CDAT, msgs[i].addr << 1); + for (byte = 0; byte < msgs[i].len; byte++) { + data = msgs[i].buf[byte]; + dm_writeb(DM1105_I2CDAT + byte + 1, data); + } + dm_writeb(DM1105_I2CCTR, 0x81 + msgs[i].len); + for (j = 0; j < 25; j++) { + mdelay(10); + status = dm_readb(DM1105_I2CSTS); + if ((status & 0xc0) == 0x40) + break; + } + + if (j >= 25) + return -1; + } + } + return num; + err: + return rc; +} + +static u32 functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C; +} + +static const struct i2c_algorithm dm1105_algo = { + .master_xfer = dm1105_i2c_xfer, + .functionality = functionality, +}; + +static inline struct dm1105_dev *feed_to_dm1105_dev(struct dvb_demux_feed *feed) +{ + return container_of(feed->demux, struct dm1105_dev, demux); +} + +static inline struct dm1105_dev *frontend_to_dm1105_dev(struct dvb_frontend *fe) +{ + return container_of(fe->dvb, struct dm1105_dev, dvb_adapter); +} + +static int dm1105_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage voltage) +{ + struct dm1105_dev *dev = frontend_to_dm1105_dev(fe); + + dm1105_gpio_enable(dev, dm1105_boards[dev->boardnr].lnb.mask, 1); + if (voltage == SEC_VOLTAGE_18) + dm1105_gpio_andor(dev, + dm1105_boards[dev->boardnr].lnb.mask, + dm1105_boards[dev->boardnr].lnb.v18); + else if (voltage == SEC_VOLTAGE_13) + dm1105_gpio_andor(dev, + dm1105_boards[dev->boardnr].lnb.mask, + dm1105_boards[dev->boardnr].lnb.v13); + else + dm1105_gpio_andor(dev, + dm1105_boards[dev->boardnr].lnb.mask, + dm1105_boards[dev->boardnr].lnb.off); + + return 0; +} + +static void dm1105_set_dma_addr(struct dm1105_dev *dev) +{ + dm_writel(DM1105_STADR, (__force u32)cpu_to_le32(dev->dma_addr)); +} + +static int dm1105_dma_map(struct dm1105_dev *dev) +{ + dev->ts_buf = dma_alloc_coherent(&dev->pdev->dev, + 6 * DM1105_DMA_BYTES, &dev->dma_addr, + GFP_KERNEL); + + return !dev->ts_buf; +} + +static void dm1105_dma_unmap(struct dm1105_dev *dev) +{ + dma_free_coherent(&dev->pdev->dev, 6 * DM1105_DMA_BYTES, dev->ts_buf, + dev->dma_addr); +} + +static void dm1105_enable_irqs(struct dm1105_dev *dev) +{ + dm_writeb(DM1105_INTMAK, INTMAK_ALLMASK); + dm_writeb(DM1105_CR, 1); +} + +static void dm1105_disable_irqs(struct dm1105_dev *dev) +{ + dm_writeb(DM1105_INTMAK, INTMAK_IRM); + dm_writeb(DM1105_CR, 0); +} + +static int dm1105_start_feed(struct dvb_demux_feed *f) +{ + struct dm1105_dev *dev = feed_to_dm1105_dev(f); + + if (dev->full_ts_users++ == 0) + dm1105_enable_irqs(dev); + + return 0; +} + +static int dm1105_stop_feed(struct dvb_demux_feed *f) +{ + struct dm1105_dev *dev = feed_to_dm1105_dev(f); + + if (--dev->full_ts_users == 0) + dm1105_disable_irqs(dev); + + return 0; +} + +/* ir work handler */ +static void dm1105_emit_key(struct work_struct *work) +{ + struct infrared *ir = container_of(work, struct infrared, work); + u32 ircom = ir->ir_command; + u8 data; + + if (ir_debug) + printk(KERN_INFO "%s: received byte 0x%04x\n", __func__, ircom); + + data = (ircom >> 8) & 0x7f; + + /* FIXME: UNKNOWN because we don't generate a full NEC scancode (yet?) */ + rc_keydown(ir->dev, RC_PROTO_UNKNOWN, data, 0); +} + +/* work handler */ +static void dm1105_dmx_buffer(struct work_struct *work) +{ + struct dm1105_dev *dev = container_of(work, struct dm1105_dev, work); + unsigned int nbpackets; + u32 oldwrp = dev->wrp; + u32 nextwrp = dev->nextwrp; + + if (!((dev->ts_buf[oldwrp] == 0x47) && + (dev->ts_buf[oldwrp + 188] == 0x47) && + (dev->ts_buf[oldwrp + 188 * 2] == 0x47))) { + dev->PacketErrorCount++; + /* bad packet found */ + if ((dev->PacketErrorCount >= 2) && + (dev->dmarst == 0)) { + dm_writeb(DM1105_RST, 1); + dev->wrp = 0; + dev->PacketErrorCount = 0; + dev->dmarst = 0; + return; + } + } + + if (nextwrp < oldwrp) { + memcpy(dev->ts_buf + dev->buffer_size, dev->ts_buf, nextwrp); + nbpackets = ((dev->buffer_size - oldwrp) + nextwrp) / 188; + } else + nbpackets = (nextwrp - oldwrp) / 188; + + dev->wrp = nextwrp; + dvb_dmx_swfilter_packets(&dev->demux, &dev->ts_buf[oldwrp], nbpackets); +} + +static irqreturn_t dm1105_irq(int irq, void *dev_id) +{ + struct dm1105_dev *dev = dev_id; + + /* Read-Write INSTS Ack's Interrupt for DM1105 chip 16.03.2008 */ + unsigned int intsts = dm_readb(DM1105_INTSTS); + dm_writeb(DM1105_INTSTS, intsts); + + switch (intsts) { + case INTSTS_TSIRQ: + case (INTSTS_TSIRQ | INTSTS_IR): + dev->nextwrp = dm_readl(DM1105_WRP) - dm_readl(DM1105_STADR); + queue_work(dev->wq, &dev->work); + break; + case INTSTS_IR: + dev->ir.ir_command = dm_readl(DM1105_IRCODE); + schedule_work(&dev->ir.work); + break; + } + + return IRQ_HANDLED; +} + +static int dm1105_ir_init(struct dm1105_dev *dm1105) +{ + struct rc_dev *dev; + int err = -ENOMEM; + + dev = rc_allocate_device(RC_DRIVER_SCANCODE); + if (!dev) + return -ENOMEM; + + snprintf(dm1105->ir.input_phys, sizeof(dm1105->ir.input_phys), + "pci-%s/ir0", pci_name(dm1105->pdev)); + + dev->driver_name = MODULE_NAME; + dev->map_name = RC_MAP_DM1105_NEC; + dev->device_name = "DVB on-card IR receiver"; + dev->input_phys = dm1105->ir.input_phys; + dev->input_id.bustype = BUS_PCI; + dev->input_id.version = 1; + if (dm1105->pdev->subsystem_vendor) { + dev->input_id.vendor = dm1105->pdev->subsystem_vendor; + dev->input_id.product = dm1105->pdev->subsystem_device; + } else { + dev->input_id.vendor = dm1105->pdev->vendor; + dev->input_id.product = dm1105->pdev->device; + } + dev->dev.parent = &dm1105->pdev->dev; + + INIT_WORK(&dm1105->ir.work, dm1105_emit_key); + + err = rc_register_device(dev); + if (err < 0) { + rc_free_device(dev); + return err; + } + + dm1105->ir.dev = dev; + return 0; +} + +static void dm1105_ir_exit(struct dm1105_dev *dm1105) +{ + rc_unregister_device(dm1105->ir.dev); + rc_free_device(dm1105->ir.dev); +} + +static int dm1105_hw_init(struct dm1105_dev *dev) +{ + int ret; + + dm1105_disable_irqs(dev); + + dm_writeb(DM1105_HOST_CTR, 0); + + /*DATALEN 188,*/ + dm_writeb(DM1105_DTALENTH, 188); + /*TS_STRT TS_VALP MSBFIRST TS_MODE ALPAS TSPES*/ + dm_writew(DM1105_TSCTR, 0xc10a); + + /* map DMA and set address */ + ret = dm1105_dma_map(dev); + if (ret) + return -ENOMEM; + + dm1105_set_dma_addr(dev); + /* big buffer */ + dm_writel(DM1105_RLEN, 5 * DM1105_DMA_BYTES); + dm_writeb(DM1105_INTCNT, 47); + + /* IR NEC mode enable */ + dm_writeb(DM1105_IRCTR, (DM1105_IR_EN | DM1105_SYS_CHK)); + dm_writeb(DM1105_IRMODE, 0); + dm_writew(DM1105_SYSTEMCODE, 0); + + return 0; +} + +static void dm1105_hw_exit(struct dm1105_dev *dev) +{ + dm1105_disable_irqs(dev); + + /* IR disable */ + dm_writeb(DM1105_IRCTR, 0); + dm_writeb(DM1105_INTMAK, INTMAK_NONEMASK); + + dm1105_dma_unmap(dev); +} + +static const struct stv0299_config sharp_z0194a_config = { + .demod_address = 0x68, + .inittab = sharp_z0194a_inittab, + .mclk = 88000000UL, + .invert = 1, + .skip_reinit = 0, + .lock_output = STV0299_LOCKOUTPUT_1, + .volt13_op0_op1 = STV0299_VOLT13_OP1, + .min_delay_ms = 100, + .set_symbol_rate = sharp_z0194a_set_symbol_rate, +}; + +static struct stv0288_config earda_config = { + .demod_address = 0x68, + .min_delay_ms = 100, +}; + +static struct si21xx_config serit_config = { + .demod_address = 0x68, + .min_delay_ms = 100, + +}; + +static struct cx24116_config serit_sp2633_config = { + .demod_address = 0x55, +}; + +static struct ds3000_config dvbworld_ds3000_config = { + .demod_address = 0x68, +}; + +static struct ts2020_config dvbworld_ts2020_config = { + .tuner_address = 0x60, + .clk_out_div = 1, +}; + +static int frontend_init(struct dm1105_dev *dev) +{ + int ret; + + switch (dev->boardnr) { + case DM1105_BOARD_UNBRANDED_I2C_ON_GPIO: + dm1105_gpio_enable(dev, GPIO15, 1); + dm1105_gpio_clear(dev, GPIO15); + msleep(100); + dm1105_gpio_set(dev, GPIO15); + msleep(200); + dev->fe = dvb_attach( + stv0299_attach, &sharp_z0194a_config, + &dev->i2c_bb_adap); + if (dev->fe) { + dev->fe->ops.set_voltage = dm1105_set_voltage; + dvb_attach(dvb_pll_attach, dev->fe, 0x60, + &dev->i2c_bb_adap, DVB_PLL_OPERA1); + break; + } + + dev->fe = dvb_attach( + stv0288_attach, &earda_config, + &dev->i2c_bb_adap); + if (dev->fe) { + dev->fe->ops.set_voltage = dm1105_set_voltage; + dvb_attach(stb6000_attach, dev->fe, 0x61, + &dev->i2c_bb_adap); + break; + } + + dev->fe = dvb_attach( + si21xx_attach, &serit_config, + &dev->i2c_bb_adap); + if (dev->fe) + dev->fe->ops.set_voltage = dm1105_set_voltage; + break; + case DM1105_BOARD_DVBWORLD_2004: + dev->fe = dvb_attach( + cx24116_attach, &serit_sp2633_config, + &dev->i2c_adap); + if (dev->fe) { + dev->fe->ops.set_voltage = dm1105_set_voltage; + break; + } + + dev->fe = dvb_attach( + ds3000_attach, &dvbworld_ds3000_config, + &dev->i2c_adap); + if (dev->fe) { + dvb_attach(ts2020_attach, dev->fe, + &dvbworld_ts2020_config, &dev->i2c_adap); + dev->fe->ops.set_voltage = dm1105_set_voltage; + } + + break; + case DM1105_BOARD_DVBWORLD_2002: + case DM1105_BOARD_AXESS_DM05: + default: + dev->fe = dvb_attach( + stv0299_attach, &sharp_z0194a_config, + &dev->i2c_adap); + if (dev->fe) { + dev->fe->ops.set_voltage = dm1105_set_voltage; + dvb_attach(dvb_pll_attach, dev->fe, 0x60, + &dev->i2c_adap, DVB_PLL_OPERA1); + break; + } + + dev->fe = dvb_attach( + stv0288_attach, &earda_config, + &dev->i2c_adap); + if (dev->fe) { + dev->fe->ops.set_voltage = dm1105_set_voltage; + dvb_attach(stb6000_attach, dev->fe, 0x61, + &dev->i2c_adap); + break; + } + + dev->fe = dvb_attach( + si21xx_attach, &serit_config, + &dev->i2c_adap); + if (dev->fe) + dev->fe->ops.set_voltage = dm1105_set_voltage; + + } + + if (!dev->fe) { + dev_err(&dev->pdev->dev, "could not attach frontend\n"); + return -ENODEV; + } + + ret = dvb_register_frontend(&dev->dvb_adapter, dev->fe); + if (ret < 0) { + if (dev->fe->ops.release) + dev->fe->ops.release(dev->fe); + dev->fe = NULL; + return ret; + } + + return 0; +} + +static void dm1105_read_mac(struct dm1105_dev *dev, u8 *mac) +{ + static u8 command[1] = { 0x28 }; + + struct i2c_msg msg[] = { + { + .addr = IIC_24C01_addr >> 1, + .flags = 0, + .buf = command, + .len = 1 + }, { + .addr = IIC_24C01_addr >> 1, + .flags = I2C_M_RD, + .buf = mac, + .len = 6 + }, + }; + + dm1105_i2c_xfer(&dev->i2c_adap, msg , 2); + dev_info(&dev->pdev->dev, "MAC %pM\n", mac); +} + +static int dm1105_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct dm1105_dev *dev; + struct dvb_adapter *dvb_adapter; + struct dvb_demux *dvbdemux; + struct dmx_demux *dmx; + int ret = -ENOMEM; + int i; + + if (dm1105_devcount >= ARRAY_SIZE(card)) + return -ENODEV; + + dev = kzalloc_obj(struct dm1105_dev); + if (!dev) + return -ENOMEM; + + /* board config */ + dev->nr = dm1105_devcount; + dev->boardnr = UNSET; + if (card[dev->nr] < ARRAY_SIZE(dm1105_boards)) + dev->boardnr = card[dev->nr]; + for (i = 0; UNSET == dev->boardnr && + i < ARRAY_SIZE(dm1105_subids); i++) + if (pdev->subsystem_vendor == + dm1105_subids[i].subvendor && + pdev->subsystem_device == + dm1105_subids[i].subdevice) + dev->boardnr = dm1105_subids[i].card; + + if (UNSET == dev->boardnr) { + dev->boardnr = DM1105_BOARD_UNKNOWN; + dm1105_card_list(pdev); + } + + dm1105_devcount++; + dev->pdev = pdev; + dev->buffer_size = 5 * DM1105_DMA_BYTES; + dev->PacketErrorCount = 0; + dev->dmarst = 0; + + ret = pci_enable_device(pdev); + if (ret < 0) + goto err_kfree; + + ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (ret < 0) + goto err_pci_disable_device; + + pci_set_master(pdev); + + ret = pci_request_regions(pdev, DRIVER_NAME); + if (ret < 0) + goto err_pci_disable_device; + + dev->io_mem = pci_iomap(pdev, 0, pci_resource_len(pdev, 0)); + if (!dev->io_mem) { + ret = -EIO; + goto err_pci_release_regions; + } + + spin_lock_init(&dev->lock); + pci_set_drvdata(pdev, dev); + + ret = dm1105_hw_init(dev); + if (ret < 0) + goto err_pci_iounmap; + + /* i2c */ + i2c_set_adapdata(&dev->i2c_adap, dev); + strscpy(dev->i2c_adap.name, DRIVER_NAME, sizeof(dev->i2c_adap.name)); + dev->i2c_adap.owner = THIS_MODULE; + dev->i2c_adap.dev.parent = &pdev->dev; + dev->i2c_adap.algo = &dm1105_algo; + dev->i2c_adap.algo_data = dev; + ret = i2c_add_adapter(&dev->i2c_adap); + + if (ret < 0) + goto err_dm1105_hw_exit; + + i2c_set_adapdata(&dev->i2c_bb_adap, dev); + strscpy(dev->i2c_bb_adap.name, DM1105_I2C_GPIO_NAME, + sizeof(dev->i2c_bb_adap.name)); + dev->i2c_bb_adap.owner = THIS_MODULE; + dev->i2c_bb_adap.dev.parent = &pdev->dev; + dev->i2c_bb_adap.algo_data = &dev->i2c_bit; + dev->i2c_bit.data = dev; + dev->i2c_bit.setsda = dm1105_setsda; + dev->i2c_bit.setscl = dm1105_setscl; + dev->i2c_bit.getsda = dm1105_getsda; + dev->i2c_bit.getscl = dm1105_getscl; + dev->i2c_bit.udelay = 10; + dev->i2c_bit.timeout = 10; + + /* Raise SCL and SDA */ + dm1105_setsda(dev, 1); + dm1105_setscl(dev, 1); + + ret = i2c_bit_add_bus(&dev->i2c_bb_adap); + if (ret < 0) + goto err_i2c_del_adapter; + + /* dvb */ + ret = dvb_register_adapter(&dev->dvb_adapter, DRIVER_NAME, + THIS_MODULE, &pdev->dev, adapter_nr); + if (ret < 0) + goto err_i2c_del_adapters; + + dvb_adapter = &dev->dvb_adapter; + + dm1105_read_mac(dev, dvb_adapter->proposed_mac); + + dvbdemux = &dev->demux; + dvbdemux->filternum = 256; + dvbdemux->feednum = 256; + dvbdemux->start_feed = dm1105_start_feed; + dvbdemux->stop_feed = dm1105_stop_feed; + dvbdemux->dmx.capabilities = (DMX_TS_FILTERING | + DMX_SECTION_FILTERING | DMX_MEMORY_BASED_FILTERING); + ret = dvb_dmx_init(dvbdemux); + if (ret < 0) + goto err_dvb_unregister_adapter; + + dmx = &dvbdemux->dmx; + dev->dmxdev.filternum = 256; + dev->dmxdev.demux = dmx; + dev->dmxdev.capabilities = 0; + + ret = dvb_dmxdev_init(&dev->dmxdev, dvb_adapter); + if (ret < 0) + goto err_dvb_dmx_release; + + dev->hw_frontend.source = DMX_FRONTEND_0; + + ret = dmx->add_frontend(dmx, &dev->hw_frontend); + if (ret < 0) + goto err_dvb_dmxdev_release; + + dev->mem_frontend.source = DMX_MEMORY_FE; + + ret = dmx->add_frontend(dmx, &dev->mem_frontend); + if (ret < 0) + goto err_remove_hw_frontend; + + ret = dmx->connect_frontend(dmx, &dev->hw_frontend); + if (ret < 0) + goto err_remove_mem_frontend; + + ret = dvb_net_init(dvb_adapter, &dev->dvbnet, dmx); + if (ret < 0) + goto err_disconnect_frontend; + + ret = frontend_init(dev); + if (ret < 0) + goto err_dvb_net; + + dm1105_ir_init(dev); + + INIT_WORK(&dev->work, dm1105_dmx_buffer); + sprintf(dev->wqn, "%s/%d", dvb_adapter->name, dvb_adapter->num); + dev->wq = create_singlethread_workqueue(dev->wqn); + if (!dev->wq) { + ret = -ENOMEM; + goto err_dvb_net; + } + + ret = request_irq(pdev->irq, dm1105_irq, IRQF_SHARED, + DRIVER_NAME, dev); + if (ret < 0) + goto err_workqueue; + + return 0; + +err_workqueue: + destroy_workqueue(dev->wq); +err_dvb_net: + dvb_net_release(&dev->dvbnet); +err_disconnect_frontend: + dmx->disconnect_frontend(dmx); +err_remove_mem_frontend: + dmx->remove_frontend(dmx, &dev->mem_frontend); +err_remove_hw_frontend: + dmx->remove_frontend(dmx, &dev->hw_frontend); +err_dvb_dmxdev_release: + dvb_dmxdev_release(&dev->dmxdev); +err_dvb_dmx_release: + dvb_dmx_release(dvbdemux); +err_dvb_unregister_adapter: + dvb_unregister_adapter(dvb_adapter); +err_i2c_del_adapters: + i2c_del_adapter(&dev->i2c_bb_adap); +err_i2c_del_adapter: + i2c_del_adapter(&dev->i2c_adap); +err_dm1105_hw_exit: + dm1105_hw_exit(dev); +err_pci_iounmap: + pci_iounmap(pdev, dev->io_mem); +err_pci_release_regions: + pci_release_regions(pdev); +err_pci_disable_device: + pci_disable_device(pdev); +err_kfree: + kfree(dev); + return ret; +} + +static void dm1105_remove(struct pci_dev *pdev) +{ + struct dm1105_dev *dev = pci_get_drvdata(pdev); + struct dvb_adapter *dvb_adapter = &dev->dvb_adapter; + struct dvb_demux *dvbdemux = &dev->demux; + struct dmx_demux *dmx = &dvbdemux->dmx; + + cancel_work_sync(&dev->ir.work); + dm1105_ir_exit(dev); + dmx->close(dmx); + dvb_net_release(&dev->dvbnet); + if (dev->fe) + dvb_unregister_frontend(dev->fe); + + dmx->disconnect_frontend(dmx); + dmx->remove_frontend(dmx, &dev->mem_frontend); + dmx->remove_frontend(dmx, &dev->hw_frontend); + dvb_dmxdev_release(&dev->dmxdev); + dvb_dmx_release(dvbdemux); + dvb_unregister_adapter(dvb_adapter); + i2c_del_adapter(&dev->i2c_adap); + + dm1105_hw_exit(dev); + free_irq(pdev->irq, dev); + destroy_workqueue(dev->wq); + pci_iounmap(pdev, dev->io_mem); + pci_release_regions(pdev); + pci_disable_device(pdev); + dm1105_devcount--; + kfree(dev); +} + +static const struct pci_device_id dm1105_id_table[] = { + { + PCI_VDEVICE(TRIGEM, PCI_DEVICE_ID_DM1105), + }, { + PCI_VDEVICE(AXESS, PCI_DEVICE_ID_DM05), + }, { + /* empty */ + }, +}; + +MODULE_DEVICE_TABLE(pci, dm1105_id_table); + +static struct pci_driver dm1105_driver = { + .name = DRIVER_NAME, + .id_table = dm1105_id_table, + .probe = dm1105_probe, + .remove = dm1105_remove, +}; + +module_pci_driver(dm1105_driver); + +MODULE_AUTHOR("Igor M. Liplianin "); +MODULE_DESCRIPTION("SDMC DM1105 DVB driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/dt3155/Kconfig b/drivers/media/pci/dt3155/Kconfig new file mode 100644 index 000000000..2b76de195 --- /dev/null +++ b/drivers/media/pci/dt3155/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_DT3155 + tristate "DT3155 frame grabber" + depends on PCI && VIDEO_DEV + select VIDEOBUF2_DMA_CONTIG + help + Enables dt3155 device driver for the DataTranslation DT3155 frame grabber. + Say Y here if you have this hardware. + In doubt, say N. + + To compile this driver as a module, choose M here: the + module will be called dt3155. diff --git a/drivers/media/pci/dt3155/Makefile b/drivers/media/pci/dt3155/Makefile new file mode 100644 index 000000000..6bdd07141 --- /dev/null +++ b/drivers/media/pci/dt3155/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_VIDEO_DT3155) += dt3155.o diff --git a/drivers/media/pci/dt3155/dt3155.c b/drivers/media/pci/dt3155/dt3155.c new file mode 100644 index 000000000..660aab6b6 --- /dev/null +++ b/drivers/media/pci/dt3155/dt3155.c @@ -0,0 +1,597 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/*************************************************************************** + * Copyright (C) 2006-2010 by Marin Mitov * + * mitov@issp.bas.bg * + * * + * * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dt3155.h" + +#define DT3155_DEVICE_ID 0x1223 + +/** + * read_i2c_reg - reads an internal i2c register + * + * @addr: dt3155 mmio base address + * @index: index (internal address) of register to read + * @data: pointer to byte the read data will be placed in + * + * returns: zero on success or error code + * + * This function starts reading the specified (by index) register + * and busy waits for the process to finish. The result is placed + * in a byte pointed by data. + */ +static int read_i2c_reg(void __iomem *addr, u8 index, u8 *data) +{ + u32 tmp = index; + + iowrite32((tmp << 17) | IIC_READ, addr + IIC_CSR2); + udelay(45); /* wait at least 43 usec for NEW_CYCLE to clear */ + if (ioread32(addr + IIC_CSR2) & NEW_CYCLE) + return -EIO; /* error: NEW_CYCLE not cleared */ + tmp = ioread32(addr + IIC_CSR1); + if (tmp & DIRECT_ABORT) { + /* reset DIRECT_ABORT bit */ + iowrite32(DIRECT_ABORT, addr + IIC_CSR1); + return -EIO; /* error: DIRECT_ABORT set */ + } + *data = tmp >> 24; + return 0; +} + +/** + * write_i2c_reg - writes to an internal i2c register + * + * @addr: dt3155 mmio base address + * @index: index (internal address) of register to read + * @data: data to be written + * + * returns: zero on success or error code + * + * This function starts writing the specified (by index) register + * and busy waits for the process to finish. + */ +static int write_i2c_reg(void __iomem *addr, u8 index, u8 data) +{ + u32 tmp = index; + + iowrite32((tmp << 17) | IIC_WRITE | data, addr + IIC_CSR2); + udelay(65); /* wait at least 63 usec for NEW_CYCLE to clear */ + if (ioread32(addr + IIC_CSR2) & NEW_CYCLE) + return -EIO; /* error: NEW_CYCLE not cleared */ + if (ioread32(addr + IIC_CSR1) & DIRECT_ABORT) { + /* reset DIRECT_ABORT bit */ + iowrite32(DIRECT_ABORT, addr + IIC_CSR1); + return -EIO; /* error: DIRECT_ABORT set */ + } + return 0; +} + +/** + * write_i2c_reg_nowait - writes to an internal i2c register + * + * @addr: dt3155 mmio base address + * @index: index (internal address) of register to read + * @data: data to be written + * + * This function starts writing the specified (by index) register + * and then returns. + */ +static void write_i2c_reg_nowait(void __iomem *addr, u8 index, u8 data) +{ + u32 tmp = index; + + iowrite32((tmp << 17) | IIC_WRITE | data, addr + IIC_CSR2); +} + +/** + * wait_i2c_reg - waits the read/write to finish + * + * @addr: dt3155 mmio base address + * + * returns: zero on success or error code + * + * This function waits reading/writing to finish. + */ +static int wait_i2c_reg(void __iomem *addr) +{ + if (ioread32(addr + IIC_CSR2) & NEW_CYCLE) + udelay(65); /* wait at least 63 usec for NEW_CYCLE to clear */ + if (ioread32(addr + IIC_CSR2) & NEW_CYCLE) + return -EIO; /* error: NEW_CYCLE not cleared */ + if (ioread32(addr + IIC_CSR1) & DIRECT_ABORT) { + /* reset DIRECT_ABORT bit */ + iowrite32(DIRECT_ABORT, addr + IIC_CSR1); + return -EIO; /* error: DIRECT_ABORT set */ + } + return 0; +} + +static int +dt3155_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) + +{ + struct dt3155_priv *pd = vb2_get_drv_priv(vq); + unsigned size = pd->width * pd->height; + + if (*num_planes) + return sizes[0] < size ? -EINVAL : 0; + *num_planes = 1; + sizes[0] = size; + return 0; +} + +static int dt3155_buf_prepare(struct vb2_buffer *vb) +{ + struct dt3155_priv *pd = vb2_get_drv_priv(vb->vb2_queue); + + vb2_set_plane_payload(vb, 0, pd->width * pd->height); + return 0; +} + +static int dt3155_start_streaming(struct vb2_queue *q, unsigned count) +{ + struct dt3155_priv *pd = vb2_get_drv_priv(q); + struct vb2_buffer *vb = &pd->curr_buf->vb2_buf; + dma_addr_t dma_addr; + + pd->sequence = 0; + dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0); + iowrite32(dma_addr, pd->regs + EVEN_DMA_START); + iowrite32(dma_addr + pd->width, pd->regs + ODD_DMA_START); + iowrite32(pd->width, pd->regs + EVEN_DMA_STRIDE); + iowrite32(pd->width, pd->regs + ODD_DMA_STRIDE); + /* enable interrupts, clear all irq flags */ + iowrite32(FLD_START_EN | FLD_END_ODD_EN | FLD_START | + FLD_END_EVEN | FLD_END_ODD, pd->regs + INT_CSR); + iowrite32(FIFO_EN | SRST | FLD_CRPT_ODD | FLD_CRPT_EVEN | + FLD_DN_ODD | FLD_DN_EVEN | CAP_CONT_EVEN | CAP_CONT_ODD, + pd->regs + CSR1); + wait_i2c_reg(pd->regs); + write_i2c_reg(pd->regs, CONFIG, pd->config); + write_i2c_reg(pd->regs, EVEN_CSR, CSR_ERROR | CSR_DONE); + write_i2c_reg(pd->regs, ODD_CSR, CSR_ERROR | CSR_DONE); + + /* start the board */ + write_i2c_reg(pd->regs, CSR2, pd->csr2 | BUSY_EVEN | BUSY_ODD); + return 0; +} + +static void dt3155_stop_streaming(struct vb2_queue *q) +{ + struct dt3155_priv *pd = vb2_get_drv_priv(q); + struct vb2_buffer *vb; + + spin_lock_irq(&pd->lock); + /* stop the board */ + write_i2c_reg_nowait(pd->regs, CSR2, pd->csr2); + iowrite32(FIFO_EN | SRST | FLD_CRPT_ODD | FLD_CRPT_EVEN | + FLD_DN_ODD | FLD_DN_EVEN, pd->regs + CSR1); + /* disable interrupts, clear all irq flags */ + iowrite32(FLD_START | FLD_END_EVEN | FLD_END_ODD, pd->regs + INT_CSR); + spin_unlock_irq(&pd->lock); + + /* + * It is not clear whether the DMA stops at once or whether it + * will finish the current frame or field first. To be on the + * safe side we wait a bit. + */ + msleep(45); + + spin_lock_irq(&pd->lock); + if (pd->curr_buf) { + vb2_buffer_done(&pd->curr_buf->vb2_buf, VB2_BUF_STATE_ERROR); + pd->curr_buf = NULL; + } + + while (!list_empty(&pd->dmaq)) { + vb = list_first_entry(&pd->dmaq, typeof(*vb), done_entry); + list_del(&vb->done_entry); + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + } + spin_unlock_irq(&pd->lock); +} + +static void dt3155_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct dt3155_priv *pd = vb2_get_drv_priv(vb->vb2_queue); + + /* pd->vidq.streaming = 1 when dt3155_buf_queue() is invoked */ + spin_lock_irq(&pd->lock); + if (pd->curr_buf) + list_add_tail(&vb->done_entry, &pd->dmaq); + else + pd->curr_buf = vbuf; + spin_unlock_irq(&pd->lock); +} + +static const struct vb2_ops q_ops = { + .queue_setup = dt3155_queue_setup, + .buf_prepare = dt3155_buf_prepare, + .start_streaming = dt3155_start_streaming, + .stop_streaming = dt3155_stop_streaming, + .buf_queue = dt3155_buf_queue, +}; + +static irqreturn_t dt3155_irq_handler_even(int irq, void *dev_id) +{ + struct dt3155_priv *ipd = dev_id; + struct vb2_buffer *ivb; + dma_addr_t dma_addr; + u32 tmp; + + tmp = ioread32(ipd->regs + INT_CSR) & (FLD_START | FLD_END_ODD); + if (!tmp) + return IRQ_NONE; /* not our irq */ + if ((tmp & FLD_START) && !(tmp & FLD_END_ODD)) { + iowrite32(FLD_START_EN | FLD_END_ODD_EN | FLD_START, + ipd->regs + INT_CSR); + return IRQ_HANDLED; /* start of field irq */ + } + tmp = ioread32(ipd->regs + CSR1) & (FLD_CRPT_EVEN | FLD_CRPT_ODD); + if (tmp) { + iowrite32(FIFO_EN | SRST | FLD_CRPT_ODD | FLD_CRPT_EVEN | + FLD_DN_ODD | FLD_DN_EVEN | + CAP_CONT_EVEN | CAP_CONT_ODD, + ipd->regs + CSR1); + } + + spin_lock(&ipd->lock); + if (ipd->curr_buf && !list_empty(&ipd->dmaq)) { + ipd->curr_buf->vb2_buf.timestamp = ktime_get_ns(); + ipd->curr_buf->sequence = ipd->sequence++; + ipd->curr_buf->field = V4L2_FIELD_NONE; + vb2_buffer_done(&ipd->curr_buf->vb2_buf, VB2_BUF_STATE_DONE); + + ivb = list_first_entry(&ipd->dmaq, typeof(*ivb), done_entry); + list_del(&ivb->done_entry); + ipd->curr_buf = to_vb2_v4l2_buffer(ivb); + dma_addr = vb2_dma_contig_plane_dma_addr(ivb, 0); + iowrite32(dma_addr, ipd->regs + EVEN_DMA_START); + iowrite32(dma_addr + ipd->width, ipd->regs + ODD_DMA_START); + iowrite32(ipd->width, ipd->regs + EVEN_DMA_STRIDE); + iowrite32(ipd->width, ipd->regs + ODD_DMA_STRIDE); + } + + /* enable interrupts, clear all irq flags */ + iowrite32(FLD_START_EN | FLD_END_ODD_EN | FLD_START | + FLD_END_EVEN | FLD_END_ODD, ipd->regs + INT_CSR); + spin_unlock(&ipd->lock); + return IRQ_HANDLED; +} + +static const struct v4l2_file_operations dt3155_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .unlocked_ioctl = video_ioctl2, + .read = vb2_fop_read, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll +}; + +static int dt3155_querycap(struct file *filp, void *p, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, DT3155_NAME, sizeof(cap->driver)); + strscpy(cap->card, DT3155_NAME " frame grabber", sizeof(cap->card)); + return 0; +} + +static int dt3155_enum_fmt_vid_cap(struct file *filp, + void *p, struct v4l2_fmtdesc *f) +{ + if (f->index) + return -EINVAL; + f->pixelformat = V4L2_PIX_FMT_GREY; + return 0; +} + +static int dt3155_fmt_vid_cap(struct file *filp, void *p, struct v4l2_format *f) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + f->fmt.pix.width = pd->width; + f->fmt.pix.height = pd->height; + f->fmt.pix.pixelformat = V4L2_PIX_FMT_GREY; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.bytesperline = f->fmt.pix.width; + f->fmt.pix.sizeimage = f->fmt.pix.width * f->fmt.pix.height; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + return 0; +} + +static int dt3155_g_std(struct file *filp, void *p, v4l2_std_id *norm) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + *norm = pd->std; + return 0; +} + +static int dt3155_s_std(struct file *filp, void *p, v4l2_std_id norm) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + if (pd->std == norm) + return 0; + if (vb2_is_busy(&pd->vidq)) + return -EBUSY; + pd->std = norm; + if (pd->std & V4L2_STD_525_60) { + pd->csr2 = VT_60HZ; + pd->width = 640; + pd->height = 480; + } else { + pd->csr2 = VT_50HZ; + pd->width = 768; + pd->height = 576; + } + return 0; +} + +static int dt3155_enum_input(struct file *filp, void *p, + struct v4l2_input *input) +{ + if (input->index > 3) + return -EINVAL; + if (input->index) + snprintf(input->name, sizeof(input->name), "VID%d", + input->index); + else + strscpy(input->name, "J2/VID0", sizeof(input->name)); + input->type = V4L2_INPUT_TYPE_CAMERA; + input->std = V4L2_STD_ALL; + input->status = 0; + return 0; +} + +static int dt3155_g_input(struct file *filp, void *p, unsigned int *i) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + *i = pd->input; + return 0; +} + +static int dt3155_s_input(struct file *filp, void *p, unsigned int i) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + if (i > 3) + return -EINVAL; + pd->input = i; + write_i2c_reg(pd->regs, AD_ADDR, AD_CMD_REG); + write_i2c_reg(pd->regs, AD_CMD, (i << 6) | (i << 4) | SYNC_LVL_3); + return 0; +} + +static const struct v4l2_ioctl_ops dt3155_ioctl_ops = { + .vidioc_querycap = dt3155_querycap, + .vidioc_enum_fmt_vid_cap = dt3155_enum_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = dt3155_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = dt3155_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = dt3155_fmt_vid_cap, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_g_std = dt3155_g_std, + .vidioc_s_std = dt3155_s_std, + .vidioc_enum_input = dt3155_enum_input, + .vidioc_g_input = dt3155_g_input, + .vidioc_s_input = dt3155_s_input, +}; + +static int dt3155_init_board(struct dt3155_priv *pd) +{ + struct pci_dev *pdev = pd->pdev; + int i; + u8 tmp = 0; + + pci_set_master(pdev); /* dt3155 needs it */ + + /* resetting the adapter */ + iowrite32(ADDR_ERR_ODD | ADDR_ERR_EVEN | FLD_CRPT_ODD | FLD_CRPT_EVEN | + FLD_DN_ODD | FLD_DN_EVEN, pd->regs + CSR1); + msleep(20); + + /* initializing adapter registers */ + iowrite32(FIFO_EN | SRST, pd->regs + CSR1); + iowrite32(0xEEEEEE01, pd->regs + EVEN_PIXEL_FMT); + iowrite32(0xEEEEEE01, pd->regs + ODD_PIXEL_FMT); + iowrite32(0x00000020, pd->regs + FIFO_TRIGGER); + iowrite32(0x00000103, pd->regs + XFER_MODE); + iowrite32(0, pd->regs + RETRY_WAIT_CNT); + iowrite32(0, pd->regs + INT_CSR); + iowrite32(1, pd->regs + EVEN_FLD_MASK); + iowrite32(1, pd->regs + ODD_FLD_MASK); + iowrite32(0, pd->regs + MASK_LENGTH); + iowrite32(0x0005007C, pd->regs + FIFO_FLAG_CNT); + iowrite32(0x01010101, pd->regs + IIC_CLK_DUR); + + /* verifying that we have a DT3155 board (not just a SAA7116 chip) */ + read_i2c_reg(pd->regs, DT_ID, &tmp); + if (tmp != DT3155_ID) + return -ENODEV; + + /* initialize AD LUT */ + write_i2c_reg(pd->regs, AD_ADDR, 0); + for (i = 0; i < 256; i++) + write_i2c_reg(pd->regs, AD_LUT, i); + + /* initialize ADC references */ + /* FIXME: pos_ref & neg_ref depend on VT_50HZ */ + write_i2c_reg(pd->regs, AD_ADDR, AD_CMD_REG); + write_i2c_reg(pd->regs, AD_CMD, VIDEO_CNL_1 | SYNC_CNL_1 | SYNC_LVL_3); + write_i2c_reg(pd->regs, AD_ADDR, AD_POS_REF); + write_i2c_reg(pd->regs, AD_CMD, 34); + write_i2c_reg(pd->regs, AD_ADDR, AD_NEG_REF); + write_i2c_reg(pd->regs, AD_CMD, 0); + + /* initialize PM LUT */ + write_i2c_reg(pd->regs, CONFIG, pd->config | PM_LUT_PGM); + for (i = 0; i < 256; i++) { + write_i2c_reg(pd->regs, PM_LUT_ADDR, i); + write_i2c_reg(pd->regs, PM_LUT_DATA, i); + } + write_i2c_reg(pd->regs, CONFIG, pd->config | PM_LUT_PGM | PM_LUT_SEL); + for (i = 0; i < 256; i++) { + write_i2c_reg(pd->regs, PM_LUT_ADDR, i); + write_i2c_reg(pd->regs, PM_LUT_DATA, i); + } + write_i2c_reg(pd->regs, CONFIG, pd->config); /* ACQ_MODE_EVEN */ + + /* select channel 1 for input and set sync level */ + write_i2c_reg(pd->regs, AD_ADDR, AD_CMD_REG); + write_i2c_reg(pd->regs, AD_CMD, VIDEO_CNL_1 | SYNC_CNL_1 | SYNC_LVL_3); + + /* disable all irqs, clear all irq flags */ + iowrite32(FLD_START | FLD_END_EVEN | FLD_END_ODD, + pd->regs + INT_CSR); + + return 0; +} + +static const struct video_device dt3155_vdev = { + .name = DT3155_NAME, + .fops = &dt3155_fops, + .ioctl_ops = &dt3155_ioctl_ops, + .minor = -1, + .release = video_device_release_empty, + .tvnorms = V4L2_STD_ALL, + .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE, +}; + +static int dt3155_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + int err; + struct dt3155_priv *pd; + + err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (err) + return -ENODEV; + pd = devm_kzalloc(&pdev->dev, sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + + err = v4l2_device_register(&pdev->dev, &pd->v4l2_dev); + if (err) + return err; + pd->vdev = dt3155_vdev; + pd->vdev.v4l2_dev = &pd->v4l2_dev; + video_set_drvdata(&pd->vdev, pd); /* for use in video_fops */ + pd->pdev = pdev; + pd->std = V4L2_STD_625_50; + pd->csr2 = VT_50HZ; + pd->width = 768; + pd->height = 576; + INIT_LIST_HEAD(&pd->dmaq); + mutex_init(&pd->mux); + pd->vdev.lock = &pd->mux; /* for locking v4l2_file_operations */ + pd->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + pd->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + pd->vidq.io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ; + pd->vidq.ops = &q_ops; + pd->vidq.mem_ops = &vb2_dma_contig_memops; + pd->vidq.drv_priv = pd; + pd->vidq.min_queued_buffers = 2; + pd->vidq.gfp_flags = GFP_DMA32; + pd->vidq.lock = &pd->mux; /* for locking v4l2_file_operations */ + pd->vidq.dev = &pdev->dev; + pd->vdev.queue = &pd->vidq; + err = vb2_queue_init(&pd->vidq); + if (err < 0) + goto err_v4l2_dev_unreg; + spin_lock_init(&pd->lock); + pd->config = ACQ_MODE_EVEN; + err = pci_enable_device(pdev); + if (err) + goto err_v4l2_dev_unreg; + err = pci_request_region(pdev, 0, pci_name(pdev)); + if (err) + goto err_pci_disable; + pd->regs = pci_iomap(pdev, 0, pci_resource_len(pd->pdev, 0)); + if (!pd->regs) { + err = -ENOMEM; + goto err_free_reg; + } + err = dt3155_init_board(pd); + if (err) + goto err_iounmap; + err = request_irq(pd->pdev->irq, dt3155_irq_handler_even, + IRQF_SHARED, DT3155_NAME, pd); + if (err) + goto err_iounmap; + err = video_register_device(&pd->vdev, VFL_TYPE_VIDEO, -1); + if (err) + goto err_free_irq; + dev_info(&pdev->dev, "/dev/video%i is ready\n", pd->vdev.minor); + return 0; /* success */ + +err_free_irq: + free_irq(pd->pdev->irq, pd); +err_iounmap: + pci_iounmap(pdev, pd->regs); +err_free_reg: + pci_release_region(pdev, 0); +err_pci_disable: + pci_disable_device(pdev); +err_v4l2_dev_unreg: + v4l2_device_unregister(&pd->v4l2_dev); + return err; +} + +static void dt3155_remove(struct pci_dev *pdev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev); + struct dt3155_priv *pd = container_of(v4l2_dev, struct dt3155_priv, + v4l2_dev); + + vb2_video_unregister_device(&pd->vdev); + free_irq(pd->pdev->irq, pd); + v4l2_device_unregister(&pd->v4l2_dev); + pci_iounmap(pdev, pd->regs); + pci_release_region(pdev, 0); + pci_disable_device(pdev); +} + +static const struct pci_device_id pci_ids[] = { + { PCI_VDEVICE(INTEL, DT3155_DEVICE_ID) }, + { /* zero marks the end */ }, +}; +MODULE_DEVICE_TABLE(pci, pci_ids); + +static struct pci_driver pci_driver = { + .name = DT3155_NAME, + .id_table = pci_ids, + .probe = dt3155_probe, + .remove = dt3155_remove, +}; + +module_pci_driver(pci_driver); + +MODULE_DESCRIPTION("video4linux pci-driver for dt3155 frame grabber"); +MODULE_AUTHOR("Marin Mitov "); +MODULE_VERSION(DT3155_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/dt3155/dt3155.h b/drivers/media/pci/dt3155/dt3155.h new file mode 100644 index 000000000..ce1835d96 --- /dev/null +++ b/drivers/media/pci/dt3155/dt3155.h @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/*************************************************************************** + * Copyright (C) 2006-2010 by Marin Mitov * + * mitov@issp.bas.bg * + * * + * * + ***************************************************************************/ + +/* DT3155 header file */ +#ifndef _DT3155_H_ +#define _DT3155_H_ + +#include +#include +#include +#include +#include + +#define DT3155_NAME "dt3155" +#define DT3155_VER_MAJ 2 +#define DT3155_VER_MIN 0 +#define DT3155_VER_EXT 0 +#define DT3155_VERSION __stringify(DT3155_VER_MAJ) "." \ + __stringify(DT3155_VER_MIN) "." \ + __stringify(DT3155_VER_EXT) + +/* DT3155 Base Register offsets (memory mapped) */ +#define EVEN_DMA_START 0x00 +#define ODD_DMA_START 0x0C +#define EVEN_DMA_STRIDE 0x18 +#define ODD_DMA_STRIDE 0x24 +#define EVEN_PIXEL_FMT 0x30 +#define ODD_PIXEL_FMT 0x34 +#define FIFO_TRIGGER 0x38 +#define XFER_MODE 0x3C +#define CSR1 0x40 +#define RETRY_WAIT_CNT 0x44 +#define INT_CSR 0x48 +#define EVEN_FLD_MASK 0x4C +#define ODD_FLD_MASK 0x50 +#define MASK_LENGTH 0x54 +#define FIFO_FLAG_CNT 0x58 +#define IIC_CLK_DUR 0x5C +#define IIC_CSR1 0x60 +#define IIC_CSR2 0x64 + +/* DT3155 Internal Registers indexes (i2c/IIC mapped) */ +#define CSR2 0x10 +#define EVEN_CSR 0x11 +#define ODD_CSR 0x12 +#define CONFIG 0x13 +#define DT_ID 0x1F +#define X_CLIP_START 0x20 +#define Y_CLIP_START 0x22 +#define X_CLIP_END 0x24 +#define Y_CLIP_END 0x26 +#define AD_ADDR 0x30 +#define AD_LUT 0x31 +#define AD_CMD 0x32 +#define DIG_OUT 0x40 +#define PM_LUT_ADDR 0x50 +#define PM_LUT_DATA 0x51 + +/* AD command register values */ +#define AD_CMD_REG 0x00 +#define AD_POS_REF 0x01 +#define AD_NEG_REF 0x02 + +/* CSR1 bit masks */ +#define RANGE_EN 0x00008000 +#define CRPT_DIS 0x00004000 +#define ADDR_ERR_ODD 0x00000800 +#define ADDR_ERR_EVEN 0x00000400 +#define FLD_CRPT_ODD 0x00000200 +#define FLD_CRPT_EVEN 0x00000100 +#define FIFO_EN 0x00000080 +#define SRST 0x00000040 +#define FLD_DN_ODD 0x00000020 +#define FLD_DN_EVEN 0x00000010 +/* These should not be used. + * Use CAP_CONT_ODD/EVEN instead +#define CAP_SNGL_ODD 0x00000008 +#define CAP_SNGL_EVEN 0x00000004 +*/ +#define CAP_CONT_ODD 0x00000002 +#define CAP_CONT_EVEN 0x00000001 + +/* INT_CSR bit masks */ +#define FLD_START_EN 0x00000400 +#define FLD_END_ODD_EN 0x00000200 +#define FLD_END_EVEN_EN 0x00000100 +#define FLD_START 0x00000004 +#define FLD_END_ODD 0x00000002 +#define FLD_END_EVEN 0x00000001 + +/* IIC_CSR1 bit masks */ +#define DIRECT_ABORT 0x00000200 + +/* IIC_CSR2 bit masks */ +#define NEW_CYCLE 0x01000000 +#define DIR_RD 0x00010000 +#define IIC_READ 0x01010000 +#define IIC_WRITE 0x01000000 + +/* CSR2 bit masks */ +#define DISP_PASS 0x40 +#define BUSY_ODD 0x20 +#define BUSY_EVEN 0x10 +#define SYNC_PRESENT 0x08 +#define VT_50HZ 0x04 +#define SYNC_SNTL 0x02 +#define CHROM_FILT 0x01 +#define VT_60HZ 0x00 + +/* CSR_EVEN/ODD bit masks */ +#define CSR_ERROR 0x04 +#define CSR_SNGL 0x02 +#define CSR_DONE 0x01 + +/* CONFIG bit masks */ +#define PM_LUT_PGM 0x80 +#define PM_LUT_SEL 0x40 +#define CLIP_EN 0x20 +#define HSCALE_EN 0x10 +#define EXT_TRIG_UP 0x0C +#define EXT_TRIG_DOWN 0x04 +#define ACQ_MODE_NEXT 0x02 +#define ACQ_MODE_ODD 0x01 +#define ACQ_MODE_EVEN 0x00 + +/* AD_CMD bit masks */ +#define VIDEO_CNL_1 0x00 +#define VIDEO_CNL_2 0x40 +#define VIDEO_CNL_3 0x80 +#define VIDEO_CNL_4 0xC0 +#define SYNC_CNL_1 0x00 +#define SYNC_CNL_2 0x10 +#define SYNC_CNL_3 0x20 +#define SYNC_CNL_4 0x30 +#define SYNC_LVL_1 0x00 +#define SYNC_LVL_2 0x04 +#define SYNC_LVL_3 0x08 +#define SYNC_LVL_4 0x0C + +/* DT3155 identificator */ +#define DT3155_ID 0x20 + +/* per board private data structure */ +/** + * struct dt3155_priv - private data structure + * + * @v4l2_dev: v4l2_device structure + * @vdev: video_device structure + * @pdev: pointer to pci_dev structure + * @vidq: vb2_queue structure + * @curr_buf: pointer to curren buffer + * @mux: mutex to protect the instance + * @dmaq: queue for dma buffers + * @lock: spinlock for dma queue + * @std: input standard + * @width: frame width + * @height: frame height + * @input: current input + * @sequence: frame counter + * @regs: local copy of mmio base register + * @csr2: local copy of csr2 register + * @config: local copy of config register + */ +struct dt3155_priv { + struct v4l2_device v4l2_dev; + struct video_device vdev; + struct pci_dev *pdev; + struct vb2_queue vidq; + struct vb2_v4l2_buffer *curr_buf; + struct mutex mux; + struct list_head dmaq; + spinlock_t lock; + v4l2_std_id std; + unsigned width, height; + unsigned input; + unsigned int sequence; + void __iomem *regs; + u8 csr2, config; +}; + +#endif /* _DT3155_H_ */ diff --git a/drivers/media/pci/hws/Kconfig b/drivers/media/pci/hws/Kconfig new file mode 100644 index 000000000..ab0bbf49c --- /dev/null +++ b/drivers/media/pci/hws/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_HWS + tristate "AVMatrix HWS PCIe capture devices" + depends on PCI && VIDEO_DEV + select VIDEOBUF2_DMA_CONTIG + help + Enable support for AVMatrix HWS series multi-channel PCIe capture + devices that provide HDMI video capture. + + To compile this driver as a module, choose M here: the module + will be named hws. diff --git a/drivers/media/pci/hws/Makefile b/drivers/media/pci/hws/Makefile new file mode 100644 index 000000000..f9c7dc4f2 --- /dev/null +++ b/drivers/media/pci/hws/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +hws-objs := hws_pci.o hws_irq.o hws_video.o hws_v4l2_ioctl.o + +obj-$(CONFIG_VIDEO_HWS) += hws.o diff --git a/drivers/media/pci/hws/hws.h b/drivers/media/pci/hws/hws.h new file mode 100644 index 000000000..8fbe1fe27 --- /dev/null +++ b/drivers/media/pci/hws/hws.h @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef HWS_PCIE_H +#define HWS_PCIE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "hws_reg.h" + +struct hwsmem_param { + u32 index; + u32 type; + u32 status; +}; + +struct hws_pix_state { + u32 width; + u32 height; + u32 fourcc; /* V4L2_PIX_FMT_* (YUYV only here) */ + u32 bytesperline; /* stride */ + u32 sizeimage; /* full frame */ + enum v4l2_field field; /* V4L2_FIELD_NONE or INTERLACED */ + enum v4l2_colorspace colorspace; /* e.g., REC709 */ + enum v4l2_ycbcr_encoding ycbcr_enc; /* V4L2_YCBCR_ENC_DEFAULT */ + enum v4l2_quantization quantization; /* V4L2_QUANTIZATION_LIM_RANGE */ + enum v4l2_xfer_func xfer_func; /* V4L2_XFER_FUNC_DEFAULT */ + bool interlaced; /* cached hardware state */ + u32 half_size; /* hardware half-frame size */ +}; + +#define UNSET (-1U) + +struct hws_pcie_dev; +struct hws_adapter; +struct hws_video; + +struct hwsvideo_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; + int slot; +}; + +struct hws_video { + /* Linkage */ + struct hws_pcie_dev *parent; + struct video_device *video_device; + + struct vb2_queue buffer_queue; + struct list_head capture_queue; + struct hwsvideo_buffer *active; + struct hwsvideo_buffer *next_prepared; + + /* Locking */ + struct mutex state_lock; + spinlock_t irq_lock; /* Protects capture_queue and active buffers. */ + + /* Indices */ + int channel_index; + + /* Color controls */ + int current_brightness; + int current_contrast; + int current_saturation; + int current_hue; + + /* V4L2 controls */ + struct v4l2_ctrl_handler control_handler; + struct v4l2_ctrl *ctrl_brightness; + struct v4l2_ctrl *ctrl_contrast; + struct v4l2_ctrl *ctrl_saturation; + struct v4l2_ctrl *ctrl_hue; + + /* Capture queue status */ + struct hws_pix_state pix; + struct v4l2_dv_timings cur_dv_timings; /* last configured/notified DV timings */ + u32 current_fps; /* Hz, updated by mode changes, not by read-only queries */ + + /* Per-channel capture state */ + bool cap_active; + bool stop_requested; + u8 last_buf_half_toggle; + bool half_seen; + atomic_t sequence_number; + u32 queued_count; + + /* Timeout and error handling */ + u32 timeout_count; + u32 error_count; + + bool window_valid; + u32 last_dma_hi; + u32 last_dma_page; + u32 last_pci_addr; + u32 last_half16; + + /* Misc counters */ + int signal_loss_cnt; +}; + +static inline void hws_set_current_dv_timings(struct hws_video *vid, + u32 width, u32 height, + bool interlaced) +{ + if (!vid) + return; + + vid->cur_dv_timings = (struct v4l2_dv_timings) { + .type = V4L2_DV_BT_656_1120, + .bt = { + .width = width, + .height = height, + .interlaced = interlaced, + }, + }; +} + +struct hws_scratch_dma { + void *cpu; + dma_addr_t dma; + size_t size; +}; + +struct hws_pcie_dev { + /* Core objects */ + struct pci_dev *pdev; + struct hws_video video[MAX_VID_CHANNELS]; + + /* BAR and workqueues */ + void __iomem *bar0_base; + + /* Device identity and capabilities */ + u16 vendor_id; + u16 device_id; + u16 device_ver; + u16 hw_ver; + u32 sub_ver; + u32 port_id; + /* Tri-state support flag used by set_video_format_size(). */ + u32 support_yv12; + u32 max_hw_video_buf_sz; + u8 max_channels; + u8 cur_max_video_ch; + bool start_run; + + bool buf_allocated; + + /* V4L2 framework objects */ + struct v4l2_device v4l2_device; + + /* Kernel thread */ + struct task_struct *main_task; + struct hws_scratch_dma scratch_vid[MAX_VID_CHANNELS]; + + bool suspended; + int irq; + + /* Error flags */ + int pci_lost; +}; + +#endif diff --git a/drivers/media/pci/hws/hws_irq.c b/drivers/media/pci/hws/hws_irq.c new file mode 100644 index 000000000..eebb4b8a5 --- /dev/null +++ b/drivers/media/pci/hws/hws_irq.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "hws_irq.h" +#include "hws_reg.h" +#include "hws_video.h" +#include "hws.h" + +#define MAX_INT_LOOPS 100 + +static bool hws_toggle_debug; +module_param_named(toggle_debug, hws_toggle_debug, bool, 0644); +MODULE_PARM_DESC(toggle_debug, + "Read toggle registers in IRQ handler for debug logging"); + +static int hws_arm_next(struct hws_pcie_dev *hws, u32 ch) +{ + struct hws_video *v = &hws->video[ch]; + unsigned long flags; + struct hwsvideo_buffer *buf; + + dev_dbg(&hws->pdev->dev, + "arm_next(ch=%u): stop=%d cap=%d queued=%d\n", + ch, READ_ONCE(v->stop_requested), READ_ONCE(v->cap_active), + !list_empty(&v->capture_queue)); + + if (READ_ONCE(hws->suspended)) { + dev_dbg(&hws->pdev->dev, "arm_next(ch=%u): suspended\n", ch); + return -EBUSY; + } + + if (READ_ONCE(v->stop_requested) || !READ_ONCE(v->cap_active)) { + dev_dbg(&hws->pdev->dev, + "arm_next(ch=%u): stop=%d cap=%d -> cancel\n", ch, + v->stop_requested, v->cap_active); + return -ECANCELED; + } + + spin_lock_irqsave(&v->irq_lock, flags); + if (list_empty(&v->capture_queue)) { + spin_unlock_irqrestore(&v->irq_lock, flags); + dev_dbg(&hws->pdev->dev, "arm_next(ch=%u): queue empty\n", ch); + return -EAGAIN; + } + + buf = list_first_entry(&v->capture_queue, struct hwsvideo_buffer, list); + list_del_init(&buf->list); /* keep buffer safe for later cleanup */ + if (v->queued_count) + v->queued_count--; + v->active = buf; + spin_unlock_irqrestore(&v->irq_lock, flags); + dev_dbg(&hws->pdev->dev, "arm_next(ch=%u): picked buffer %p\n", ch, + buf); + + /* Publish descriptor(s) before doorbell/MMIO kicks. */ + wmb(); + + /* Avoid MMIO during suspend */ + if (READ_ONCE(hws->suspended)) { + unsigned long f; + + dev_dbg(&hws->pdev->dev, + "arm_next(ch=%u): suspended after pick\n", ch); + spin_lock_irqsave(&v->irq_lock, f); + if (v->active) { + list_add(&buf->list, &v->capture_queue); + v->queued_count++; + v->active = NULL; + } + spin_unlock_irqrestore(&v->irq_lock, f); + return -EBUSY; + } + + /* Also program the DMA address register directly */ + { + dma_addr_t dma_addr = + vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + hws_program_dma_for_addr(hws, ch, dma_addr); + iowrite32(lower_32_bits(dma_addr), + hws->bar0_base + HWS_REG_DMA_ADDR(ch)); + } + + dev_dbg(&hws->pdev->dev, "arm_next(ch=%u): programmed buffer %p\n", ch, + buf); + spin_lock_irqsave(&v->irq_lock, flags); + hws_prime_next_locked(v); + spin_unlock_irqrestore(&v->irq_lock, flags); + return 0; +} + +static void hws_video_handle_vdone(struct hws_video *v) +{ + struct hws_pcie_dev *hws = v->parent; + unsigned int ch = v->channel_index; + struct hwsvideo_buffer *done; + unsigned long flags; + bool promoted = false; + + dev_dbg(&hws->pdev->dev, + "bh_video(ch=%u): stop=%d cap=%d active=%p\n", + ch, READ_ONCE(v->stop_requested), READ_ONCE(v->cap_active), + v->active); + + int ret; + + dev_dbg(&hws->pdev->dev, + "bh_video(ch=%u): entry stop=%d cap=%d\n", ch, + v->stop_requested, v->cap_active); + if (READ_ONCE(hws->suspended)) + return; + + if (READ_ONCE(v->stop_requested) || !READ_ONCE(v->cap_active)) + return; + + spin_lock_irqsave(&v->irq_lock, flags); + done = v->active; + if (done && v->next_prepared) { + v->active = v->next_prepared; + v->next_prepared = NULL; + promoted = true; + } + spin_unlock_irqrestore(&v->irq_lock, flags); + + /* 1) Complete the buffer the HW just finished (if any) */ + if (done) { + struct vb2_v4l2_buffer *vb2v = &done->vb; + size_t expected = v->pix.sizeimage; + size_t plane_size = vb2_plane_size(&vb2v->vb2_buf, 0); + + if (expected > plane_size) { + dev_warn_ratelimited(&hws->pdev->dev, + "bh_video(ch=%u): sizeimage %zu > plane %zu, dropping seq=%u\n", + ch, expected, plane_size, + (u32)atomic_read(&v->sequence_number) + 1); + vb2_buffer_done(&vb2v->vb2_buf, VB2_BUF_STATE_ERROR); + goto arm_next; + } + vb2_set_plane_payload(&vb2v->vb2_buf, 0, expected); + + dma_rmb(); /* device writes visible before userspace sees it */ + + vb2v->sequence = (u32)atomic_inc_return(&v->sequence_number); + vb2v->vb2_buf.timestamp = ktime_get_ns(); + dev_dbg(&hws->pdev->dev, + "bh_video(ch=%u): DONE buf=%p seq=%u half_seen=%d toggle=%u\n", + ch, done, vb2v->sequence, v->half_seen, + v->last_buf_half_toggle); + + if (!promoted) + v->active = NULL; /* channel no longer owns this buffer */ + vb2_buffer_done(&vb2v->vb2_buf, VB2_BUF_STATE_DONE); + } + + if (READ_ONCE(hws->suspended)) + return; + + if (promoted) { + dev_dbg(&hws->pdev->dev, + "bh_video(ch=%u): promoted pre-armed buffer active=%p\n", + ch, v->active); + spin_lock_irqsave(&v->irq_lock, flags); + hws_prime_next_locked(v); + spin_unlock_irqrestore(&v->irq_lock, flags); + return; + } + +arm_next: + /* 2) Immediately arm the next queued buffer (if present) */ + ret = hws_arm_next(hws, ch); + if (ret == -EAGAIN) { + dev_dbg(&hws->pdev->dev, + "bh_video(ch=%u): no queued buffer to arm\n", ch); + return; + } + dev_dbg(&hws->pdev->dev, + "bh_video(ch=%u): armed next buffer, active=%p\n", ch, + v->active); + /* On success the engine now points at v->active's DMA address */ +} + +irqreturn_t hws_irq_handler(int irq, void *info) +{ + struct hws_pcie_dev *pdx = info; + u32 int_state; + + dev_dbg(&pdx->pdev->dev, "irq: entry\n"); + if (pdx->bar0_base) { + dev_dbg(&pdx->pdev->dev, + "irq: INT_EN=0x%08x INT_STATUS=0x%08x\n", + readl(pdx->bar0_base + INT_EN_REG_BASE), + readl(pdx->bar0_base + HWS_REG_INT_STATUS)); + } + + /* Fast path: if suspended, quietly ack and exit */ + if (READ_ONCE(pdx->suspended)) { + int_state = readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS); + if (int_state) { + writel(int_state, pdx->bar0_base + HWS_REG_INT_STATUS); + (void)readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS); + } + return int_state ? IRQ_HANDLED : IRQ_NONE; + } + int_state = readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS); + if (!int_state || int_state == 0xFFFFFFFF) { + dev_dbg(&pdx->pdev->dev, + "irq: spurious or device-gone int_state=0x%08x\n", + int_state); + return IRQ_NONE; + } + dev_dbg(&pdx->pdev->dev, "irq: entry INT_STATUS=0x%08x\n", int_state); + + /* Loop until all pending bits are serviced (max 100 iterations) */ + for (u32 cnt = 0; int_state && cnt < MAX_INT_LOOPS; ++cnt) { + for (unsigned int ch = 0; ch < pdx->cur_max_video_ch; ++ch) { + u32 vbit = HWS_INT_VDONE_BIT(ch); + + if (!(int_state & vbit)) + continue; + + if (READ_ONCE(pdx->video[ch].cap_active) && + !READ_ONCE(pdx->video[ch].stop_requested)) { + if (hws_toggle_debug) { + u32 toggle = + readl_relaxed(pdx->bar0_base + + HWS_REG_VBUF_TOGGLE(ch)) & 0x01; + WRITE_ONCE(pdx->video[ch].last_buf_half_toggle, + toggle); + } + dma_rmb(); + WRITE_ONCE(pdx->video[ch].half_seen, true); + dev_dbg(&pdx->pdev->dev, + "irq: VDONE ch=%u toggle=%u handling inline (cap=%d)\n", + ch, + READ_ONCE(pdx->video[ch].last_buf_half_toggle), + READ_ONCE(pdx->video[ch].cap_active)); + hws_video_handle_vdone(&pdx->video[ch]); + } else { + dev_dbg(&pdx->pdev->dev, + "irq: VDONE ch=%u ignored (cap=%d stop=%d)\n", + ch, + READ_ONCE(pdx->video[ch].cap_active), + READ_ONCE(pdx->video[ch].stop_requested)); + } + + writel(vbit, pdx->bar0_base + HWS_REG_INT_STATUS); + (void)readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS); + } + + /* Re-read in case new interrupt bits popped while processing */ + int_state = readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS); + dev_dbg(&pdx->pdev->dev, + "irq: loop cnt=%u new INT_STATUS=0x%08x\n", cnt, + int_state); + if (cnt + 1 == MAX_INT_LOOPS) + dev_warn_ratelimited(&pdx->pdev->dev, + "IRQ storm? status=0x%08x\n", + int_state); + } + + return IRQ_HANDLED; +} diff --git a/drivers/media/pci/hws/hws_irq.h b/drivers/media/pci/hws/hws_irq.h new file mode 100644 index 000000000..a42867aa0 --- /dev/null +++ b/drivers/media/pci/hws/hws_irq.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef HWS_INTERRUPT_H +#define HWS_INTERRUPT_H + +#include +#include "hws.h" + +irqreturn_t hws_irq_handler(int irq, void *info); + +#endif /* HWS_INTERRUPT_H */ diff --git a/drivers/media/pci/hws/hws_pci.c b/drivers/media/pci/hws/hws_pci.c new file mode 100644 index 000000000..30bb7d344 --- /dev/null +++ b/drivers/media/pci/hws/hws_pci.c @@ -0,0 +1,865 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "hws.h" +#include "hws_reg.h" +#include "hws_video.h" +#include "hws_irq.h" +#include "hws_v4l2_ioctl.h" + +#define DRV_NAME "hws" +#define HWS_BUSY_POLL_DELAY_US 10 +#define HWS_BUSY_POLL_TIMEOUT_US 1000000 + +static unsigned long long hws_elapsed_us(u64 start_ns) +{ + return div_u64(ktime_get_mono_fast_ns() - start_ns, 1000); +} + +/* register layout inside HWS_REG_DEVICE_INFO */ +#define DEVINFO_VER GENMASK(7, 0) +#define DEVINFO_SUBVER GENMASK(15, 8) +#define DEVINFO_YV12 GENMASK(31, 28) +#define DEVINFO_HWKEY GENMASK(27, 24) +#define DEVINFO_PORTID GENMASK(25, 24) /* low 2 bits of HW-key */ + +#define MAKE_ENTRY(__vend, __chip, __subven, __subdev, __configptr) \ + { .vendor = (__vend), \ + .device = (__chip), \ + .subvendor = (__subven), \ + .subdevice = (__subdev), \ + .driver_data = (unsigned long)(__configptr) } + +/* + * PCI IDs for HWS family cards. + * + * The subsystem IDs are fixed at 0x8888:0x0007 for this family. Some boards + * enumerate with vendor ID 0x8888 or 0x1f33. Exact SKU names are not fully + * pinned down yet; update these comments when vendor documentation or INF + * strings are available. + */ +static const struct pci_device_id hws_pci_table[] = { + /* HWS family, SKU unknown. */ + MAKE_ENTRY(0x8888, 0x9534, 0x8888, 0x0007, NULL), + MAKE_ENTRY(0x1F33, 0x8534, 0x8888, 0x0007, NULL), + MAKE_ENTRY(0x1F33, 0x8554, 0x8888, 0x0007, NULL), + + /* HWS 2x2 HDMI family. */ + MAKE_ENTRY(0x8888, 0x8524, 0x8888, 0x0007, NULL), + /* HWS 2x2 SDI family. */ + MAKE_ENTRY(0x1F33, 0x6524, 0x8888, 0x0007, NULL), + + /* HWS X4 HDMI family. */ + MAKE_ENTRY(0x8888, 0x8504, 0x8888, 0x0007, NULL), + /* HWS X4 SDI family. */ + MAKE_ENTRY(0x8888, 0x6504, 0x8888, 0x0007, NULL), + + /* HWS family, SKU unknown. */ + MAKE_ENTRY(0x8888, 0x8532, 0x8888, 0x0007, NULL), + MAKE_ENTRY(0x8888, 0x8512, 0x8888, 0x0007, NULL), + MAKE_ENTRY(0x8888, 0x8501, 0x8888, 0x0007, NULL), + MAKE_ENTRY(0x1F33, 0x6502, 0x8888, 0x0007, NULL), + + /* HWS X4 HDMI family (alternate vendor ID). */ + MAKE_ENTRY(0x1F33, 0x8504, 0x8888, 0x0007, NULL), + /* HWS 2x2 HDMI family (alternate vendor ID). */ + MAKE_ENTRY(0x1F33, 0x8524, 0x8888, 0x0007, NULL), + + {} +}; + +static void enable_pcie_relaxed_ordering(struct pci_dev *dev) +{ + pcie_capability_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_RELAX_EN); +} + +static void hws_configure_hardware_capabilities(struct hws_pcie_dev *hdev) +{ + u16 id = hdev->device_id; + + /* select per-chip channel counts */ + switch (id) { + case 0x9534: + case 0x6524: + case 0x8524: + case 0x8504: + case 0x6504: + hdev->cur_max_video_ch = 4; + break; + case 0x8532: + hdev->cur_max_video_ch = 2; + break; + case 0x8512: + case 0x6502: + hdev->cur_max_video_ch = 2; + break; + case 0x8501: + hdev->cur_max_video_ch = 1; + break; + default: + hdev->cur_max_video_ch = 4; + break; + } + + /* universal buffer capacity */ + hdev->max_hw_video_buf_sz = MAX_MM_VIDEO_SIZE; + + /* decide hardware-version and program DMA max size if needed */ + if (hdev->device_ver > 121) { + if (id == 0x8501 && hdev->device_ver == 122) { + hdev->hw_ver = 0; + } else { + hdev->hw_ver = 1; + u32 dma_max = (u32)(MAX_VIDEO_SCALER_SIZE / 16); + + writel(dma_max, hdev->bar0_base + HWS_REG_DMA_MAX_SIZE); + /* readback to flush posted MMIO write */ + (void)readl(hdev->bar0_base + HWS_REG_DMA_MAX_SIZE); + } + } else { + hdev->hw_ver = 0; + } +} + +static void hws_stop_device(struct hws_pcie_dev *hws); + +static void hws_log_lifecycle_snapshot(struct hws_pcie_dev *hws, + const char *action, + const char *phase) +{ + struct device *dev; + u32 int_en, int_status, vcap, sys_status, dec_mode; + + if (!hws || !hws->pdev) + return; + + dev = &hws->pdev->dev; + if (!hws->bar0_base) { + dev_dbg(dev, + "lifecycle:%s:%s bar0-unmapped suspended=%d start_run=%d pci_lost=%d irq=%d\n", + action, phase, READ_ONCE(hws->suspended), hws->start_run, + hws->pci_lost, hws->irq); + return; + } + + int_en = readl(hws->bar0_base + INT_EN_REG_BASE); + int_status = readl(hws->bar0_base + HWS_REG_INT_STATUS); + vcap = readl(hws->bar0_base + HWS_REG_VCAP_ENABLE); + sys_status = readl(hws->bar0_base + HWS_REG_SYS_STATUS); + dec_mode = readl(hws->bar0_base + HWS_REG_DEC_MODE); + + dev_dbg(dev, + "lifecycle:%s:%s suspended=%d start_run=%d pci_lost=%d irq=%d INT_EN=0x%08x INT_STATUS=0x%08x VCAP=0x%08x SYS=0x%08x DEC=0x%08x\n", + action, phase, READ_ONCE(hws->suspended), hws->start_run, + hws->pci_lost, hws->irq, int_en, int_status, vcap, + sys_status, dec_mode); +} + +static int read_chip_id(struct hws_pcie_dev *hdev) +{ + u32 reg; + /* mirror PCI IDs for later switches */ + hdev->device_id = hdev->pdev->device; + hdev->vendor_id = hdev->pdev->vendor; + + reg = readl(hdev->bar0_base + HWS_REG_DEVICE_INFO); + + hdev->device_ver = FIELD_GET(DEVINFO_VER, reg); + hdev->sub_ver = FIELD_GET(DEVINFO_SUBVER, reg); + hdev->support_yv12 = FIELD_GET(DEVINFO_YV12, reg); + hdev->port_id = FIELD_GET(DEVINFO_PORTID, reg); + + hdev->max_hw_video_buf_sz = MAX_MM_VIDEO_SIZE; + hdev->max_channels = 4; + hdev->buf_allocated = false; + hdev->main_task = NULL; + hdev->start_run = false; + hdev->pci_lost = 0; + + writel(0x00, hdev->bar0_base + HWS_REG_DEC_MODE); + writel(0x10, hdev->bar0_base + HWS_REG_DEC_MODE); + + hws_configure_hardware_capabilities(hdev); + + dev_info(&hdev->pdev->dev, + "chip detected: ver=%u subver=%u port=%u yv12=%u\n", + hdev->device_ver, hdev->sub_ver, hdev->port_id, + hdev->support_yv12); + + return 0; +} + +static int main_ks_thread_handle(void *data) +{ + struct hws_pcie_dev *pdx = data; + + set_freezable(); + + while (!kthread_should_stop()) { + /* If we're suspending, don't touch hardware; just sleep/freeze. */ + if (READ_ONCE(pdx->suspended)) { + try_to_freeze(); + schedule_timeout_interruptible(msecs_to_jiffies(1000)); + continue; + } + + /* avoid MMIO when suspended (guarded above) */ + check_video_format(pdx); + + try_to_freeze(); /* cooperate with freezer each loop */ + + /* Sleep 1s or until signaled to wake/stop */ + schedule_timeout_interruptible(msecs_to_jiffies(1000)); + } + + dev_dbg(&pdx->pdev->dev, "%s: exiting\n", __func__); + return 0; +} + +static void hws_stop_kthread_action(void *data) +{ + struct hws_pcie_dev *hws = data; + struct task_struct *t; + u64 start_ns; + + if (!hws) + return; + + t = READ_ONCE(hws->main_task); + if (!IS_ERR_OR_NULL(t)) { + start_ns = ktime_get_mono_fast_ns(); + dev_dbg(&hws->pdev->dev, + "lifecycle:kthread-stop:begin task=%s[%d]\n", + t->comm, t->pid); + WRITE_ONCE(hws->main_task, NULL); + kthread_stop(t); + dev_dbg(&hws->pdev->dev, + "lifecycle:kthread-stop:done (%lluus)\n", + hws_elapsed_us(start_ns)); + } +} + +static int hws_alloc_seed_buffers(struct hws_pcie_dev *hws) +{ + int ch; + /* 64 KiB is plenty for a safe dummy; hardware needs 64-byte alignment. */ + const size_t need = ALIGN(64 * 1024, 64); + + for (ch = 0; ch < hws->cur_max_video_ch; ch++) { +#if defined(CONFIG_HAS_DMA) /* normal on PCIe platforms */ + void *cpu = dma_alloc_coherent(&hws->pdev->dev, need, + &hws->scratch_vid[ch].dma, + GFP_KERNEL); +#else + void *cpu = NULL; +#endif + if (!cpu) { + dev_warn(&hws->pdev->dev, + "scratch: dma_alloc_coherent failed ch=%d\n", ch); + /* not fatal: free earlier ones and continue without seeding */ + while (--ch >= 0) { + if (hws->scratch_vid[ch].cpu) + dma_free_coherent(&hws->pdev->dev, + hws->scratch_vid[ch].size, + hws->scratch_vid[ch].cpu, + hws->scratch_vid[ch].dma); + hws->scratch_vid[ch].cpu = NULL; + hws->scratch_vid[ch].size = 0; + } + return -ENOMEM; + } + hws->scratch_vid[ch].cpu = cpu; + hws->scratch_vid[ch].size = need; + } + return 0; +} + +static void hws_free_seed_buffers(struct hws_pcie_dev *hws) +{ + int ch; + + for (ch = 0; ch < hws->cur_max_video_ch; ch++) { + if (hws->scratch_vid[ch].cpu) { + dma_free_coherent(&hws->pdev->dev, + hws->scratch_vid[ch].size, + hws->scratch_vid[ch].cpu, + hws->scratch_vid[ch].dma); + hws->scratch_vid[ch].cpu = NULL; + hws->scratch_vid[ch].size = 0; + } + } +} + +static void hws_seed_channel(struct hws_pcie_dev *hws, int ch) +{ + dma_addr_t paddr = hws->scratch_vid[ch].dma; + u32 lo = lower_32_bits(paddr); + u32 hi = upper_32_bits(paddr); + u32 pci_addr = lo & PCI_E_BAR_ADD_LOWMASK; + + lo &= PCI_E_BAR_ADD_MASK; + + /* Program 64-bit BAR remap entry for this channel (table @ 0x208 + ch * 8) */ + writel_relaxed(hi, hws->bar0_base + + PCI_ADDR_TABLE_BASE + 0x208 + ch * 8); + writel_relaxed(lo, hws->bar0_base + + PCI_ADDR_TABLE_BASE + 0x208 + ch * 8 + + PCIE_BARADDROFSIZE); + + /* Program capture engine per-channel base/half */ + writel_relaxed((ch + 1) * PCIEBAR_AXI_BASE + pci_addr, + hws->bar0_base + CVBS_IN_BUF_BASE + + ch * PCIE_BARADDROFSIZE); + + /* Half size: use either the current format's half or half of scratch. */ + { + u32 half = hws->video[ch].pix.half_size ? + hws->video[ch].pix.half_size : + (u32)(hws->scratch_vid[ch].size / 2); + + writel_relaxed(half / 16, + hws->bar0_base + CVBS_IN_BUF_BASE2 + + ch * PCIE_BARADDROFSIZE); + } + + (void)readl(hws->bar0_base + HWS_REG_INT_STATUS); /* flush posted writes */ +} + +static void hws_seed_all_channels(struct hws_pcie_dev *hws) +{ + int ch; + + for (ch = 0; ch < hws->cur_max_video_ch; ch++) { + if (hws->scratch_vid[ch].cpu) + hws_seed_channel(hws, ch); + } +} + +static void hws_irq_mask_gate(struct hws_pcie_dev *hws) +{ + writel(0x00000000, hws->bar0_base + INT_EN_REG_BASE); + (void)readl(hws->bar0_base + INT_EN_REG_BASE); +} + +static void hws_irq_unmask_gate(struct hws_pcie_dev *hws) +{ + writel(HWS_INT_EN_MASK, hws->bar0_base + INT_EN_REG_BASE); + (void)readl(hws->bar0_base + INT_EN_REG_BASE); +} + +static void hws_irq_clear_pending(struct hws_pcie_dev *hws) +{ + u32 st = readl(hws->bar0_base + HWS_REG_INT_STATUS); + + if (st) { + writel(st, hws->bar0_base + HWS_REG_INT_STATUS); /* W1C */ + (void)readl(hws->bar0_base + HWS_REG_INT_STATUS); + } +} + +static void hws_block_hotpaths(struct hws_pcie_dev *hws) +{ + WRITE_ONCE(hws->suspended, true); + if (hws->irq >= 0) + disable_irq(hws->irq); + + if (!hws->bar0_base) + return; + + hws_irq_mask_gate(hws); + hws_irq_clear_pending(hws); +} + +static int hws_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id) +{ + struct hws_pcie_dev *hws; + int i, ret, irq; + unsigned long irqf = 0; + bool v4l2_registered = false; + + /* devres-backed device object */ + hws = devm_kzalloc(&pdev->dev, sizeof(*hws), GFP_KERNEL); + if (!hws) + return -ENOMEM; + + hws->pdev = pdev; + hws->irq = -1; + hws->suspended = false; + pci_set_drvdata(pdev, hws); + + /* 1) Enable device + bus mastering (managed) */ + ret = pcim_enable_device(pdev); + if (ret) + return dev_err_probe(&pdev->dev, ret, "pcim_enable_device\n"); + pci_set_master(pdev); + + /* 2) Map BAR0 (managed) */ + ret = pcim_iomap_regions(pdev, BIT(0), KBUILD_MODNAME); + if (ret) + return dev_err_probe(&pdev->dev, ret, "pcim_iomap_regions BAR0\n"); + hws->bar0_base = pcim_iomap_table(pdev)[0]; + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (ret) { + dev_warn(&pdev->dev, + "64-bit DMA mask unavailable, falling back to 32-bit (%d)\n", + ret); + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "No suitable DMA configuration\n"); + } else { + dev_dbg(&pdev->dev, "Using 64-bit DMA mask\n"); + } + + /* 3) Apply optional PCIe tuning. */ + enable_pcie_relaxed_ordering(pdev); +#ifdef CONFIG_ARCH_TI816X + pcie_set_readrq(pdev, 128); +#endif + + /* 4) Identify chip & capabilities */ + read_chip_id(hws); + dev_info(&pdev->dev, "Device VID=0x%04x DID=0x%04x\n", + pdev->vendor, pdev->device); + hws_init_video_sys(hws, false); + + /* 5) Init channels (video state, locks, vb2, ctrls) */ + for (i = 0; i < hws->max_channels; i++) { + ret = hws_video_init_channel(hws, i); + if (ret) { + dev_err(&pdev->dev, "video channel init failed (ch=%d)\n", i); + goto err_unwind_channels; + } + } + + /* 6) Allocate scratch DMA and seed BAR table + channel base/half (legacy SetDMAAddress) */ + ret = hws_alloc_seed_buffers(hws); + if (!ret) + hws_seed_all_channels(hws); + + /* 7) Start-run sequence. */ + hws_init_video_sys(hws, false); + + /* A) Force legacy INTx; legacy used request_irq(pdev->irq, ..., IRQF_SHARED) */ + pci_intx(pdev, 1); + irqf = IRQF_SHARED; + irq = pdev->irq; + hws->irq = irq; + dev_info(&pdev->dev, "IRQ mode: legacy INTx (shared), irq=%d\n", irq); + + /* B) Mask the device's global/bridge gate (INT_EN_REG_BASE) */ + hws_irq_mask_gate(hws); + + /* C) Clear any sticky pending interrupt status (W1C) before we arm the line */ + hws_irq_clear_pending(hws); + + /* D) Request the legacy shared interrupt line (no vectors/MSI/MSI-X) */ + ret = devm_request_irq(&pdev->dev, irq, hws_irq_handler, irqf, + dev_name(&pdev->dev), hws); + if (ret) { + dev_err(&pdev->dev, "request_irq(%d) failed: %d\n", irq, ret); + goto err_unwind_channels; + } + + /* E) Set the global interrupt enable bit in main control register */ + { + u32 ctl_reg = readl(hws->bar0_base + HWS_REG_CTL); + + ctl_reg |= HWS_CTL_IRQ_ENABLE_BIT; + writel(ctl_reg, hws->bar0_base + HWS_REG_CTL); + (void)readl(hws->bar0_base + HWS_REG_CTL); /* flush write */ + dev_info(&pdev->dev, "Global IRQ enable bit set in control register\n"); + } + + /* F) Open the global gate just like legacy did */ + hws_irq_unmask_gate(hws); + dev_info(&pdev->dev, "INT_EN_GATE readback=0x%08x\n", + readl(hws->bar0_base + INT_EN_REG_BASE)); + + /* 11) Register V4L2 */ + ret = hws_video_register(hws); + if (ret) { + dev_err(&pdev->dev, "video_register: %d\n", ret); + goto err_unwind_channels; + } + v4l2_registered = true; + + /* 12) Background monitor thread (managed) */ + hws->main_task = kthread_run(main_ks_thread_handle, hws, "hws-mon"); + if (IS_ERR(hws->main_task)) { + ret = PTR_ERR(hws->main_task); + hws->main_task = NULL; + dev_err(&pdev->dev, "kthread_run: %d\n", ret); + goto err_unregister_va; + } + ret = devm_add_action_or_reset(&pdev->dev, hws_stop_kthread_action, hws); + if (ret) { + dev_err(&pdev->dev, "devm_add_action kthread_stop: %d\n", ret); + goto err_unregister_va; /* reset already stopped the thread */ + } + + /* 13) Final: show the line is armed */ + dev_info(&pdev->dev, "irq handler installed on irq=%d\n", irq); + return 0; + +err_unregister_va: + hws_stop_device(hws); + hws_video_unregister(hws); + hws_free_seed_buffers(hws); + return ret; +err_unwind_channels: + hws_free_seed_buffers(hws); + if (!v4l2_registered) { + while (--i >= 0) + hws_video_cleanup_channel(hws, i); + } + return ret; +} + +static int hws_check_busy(struct hws_pcie_dev *pdx) +{ + void __iomem *reg = pdx->bar0_base + HWS_REG_SYS_STATUS; + u32 val; + int ret; + + /* poll until !(val & BUSY_BIT), sleeping HWS_BUSY_POLL_DELAY_US between reads */ + ret = readl_poll_timeout(reg, val, !(val & HWS_SYS_DMA_BUSY_BIT), + HWS_BUSY_POLL_DELAY_US, + HWS_BUSY_POLL_TIMEOUT_US); + if (ret) { + dev_err(&pdx->pdev->dev, + "SYS_STATUS busy bit never cleared (0x%08x)\n", val); + return -ETIMEDOUT; + } + + return 0; +} + +static void hws_stop_dsp(struct hws_pcie_dev *hws) +{ + u32 status; + + /* Read the decoder mode/status register */ + status = readl(hws->bar0_base + HWS_REG_DEC_MODE); + dev_dbg(&hws->pdev->dev, "%s: status=0x%08x\n", __func__, status); + + /* If the device looks unplugged/stuck, bail out */ + if (status == 0xFFFFFFFF) + return; + + /* Tell the DSP to stop */ + writel(0x10, hws->bar0_base + HWS_REG_DEC_MODE); + + if (hws_check_busy(hws)) + dev_warn(&hws->pdev->dev, "DSP busy timeout on stop\n"); + /* Disable video capture engine in the DSP */ + writel(0x0, hws->bar0_base + HWS_REG_VCAP_ENABLE); +} + +/* Publish stop so ISR/BH will not touch video buffers anymore. */ +static void hws_publish_stop_flags(struct hws_pcie_dev *hws) +{ + unsigned int i; + + for (i = 0; i < hws->cur_max_video_ch; ++i) { + struct hws_video *v = &hws->video[i]; + + WRITE_ONCE(v->cap_active, false); + WRITE_ONCE(v->stop_requested, true); + } + + smp_wmb(); /* make flags visible before we touch MMIO/queues */ +} + +/* Drain engines + ISR/BH after flags are published. */ +static void hws_drain_after_stop(struct hws_pcie_dev *hws) +{ + u32 ackmask = 0; + unsigned int i; + u64 start_ns = ktime_get_mono_fast_ns(); + + /* Mask device enables: no new DMA starts. */ + writel(0x0, hws->bar0_base + HWS_REG_VCAP_ENABLE); + (void)readl(hws->bar0_base + HWS_REG_INT_STATUS); /* flush */ + + /* Let any in-flight DMAs finish (best-effort). */ + (void)hws_check_busy(hws); + + /* Ack any latched VDONE. */ + for (i = 0; i < hws->cur_max_video_ch; ++i) + ackmask |= HWS_INT_VDONE_BIT(i); + if (ackmask) { + writel(ackmask, hws->bar0_base + HWS_REG_INT_STATUS); + (void)readl(hws->bar0_base + HWS_REG_INT_STATUS); + } + + /* Ensure no hard IRQ is still running. */ + if (hws->irq >= 0) + synchronize_irq(hws->irq); + + dev_dbg(&hws->pdev->dev, "lifecycle:drain-after-stop:done (%lluus)\n", + hws_elapsed_us(start_ns)); +} + +static void hws_stop_device(struct hws_pcie_dev *hws) +{ + u32 status = readl(hws->bar0_base + HWS_REG_SYS_STATUS); + u64 start_ns = ktime_get_mono_fast_ns(); + bool live = status != 0xFFFFFFFF; + + dev_dbg(&hws->pdev->dev, "%s: status=0x%08x\n", __func__, status); + if (!live) { + hws->pci_lost = true; + goto out; + } + hws_log_lifecycle_snapshot(hws, "stop-device", "begin"); + + /* Make ISR/BH a no-op, then drain engines/IRQ. */ + hws_publish_stop_flags(hws); + hws_drain_after_stop(hws); + + /* 1) Stop the on-board DSP */ + hws_stop_dsp(hws); + +out: + hws->start_run = false; + if (live) + hws_log_lifecycle_snapshot(hws, "stop-device", "end"); + else + dev_dbg(&hws->pdev->dev, "lifecycle:stop-device:device-lost\n"); + dev_dbg(&hws->pdev->dev, "lifecycle:stop-device:done (%lluus)\n", + hws_elapsed_us(start_ns)); + dev_dbg(&hws->pdev->dev, "%s: complete\n", __func__); +} + +static int hws_quiesce_for_transition(struct hws_pcie_dev *hws, + const char *action, + bool stop_thread) +{ + struct device *dev = &hws->pdev->dev; + u64 start_ns = ktime_get_mono_fast_ns(); + u64 step_ns; + int vret; + + hws_log_lifecycle_snapshot(hws, action, "begin"); + + step_ns = ktime_get_mono_fast_ns(); + hws_block_hotpaths(hws); + dev_dbg(dev, "lifecycle:%s:block-hotpaths (%lluus)\n", action, + hws_elapsed_us(step_ns)); + hws_log_lifecycle_snapshot(hws, action, "blocked"); + + if (stop_thread) { + step_ns = ktime_get_mono_fast_ns(); + hws_stop_kthread_action(hws); + dev_dbg(dev, "lifecycle:%s:stop-kthread (%lluus)\n", action, + hws_elapsed_us(step_ns)); + } + + step_ns = ktime_get_mono_fast_ns(); + vret = hws_video_quiesce(hws, action); + dev_dbg(dev, "lifecycle:%s:video-quiesce ret=%d (%lluus)\n", action, + vret, hws_elapsed_us(step_ns)); + if (vret) + dev_warn(dev, "lifecycle:%s video quiesce returned %d\n", + action, vret); + + step_ns = ktime_get_mono_fast_ns(); + hws_stop_device(hws); + dev_dbg(dev, "lifecycle:%s:stop-device (%lluus)\n", action, + hws_elapsed_us(step_ns)); + hws_log_lifecycle_snapshot(hws, action, "end"); + dev_dbg(dev, "lifecycle:%s:quiesce-done ret=%d (%lluus)\n", action, + vret, hws_elapsed_us(start_ns)); + + return vret; +} + +static void hws_remove(struct pci_dev *pdev) +{ + struct hws_pcie_dev *hws = pci_get_drvdata(pdev); + u64 start_ns; + + if (!hws) + return; + + start_ns = ktime_get_mono_fast_ns(); + dev_info(&pdev->dev, "lifecycle:remove begin\n"); + hws_log_lifecycle_snapshot(hws, "remove", "begin"); + + /* Stop the monitor thread before tearing down V4L2/vb2 objects. */ + hws_block_hotpaths(hws); + hws_stop_kthread_action(hws); + + /* Stop hardware and capture cleanly. */ + hws_stop_device(hws); + + /* Unregister V4L2 resources. */ + hws_video_unregister(hws); + + /* Release seeded DMA buffers */ + hws_free_seed_buffers(hws); + /* kthread is stopped by the devm action registered in probe. */ + hws_log_lifecycle_snapshot(hws, "remove", "end"); + dev_info(&pdev->dev, "lifecycle:remove done (%lluus)\n", + hws_elapsed_us(start_ns)); +} + +#ifdef CONFIG_PM_SLEEP +static int hws_pm_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct hws_pcie_dev *hws = pci_get_drvdata(pdev); + int vret; + u64 start_ns = ktime_get_mono_fast_ns(); + u64 step_ns; + + dev_info(dev, "lifecycle:pm_suspend begin\n"); + vret = hws_quiesce_for_transition(hws, "pm_suspend", false); + + step_ns = ktime_get_mono_fast_ns(); + pci_save_state(pdev); + pci_clear_master(pdev); + pci_disable_device(pdev); + pci_set_power_state(pdev, PCI_D3hot); + dev_dbg(dev, "lifecycle:pm_suspend:pci-d3hot (%lluus)\n", + hws_elapsed_us(step_ns)); + dev_info(dev, "lifecycle:pm_suspend done ret=%d (%lluus)\n", vret, + hws_elapsed_us(start_ns)); + + return 0; +} + +static int hws_pm_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct hws_pcie_dev *hws = pci_get_drvdata(pdev); + int ret; + u64 start_ns = ktime_get_mono_fast_ns(); + u64 step_ns; + + dev_info(dev, "lifecycle:pm_resume begin\n"); + + /* Back to D0 and re-enable the function */ + step_ns = ktime_get_mono_fast_ns(); + pci_set_power_state(pdev, PCI_D0); + + ret = pci_enable_device(pdev); + if (ret) { + dev_err(dev, "pci_enable_device: %d\n", ret); + return ret; + } + pci_restore_state(pdev); + pci_set_master(pdev); + dev_dbg(dev, "lifecycle:pm_resume:pci-enable (%lluus)\n", + hws_elapsed_us(step_ns)); + + /* Reapply any PCIe tuning lost across D3 */ + enable_pcie_relaxed_ordering(pdev); + + /* Reinitialize chip-side capabilities / registers */ + step_ns = ktime_get_mono_fast_ns(); + read_chip_id(hws); + /* Re-seed BAR remaps/DMA windows and restart the capture core */ + hws_seed_all_channels(hws); + hws_init_video_sys(hws, true); + hws_irq_clear_pending(hws); + dev_dbg(dev, "lifecycle:pm_resume:chip-reinit (%lluus)\n", + hws_elapsed_us(step_ns)); + + /* IRQs can be re-enabled now that MMIO is sane */ + step_ns = ktime_get_mono_fast_ns(); + if (hws->irq >= 0) + enable_irq(hws->irq); + + WRITE_ONCE(hws->suspended, false); + dev_dbg(dev, "lifecycle:pm_resume:irq-unsuspend (%lluus)\n", + hws_elapsed_us(step_ns)); + + /* vb2: nothing mandatory; userspace will STREAMON again when ready */ + step_ns = ktime_get_mono_fast_ns(); + hws_video_pm_resume(hws); + dev_dbg(dev, "lifecycle:pm_resume:video-resume (%lluus)\n", + hws_elapsed_us(step_ns)); + hws_log_lifecycle_snapshot(hws, "pm_resume", "end"); + dev_info(dev, "lifecycle:pm_resume done (%lluus)\n", + hws_elapsed_us(start_ns)); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(hws_pm_ops, hws_pm_suspend, hws_pm_resume); +# define HWS_PM_OPS (&hws_pm_ops) +#else +# define HWS_PM_OPS NULL +#endif + +static void hws_shutdown(struct pci_dev *pdev) +{ + struct hws_pcie_dev *hws = pci_get_drvdata(pdev); + int vret = 0; + u64 start_ns = ktime_get_mono_fast_ns(); + u64 step_ns; + + if (!hws) + return; + + dev_info(&pdev->dev, "lifecycle:pci_shutdown begin\n"); + vret = hws_quiesce_for_transition(hws, "pci_shutdown", true); + + step_ns = ktime_get_mono_fast_ns(); + pci_clear_master(pdev); + dev_dbg(&pdev->dev, "lifecycle:pci_shutdown:clear-master (%lluus)\n", + hws_elapsed_us(step_ns)); + dev_info(&pdev->dev, "lifecycle:pci_shutdown done ret=%d (%lluus)\n", + vret, hws_elapsed_us(start_ns)); +} + +static struct pci_driver hws_pci_driver = { + .name = KBUILD_MODNAME, + .id_table = hws_pci_table, + .probe = hws_probe, + .remove = hws_remove, + .shutdown = hws_shutdown, + .driver = { + .pm = HWS_PM_OPS, + }, +}; + +MODULE_DEVICE_TABLE(pci, hws_pci_table); + +static int __init pcie_hws_init(void) +{ + return pci_register_driver(&hws_pci_driver); +} + +static void __exit pcie_hws_exit(void) +{ + pci_unregister_driver(&hws_pci_driver); +} + +module_init(pcie_hws_init); +module_exit(pcie_hws_exit); + +MODULE_DESCRIPTION(DRV_NAME); +MODULE_AUTHOR("Ben Hoff "); +MODULE_AUTHOR("Sales "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("DMA_BUF"); diff --git a/drivers/media/pci/hws/hws_reg.h b/drivers/media/pci/hws/hws_reg.h new file mode 100644 index 000000000..e4fb4af44 --- /dev/null +++ b/drivers/media/pci/hws/hws_reg.h @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _HWS_PCIE_REG_H +#define _HWS_PCIE_REG_H + +#include +#include + +#define XDMA_CHANNEL_NUM_MAX (1) +#define MAX_NUM_ENGINES (XDMA_CHANNEL_NUM_MAX * 2) + +#define PCIE_BARADDROFSIZE 4u + +#define PCI_BUS_ACCESS_BASE 0x00000000U +#define INT_EN_REG_BASE (PCI_BUS_ACCESS_BASE + 0x0134U) +#define PCIEBR_EN_REG_BASE (PCI_BUS_ACCESS_BASE + 0x0148U) +#define PCIE_INT_DEC_REG_BASE (PCI_BUS_ACCESS_BASE + 0x0138U) + +#define HWS_INT_EN_MASK 0x0003FFFFU + +#define PCIEBAR_AXI_BASE 0x20000000U + +#define CTL_REG_ACC_BASE 0x0 +#define PCI_ADDR_TABLE_BASE CTL_REG_ACC_BASE + +#define CVBS_IN_BASE 0x00004000U +#define CVBS_IN_BUF_BASE (CVBS_IN_BASE + (16U * PCIE_BARADDROFSIZE)) +#define CVBS_IN_BUF_BASE2 (CVBS_IN_BASE + (50U * PCIE_BARADDROFSIZE)) + +/* 2 Mib */ +#define MAX_L_VIDEO_SIZE 0x200000U + +#define PCI_E_BAR_PAGE_SIZE 0x20000000 +#define PCI_E_BAR_ADD_MASK 0xE0000000 +#define PCI_E_BAR_ADD_LOWMASK 0x1FFFFFFF + +#define MAX_VID_CHANNELS 4 + +#define MAX_MM_VIDEO_SIZE SZ_4M + +#define MAX_VIDEO_HW_W 1920 +#define MAX_VIDEO_HW_H 1080 +#define MAX_VIDEO_SCALER_SIZE (1920U * 1080U * 2U) + +#define MIN_VAMP_BRIGHTNESS_UNITS 0 +#define MAX_VAMP_BRIGHTNESS_UNITS 0xff + +#define MIN_VAMP_CONTRAST_UNITS 0 +#define MAX_VAMP_CONTRAST_UNITS 0xff + +#define MIN_VAMP_SATURATION_UNITS 0 +#define MAX_VAMP_SATURATION_UNITS 0xff + +#define MIN_VAMP_HUE_UNITS 0 +#define MAX_VAMP_HUE_UNITS 0xff + +#define HWS_BRIGHTNESS_DEFAULT 0x80 +#define HWS_CONTRAST_DEFAULT 0x80 +#define HWS_SATURATION_DEFAULT 0x80 +#define HWS_HUE_DEFAULT 0x00 + +/* Core/global status. */ +#define HWS_REG_SYS_STATUS (CVBS_IN_BASE + 0 * PCIE_BARADDROFSIZE) +/* bit3: DMA busy, bit2: int, ... */ + +#define HWS_SYS_DMA_BUSY_BIT BIT(3) /* 0x08 = DMA busy flag */ + +#define HWS_REG_DEC_MODE (CVBS_IN_BASE + 0 * PCIE_BARADDROFSIZE) +/* Main control register */ +#define HWS_REG_CTL (CVBS_IN_BASE + 4 * PCIE_BARADDROFSIZE) +#define HWS_CTL_IRQ_ENABLE_BIT BIT(0) /* Global interrupt enable bit */ +/* Write 0x00 to fully reset decoder, + * set bit 31=1 to "start run", + * low byte=0x13 selects YUYV/BT.709/etc, + * in ReadChipId() we also write 0x00 and 0x10 here for chip-ID sequencing. + */ + +/* Per-channel done flags. */ +#define HWS_REG_INT_STATUS (CVBS_IN_BASE + 1 * PCIE_BARADDROFSIZE) +#define HWS_SYS_BUSY_BIT BIT(2) /* matches old 0x04 test */ + +/* Capture enable switches. */ +/* bit0-3: CH0-CH3 video enable */ +#define HWS_REG_VCAP_ENABLE (CVBS_IN_BASE + 2 * PCIE_BARADDROFSIZE) +/* bits0-3: signal present, bits8-11: interlace */ +#define HWS_REG_ACTIVE_STATUS (CVBS_IN_BASE + 5 * PCIE_BARADDROFSIZE) +/* bits0-3: HDCP detected */ +#define HWS_REG_HDCP_STATUS (CVBS_IN_BASE + 8 * PCIE_BARADDROFSIZE) +#define HWS_REG_DMA_MAX_SIZE (CVBS_IN_BASE + 9 * PCIE_BARADDROFSIZE) + +/* Buffer addresses (written once during init/reset). */ +/* Base of host-visible buffer. */ +#define HWS_REG_VBUF1_ADDR (CVBS_IN_BASE + 25 * PCIE_BARADDROFSIZE) +/* Per-channel DMA address. */ +#define HWS_REG_DMA_ADDR(ch) (CVBS_IN_BASE + (26 + (ch)) * PCIE_BARADDROFSIZE) + +/* Per-channel live buffer toggles (read-only). */ +#define HWS_REG_VBUF_TOGGLE(ch) (CVBS_IN_BASE + (32 + (ch)) * PCIE_BARADDROFSIZE) +/* + * Returns 0 or 1 = which half of the video ring the DMA engine is + * currently filling for channel *ch* (0-3). + */ + +/* Per-interrupt bits (video 0-3). */ +#define HWS_INT_VDONE_BIT(ch) BIT(ch) /* 0x01,0x02,0x04,0x08 */ + +#define HWS_REG_INT_ACK (CVBS_IN_BASE + 0x4000 + 1 * PCIE_BARADDROFSIZE) + +/* 16-bit W | 16-bit H. */ +#define HWS_REG_IN_RES(ch) (CVBS_IN_BASE + (90 + (ch) * 2) * PCIE_BARADDROFSIZE) +/* B|C|H|S packed bytes. */ +#define HWS_REG_BCHS(ch) (CVBS_IN_BASE + (91 + (ch) * 2) * PCIE_BARADDROFSIZE) + +/* Input fps. */ +#define HWS_REG_FRAME_RATE(ch) (CVBS_IN_BASE + (110 + (ch)) * PCIE_BARADDROFSIZE) +/* Programmed out W|H. */ +#define HWS_REG_OUT_RES(ch) (CVBS_IN_BASE + (120 + (ch)) * PCIE_BARADDROFSIZE) +/* Programmed out fps. */ +#define HWS_REG_OUT_FRAME_RATE(ch) (CVBS_IN_BASE + (130 + (ch)) * PCIE_BARADDROFSIZE) + +/* Device version/port ID/subversion register. */ +#define HWS_REG_DEVICE_INFO (CVBS_IN_BASE + 88 * PCIE_BARADDROFSIZE) +/* + * Reading this 32-bit word returns: + * bits 7:0 = "device version" + * bits 15:8 = "device sub-version" + * bits 23:24 = "HW key / port ID" etc. + * bits 31:28 = "support YV12" flags + */ + +/* Convenience aliases for individual channels. */ +#define HWS_REG_VBUF_TOGGLE_CH0 HWS_REG_VBUF_TOGGLE(0) +#define HWS_REG_VBUF_TOGGLE_CH1 HWS_REG_VBUF_TOGGLE(1) +#define HWS_REG_VBUF_TOGGLE_CH2 HWS_REG_VBUF_TOGGLE(2) +#define HWS_REG_VBUF_TOGGLE_CH3 HWS_REG_VBUF_TOGGLE(3) + +#endif /* _HWS_PCIE_REG_H */ diff --git a/drivers/media/pci/hws/hws_v4l2_ioctl.c b/drivers/media/pci/hws/hws_v4l2_ioctl.c new file mode 100644 index 000000000..0303e311e --- /dev/null +++ b/drivers/media/pci/hws/hws_v4l2_ioctl.c @@ -0,0 +1,919 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "hws.h" +#include "hws_reg.h" +#include "hws_video.h" +#include "hws_v4l2_ioctl.h" + +struct hws_dv_mode { + struct v4l2_dv_timings timings; + u32 refresh_hz; +}; + +static const struct hws_dv_mode * +hws_find_dv_by_wh(u32 w, u32 h, bool interlaced); +static const struct hws_dv_mode * +hws_find_dv_by_wh_fps(u32 w, u32 h, bool interlaced, u32 fps); +static u32 hws_get_live_fps(struct hws_video *vid); +static u32 hws_input_status(struct hws_video *vid); +static int hws_fill_dv_timings(u32 w, u32 h, bool interlace, u32 fps, + struct v4l2_dv_timings *timings); + +static const struct hws_dv_mode hws_dv_modes[] = { + { + { + .type = V4L2_DV_BT_656_1120, + .bt = { + .width = 1920, + .height = 1080, + .hfrontporch = 88, + .hsync = 44, + .hbackporch = 148, + .vfrontporch = 4, + .vsync = 5, + .vbackporch = 36, + .pixelclock = 148500000, + .polarities = V4L2_DV_VSYNC_POS_POL | + V4L2_DV_HSYNC_POS_POL, + .interlaced = 0, + }, + }, + 60, + }, + { + { + .type = V4L2_DV_BT_656_1120, + .bt = { + .width = 1920, + .height = 1080, + .hfrontporch = 88, + .hsync = 44, + .hbackporch = 148, + .vfrontporch = 4, + .vsync = 5, + .vbackporch = 36, + .pixelclock = 74250000, + .polarities = V4L2_DV_VSYNC_POS_POL | + V4L2_DV_HSYNC_POS_POL, + .interlaced = 0, + }, + }, + 30, + }, + { + { + .type = V4L2_DV_BT_656_1120, + .bt = { + .width = 1280, + .height = 720, + .hfrontporch = 110, + .hsync = 40, + .hbackporch = 220, + .vfrontporch = 5, + .vsync = 5, + .vbackporch = 20, + .pixelclock = 74250000, + .polarities = V4L2_DV_VSYNC_POS_POL | + V4L2_DV_HSYNC_POS_POL, + .interlaced = 0, + }, + }, + 60, + }, + { + { + .type = V4L2_DV_BT_656_1120, + .bt = { + .width = 720, + .height = 480, + .interlaced = 0, + }, + }, + 60, + }, + { + { + .type = V4L2_DV_BT_656_1120, + .bt = { + .width = 720, + .height = 576, + .interlaced = 0, + }, + }, + 50, + }, + { + { + .type = V4L2_DV_BT_656_1120, + .bt = { + .width = 800, + .height = 600, + .interlaced = 0, + }, + }, + 60, + }, + { + { + .type = V4L2_DV_BT_656_1120, + .bt = { + .width = 640, + .height = 480, + .interlaced = 0, + }, + }, + 60, + }, + { + { + .type = V4L2_DV_BT_656_1120, + .bt = { + .width = 1024, + .height = 768, + .interlaced = 0, + }, + }, + 60, + }, + { + { + .type = V4L2_DV_BT_656_1120, + .bt = { + .width = 1280, + .height = 768, + .interlaced = 0, + }, + }, + 60, + }, + { + { + .type = V4L2_DV_BT_656_1120, + .bt = { + .width = 1280, + .height = 800, + .interlaced = 0, + }, + }, + 60, + }, + { + { + .type = V4L2_DV_BT_656_1120, + .bt = { + .width = 1280, + .height = 1024, + .interlaced = 0, + }, + }, + 60, + }, + { + { + .type = V4L2_DV_BT_656_1120, + .bt = { + .width = 1360, + .height = 768, + .interlaced = 0, + }, + }, + 60, + }, + { + { + .type = V4L2_DV_BT_656_1120, + .bt = { + .width = 1440, + .height = 900, + .interlaced = 0, + }, + }, + 60, + }, + { + { + .type = V4L2_DV_BT_656_1120, + .bt = { + .width = 1680, + .height = 1050, + .interlaced = 0, + }, + }, + 60, + }, + /* Portrait */ + { + { + .type = V4L2_DV_BT_656_1120, + .bt = { + .width = 1080, + .height = 1920, + .interlaced = 0, + }, + }, + 60, + }, +}; + +static const size_t hws_dv_modes_cnt = ARRAY_SIZE(hws_dv_modes); + +/* YUYV: 16 bpp; align to 64 as you did elsewhere */ +static inline u32 hws_calc_bpl_yuyv(u32 w) { return ALIGN(w * 2, 64); } +static inline u32 hws_calc_size_yuyv(u32 w, u32 h) { return hws_calc_bpl_yuyv(w) * h; } +static inline u32 hws_calc_half_size(u32 sizeimage) +{ + return sizeimage / 2; +} + +static inline void hws_hw_write_bchs(struct hws_pcie_dev *hws, unsigned int ch, + u8 br, u8 co, u8 hu, u8 sa) +{ + u32 packed = (sa << 24) | (hu << 16) | (co << 8) | br; + + if (!hws || !hws->bar0_base || ch >= hws->max_channels) + return; + writel_relaxed(packed, hws->bar0_base + HWS_REG_BCHS(ch)); + (void)readl(hws->bar0_base + HWS_REG_BCHS(ch)); /* post write */ +} + +/* Helper: find a supported DV mode by W/H + interlace flag */ +static const struct hws_dv_mode * +hws_match_supported_dv(const struct v4l2_dv_timings *req) +{ + const struct v4l2_bt_timings *bt; + u32 fps; + + if (!req || req->type != V4L2_DV_BT_656_1120) + return NULL; + + bt = &req->bt; + fps = 0; + if (bt->pixelclock) { + u32 total_w = bt->width + bt->hfrontporch + bt->hsync + + bt->hbackporch; + u32 total_h = bt->height + bt->vfrontporch + bt->vsync + + bt->vbackporch; + + if (total_w && total_h) + fps = DIV_ROUND_CLOSEST_ULL((u64)bt->pixelclock, + (u64)total_w * total_h); + } + if (fps) { + const struct hws_dv_mode *exact = + hws_find_dv_by_wh_fps(bt->width, bt->height, + !!bt->interlaced, fps); + if (exact) + return exact; + } + return hws_find_dv_by_wh(bt->width, bt->height, !!bt->interlaced); +} + +/* Helper: find a supported DV mode by W/H + interlace flag */ +static const struct hws_dv_mode * +hws_find_dv_by_wh(u32 w, u32 h, bool interlaced) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(hws_dv_modes); i++) { + const struct hws_dv_mode *t = &hws_dv_modes[i]; + const struct v4l2_bt_timings *bt = &t->timings.bt; + + if (t->timings.type != V4L2_DV_BT_656_1120) + continue; + + if (bt->width == w && bt->height == h && + !!bt->interlaced == interlaced) + return t; + } + return NULL; +} + +static const struct hws_dv_mode * +hws_find_dv_by_wh_fps(u32 w, u32 h, bool interlaced, u32 fps) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(hws_dv_modes); i++) { + const struct hws_dv_mode *t = &hws_dv_modes[i]; + const struct v4l2_bt_timings *bt = &t->timings.bt; + + if (t->timings.type != V4L2_DV_BT_656_1120) + continue; + + if (bt->width == w && bt->height == h && + !!bt->interlaced == interlaced && + t->refresh_hz == fps) + return t; + } + return NULL; +} + +static bool hws_get_live_dv_geometry(struct hws_video *vid, + u32 *w, u32 *h, bool *interlaced) +{ + struct hws_pcie_dev *pdx; + u32 reg; + + if (!vid) + return false; + + pdx = vid->parent; + if (!pdx || !pdx->bar0_base) + return false; + + reg = readl(pdx->bar0_base + HWS_REG_IN_RES(vid->channel_index)); + if (!reg || reg == 0xFFFFFFFF) + return false; + + if (w) + *w = reg & 0xFFFF; + if (h) + *h = (reg >> 16) & 0xFFFF; + if (interlaced) { + reg = readl(pdx->bar0_base + HWS_REG_ACTIVE_STATUS); + *interlaced = !!(reg & BIT(8 + vid->channel_index)); + } + return true; +} + +static u32 hws_get_live_fps(struct hws_video *vid) +{ + struct hws_pcie_dev *pdx; + u32 fps; + + if (!vid) + return 0; + + pdx = vid->parent; + if (!pdx || !pdx->bar0_base) + return 0; + + fps = readl(pdx->bar0_base + HWS_REG_FRAME_RATE(vid->channel_index)); + if (!fps || fps == 0xFFFFFFFF || fps > 240) + return 0; + + return fps; +} + +static u32 hws_pick_fps_from_mode(u32 w, u32 h, bool interlaced) +{ + const struct hws_dv_mode *m = hws_find_dv_by_wh(w, h, interlaced); + + if (m && m->refresh_hz) + return m->refresh_hz; + /* Fallback to a sane default */ + return 60; +} + +static int hws_fill_dv_timings(u32 w, u32 h, bool interlace, u32 fps, + struct v4l2_dv_timings *timings) +{ + const struct hws_dv_mode *m; + + m = fps ? hws_find_dv_by_wh_fps(w, h, interlace, fps) : NULL; + if (!m) + m = hws_find_dv_by_wh(w, h, interlace); + if (!m) + return -ENOLINK; + + *timings = m->timings; + return 0; +} + +static u32 hws_input_status(struct hws_video *vid) +{ + struct hws_pcie_dev *pdx; + u32 reg; + + if (!vid) + return V4L2_IN_ST_NO_SIGNAL; + + pdx = vid->parent; + if (!pdx || !pdx->bar0_base) + return V4L2_IN_ST_NO_SIGNAL; + + reg = readl(pdx->bar0_base + HWS_REG_ACTIVE_STATUS); + if (reg == 0xffffffff) + return V4L2_IN_ST_NO_SIGNAL; + + return (reg & BIT(vid->channel_index)) ? 0 : V4L2_IN_ST_NO_SIGNAL; +} + +/* Query the *current detected* DV timings on the input. + * If you have a real hardware detector, call it here; otherwise we + * derive from the cached pix state and map to the closest supported DV mode. + */ +int hws_vidioc_query_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct hws_video *vid = video_drvdata(file); + u32 w, h; + u32 fps; + bool interlace; + + if (!timings) + return -EINVAL; + + w = vid->pix.width; + h = vid->pix.height; + interlace = vid->pix.interlaced; + hws_get_live_dv_geometry(vid, &w, &h, &interlace); + fps = hws_get_live_fps(vid); + if (!fps) + fps = vid->current_fps ? vid->current_fps : + hws_pick_fps_from_mode(w, h, interlace); + + return hws_fill_dv_timings(w, h, interlace, fps, timings); +} + +/* Enumerate the Nth supported DV timings from our static table. */ +int hws_vidioc_enum_dv_timings(struct file *file, void *fh, + struct v4l2_enum_dv_timings *edv) +{ + struct hws_video *vid = video_drvdata(file); + const struct hws_dv_mode *m; + u32 w, h; + u32 fps; + bool interlace; + + if (!edv) + return -EINVAL; + + if (edv->pad) + return -EINVAL; + + w = 0; + h = 0; + interlace = false; + if (hws_get_live_dv_geometry(vid, &w, &h, &interlace)) { + fps = hws_get_live_fps(vid); + if (!fps) + fps = vid->current_fps ? vid->current_fps : + hws_pick_fps_from_mode(w, h, interlace); + m = fps ? hws_find_dv_by_wh_fps(w, h, interlace, fps) : NULL; + if (!m) + m = hws_find_dv_by_wh(w, h, interlace); + if (m) { + if (edv->index) + return -EINVAL; + edv->timings = m->timings; + return 0; + } + } + + if (edv->index >= hws_dv_modes_cnt) + return -EINVAL; + + edv->timings = hws_dv_modes[edv->index].timings; + return 0; +} + +/* Get the *currently configured* DV timings. */ +int hws_vidioc_g_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct hws_video *vid = video_drvdata(file); + u32 w, h; + u32 fps; + bool interlace; + + if (!timings) + return -EINVAL; + + w = vid->pix.width; + h = vid->pix.height; + interlace = vid->pix.interlaced; + if (hws_get_live_dv_geometry(vid, &w, &h, &interlace)) { + fps = hws_get_live_fps(vid); + if (!fps) + fps = vid->current_fps ? vid->current_fps : + hws_pick_fps_from_mode(w, h, interlace); + return hws_fill_dv_timings(w, h, interlace, fps, timings); + } + + *timings = vid->cur_dv_timings; + return 0; +} + +static inline void hws_set_colorimetry_state(struct hws_pix_state *p) +{ + bool sd = p->height <= 576; + + p->colorspace = sd ? V4L2_COLORSPACE_SMPTE170M : V4L2_COLORSPACE_REC709; + p->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + p->quantization = V4L2_QUANTIZATION_FULL_RANGE; + p->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +/* Set DV timings: must match one of our supported modes. + * If buffers are queued and this implies a size change, we reject with -EBUSY. + * Otherwise we update pix state and (optionally) reprogram the HW. + */ +int hws_vidioc_s_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct hws_video *vid = video_drvdata(file); + const struct hws_dv_mode *m; + const struct v4l2_bt_timings *bt; + u32 new_w, new_h; + bool interlaced; + int ret = 0; + unsigned long was_busy; + u32 live_w, live_h; + u32 live_fps; + bool live_interlaced; + bool live_present; + + if (!timings) + return -EINVAL; + + m = hws_match_supported_dv(timings); + if (!m) + return -EINVAL; + + bt = &m->timings.bt; + if (bt->interlaced) + return -EINVAL; /* only progressive modes are advertised */ + new_w = bt->width; + new_h = bt->height; + interlaced = false; + + lockdep_assert_held(&vid->state_lock); + live_present = hws_get_live_dv_geometry(vid, &live_w, &live_h, + &live_interlaced); + + /* If vb2 has active buffers and size would change, reject. */ + was_busy = vb2_is_busy(&vid->buffer_queue); + if (was_busy && + (new_w != vid->pix.width || new_h != vid->pix.height || + interlaced != vid->pix.interlaced)) { + ret = -EBUSY; + return ret; + } + + /* When a live input signal is present, the receiver owns the timing. + * Allow setting the already-active timings so v4l2-compliance can + * round-trip them, but reject attempts to retime the live source. + */ + if (live_present) { + live_fps = hws_get_live_fps(vid); + if (!live_fps) + live_fps = vid->current_fps ? vid->current_fps : + hws_pick_fps_from_mode(live_w, live_h, + live_interlaced); + if (live_w == new_w && live_h == new_h && + live_interlaced == interlaced && + m->refresh_hz == live_fps) + return 0; + return -EBUSY; + } + + /* Update software pixel state (and recalc sizes) */ + vid->pix.width = new_w; + vid->pix.height = new_h; + vid->pix.field = interlaced ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_NONE; + vid->pix.interlaced = interlaced; + vid->pix.fourcc = V4L2_PIX_FMT_YUYV; + + hws_set_colorimetry_state(&vid->pix); + + /* Recompute stride, sizeimage, and half_size. */ + vid->pix.bytesperline = hws_calc_bpl_yuyv(new_w); + vid->pix.sizeimage = hws_calc_size_yuyv(new_w, new_h); + vid->pix.half_size = hws_calc_half_size(vid->pix.sizeimage); + vid->cur_dv_timings = m->timings; + vid->current_fps = m->refresh_hz; + return ret; +} + +/* Report DV timings capability: advertise BT.656/1120 with + * the min/max WxH derived from our table and basic progressive support. + */ +int hws_vidioc_dv_timings_cap(struct file *file, void *fh, + struct v4l2_dv_timings_cap *cap) +{ + u32 min_w = ~0U, min_h = ~0U; + u32 max_w = 0, max_h = 0; + size_t i, n = 0; + + if (!cap) + return -EINVAL; + + memset(cap, 0, sizeof(*cap)); + cap->type = V4L2_DV_BT_656_1120; + + for (i = 0; i < ARRAY_SIZE(hws_dv_modes); i++) { + const struct v4l2_bt_timings *bt = &hws_dv_modes[i].timings.bt; + + if (hws_dv_modes[i].timings.type != V4L2_DV_BT_656_1120) + continue; + n++; + + if (bt->width < min_w) + min_w = bt->width; + if (bt->height < min_h) + min_h = bt->height; + if (bt->width > max_w) + max_w = bt->width; + if (bt->height > max_h) + max_h = bt->height; + } + + /* If the table was empty, fail gracefully. */ + if (!n || min_w == U32_MAX) + return -ENODATA; + + cap->bt.min_width = min_w; + cap->bt.max_width = max_w; + cap->bt.min_height = min_h; + cap->bt.max_height = max_h; + + /* We support both CEA-861- and VESA-style modes in the list. */ + cap->bt.standards = + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | V4L2_DV_BT_STD_CVT; + + /* Only progressive modes are advertised. */ + cap->bt.capabilities = V4L2_DV_BT_CAP_PROGRESSIVE; + + /* Leave pixelclock/porch limits unconstrained (0) for now. */ + return 0; +} + +static int hws_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct hws_video *vid = + container_of(ctrl->handler, struct hws_video, control_handler); + struct hws_pcie_dev *pdx = vid->parent; + bool program = false; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + vid->current_brightness = ctrl->val; + program = true; + break; + case V4L2_CID_CONTRAST: + vid->current_contrast = ctrl->val; + program = true; + break; + case V4L2_CID_SATURATION: + vid->current_saturation = ctrl->val; + program = true; + break; + case V4L2_CID_HUE: + vid->current_hue = ctrl->val; + program = true; + break; + default: + return -EINVAL; + } + + if (program) { + hws_hw_write_bchs(pdx, vid->channel_index, + (u8)vid->current_brightness, + (u8)vid->current_contrast, + (u8)vid->current_hue, + (u8)vid->current_saturation); + } + return 0; +} + +const struct v4l2_ctrl_ops hws_ctrl_ops = { + .s_ctrl = hws_s_ctrl, +}; + +int hws_vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap) +{ + struct hws_video *vid = video_drvdata(file); + int vi_index = vid->channel_index + 1; /* keep it simple */ + + strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + snprintf(cap->card, sizeof(cap->card), + "AVMatrix HWS Capture %d", vi_index); + return 0; +} + +int hws_vidioc_enum_fmt_vid_cap(struct file *file, void *priv_fh, struct v4l2_fmtdesc *f) +{ + if (f->index != 0) + return -EINVAL; /* only one format */ + + f->pixelformat = V4L2_PIX_FMT_YUYV; + return 0; +} + +int hws_vidioc_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct hws_video *vid = video_drvdata(file); + + fmt->fmt.pix.width = vid->pix.width; + fmt->fmt.pix.height = vid->pix.height; + fmt->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; + fmt->fmt.pix.field = vid->pix.field; + fmt->fmt.pix.bytesperline = vid->pix.bytesperline; + fmt->fmt.pix.sizeimage = vid->pix.sizeimage; + fmt->fmt.pix.colorspace = vid->pix.colorspace; + fmt->fmt.pix.ycbcr_enc = vid->pix.ycbcr_enc; + fmt->fmt.pix.quantization = vid->pix.quantization; + fmt->fmt.pix.xfer_func = vid->pix.xfer_func; + return 0; +} + +static inline void hws_set_colorimetry_fmt(struct v4l2_pix_format *p) +{ + bool sd = p->height <= 576; + + p->colorspace = sd ? V4L2_COLORSPACE_SMPTE170M : V4L2_COLORSPACE_REC709; + p->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + p->quantization = V4L2_QUANTIZATION_FULL_RANGE; + p->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +int hws_vidioc_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f) +{ + struct hws_video *vid = file ? video_drvdata(file) : NULL; + struct hws_pcie_dev *pdev = vid ? vid->parent : NULL; + struct v4l2_pix_format *pix = &f->fmt.pix; + u32 req_w = pix->width, req_h = pix->height; + u32 w, h, min_bpl, bpl; + size_t size; /* wider than u32 for overflow check */ + size_t max_frame = pdev ? pdev->max_hw_video_buf_sz : MAX_MM_VIDEO_SIZE; + + /* Only YUYV */ + pix->pixelformat = V4L2_PIX_FMT_YUYV; + + /* Defaults then clamp */ + w = (req_w ? req_w : 640); + h = (req_h ? req_h : 480); + if (w > MAX_VIDEO_HW_W) + w = MAX_VIDEO_HW_W; + if (h > MAX_VIDEO_HW_H) + h = MAX_VIDEO_HW_H; + if (!w) + w = 640; /* hard fallback in case macros are odd */ + if (!h) + h = 480; + + /* Field policy */ + pix->field = V4L2_FIELD_NONE; + + /* Stride policy for packed 16bpp, 64B align */ + min_bpl = ALIGN(w * 2, 64); + + /* Bound requested bpl to something sane, then align */ + bpl = pix->bytesperline; + if (bpl < min_bpl) { + bpl = min_bpl; + } else { + /* Cap at 16x width to avoid silly values that overflow sizeimage */ + u32 max_bpl = ALIGN(w * 2 * 16, 64); + + if (bpl > max_bpl) + bpl = max_bpl; + bpl = ALIGN(bpl, 64); + } + if (h && max_frame) { + size_t max_bpl_hw = max_frame / h; + + if (max_bpl_hw < min_bpl) + return -ERANGE; + max_bpl_hw = rounddown(max_bpl_hw, 64); + if (!max_bpl_hw) + return -ERANGE; + if (bpl > max_bpl_hw) { + if (pdev) + dev_dbg(&pdev->pdev->dev, + "try_fmt: clamp bpl %u -> %zu due to hw buf cap %zu\n", + bpl, max_bpl_hw, max_frame); + bpl = (u32)max_bpl_hw; + } + } + size = (size_t)bpl * (size_t)h; + if (size > max_frame) + return -ERANGE; + + pix->width = w; + pix->height = h; + pix->bytesperline = bpl; + pix->sizeimage = (u32)size; /* logical size, not page-aligned */ + + hws_set_colorimetry_fmt(pix); + if (pdev) + dev_dbg(&pdev->pdev->dev, + "try_fmt: w=%u h=%u bpl=%u size=%u field=%u\n", + pix->width, pix->height, pix->bytesperline, + pix->sizeimage, pix->field); + return 0; +} + +int hws_vidioc_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) +{ + struct hws_video *vid = video_drvdata(file); + int ret; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + /* Normalize the request */ + ret = hws_vidioc_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + /* Don't allow buffer layout changes while buffers are queued. */ + if (vb2_is_busy(&vid->buffer_queue)) { + if (f->fmt.pix.width != vid->pix.width || + f->fmt.pix.height != vid->pix.height || + f->fmt.pix.bytesperline != vid->pix.bytesperline) + return -EBUSY; + } + + /* Apply to driver state */ + vid->pix.width = f->fmt.pix.width; + vid->pix.height = f->fmt.pix.height; + vid->pix.fourcc = V4L2_PIX_FMT_YUYV; + vid->pix.field = f->fmt.pix.field; + vid->pix.colorspace = f->fmt.pix.colorspace; + vid->pix.ycbcr_enc = f->fmt.pix.ycbcr_enc; + vid->pix.quantization = f->fmt.pix.quantization; + vid->pix.xfer_func = f->fmt.pix.xfer_func; + + /* Update negotiated buffer sizes. */ + vid->pix.bytesperline = f->fmt.pix.bytesperline; /* aligned */ + vid->pix.sizeimage = f->fmt.pix.sizeimage; /* logical */ + vid->pix.half_size = hws_calc_half_size(vid->pix.sizeimage); + vid->pix.interlaced = false; + /* S_FMT negotiates buffer layout only. Keep detector-owned DV timing + * state unchanged so a harmless restart cannot clobber the live FPS. + */ + /* Or: + * hws_calc_sizeimage(vid, vid->pix.width, vid->pix.height, false); + */ + + dev_dbg(&vid->parent->pdev->dev, + "s_fmt: w=%u h=%u bpl=%u size=%u\n", + vid->pix.width, vid->pix.height, vid->pix.bytesperline, + vid->pix.sizeimage); + + return 0; +} + +int hws_vidioc_g_parm(struct file *file, void *fh, struct v4l2_streamparm *param) +{ + struct hws_video *vid = video_drvdata(file); + u32 fps; + + if (param->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + fps = hws_get_live_fps(vid); + if (!fps) + fps = vid->current_fps ? vid->current_fps : 60; + + /* HDMI receivers report the detected frame period, they don't set it. */ + param->parm.capture.capability = 0; + param->parm.capture.capturemode = 0; + param->parm.capture.timeperframe.numerator = 1; + param->parm.capture.timeperframe.denominator = fps; + param->parm.capture.extendedmode = 0; + param->parm.capture.readbuffers = 0; + + return 0; +} + +int hws_vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + struct hws_video *vid = video_drvdata(file); + + if (input->index) + return -EINVAL; + input->type = V4L2_INPUT_TYPE_CAMERA; + strscpy(input->name, KBUILD_MODNAME, sizeof(input->name)); + input->capabilities = V4L2_IN_CAP_DV_TIMINGS; + input->status = hws_input_status(vid); + + return 0; +} + +int hws_vidioc_g_input(struct file *file, void *priv, unsigned int *index) +{ + *index = 0; + return 0; +} + +int hws_vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + return i ? -EINVAL : 0; +} diff --git a/drivers/media/pci/hws/hws_v4l2_ioctl.h b/drivers/media/pci/hws/hws_v4l2_ioctl.h new file mode 100644 index 000000000..9a81e940e --- /dev/null +++ b/drivers/media/pci/hws/hws_v4l2_ioctl.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef HWS_V4L2_IOCTL_H +#define HWS_V4L2_IOCTL_H + +#include +#include + +extern const struct v4l2_ctrl_ops hws_ctrl_ops; + +int hws_vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap); +int hws_vidioc_enum_fmt_vid_cap(struct file *file, void *priv_fh, struct v4l2_fmtdesc *f); +int hws_vidioc_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt); +int hws_vidioc_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f); +int hws_vidioc_g_std(struct file *file, void *priv, v4l2_std_id *tvnorms); +int hws_vidioc_s_std(struct file *file, void *priv, v4l2_std_id tvnorms); +int hws_vidioc_g_parm(struct file *file, void *fh, struct v4l2_streamparm *setfps); +int hws_vidioc_enum_input(struct file *file, void *priv, struct v4l2_input *i); +int hws_vidioc_g_input(struct file *file, void *priv, unsigned int *i); +int hws_vidioc_s_input(struct file *file, void *priv, unsigned int i); +int hws_vidioc_dv_timings_cap(struct file *file, void *fh, + struct v4l2_dv_timings_cap *cap); +int hws_vidioc_s_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings); +int hws_vidioc_g_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings); +int hws_vidioc_enum_dv_timings(struct file *file, void *fh, + struct v4l2_enum_dv_timings *edv); +int hws_vidioc_query_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings); +int hws_vidioc_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f); + +#endif diff --git a/drivers/media/pci/hws/hws_video.c b/drivers/media/pci/hws/hws_video.c new file mode 100644 index 000000000..18e4bc690 --- /dev/null +++ b/drivers/media/pci/hws/hws_video.c @@ -0,0 +1,1490 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "hws.h" +#include "hws_reg.h" +#include "hws_video.h" +#include "hws_irq.h" +#include "hws_v4l2_ioctl.h" + +#define HWS_REMAP_SLOT_OFF(ch) (0x208 + (ch) * 8) /* one 64-bit slot per ch */ +#define HWS_BUF_BASE_OFF(ch) (CVBS_IN_BUF_BASE + (ch) * PCIE_BARADDROFSIZE) +#define HWS_HALF_SZ_OFF(ch) (CVBS_IN_BUF_BASE2 + (ch) * PCIE_BARADDROFSIZE) + +static void update_live_resolution(struct hws_pcie_dev *pdx, unsigned int ch, + bool interlace); +static bool hws_read_active_state(struct hws_pcie_dev *pdx, unsigned int ch, + bool *interlace); +static void handle_hwv2_path(struct hws_pcie_dev *hws, unsigned int ch); +static void handle_legacy_path(struct hws_pcie_dev *hws, unsigned int ch); +static u32 hws_calc_sizeimage(struct hws_video *v, u16 w, u16 h, + bool interlaced); + +/* DMA helper functions */ +static void hws_program_dma_window(struct hws_video *vid, dma_addr_t dma); +static struct hwsvideo_buffer * +hws_take_queued_buffer_locked(struct hws_video *vid); + +static unsigned long long hws_elapsed_us(u64 start_ns) +{ + return div_u64(ktime_get_mono_fast_ns() - start_ns, 1000); +} + +static inline bool list_node_unlinked(const struct list_head *n) +{ + return n->next == LIST_POISON1 || n->prev == LIST_POISON2; +} + +static bool dma_window_verify; +module_param_named(dma_window_verify, dma_window_verify, bool, 0644); +MODULE_PARM_DESC(dma_window_verify, + "Read back DMA window registers after programming (debug)"); + +void hws_set_dma_doorbell(struct hws_pcie_dev *hws, unsigned int ch, + dma_addr_t dma, const char *tag) +{ + iowrite32(lower_32_bits(dma), hws->bar0_base + HWS_REG_DMA_ADDR(ch)); + dev_dbg(&hws->pdev->dev, "dma_doorbell ch%u: dma=0x%llx tag=%s\n", ch, + (u64)dma, tag ? tag : ""); +} + +static void hws_program_dma_window(struct hws_video *vid, dma_addr_t dma) +{ + const u32 addr_mask = PCI_E_BAR_ADD_MASK; + const u32 addr_low_mask = PCI_E_BAR_ADD_LOWMASK; + struct hws_pcie_dev *hws = vid->parent; + unsigned int ch = vid->channel_index; + u32 table_off = HWS_REMAP_SLOT_OFF(ch); + u32 lo = lower_32_bits(dma); + u32 hi = upper_32_bits(dma); + u32 pci_addr = lo & addr_low_mask; + u32 page_lo = lo & addr_mask; + + bool wrote = false; + + /* Remap entry only when DMA crosses into a new 512 MB page */ + if (!vid->window_valid || vid->last_dma_hi != hi || + vid->last_dma_page != page_lo) { + writel(hi, hws->bar0_base + PCI_ADDR_TABLE_BASE + table_off); + writel(page_lo, + hws->bar0_base + PCI_ADDR_TABLE_BASE + table_off + + PCIE_BARADDROFSIZE); + vid->last_dma_hi = hi; + vid->last_dma_page = page_lo; + wrote = true; + } + + /* Base pointer only needs low 29 bits */ + if (!vid->window_valid || vid->last_pci_addr != pci_addr) { + writel((ch + 1) * PCIEBAR_AXI_BASE + pci_addr, + hws->bar0_base + HWS_BUF_BASE_OFF(ch)); + vid->last_pci_addr = pci_addr; + wrote = true; + } + + /* Half-size only changes when resolution changes */ + if (!vid->window_valid || vid->last_half16 != vid->pix.half_size / 16) { + writel(vid->pix.half_size / 16, + hws->bar0_base + HWS_HALF_SZ_OFF(ch)); + vid->last_half16 = vid->pix.half_size / 16; + wrote = true; + } + + vid->window_valid = true; + + if (dma_window_verify && wrote) { + u32 r_hi = + readl(hws->bar0_base + PCI_ADDR_TABLE_BASE + table_off); + u32 r_lo = + readl(hws->bar0_base + PCI_ADDR_TABLE_BASE + table_off + + PCIE_BARADDROFSIZE); + u32 r_base = readl(hws->bar0_base + HWS_BUF_BASE_OFF(ch)); + u32 r_half = readl(hws->bar0_base + HWS_HALF_SZ_OFF(ch)); + + dev_dbg(&hws->pdev->dev, + "ch%u remap verify: hi=0x%08x page_lo=0x%08x exp_page=0x%08x base=0x%08x exp_base=0x%08x half16B=0x%08x exp_half=0x%08x\n", + ch, r_hi, r_lo, page_lo, r_base, + (ch + 1) * PCIEBAR_AXI_BASE + pci_addr, r_half, + vid->pix.half_size / 16); + } else if (wrote) { + /* Flush posted writes before arming DMA */ + readl_relaxed(hws->bar0_base + HWS_HALF_SZ_OFF(ch)); + } +} + +static struct hwsvideo_buffer * +hws_take_queued_buffer_locked(struct hws_video *vid) +{ + struct hwsvideo_buffer *buf; + + if (!vid || list_empty(&vid->capture_queue)) + return NULL; + + buf = list_first_entry(&vid->capture_queue, + struct hwsvideo_buffer, list); + list_del_init(&buf->list); + if (vid->queued_count) + vid->queued_count--; + return buf; +} + +void hws_prime_next_locked(struct hws_video *vid) +{ + struct hws_pcie_dev *hws; + struct hwsvideo_buffer *next; + dma_addr_t dma; + + if (!vid) + return; + + hws = vid->parent; + if (!hws || !hws->bar0_base) + return; + + if (!READ_ONCE(vid->cap_active) || !vid->active || vid->next_prepared) + return; + + next = hws_take_queued_buffer_locked(vid); + if (!next) + return; + + vid->next_prepared = next; + dma = vb2_dma_contig_plane_dma_addr(&next->vb.vb2_buf, 0); + hws_program_dma_for_addr(hws, vid->channel_index, dma); + iowrite32(lower_32_bits(dma), + hws->bar0_base + HWS_REG_DMA_ADDR(vid->channel_index)); + dev_dbg(&hws->pdev->dev, + "ch%u pre-armed next buffer %p dma=0x%llx\n", + vid->channel_index, next, (u64)dma); +} + +static bool hws_force_no_signal_frame(struct hws_video *v, const char *tag) +{ + struct hws_pcie_dev *hws; + unsigned long flags; + struct hwsvideo_buffer *buf = NULL, *next = NULL; + bool have_next = false; + bool doorbell = false; + + if (!v) + return false; + hws = v->parent; + if (!hws || READ_ONCE(v->stop_requested) || !READ_ONCE(v->cap_active)) + return false; + spin_lock_irqsave(&v->irq_lock, flags); + if (v->active) { + buf = v->active; + v->active = NULL; + buf->slot = 0; + } else if (!list_empty(&v->capture_queue)) { + buf = list_first_entry(&v->capture_queue, + struct hwsvideo_buffer, list); + list_del_init(&buf->list); + if (v->queued_count) + v->queued_count--; + buf->slot = 0; + } + if (v->next_prepared) { + next = v->next_prepared; + v->next_prepared = NULL; + next->slot = 0; + v->active = next; + have_next = true; + } else if (!list_empty(&v->capture_queue)) { + next = list_first_entry(&v->capture_queue, + struct hwsvideo_buffer, list); + list_del_init(&next->list); + if (v->queued_count) + v->queued_count--; + next->slot = 0; + v->active = next; + have_next = true; + } else { + v->active = NULL; + } + spin_unlock_irqrestore(&v->irq_lock, flags); + if (!buf) + return false; + /* Complete buffer with a neutral frame so dequeuers keep running. */ + { + struct vb2_v4l2_buffer *vb2v = &buf->vb; + void *dst = vb2_plane_vaddr(&vb2v->vb2_buf, 0); + + if (dst) + memset(dst, 0x10, v->pix.sizeimage); + vb2_set_plane_payload(&vb2v->vb2_buf, 0, v->pix.sizeimage); + vb2v->sequence = (u32)atomic_inc_return(&v->sequence_number); + vb2v->vb2_buf.timestamp = ktime_get_ns(); + vb2_buffer_done(&vb2v->vb2_buf, VB2_BUF_STATE_DONE); + } + if (have_next && next) { + dma_addr_t dma = + vb2_dma_contig_plane_dma_addr(&next->vb.vb2_buf, 0); + hws_program_dma_for_addr(hws, v->channel_index, dma); + hws_set_dma_doorbell(hws, v->channel_index, dma, + tag ? tag : "nosignal_zero"); + doorbell = true; + } + if (doorbell) { + wmb(); /* ensure descriptors visible before enabling capture */ + hws_enable_video_capture(hws, v->channel_index, true); + } + return true; +} + +static int hws_ctrls_init(struct hws_video *vid) +{ + struct v4l2_ctrl_handler *hdl = &vid->control_handler; + + /* Create BCHS controls. */ + v4l2_ctrl_handler_init(hdl, 4); + + vid->ctrl_brightness = v4l2_ctrl_new_std(hdl, &hws_ctrl_ops, + V4L2_CID_BRIGHTNESS, + MIN_VAMP_BRIGHTNESS_UNITS, + MAX_VAMP_BRIGHTNESS_UNITS, 1, + HWS_BRIGHTNESS_DEFAULT); + + vid->ctrl_contrast = + v4l2_ctrl_new_std(hdl, &hws_ctrl_ops, V4L2_CID_CONTRAST, + MIN_VAMP_CONTRAST_UNITS, MAX_VAMP_CONTRAST_UNITS, + 1, HWS_CONTRAST_DEFAULT); + + vid->ctrl_saturation = v4l2_ctrl_new_std(hdl, &hws_ctrl_ops, + V4L2_CID_SATURATION, + MIN_VAMP_SATURATION_UNITS, + MAX_VAMP_SATURATION_UNITS, 1, + HWS_SATURATION_DEFAULT); + + vid->ctrl_hue = v4l2_ctrl_new_std(hdl, &hws_ctrl_ops, V4L2_CID_HUE, + MIN_VAMP_HUE_UNITS, + MAX_VAMP_HUE_UNITS, 1, + HWS_HUE_DEFAULT); + if (hdl->error) { + int err = hdl->error; + + v4l2_ctrl_handler_free(hdl); + return err; + } + return 0; +} + +int hws_video_init_channel(struct hws_pcie_dev *pdev, int ch) +{ + struct hws_video *vid; + + /* basic sanity */ + if (!pdev || ch < 0 || ch >= pdev->max_channels) + return -EINVAL; + + vid = &pdev->video[ch]; + + /* hard reset the per-channel struct (safe here since we init everything next) */ + memset(vid, 0, sizeof(*vid)); + + /* identity */ + vid->parent = pdev; + vid->channel_index = ch; + + /* locks & lists */ + mutex_init(&vid->state_lock); + spin_lock_init(&vid->irq_lock); + INIT_LIST_HEAD(&vid->capture_queue); + atomic_set(&vid->sequence_number, 0); + vid->active = NULL; + + /* DMA watchdog removed; retain counters for diagnostics */ + vid->timeout_count = 0; + vid->error_count = 0; + + vid->queued_count = 0; + vid->window_valid = false; + + /* Default format. */ + vid->pix.width = 1920; + vid->pix.height = 1080; + vid->pix.fourcc = V4L2_PIX_FMT_YUYV; + vid->pix.bytesperline = ALIGN(vid->pix.width * 2, 64); + vid->pix.sizeimage = vid->pix.bytesperline * vid->pix.height; + vid->pix.field = V4L2_FIELD_NONE; + vid->pix.colorspace = V4L2_COLORSPACE_REC709; + vid->pix.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + vid->pix.quantization = V4L2_QUANTIZATION_FULL_RANGE; + vid->pix.xfer_func = V4L2_XFER_FUNC_DEFAULT; + vid->pix.interlaced = false; + vid->pix.half_size = vid->pix.sizeimage / 2; + hws_set_current_dv_timings(vid, vid->pix.width, + vid->pix.height, vid->pix.interlaced); + vid->current_fps = 60; + + /* color controls default (mid-scale) */ + vid->current_brightness = 0x80; + vid->current_contrast = 0x80; + vid->current_saturation = 0x80; + vid->current_hue = 0x80; + + /* capture state */ + vid->cap_active = false; + vid->stop_requested = false; + vid->last_buf_half_toggle = 0; + vid->half_seen = false; + vid->signal_loss_cnt = 0; + + /* Create BCHS + DV power-present as modern controls */ + { + int err = hws_ctrls_init(vid); + + if (err) { + dev_err(&pdev->pdev->dev, + "v4l2 ctrl init failed on ch%d: %d\n", ch, err); + return err; + } + } + + return 0; +} + +static void hws_video_drain_queue_locked(struct hws_video *vid) +{ + /* Return in-flight first */ + if (vid->active) { + vb2_buffer_done(&vid->active->vb.vb2_buf, VB2_BUF_STATE_ERROR); + vid->active = NULL; + } + + /* Then everything queued */ + while (!list_empty(&vid->capture_queue)) { + struct hwsvideo_buffer *b = + list_first_entry(&vid->capture_queue, + struct hwsvideo_buffer, + list); + list_del_init(&b->list); + vb2_buffer_done(&b->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } +} + +static void hws_video_release_registration(struct hws_video *vid) +{ + if (vid->buffer_queue.ops) { + vb2_queue_release(&vid->buffer_queue); + vid->buffer_queue.ops = NULL; + } + + if (!vid->video_device) + return; + + if (video_is_registered(vid->video_device)) + vb2_video_unregister_device(vid->video_device); + else + video_device_release(vid->video_device); + vid->video_device = NULL; +} + +static void hws_video_collect_done_locked(struct hws_video *vid, + struct list_head *done) +{ + struct hwsvideo_buffer *b; + + if (vid->active) { + if (!list_node_unlinked(&vid->active->list)) { + list_move_tail(&vid->active->list, done); + } else { + INIT_LIST_HEAD(&vid->active->list); + list_add_tail(&vid->active->list, done); + } + vid->active = NULL; + } + + if (vid->next_prepared) { + if (!list_node_unlinked(&vid->next_prepared->list)) { + list_move_tail(&vid->next_prepared->list, done); + } else { + INIT_LIST_HEAD(&vid->next_prepared->list); + list_add_tail(&vid->next_prepared->list, done); + } + vid->next_prepared = NULL; + } + + while (!list_empty(&vid->capture_queue)) { + b = list_first_entry(&vid->capture_queue, struct hwsvideo_buffer, + list); + list_move_tail(&b->list, done); + } + + vid->queued_count = 0; +} + +void hws_video_cleanup_channel(struct hws_pcie_dev *pdev, int ch) +{ + struct hws_video *vid; + unsigned long flags; + + if (!pdev || ch < 0 || ch >= pdev->max_channels) + return; + + vid = &pdev->video[ch]; + + /* 1) Stop HW best-effort for this channel */ + hws_enable_video_capture(vid->parent, vid->channel_index, false); + + /* 2) Flip software state so IRQ/BH will be no-ops if they run */ + WRITE_ONCE(vid->stop_requested, true); + WRITE_ONCE(vid->cap_active, false); + + /* 3) Ensure the IRQ handler finished any in-flight completions */ + if (vid->parent && vid->parent->irq >= 0) + synchronize_irq(vid->parent->irq); + + /* 4) Drain SW capture queue & in-flight under lock */ + spin_lock_irqsave(&vid->irq_lock, flags); + hws_video_drain_queue_locked(vid); + spin_unlock_irqrestore(&vid->irq_lock, flags); + + /* 5) Release VB2 queue if initialized */ + hws_video_release_registration(vid); + + /* 6) Free V4L2 controls */ + v4l2_ctrl_handler_free(&vid->control_handler); + + /* 8) Reset simple state; do not memset the whole struct here. */ + mutex_destroy(&vid->state_lock); + INIT_LIST_HEAD(&vid->capture_queue); + vid->active = NULL; + vid->stop_requested = false; + vid->last_buf_half_toggle = 0; + vid->half_seen = false; + vid->signal_loss_cnt = 0; +} + +/* Convenience cast */ +static inline struct hwsvideo_buffer *to_hwsbuf(struct vb2_buffer *vb) +{ + return container_of(to_vb2_v4l2_buffer(vb), struct hwsvideo_buffer, vb); +} + +static int hws_buf_init(struct vb2_buffer *vb) +{ + struct hwsvideo_buffer *b = to_hwsbuf(vb); + + INIT_LIST_HEAD(&b->list); + return 0; +} + +static void hws_buf_finish(struct vb2_buffer *vb) +{ + /* vb2 core handles cache maintenance for dma-contig buffers */ + (void)vb; +} + +static void hws_buf_cleanup(struct vb2_buffer *vb) +{ + struct hwsvideo_buffer *b = to_hwsbuf(vb); + + if (!list_empty(&b->list)) + list_del_init(&b->list); +} + +void hws_program_dma_for_addr(struct hws_pcie_dev *hws, unsigned int ch, + dma_addr_t dma) +{ + struct hws_video *vid = &hws->video[ch]; + + hws_program_dma_window(vid, dma); +} + +void hws_enable_video_capture(struct hws_pcie_dev *hws, unsigned int chan, + bool on) +{ + u32 status; + + if (!hws || hws->pci_lost || chan >= hws->max_channels) + return; + + status = readl(hws->bar0_base + HWS_REG_VCAP_ENABLE); + status = on ? (status | BIT(chan)) : (status & ~BIT(chan)); + writel(status, hws->bar0_base + HWS_REG_VCAP_ENABLE); + (void)readl(hws->bar0_base + HWS_REG_VCAP_ENABLE); + + WRITE_ONCE(hws->video[chan].cap_active, on); + + dev_dbg(&hws->pdev->dev, "vcap %s ch%u (reg=0x%08x)\n", + on ? "ON" : "OFF", chan, status); +} + +static void hws_seed_dma_windows(struct hws_pcie_dev *hws) +{ + const u32 addr_mask = PCI_E_BAR_ADD_MASK; + const u32 addr_low_mask = PCI_E_BAR_ADD_LOWMASK; + u32 table = 0x208; /* one 64-bit entry per channel */ + unsigned int ch; + + if (!hws || !hws->bar0_base) + return; + + /* If cur_max_video_ch is not set yet, default to max_channels. */ + if (!hws->cur_max_video_ch || hws->cur_max_video_ch > hws->max_channels) + hws->cur_max_video_ch = hws->max_channels; + + for (ch = 0; ch < hws->cur_max_video_ch; ch++, table += 8) { + if (!hws->scratch_vid[ch].cpu) + continue; + + /* Program 64-bit BAR remap entry for this channel */ + { + dma_addr_t p = hws->scratch_vid[ch].dma; + u32 lo = lower_32_bits(p) & addr_mask; + u32 hi = upper_32_bits(p); + u32 pci_addr_low = lower_32_bits(p) & addr_low_mask; + + writel_relaxed(hi, + hws->bar0_base + PCI_ADDR_TABLE_BASE + + table); + writel_relaxed(lo, + hws->bar0_base + PCI_ADDR_TABLE_BASE + + table + PCIE_BARADDROFSIZE); + + /* Per-channel AXI base + PCI low */ + writel_relaxed((ch + 1) * PCIEBAR_AXI_BASE + + pci_addr_low, + hws->bar0_base + CVBS_IN_BUF_BASE + + ch * PCIE_BARADDROFSIZE); + + /* Half-frame length in /16 units. + * Prefer the current channel's computed half_size if available. + * Fall back to half of the probe-owned scratch buffer. + */ + { + u32 half_bytes = hws->video[ch].pix.half_size ? + hws->video[ch].pix.half_size : + (u32)(hws->scratch_vid[ch].size / 2); + writel_relaxed(half_bytes / 16, + hws->bar0_base + + CVBS_IN_BUF_BASE2 + + ch * PCIE_BARADDROFSIZE); + } + } + } + + /* Post writes so device sees them before we move on */ + (void)readl(hws->bar0_base + HWS_REG_INT_STATUS); +} + +static void hws_ack_all_irqs(struct hws_pcie_dev *hws) +{ + u32 st = readl(hws->bar0_base + HWS_REG_INT_STATUS); + + if (st) { + writel(st, hws->bar0_base + HWS_REG_INT_STATUS); /* W1C */ + (void)readl(hws->bar0_base + HWS_REG_INT_STATUS); + } +} + +static void hws_open_irq_fabric(struct hws_pcie_dev *hws) +{ + /* Route all sources to vector 0. */ + writel(0x00000000, hws->bar0_base + PCIE_INT_DEC_REG_BASE); + (void)readl(hws->bar0_base + PCIE_INT_DEC_REG_BASE); + + /* Enable the PCIe bridge. */ + writel(0x00000001, hws->bar0_base + PCIEBR_EN_REG_BASE); + (void)readl(hws->bar0_base + PCIEBR_EN_REG_BASE); + + /* Open the global/bridge gate (legacy 0x3FFFF) */ + writel(HWS_INT_EN_MASK, hws->bar0_base + INT_EN_REG_BASE); + (void)readl(hws->bar0_base + INT_EN_REG_BASE); +} + +void hws_init_video_sys(struct hws_pcie_dev *hws, bool enable) +{ + int i; + + if (hws->start_run && !enable) + return; + + /* 1) reset the decoder mode register to 0 */ + writel(0x00000000, hws->bar0_base + HWS_REG_DEC_MODE); + hws_seed_dma_windows(hws); + + /* 3) on a full reset, clear all per-channel status and indices */ + if (!enable) { + for (i = 0; i < hws->max_channels; i++) { + /* helpers to arm/disable capture engines */ + hws_enable_video_capture(hws, i, false); + } + } + + /* 4) Start run: set bit31, wait a bit, then program low 24 bits. */ + writel(0x80000000, hws->bar0_base + HWS_REG_DEC_MODE); + writel(0x80FFFFFF, hws->bar0_base + HWS_REG_DEC_MODE); + writel(0x13, hws->bar0_base + HWS_REG_DEC_MODE); + hws_ack_all_irqs(hws); + hws_open_irq_fabric(hws); + /* 6) record that we're now running */ + hws->start_run = true; +} + +int hws_check_card_status(struct hws_pcie_dev *hws) +{ + u32 status; + + if (!hws || !hws->bar0_base) + return -ENODEV; + + status = readl(hws->bar0_base + HWS_REG_SYS_STATUS); + + /* Common device-missing pattern. */ + if (status == 0xFFFFFFFF) { + hws->pci_lost = true; + dev_err(&hws->pdev->dev, "PCIe device not responding\n"); + return -ENODEV; + } + + /* If RUN/READY bit (bit0) is not set, reinitialize the video core. */ + if (!(status & BIT(0))) { + dev_dbg(&hws->pdev->dev, + "SYS_STATUS not ready (0x%08x), reinitializing\n", + status); + hws_init_video_sys(hws, true); + } + + return 0; +} + +void check_video_format(struct hws_pcie_dev *pdx) +{ + int i; + + for (i = 0; i < pdx->cur_max_video_ch; i++) { + bool interlace = false; + + if (!hws_read_active_state(pdx, i, &interlace)) { + /* No active video; optionally feed neutral frames to keep streaming. */ + if (pdx->video[i].signal_loss_cnt == 0) + pdx->video[i].signal_loss_cnt = 1; + if (READ_ONCE(pdx->video[i].cap_active)) + hws_force_no_signal_frame(&pdx->video[i], + "monitor_nosignal"); + } else { + if (pdx->hw_ver > 0) + handle_hwv2_path(pdx, i); + else + /* Legacy path stub; see handle_legacy_path() comment. */ + handle_legacy_path(pdx, i); + + update_live_resolution(pdx, i, interlace); + pdx->video[i].signal_loss_cnt = 0; + } + } +} + +static inline void hws_write_if_diff(struct hws_pcie_dev *hws, u32 reg_off, + u32 new_val) +{ + void __iomem *addr; + u32 old; + + if (!hws || !hws->bar0_base) + return; + + addr = hws->bar0_base + reg_off; + + old = readl(addr); + /* Treat all-ones as device gone; avoid writing garbage. */ + if (old == 0xFFFFFFFF) { + hws->pci_lost = true; + return; + } + + if (old != new_val) { + writel(new_val, addr); + /* Post the write on some bridges / enforce ordering. */ + (void)readl(addr); + } +} + +static bool hws_read_active_state(struct hws_pcie_dev *pdx, unsigned int ch, + bool *interlace) +{ + u32 reg; + bool active; + + if (ch >= pdx->cur_max_video_ch) + return false; + + reg = readl(pdx->bar0_base + HWS_REG_ACTIVE_STATUS); + active = !!(reg & BIT(ch)); + if (interlace) + *interlace = !!(reg & BIT(8 + ch)); + return active; +} + +/* Modern hardware path: keep HW registers in sync with current per-channel + * software state. + */ +static void handle_hwv2_path(struct hws_pcie_dev *hws, unsigned int ch) +{ + struct hws_video *vid; + u32 reg, in_fps, cur_out_res, want_out_res; + + if (!hws || !hws->bar0_base || ch >= hws->max_channels) + return; + + vid = &hws->video[ch]; + + /* 1) Input frame rate (read-only; log or export via debugfs if wanted) */ + in_fps = readl(hws->bar0_base + HWS_REG_FRAME_RATE(ch)); + /* dev_dbg(&hws->pdev->dev, "ch%u input fps=%u\n", ch, in_fps); */ + (void)in_fps; + + /* 2) Output resolution programming. + * For now, mirror the current format to OUT_RES. + */ + want_out_res = (vid->pix.height << 16) | vid->pix.width; + cur_out_res = readl(hws->bar0_base + HWS_REG_OUT_RES(ch)); + if (cur_out_res != want_out_res) + hws_write_if_diff(hws, HWS_REG_OUT_RES(ch), want_out_res); + + /* 3) Output FPS: only program if you actually track a target. + * Example heuristic (disabled by default): + * + * u32 out_fps = (vid->fmt_curr.height >= 1080) ? 60 : 30; + * hws_write_if_diff(hws, HWS_REG_OUT_FRAME_RATE(ch), out_fps); + */ + + /* 4) BCHS controls: pack from per-channel current_* fields */ + reg = readl(hws->bar0_base + HWS_REG_BCHS(ch)); + { + u8 br = reg & 0xFF; + u8 co = (reg >> 8) & 0xFF; + u8 hu = (reg >> 16) & 0xFF; + u8 sa = (reg >> 24) & 0xFF; + + if (br != vid->current_brightness || + co != vid->current_contrast || hu != vid->current_hue || + sa != vid->current_saturation) { + u32 packed = (vid->current_saturation << 24) | + (vid->current_hue << 16) | + (vid->current_contrast << 8) | + vid->current_brightness; + hws_write_if_diff(hws, HWS_REG_BCHS(ch), packed); + } + } + + /* 5) HDCP detect: read only (no cache field in your structs today) */ + reg = readl(hws->bar0_base + HWS_REG_HDCP_STATUS); + /* bool hdcp = !!(reg & BIT(ch)); // use if you later add a field/control */ +} + +static void handle_legacy_path(struct hws_pcie_dev *hws, unsigned int ch) +{ + /* + * Legacy (hw_ver == 0) expected behavior: + * - A per-channel SW FPS accumulator incremented on each VDONE. + * - A once-per-second poll mapped the count to discrete FPS: + * >55*2 => 60, >45*2 => 50, >25*2 => 30, >20*2 => 25, else 60, + * then reset the accumulator to 0. + * - The *2 factor assumed VDONE fired per-field; if legacy VDONE is + * per-frame, drop the factor. + * + * Current code keeps this path as a no-op; vid->current_fps stays at the + * default or mode-derived value. If accurate legacy FPS reporting is + * needed (V4L2 g_parm/timeperframe), reintroduce the accumulator in the + * IRQ path and perform the mapping/reset here. + * + * No-op by default. If you introduce a SW FPS accumulator, map it here. + * + * Example skeleton: + * + * u32 sw_rate = READ_ONCE(hws->sw_fps[ch]); // incremented elsewhere + * if (sw_rate > THRESHOLD) { + * u32 fps = pick_fps_from_rate(sw_rate); + * hws_write_if_diff(hws, HWS_REG_OUT_FRAME_RATE(ch), fps); + * WRITE_ONCE(hws->sw_fps[ch], 0); + * } + */ + (void)hws; + (void)ch; +} + +static void hws_video_apply_mode_change(struct hws_pcie_dev *pdx, + unsigned int ch, u16 w, u16 h, + bool interlaced, u32 fps) +{ + struct hws_video *v = &pdx->video[ch]; + unsigned long flags; + bool queue_busy; + bool geometry_changed; + struct list_head done; + struct hwsvideo_buffer *b, *tmp; + + if (!pdx || !pdx->bar0_base) + return; + if (ch >= pdx->max_channels) + return; + if (!w || !h || w > MAX_VIDEO_HW_W || + (!interlaced && h > MAX_VIDEO_HW_H) || + (interlaced && (h * 2) > MAX_VIDEO_HW_H)) + return; + if (!fps || fps == 0xFFFFFFFF || fps > 240) + fps = (h == 576) ? 50 : 60; + + geometry_changed = w != v->pix.width || h != v->pix.height || + interlaced != v->pix.interlaced; + if (!geometry_changed && fps == v->current_fps) + return; + + if (!geometry_changed) { + /* Refresh cached live timing state, but don't emit a resolution + * change event when only the frame rate changes. + */ + mutex_lock(&v->state_lock); + v->pix.interlaced = interlaced; + v->pix.field = interlaced ? V4L2_FIELD_INTERLACED : + V4L2_FIELD_NONE; + hws_set_current_dv_timings(v, w, h, interlaced); + v->current_fps = fps; + mutex_unlock(&v->state_lock); + return; + } + + if (!mutex_trylock(&v->state_lock)) + return; + + INIT_LIST_HEAD(&done); + queue_busy = vb2_is_busy(&v->buffer_queue); + + WRITE_ONCE(v->stop_requested, true); + WRITE_ONCE(v->cap_active, false); + /* Publish software stop first so the IRQ completion path sees the stop + * before we touch MMIO or the lists. Pairs with READ_ONCE() checks in the + * VDONE handler and hws_arm_next() to prevent completions while modes + * change. + */ + smp_wmb(); + + hws_enable_video_capture(pdx, ch, false); + readl(pdx->bar0_base + HWS_REG_INT_STATUS); + + if (v->parent && v->parent->irq >= 0) + synchronize_irq(v->parent->irq); + + spin_lock_irqsave(&v->irq_lock, flags); + hws_video_collect_done_locked(v, &done); + spin_unlock_irqrestore(&v->irq_lock, flags); + + /* Update software pixel state */ + v->pix.width = w; + v->pix.height = h; + v->pix.interlaced = interlaced; + hws_set_current_dv_timings(v, w, h, interlaced); + v->current_fps = fps; + + hws_calc_sizeimage(v, w, h, interlaced); + v->window_valid = false; + + /* Geometry changes require userspace renegotiation once buffers exist. + * Emit SOURCE_CHANGE, mark the queue in error, and let userspace + * STREAMOFF/REQBUFS/STREAMON rather than trying to restart capture + * with partially drained in-flight state. + */ + if (queue_busy) { + struct v4l2_event ev = { + .type = V4L2_EVENT_SOURCE_CHANGE, + }; + + ev.u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION; + v4l2_event_queue(v->video_device, &ev); + vb2_queue_error(&v->buffer_queue); + } else { + WRITE_ONCE(v->stop_requested, false); + } + + /* Program HW with new resolution */ + hws_write_if_diff(pdx, HWS_REG_OUT_RES(ch), (h << 16) | w); + + /* Legacy half-buffer programming */ + writel(v->pix.half_size / 16, + pdx->bar0_base + CVBS_IN_BUF_BASE2 + ch * PCIE_BARADDROFSIZE); + (void)readl(pdx->bar0_base + CVBS_IN_BUF_BASE2 + + ch * PCIE_BARADDROFSIZE); + + /* Reset per-channel toggles/counters */ + WRITE_ONCE(v->last_buf_half_toggle, 0); + atomic_set(&v->sequence_number, 0); + + mutex_unlock(&v->state_lock); + + list_for_each_entry_safe(b, tmp, &done, list) { + list_del_init(&b->list); + vb2_buffer_done(&b->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } +} + +static void update_live_resolution(struct hws_pcie_dev *pdx, unsigned int ch, + bool interlace) +{ + u32 reg = readl(pdx->bar0_base + HWS_REG_IN_RES(ch)); + u32 fps = readl(pdx->bar0_base + HWS_REG_FRAME_RATE(ch)); + u16 res_w = reg & 0xFFFF; + u16 res_h = (reg >> 16) & 0xFFFF; + struct hws_video *vid = &pdx->video[ch]; + bool geometry_changed; + bool fps_changed; + + bool within_hw = (res_w <= MAX_VIDEO_HW_W) && + ((!interlace && res_h <= MAX_VIDEO_HW_H) || + (interlace && (res_h * 2) <= MAX_VIDEO_HW_H)); + + if (!within_hw) + return; + + geometry_changed = res_w != vid->pix.width || + res_h != vid->pix.height || + interlace != vid->pix.interlaced; + fps_changed = fps && fps != 0xFFFFFFFF && fps <= 240 && + fps != vid->current_fps; + + if (geometry_changed || fps_changed) + hws_video_apply_mode_change(pdx, ch, res_w, res_h, interlace, + fps); +} + +static int hws_open(struct file *file) +{ + return v4l2_fh_open(file); +} + +static const struct v4l2_file_operations hws_fops = { + .owner = THIS_MODULE, + .open = hws_open, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +static int hws_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subscribe(fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subscribe_event(fh, sub); + default: + return -EINVAL; + } +} + +static const struct v4l2_ioctl_ops hws_ioctl_fops = { + /* Core caps/info */ + .vidioc_querycap = hws_vidioc_querycap, + + /* Pixel format: still needed to report YUYV etc. */ + .vidioc_enum_fmt_vid_cap = hws_vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = hws_vidioc_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = hws_vidioc_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = hws_vidioc_try_fmt_vid_cap, + + /* Buffer queueing / streaming */ + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + /* Inputs */ + .vidioc_enum_input = hws_vidioc_enum_input, + .vidioc_g_input = hws_vidioc_g_input, + .vidioc_s_input = hws_vidioc_s_input, + + /* DV timings (HDMI/DVI/VESA modes) */ + .vidioc_query_dv_timings = hws_vidioc_query_dv_timings, + .vidioc_enum_dv_timings = hws_vidioc_enum_dv_timings, + .vidioc_g_dv_timings = hws_vidioc_g_dv_timings, + .vidioc_s_dv_timings = hws_vidioc_s_dv_timings, + .vidioc_dv_timings_cap = hws_vidioc_dv_timings_cap, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = hws_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_g_parm = hws_vidioc_g_parm, +}; + +static u32 hws_calc_sizeimage(struct hws_video *v, u16 w, u16 h, + bool interlaced) +{ + /* HWS captures packed YUYV only; stride is 16 bpp aligned to 64 bytes. */ + u32 lines = h; /* full frame lines for sizeimage */ + u32 bytesperline = ALIGN(w * 2, 64); + u32 sizeimage, half0; + + /* publish into pix, since we now carry these in-state */ + v->pix.bytesperline = bytesperline; + sizeimage = bytesperline * lines; + + half0 = sizeimage / 2; + + v->pix.sizeimage = sizeimage; + v->pix.half_size = half0; /* first half; second = sizeimage - half0 */ + v->pix.field = interlaced ? V4L2_FIELD_INTERLACED : V4L2_FIELD_NONE; + + return v->pix.sizeimage; +} + +static int hws_queue_setup(struct vb2_queue *q, unsigned int *num_buffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct hws_video *vid = q->drv_priv; + + if (*nplanes) { + if (sizes[0] < vid->pix.sizeimage) + return -EINVAL; + } else { + *nplanes = 1; + sizes[0] = vid->pix.sizeimage; + } + + return 0; +} + +static int hws_buffer_prepare(struct vb2_buffer *vb) +{ + struct hws_video *vid = vb->vb2_queue->drv_priv; + struct hws_pcie_dev *hws = vid->parent; + size_t need = vid->pix.sizeimage; + dma_addr_t dma_addr; + + if (vb2_plane_size(vb, 0) < need) + return -EINVAL; + + /* Validate DMA address alignment */ + dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0); + if (dma_addr & 0x3F) { /* 64-byte alignment required */ + dev_err(&hws->pdev->dev, + "Buffer DMA address 0x%llx not 64-byte aligned\n", + (unsigned long long)dma_addr); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, need); + return 0; +} + +static void hws_buffer_queue(struct vb2_buffer *vb) +{ + struct hws_video *vid = vb->vb2_queue->drv_priv; + struct hwsvideo_buffer *buf = to_hwsbuf(vb); + struct hws_pcie_dev *hws = vid->parent; + unsigned long flags; + + dev_dbg(&hws->pdev->dev, + "buffer_queue(ch=%u): vb=%p sizeimage=%u q_active=%d\n", + vid->channel_index, vb, vid->pix.sizeimage, + READ_ONCE(vid->cap_active)); + + /* Initialize buffer slot */ + buf->slot = 0; + + spin_lock_irqsave(&vid->irq_lock, flags); + list_add_tail(&buf->list, &vid->capture_queue); + vid->queued_count++; + + /* If streaming and no in-flight buffer, prime HW immediately */ + if (READ_ONCE(vid->cap_active) && !vid->active) { + dma_addr_t dma_addr; + + dev_dbg(&hws->pdev->dev, + "buffer_queue(ch=%u): priming first vb=%p\n", + vid->channel_index, &buf->vb.vb2_buf); + list_del_init(&buf->list); + vid->queued_count--; + vid->active = buf; + + dma_addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + hws_program_dma_for_addr(vid->parent, vid->channel_index, + dma_addr); + iowrite32(lower_32_bits(dma_addr), + hws->bar0_base + HWS_REG_DMA_ADDR(vid->channel_index)); + + wmb(); /* ensure descriptors visible before enabling capture */ + hws_enable_video_capture(hws, vid->channel_index, true); + hws_prime_next_locked(vid); + } else if (READ_ONCE(vid->cap_active) && vid->active) { + hws_prime_next_locked(vid); + } + spin_unlock_irqrestore(&vid->irq_lock, flags); +} + +static int hws_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct hws_video *v = q->drv_priv; + struct hws_pcie_dev *hws = v->parent; + struct hwsvideo_buffer *to_program = NULL; /* local copy */ + struct vb2_buffer *prog_vb2 = NULL; + unsigned long flags; + int ret; + + dev_dbg(&hws->pdev->dev, "start_streaming: ch=%u count=%u\n", + v->channel_index, count); + + ret = hws_check_card_status(hws); + if (ret) { + struct hwsvideo_buffer *b, *tmp; + unsigned long f; + LIST_HEAD(queued); + + spin_lock_irqsave(&v->irq_lock, f); + if (v->active) { + list_add_tail(&v->active->list, &queued); + v->active = NULL; + } + if (v->next_prepared) { + list_add_tail(&v->next_prepared->list, &queued); + v->next_prepared = NULL; + } + while (!list_empty(&v->capture_queue)) { + b = list_first_entry(&v->capture_queue, + struct hwsvideo_buffer, list); + list_move_tail(&b->list, &queued); + } + spin_unlock_irqrestore(&v->irq_lock, f); + + list_for_each_entry_safe(b, tmp, &queued, list) { + list_del_init(&b->list); + vb2_buffer_done(&b->vb.vb2_buf, VB2_BUF_STATE_QUEUED); + } + return ret; + } + (void)hws_read_active_state(hws, v->channel_index, + &v->pix.interlaced); + + lockdep_assert_held(&v->state_lock); + /* init per-stream state */ + WRITE_ONCE(v->stop_requested, false); + WRITE_ONCE(v->cap_active, true); + WRITE_ONCE(v->half_seen, false); + WRITE_ONCE(v->last_buf_half_toggle, 0); + + /* Try to prime a buffer, but it's OK if none are queued yet */ + spin_lock_irqsave(&v->irq_lock, flags); + if (!v->active && !list_empty(&v->capture_queue)) { + to_program = list_first_entry(&v->capture_queue, + struct hwsvideo_buffer, list); + list_del_init(&to_program->list); + v->queued_count--; + v->active = to_program; + prog_vb2 = &to_program->vb.vb2_buf; + dev_dbg(&hws->pdev->dev, + "start_streaming: ch=%u took buffer %p\n", + v->channel_index, to_program); + } + spin_unlock_irqrestore(&v->irq_lock, flags); + + /* Only program/enable HW if we actually have a buffer */ + if (to_program) { + if (!prog_vb2) + prog_vb2 = &to_program->vb.vb2_buf; + { + dma_addr_t dma_addr; + + dma_addr = vb2_dma_contig_plane_dma_addr(prog_vb2, 0); + hws_program_dma_for_addr(hws, v->channel_index, dma_addr); + iowrite32(lower_32_bits(dma_addr), + hws->bar0_base + + HWS_REG_DMA_ADDR(v->channel_index)); + dev_dbg(&hws->pdev->dev, + "start_streaming: ch=%u programmed buffer %p dma=0x%08x\n", + v->channel_index, to_program, + lower_32_bits(dma_addr)); + (void)readl(hws->bar0_base + HWS_REG_INT_STATUS); + } + + wmb(); /* ensure descriptors visible before enabling capture */ + hws_enable_video_capture(hws, v->channel_index, true); + { + unsigned long pf; + + spin_lock_irqsave(&v->irq_lock, pf); + hws_prime_next_locked(v); + spin_unlock_irqrestore(&v->irq_lock, pf); + } + } else { + dev_dbg(&hws->pdev->dev, + "start_streaming: ch=%u no buffer yet (will arm on QBUF)\n", + v->channel_index); + } + + return 0; +} + +static void hws_log_video_state(struct hws_video *v, const char *action, + const char *phase) +{ + struct hws_pcie_dev *hws = v->parent; + unsigned long flags; + unsigned int queued = 0; + unsigned int tracked = 0; + unsigned int seq = 0; + struct hwsvideo_buffer *b; + bool streaming = vb2_is_streaming(&v->buffer_queue); + bool cap_active; + bool stop_requested; + struct hwsvideo_buffer *active; + struct hwsvideo_buffer *next_prepared; + + spin_lock_irqsave(&v->irq_lock, flags); + list_for_each_entry(b, &v->capture_queue, list) + queued++; + cap_active = READ_ONCE(v->cap_active); + stop_requested = READ_ONCE(v->stop_requested); + active = v->active; + next_prepared = v->next_prepared; + tracked = v->queued_count; + seq = (u32)atomic_read(&v->sequence_number); + spin_unlock_irqrestore(&v->irq_lock, flags); + + dev_dbg(&hws->pdev->dev, + "video:%s:%s ch=%u streaming=%d cap=%d stop=%d active=%p next=%p queued=%u tracked=%u seq=%u\n", + action, phase, v->channel_index, streaming, cap_active, + stop_requested, active, next_prepared, queued, tracked, seq); +} + +static void hws_stop_streaming(struct vb2_queue *q) +{ + struct hws_video *v = q->drv_priv; + struct hws_pcie_dev *hws = v->parent; + unsigned long flags; + struct hwsvideo_buffer *b, *tmp; + LIST_HEAD(done); + unsigned int done_cnt = 0; + u64 start_ns = ktime_get_mono_fast_ns(); + + hws_log_video_state(v, "streamoff", "begin"); + + /* 1) Quiesce SW/HW first */ + lockdep_assert_held(&v->state_lock); + WRITE_ONCE(v->cap_active, false); + WRITE_ONCE(v->stop_requested, true); + + hws_enable_video_capture(v->parent, v->channel_index, false); + + /* 2) Collect in-flight + queued under the IRQ lock */ + spin_lock_irqsave(&v->irq_lock, flags); + hws_video_collect_done_locked(v, &done); + spin_unlock_irqrestore(&v->irq_lock, flags); + + /* 3) Complete outside the lock */ + list_for_each_entry_safe(b, tmp, &done, list) { + /* Unlink from 'done' before completing */ + list_del_init(&b->list); + vb2_buffer_done(&b->vb.vb2_buf, VB2_BUF_STATE_ERROR); + done_cnt++; + } + dev_dbg(&hws->pdev->dev, + "video:streamoff:done ch=%u completed=%u (%lluus)\n", + v->channel_index, done_cnt, hws_elapsed_us(start_ns)); + hws_log_video_state(v, "streamoff", "end"); +} + +static const struct vb2_ops hwspcie_video_qops = { + .queue_setup = hws_queue_setup, + .buf_prepare = hws_buffer_prepare, + .buf_init = hws_buf_init, + .buf_finish = hws_buf_finish, + .buf_cleanup = hws_buf_cleanup, + .buf_queue = hws_buffer_queue, + .start_streaming = hws_start_streaming, + .stop_streaming = hws_stop_streaming, +}; + +int hws_video_register(struct hws_pcie_dev *dev) +{ + int i, ret; + + ret = v4l2_device_register(&dev->pdev->dev, &dev->v4l2_device); + if (ret) { + dev_err(&dev->pdev->dev, "v4l2_device_register failed: %d\n", + ret); + return ret; + } + + for (i = 0; i < dev->cur_max_video_ch; i++) { + struct hws_video *ch = &dev->video[i]; + struct video_device *vdev; + struct vb2_queue *q; + + /* hws_video_init_channel() should have set: + * - ch->parent, ch->channel_index + * - locks (state_lock, irq_lock) + * - capture_queue (INIT_LIST_HEAD) + * - control_handler + controls + * - fmt_curr (width/height) + * Do not reinitialize any of those here. + */ + + vdev = video_device_alloc(); + if (!vdev) { + dev_err(&dev->pdev->dev, + "video_device_alloc ch%u failed\n", i); + ret = -ENOMEM; + goto err_unwind; + } + ch->video_device = vdev; + + /* Basic V4L2 node setup */ + snprintf(vdev->name, sizeof(vdev->name), "%s-hdmi%u", + KBUILD_MODNAME, i); + vdev->v4l2_dev = &dev->v4l2_device; + vdev->fops = &hws_fops; + vdev->ioctl_ops = &hws_ioctl_fops; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + vdev->lock = &ch->state_lock; /* serialize file ops */ + vdev->ctrl_handler = &ch->control_handler; + vdev->vfl_dir = VFL_DIR_RX; + vdev->release = video_device_release; + if (ch->control_handler.error) { + ret = ch->control_handler.error; + goto err_unwind; + } + video_set_drvdata(vdev, ch); + + /* vb2 queue init (dma-contig) */ + q = &ch->buffer_queue; + memset(q, 0, sizeof(*q)); + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_DMABUF; + q->drv_priv = ch; + q->buf_struct_size = sizeof(struct hwsvideo_buffer); + q->ops = &hwspcie_video_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &ch->state_lock; + q->min_queued_buffers = 1; + q->dev = &dev->pdev->dev; + + ret = vb2_queue_init(q); + vdev->queue = q; + if (ret) { + dev_err(&dev->pdev->dev, + "vb2_queue_init ch%u failed: %d\n", i, ret); + goto err_unwind; + } + + /* Make controls live (no-op if none or already set up) */ + if (ch->control_handler.error) { + ret = ch->control_handler.error; + dev_err(&dev->pdev->dev, + "ctrl handler ch%u error: %d\n", i, ret); + goto err_unwind; + } + v4l2_ctrl_handler_setup(&ch->control_handler); + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(&dev->pdev->dev, + "video_register_device ch%u failed: %d\n", i, + ret); + goto err_unwind; + } + } + + return 0; + +err_unwind: + for (; i >= 0; i--) { + struct hws_video *ch = &dev->video[i]; + + hws_video_release_registration(ch); + } + v4l2_device_unregister(&dev->v4l2_device); + return ret; +} + +void hws_video_unregister(struct hws_pcie_dev *dev) +{ + int i; + + if (!dev) + return; + + for (i = 0; i < dev->cur_max_video_ch; i++) { + struct hws_video *ch = &dev->video[i]; + + hws_video_release_registration(ch); + v4l2_ctrl_handler_free(&ch->control_handler); + } + v4l2_device_unregister(&dev->v4l2_device); +} + +int hws_video_quiesce(struct hws_pcie_dev *hws, const char *reason) +{ + int i, ret = 0; + u64 start_ns = ktime_get_mono_fast_ns(); + + dev_dbg(&hws->pdev->dev, "video:%s:begin channels=%u\n", reason, + hws->cur_max_video_ch); + for (i = 0; i < hws->cur_max_video_ch; i++) { + struct hws_video *vid = &hws->video[i]; + struct vb2_queue *q = &vid->buffer_queue; + u64 ch_start_ns = ktime_get_mono_fast_ns(); + bool streaming; + + if (!q || !q->ops) { + dev_dbg(&hws->pdev->dev, + "video:%s:ch=%d skipped queue-unavailable\n", + reason, i); + continue; + } + + streaming = vb2_is_streaming(q); + hws_log_video_state(vid, reason, "channel"); + if (streaming) { + /* Stop via vb2, which runs .stop_streaming. */ + int r = vb2_streamoff(q, q->type); + + dev_dbg(&hws->pdev->dev, + "video:%s:ch=%d streamoff ret=%d (%lluus)\n", + reason, i, r, hws_elapsed_us(ch_start_ns)); + if (r && !ret) + ret = r; + } else { + dev_dbg(&hws->pdev->dev, + "video:%s:ch=%d idle (%lluus)\n", + reason, i, hws_elapsed_us(ch_start_ns)); + } + } + dev_dbg(&hws->pdev->dev, "video:%s:done ret=%d (%lluus)\n", reason, + ret, hws_elapsed_us(start_ns)); + return ret; +} + +void hws_video_pm_resume(struct hws_pcie_dev *hws) +{ + /* Nothing mandatory to do here for vb2; userspace will STREAMON + * again when ready. + */ +} diff --git a/drivers/media/pci/hws/hws_video.h b/drivers/media/pci/hws/hws_video.h new file mode 100644 index 000000000..4feaf5b2f --- /dev/null +++ b/drivers/media/pci/hws/hws_video.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef HWS_VIDEO_H +#define HWS_VIDEO_H + +struct hws_video; + +int hws_video_register(struct hws_pcie_dev *dev); +void hws_video_unregister(struct hws_pcie_dev *dev); +void hws_enable_video_capture(struct hws_pcie_dev *hws, + unsigned int chan, + bool on); +void hws_prime_next_locked(struct hws_video *vid); + +int hws_video_init_channel(struct hws_pcie_dev *pdev, int ch); +void hws_video_cleanup_channel(struct hws_pcie_dev *pdev, int ch); +void check_video_format(struct hws_pcie_dev *pdx); +int hws_check_card_status(struct hws_pcie_dev *hws); +void hws_init_video_sys(struct hws_pcie_dev *hws, bool enable); + +void hws_program_dma_for_addr(struct hws_pcie_dev *hws, + unsigned int ch, + dma_addr_t dma); +void hws_set_dma_doorbell(struct hws_pcie_dev *hws, unsigned int ch, + dma_addr_t dma, const char *tag); + +int hws_video_quiesce(struct hws_pcie_dev *hws, const char *reason); +void hws_video_pm_resume(struct hws_pcie_dev *hws); + +#endif diff --git a/drivers/media/pci/intel/Kconfig b/drivers/media/pci/intel/Kconfig new file mode 100644 index 000000000..3f14ca110 --- /dev/null +++ b/drivers/media/pci/intel/Kconfig @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-only + +source "drivers/media/pci/intel/ipu3/Kconfig" +source "drivers/media/pci/intel/ipu6/Kconfig" +source "drivers/media/pci/intel/ivsc/Kconfig" + +config IPU_BRIDGE + tristate "Intel IPU Bridge" + depends on ACPI + depends on I2C + help + The IPU bridge is a helper library for Intel IPU drivers to + function on systems shipped with Windows. + + Currently used by the ipu3-cio2 and atomisp drivers. + + Supported systems include: + + - Microsoft Surface models (except Surface Pro 3) + - The Lenovo Miix line (for example the 510, 520, 710 and 720) + - Dell 7285 diff --git a/drivers/media/pci/intel/Makefile b/drivers/media/pci/intel/Makefile new file mode 100644 index 000000000..3a2cc6567 --- /dev/null +++ b/drivers/media/pci/intel/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the IPU drivers +# +obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o +obj-y += ipu3/ +obj-y += ivsc/ +obj-$(CONFIG_VIDEO_INTEL_IPU6) += ipu6/ diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c new file mode 100644 index 000000000..1bb3a3e98 --- /dev/null +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -0,0 +1,995 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Author: Dan Scally */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ADEV_DEV(adev) ACPI_PTR(&((adev)->dev)) + +/* + * 92335fcf-3203-4472-af93-7b4453ac29da + * + * Used to build MEI CSI device name to lookup MEI CSI device by + * device_find_child_by_name(). + */ +#define MEI_CSI_UUID \ + UUID_LE(0x92335FCF, 0x3203, 0x4472, \ + 0xAF, 0x93, 0x7B, 0x44, 0x53, 0xAC, 0x29, 0xDA) + +/* + * IVSC device name + * + * Used to match IVSC device by ipu_bridge_match_ivsc_dev() + */ +#define IVSC_DEV_NAME "intel_vsc" + +/* + * Extend this array with ACPI Hardware IDs of devices known to be working + * plus the number of link-frequencies expected by their drivers, along with + * the frequency values in hertz. This is somewhat opportunistic way of adding + * support for this for now in the hopes of a better source for the information + * (possibly some encoded value in the SSDB buffer that we're unaware of) + * becoming apparent in the future. + * + * Do not add an entry for a sensor that is not actually supported. + * + * Please keep the list sorted by ACPI HID. + */ +static const struct ipu_sensor_config ipu_supported_sensors[] = { + /* Himax HM1092 */ + IPU_SENSOR_CONFIG("HIMX1092", 2, 180000000, 180480000), + /* Himax HM11B1 */ + IPU_SENSOR_CONFIG("HIMX11B1", 1, 384000000), + /* Himax HM2170 */ + IPU_SENSOR_CONFIG("HIMX2170", 1, 384000000), + /* Himax HM2172 */ + IPU_SENSOR_CONFIG("HIMX2172", 1, 384000000), + /* GalaxyCore GC0310 */ + IPU_SENSOR_CONFIG("INT0310", 1, 55692000), + /* Omnivision OV5693 */ + IPU_SENSOR_CONFIG("INT33BE", 1, 419200000), + /* Onsemi MT9M114 */ + IPU_SENSOR_CONFIG("INT33F0", 1, 384000000), + /* Omnivision OV2740 */ + IPU_SENSOR_CONFIG("INT3474", 1, 180000000), + /* Omnivision OV5670 */ + IPU_SENSOR_CONFIG("INT3479", 1, 422400000), + /* Omnivision OV8865 */ + IPU_SENSOR_CONFIG("INT347A", 1, 360000000), + /* Omnivision OV7251 */ + IPU_SENSOR_CONFIG("INT347E", 1, 319200000), + /* Hynix Hi-556 */ + IPU_SENSOR_CONFIG("INT3537", 1, 437000000), + /* Lontium lt6911uxe */ + IPU_SENSOR_CONFIG("INTC10C5", 0), + /* Omnivision OV01A10 / OV01A1S */ + IPU_SENSOR_CONFIG("OVTI01A0", 1, 400000000), + IPU_SENSOR_CONFIG("OVTI01AS", 1, 400000000), + /* Omnivision OV02C10 */ + IPU_SENSOR_CONFIG("OVTI02C1", 1, 400000000), + /* Omnivision OV02E10 */ + IPU_SENSOR_CONFIG("OVTI02E1", 1, 360000000), + /* Omnivision ov05c10 */ + IPU_SENSOR_CONFIG("OVTI05C1", 1, 480000000), + /* Omnivision OV08A10 */ + IPU_SENSOR_CONFIG("OVTI08A1", 1, 500000000), + /* Omnivision OV08x40 */ + IPU_SENSOR_CONFIG("OVTI08F4", 3, 400000000, 749000000, 800000000), + /* Omnivision OV13B10 */ + IPU_SENSOR_CONFIG("OVTI13B1", 1, 560000000), + IPU_SENSOR_CONFIG("OVTIDB10", 1, 560000000), + /* Omnivision OV2680 */ + IPU_SENSOR_CONFIG("OVTI2680", 1, 331200000), + /* Omnivision OV5675 */ + IPU_SENSOR_CONFIG("OVTI5675", 1, 450000000), + /* Omnivision OV8856 */ + IPU_SENSOR_CONFIG("OVTI8856", 3, 180000000, 360000000, 720000000), + /* Sony IMX471 */ + IPU_SENSOR_CONFIG("SONY471A", 1, 200000000), + /* Sony IMX471 (found on Lenovo X1 Carbon G14) */ + IPU_SENSOR_CONFIG("TBE20A0", 1, 200000000), + /* Toshiba T4KA3 */ + IPU_SENSOR_CONFIG("XMCC0003", 1, 321468000), +}; + +/* + * DMI matches for laptops which have their sensor mounted upside-down + * without reporting a rotation of 180° in neither the SSDB nor the _PLD. + */ +static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 13 9340"), + }, + .driver_data = "OVTI02C1", + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 13 9350"), + }, + .driver_data = "OVTI02C1", + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 14 9440"), + }, + .driver_data = "OVTI02C1", + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 16 9640"), + }, + .driver_data = "OVTI02C1", + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 16 (Dell 16 Premium) DA16250"), + }, + .driver_data = "OVTI02C1", + }, + /* + * The first four characters of DMI_BOARD_NAME identify the Lenovo + * machine type/model. For example, a DMI_BOARD_NAME starting with + * "21Q6" indicates a ThinkPad X9-15. + * + * Reference: https://psref.lenovo.com/ + */ + { + /* Lenovo X9-14 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_BOARD_NAME, "21QA"), + }, + .driver_data = "SONY471A", + }, + { + /* Lenovo X9-14 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_BOARD_NAME, "21QB"), + }, + .driver_data = "SONY471A", + }, + { + /* Lenovo X9-15 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_BOARD_NAME, "21Q6"), + }, + .driver_data = "SONY471A", + }, + { + /* Lenovo X9-15 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_BOARD_NAME, "21Q7"), + }, + .driver_data = "SONY471A", + }, + { + /* Samsung Galaxy Book5 Pro 360 */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "960QHA"), + }, + .driver_data = "OVTI02E1", + }, + {} /* Terminating entry */ +}; + +static const struct ipu_property_names prop_names = { + .clock_frequency = "clock-frequency", + .rotation = "rotation", + .orientation = "orientation", + .bus_type = "bus-type", + .data_lanes = "data-lanes", + .remote_endpoint = "remote-endpoint", + .link_frequencies = "link-frequencies", +}; + +static const char * const ipu_vcm_types[] = { + "ad5823", + "dw9714", + "ad5816", + "dw9719", + "dw9718", + "dw9806b", + "wv517s", + "lc898122xa", + "lc898212axb", +}; + +/* + * Used to figure out IVSC acpi device by ipu_bridge_get_ivsc_acpi_dev() + * instead of device and driver match to probe IVSC device. + */ +static const struct acpi_device_id ivsc_acpi_ids[] = { + { "INTC1059" }, + { "INTC1095" }, + { "INTC100A" }, + { "INTC10CF" }, + { "INTC10DE" }, /* LNL */ + { "INTC10E0" }, /* ARL */ + { "INTC10E1" }, /* PTL */ + { "INTC10FA" }, /* NVL */ +}; + +static struct acpi_device *ipu_bridge_get_ivsc_acpi_dev(struct acpi_device *adev) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ivsc_acpi_ids); i++) { + const struct acpi_device_id *acpi_id = &ivsc_acpi_ids[i]; + struct acpi_device *consumer, *ivsc_adev; + acpi_handle handle = acpi_device_handle(ACPI_PTR(adev)); + + for_each_acpi_dev_match(ivsc_adev, acpi_id->id, NULL, -1) + /* camera sensor depends on IVSC in DSDT if exist */ + for_each_acpi_consumer_dev(ivsc_adev, consumer) + if (ACPI_PTR(consumer->handle) == handle) { + acpi_dev_put(consumer); + return ivsc_adev; + } + } + + return NULL; +} + +static int ipu_bridge_match_ivsc_dev(struct device *dev, const void *adev) +{ + if (ACPI_COMPANION(dev) != adev) + return 0; + + if (!sysfs_streq(dev_name(dev), IVSC_DEV_NAME)) + return 0; + + return 1; +} + +static struct device *ipu_bridge_get_ivsc_csi_dev(struct acpi_device *adev) +{ + struct device *dev, *csi_dev; + uuid_le uuid = MEI_CSI_UUID; + char name[64]; + + /* IVSC device on platform bus */ + dev = bus_find_device(&platform_bus_type, NULL, adev, + ipu_bridge_match_ivsc_dev); + if (dev) { + snprintf(name, sizeof(name), "%s-%pUl", dev_name(dev), &uuid); + + csi_dev = device_find_child_by_name(dev, name); + + put_device(dev); + + return csi_dev; + } + + /* Try to locate CVS device on the I2C bus */ + csi_dev = bus_find_device_by_acpi_dev(&i2c_bus_type, adev); + if (csi_dev) + return csi_dev; + + /* Fallback to platform bus for CVS device */ + return bus_find_device_by_acpi_dev(&platform_bus_type, adev); +} + +static int ipu_bridge_check_ivsc_dev(struct ipu_sensor *sensor, + struct acpi_device *sensor_adev) +{ + struct acpi_device *adev; + struct device *csi_dev; + + adev = ipu_bridge_get_ivsc_acpi_dev(sensor_adev); + if (adev) { + csi_dev = ipu_bridge_get_ivsc_csi_dev(adev); + if (!csi_dev) { + acpi_dev_put(adev); + dev_err(ADEV_DEV(adev), "Failed to find MEI or CVS CSI dev\n"); + return -ENODEV; + } + + sensor->csi_dev = csi_dev; + sensor->ivsc_adev = adev; + } + + return 0; +} + +static int ipu_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, + void *data, u32 size) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + int ret = 0; + + status = acpi_evaluate_object(ACPI_PTR(adev->handle), + id, NULL, &buffer); + if (ACPI_FAILURE(status)) + return -ENODEV; + + obj = buffer.pointer; + if (!obj) { + dev_err(ADEV_DEV(adev), "Couldn't locate ACPI buffer\n"); + return -ENODEV; + } + + if (obj->type != ACPI_TYPE_BUFFER) { + dev_err(ADEV_DEV(adev), "Not an ACPI buffer\n"); + ret = -ENODEV; + goto out_free_buff; + } + + if (obj->buffer.length > size) { + dev_err(ADEV_DEV(adev), "Given buffer is too small\n"); + ret = -EINVAL; + goto out_free_buff; + } + + memcpy(data, obj->buffer.pointer, obj->buffer.length); + +out_free_buff: + kfree(buffer.pointer); + return ret; +} + +static u32 ipu_bridge_parse_rotation(struct acpi_device *adev, + struct ipu_sensor_ssdb *ssdb) +{ + const struct dmi_system_id *dmi_id; + + /* A machine may have one entry per sensor, so check all matches. */ + for (dmi_id = dmi_first_match(upside_down_sensor_dmi_ids); dmi_id; + dmi_id = dmi_first_match(dmi_id + 1)) + if (acpi_dev_hid_match(adev, dmi_id->driver_data)) + return 180; + + switch (ssdb->degree) { + case IPU_SENSOR_ROTATION_NORMAL: + return 0; + case IPU_SENSOR_ROTATION_INVERTED: + return 180; + default: + dev_warn(ADEV_DEV(adev), + "Unknown rotation %d. Assume 0 degree rotation\n", + ssdb->degree); + return 0; + } +} + +static enum v4l2_fwnode_orientation ipu_bridge_parse_orientation(struct acpi_device *adev) +{ + enum v4l2_fwnode_orientation orientation; + struct acpi_pld_info *pld = NULL; + + if (!acpi_get_physical_device_location(ACPI_PTR(adev->handle), &pld)) { + dev_warn(ADEV_DEV(adev), "_PLD call failed, using default orientation\n"); + return V4L2_FWNODE_ORIENTATION_EXTERNAL; + } + + switch (pld->panel) { + case ACPI_PLD_PANEL_FRONT: + orientation = V4L2_FWNODE_ORIENTATION_FRONT; + break; + case ACPI_PLD_PANEL_BACK: + orientation = V4L2_FWNODE_ORIENTATION_BACK; + break; + case ACPI_PLD_PANEL_TOP: + case ACPI_PLD_PANEL_LEFT: + case ACPI_PLD_PANEL_RIGHT: + case ACPI_PLD_PANEL_UNKNOWN: + orientation = V4L2_FWNODE_ORIENTATION_EXTERNAL; + break; + default: + dev_warn(ADEV_DEV(adev), "Unknown _PLD panel val %d\n", + pld->panel); + orientation = V4L2_FWNODE_ORIENTATION_EXTERNAL; + break; + } + + ACPI_FREE(pld); + return orientation; +} + +int ipu_bridge_parse_ssdb(struct acpi_device *adev, struct ipu_sensor *sensor) +{ + struct ipu_sensor_ssdb ssdb = {}; + int ret; + + ret = ipu_bridge_read_acpi_buffer(adev, "SSDB", &ssdb, sizeof(ssdb)); + if (ret) + return ret; + + if (ssdb.vcmtype > ARRAY_SIZE(ipu_vcm_types)) { + dev_warn(ADEV_DEV(adev), "Unknown VCM type %d\n", ssdb.vcmtype); + ssdb.vcmtype = 0; + } + + if (ssdb.lanes > IPU_MAX_LANES) { + dev_err(ADEV_DEV(adev), "Number of lanes in SSDB is invalid\n"); + return -EINVAL; + } + + sensor->link = ssdb.link; + sensor->lanes = ssdb.lanes; + sensor->mclkspeed = ssdb.mclkspeed; + sensor->rotation = ipu_bridge_parse_rotation(adev, &ssdb); + sensor->orientation = ipu_bridge_parse_orientation(adev); + + if (ssdb.vcmtype) + sensor->vcm_type = ipu_vcm_types[ssdb.vcmtype - 1]; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu_bridge_parse_ssdb, "INTEL_IPU_BRIDGE"); + +static void ipu_bridge_create_fwnode_properties( + struct ipu_sensor *sensor, + struct ipu_bridge *bridge, + const struct ipu_sensor_config *cfg) +{ + struct ipu_property_names *names = &sensor->prop_names; + struct software_node *nodes = sensor->swnodes; + + sensor->prop_names = prop_names; + + if (sensor->csi_dev) { + sensor->local_ref[0] = + SOFTWARE_NODE_REFERENCE(&nodes[SWNODE_IVSC_SENSOR_ENDPOINT]); + sensor->remote_ref[0] = + SOFTWARE_NODE_REFERENCE(&nodes[SWNODE_IVSC_IPU_ENDPOINT]); + sensor->ivsc_sensor_ref[0] = + SOFTWARE_NODE_REFERENCE(&nodes[SWNODE_SENSOR_ENDPOINT]); + sensor->ivsc_ipu_ref[0] = + SOFTWARE_NODE_REFERENCE(&nodes[SWNODE_IPU_ENDPOINT]); + + sensor->ivsc_sensor_ep_properties[0] = + PROPERTY_ENTRY_U32(names->bus_type, + V4L2_FWNODE_BUS_TYPE_CSI2_DPHY); + sensor->ivsc_sensor_ep_properties[1] = + PROPERTY_ENTRY_U32_ARRAY_LEN(names->data_lanes, + bridge->data_lanes, + sensor->lanes); + sensor->ivsc_sensor_ep_properties[2] = + PROPERTY_ENTRY_REF_ARRAY(names->remote_endpoint, + sensor->ivsc_sensor_ref); + + sensor->ivsc_ipu_ep_properties[0] = + PROPERTY_ENTRY_U32(names->bus_type, + V4L2_FWNODE_BUS_TYPE_CSI2_DPHY); + sensor->ivsc_ipu_ep_properties[1] = + PROPERTY_ENTRY_U32_ARRAY_LEN(names->data_lanes, + bridge->data_lanes, + sensor->lanes); + sensor->ivsc_ipu_ep_properties[2] = + PROPERTY_ENTRY_REF_ARRAY(names->remote_endpoint, + sensor->ivsc_ipu_ref); + } else { + sensor->local_ref[0] = + SOFTWARE_NODE_REFERENCE(&nodes[SWNODE_IPU_ENDPOINT]); + sensor->remote_ref[0] = + SOFTWARE_NODE_REFERENCE(&nodes[SWNODE_SENSOR_ENDPOINT]); + } + + sensor->dev_properties[0] = PROPERTY_ENTRY_U32( + sensor->prop_names.clock_frequency, + sensor->mclkspeed); + sensor->dev_properties[1] = PROPERTY_ENTRY_U32( + sensor->prop_names.rotation, + sensor->rotation); + sensor->dev_properties[2] = PROPERTY_ENTRY_U32( + sensor->prop_names.orientation, + sensor->orientation); + if (sensor->vcm_type) { + sensor->vcm_ref[0] = + SOFTWARE_NODE_REFERENCE(&sensor->swnodes[SWNODE_VCM]); + sensor->dev_properties[3] = + PROPERTY_ENTRY_REF_ARRAY("lens-focus", sensor->vcm_ref); + } + + sensor->ep_properties[0] = PROPERTY_ENTRY_U32( + sensor->prop_names.bus_type, + V4L2_FWNODE_BUS_TYPE_CSI2_DPHY); + sensor->ep_properties[1] = PROPERTY_ENTRY_U32_ARRAY_LEN( + sensor->prop_names.data_lanes, + bridge->data_lanes, sensor->lanes); + sensor->ep_properties[2] = PROPERTY_ENTRY_REF_ARRAY( + sensor->prop_names.remote_endpoint, + sensor->local_ref); + + if (cfg->nr_link_freqs > 0) + sensor->ep_properties[3] = PROPERTY_ENTRY_U64_ARRAY_LEN( + sensor->prop_names.link_frequencies, + cfg->link_freqs, + cfg->nr_link_freqs); + + sensor->ipu_properties[0] = PROPERTY_ENTRY_U32_ARRAY_LEN( + sensor->prop_names.data_lanes, + bridge->data_lanes, sensor->lanes); + sensor->ipu_properties[1] = PROPERTY_ENTRY_REF_ARRAY( + sensor->prop_names.remote_endpoint, + sensor->remote_ref); +} + +static void ipu_bridge_init_swnode_names(struct ipu_sensor *sensor) +{ + snprintf(sensor->node_names.remote_port, + sizeof(sensor->node_names.remote_port), + SWNODE_GRAPH_PORT_NAME_FMT, sensor->link); + snprintf(sensor->node_names.port, + sizeof(sensor->node_names.port), + SWNODE_GRAPH_PORT_NAME_FMT, 0); /* Always port 0 */ + snprintf(sensor->node_names.endpoint, + sizeof(sensor->node_names.endpoint), + SWNODE_GRAPH_ENDPOINT_NAME_FMT, 0); /* And endpoint 0 */ + if (sensor->vcm_type) { + /* append link to distinguish nodes with same model VCM */ + snprintf(sensor->node_names.vcm, sizeof(sensor->node_names.vcm), + "%s-%u", sensor->vcm_type, sensor->link); + } + + if (sensor->csi_dev) { + snprintf(sensor->node_names.ivsc_sensor_port, + sizeof(sensor->node_names.ivsc_sensor_port), + SWNODE_GRAPH_PORT_NAME_FMT, 0); + snprintf(sensor->node_names.ivsc_ipu_port, + sizeof(sensor->node_names.ivsc_ipu_port), + SWNODE_GRAPH_PORT_NAME_FMT, 1); + } +} + +static void ipu_bridge_init_swnode_group(struct ipu_sensor *sensor) +{ + struct software_node *nodes = sensor->swnodes; + + sensor->group[SWNODE_SENSOR_HID] = &nodes[SWNODE_SENSOR_HID]; + sensor->group[SWNODE_SENSOR_PORT] = &nodes[SWNODE_SENSOR_PORT]; + sensor->group[SWNODE_SENSOR_ENDPOINT] = &nodes[SWNODE_SENSOR_ENDPOINT]; + sensor->group[SWNODE_IPU_PORT] = &nodes[SWNODE_IPU_PORT]; + sensor->group[SWNODE_IPU_ENDPOINT] = &nodes[SWNODE_IPU_ENDPOINT]; + if (sensor->vcm_type) + sensor->group[SWNODE_VCM] = &nodes[SWNODE_VCM]; + + if (sensor->csi_dev) { + sensor->group[SWNODE_IVSC_HID] = + &nodes[SWNODE_IVSC_HID]; + sensor->group[SWNODE_IVSC_SENSOR_PORT] = + &nodes[SWNODE_IVSC_SENSOR_PORT]; + sensor->group[SWNODE_IVSC_SENSOR_ENDPOINT] = + &nodes[SWNODE_IVSC_SENSOR_ENDPOINT]; + sensor->group[SWNODE_IVSC_IPU_PORT] = + &nodes[SWNODE_IVSC_IPU_PORT]; + sensor->group[SWNODE_IVSC_IPU_ENDPOINT] = + &nodes[SWNODE_IVSC_IPU_ENDPOINT]; + + if (sensor->vcm_type) + sensor->group[SWNODE_VCM] = &nodes[SWNODE_VCM]; + } else { + if (sensor->vcm_type) + sensor->group[SWNODE_IVSC_HID] = &nodes[SWNODE_VCM]; + } +} + +static void ipu_bridge_create_connection_swnodes(struct ipu_bridge *bridge, + struct ipu_sensor *sensor) +{ + struct ipu_node_names *names = &sensor->node_names; + struct software_node *nodes = sensor->swnodes; + + ipu_bridge_init_swnode_names(sensor); + + nodes[SWNODE_SENSOR_HID] = NODE_SENSOR(sensor->name, + sensor->dev_properties); + nodes[SWNODE_SENSOR_PORT] = NODE_PORT(sensor->node_names.port, + &nodes[SWNODE_SENSOR_HID]); + nodes[SWNODE_SENSOR_ENDPOINT] = NODE_ENDPOINT( + sensor->node_names.endpoint, + &nodes[SWNODE_SENSOR_PORT], + sensor->ep_properties); + nodes[SWNODE_IPU_PORT] = NODE_PORT(sensor->node_names.remote_port, + &bridge->ipu_hid_node); + nodes[SWNODE_IPU_ENDPOINT] = NODE_ENDPOINT( + sensor->node_names.endpoint, + &nodes[SWNODE_IPU_PORT], + sensor->ipu_properties); + + if (sensor->csi_dev) { + const char *device_hid = ""; + + device_hid = acpi_device_hid(sensor->ivsc_adev); + + snprintf(sensor->ivsc_name, sizeof(sensor->ivsc_name), "%s-%u", + device_hid, sensor->link); + + nodes[SWNODE_IVSC_HID] = NODE_SENSOR(sensor->ivsc_name, + sensor->ivsc_properties); + nodes[SWNODE_IVSC_SENSOR_PORT] = + NODE_PORT(names->ivsc_sensor_port, + &nodes[SWNODE_IVSC_HID]); + nodes[SWNODE_IVSC_SENSOR_ENDPOINT] = + NODE_ENDPOINT(names->endpoint, + &nodes[SWNODE_IVSC_SENSOR_PORT], + sensor->ivsc_sensor_ep_properties); + nodes[SWNODE_IVSC_IPU_PORT] = + NODE_PORT(names->ivsc_ipu_port, + &nodes[SWNODE_IVSC_HID]); + nodes[SWNODE_IVSC_IPU_ENDPOINT] = + NODE_ENDPOINT(names->endpoint, + &nodes[SWNODE_IVSC_IPU_PORT], + sensor->ivsc_ipu_ep_properties); + } + + nodes[SWNODE_VCM] = NODE_VCM(sensor->node_names.vcm); + + ipu_bridge_init_swnode_group(sensor); +} + +/* + * The actual instantiation must be done from a workqueue to avoid + * a deadlock on taking list_lock from v4l2-async twice. + */ +struct ipu_bridge_instantiate_vcm_work_data { + struct work_struct work; + struct device *sensor; + char name[16]; + struct i2c_board_info board_info; +}; + +static void ipu_bridge_instantiate_vcm_work(struct work_struct *work) +{ + struct ipu_bridge_instantiate_vcm_work_data *data = + container_of(work, struct ipu_bridge_instantiate_vcm_work_data, + work); + struct acpi_device *adev = ACPI_COMPANION(data->sensor); + struct i2c_client *vcm_client; + bool put_fwnode = true; + int ret; + + /* + * The client may get probed before the device_link gets added below + * make sure the sensor is powered-up during probe. + */ + ret = pm_runtime_get_sync(data->sensor); + if (ret < 0) { + dev_err(data->sensor, "Error %d runtime-resuming sensor, cannot instantiate VCM\n", + ret); + goto out_pm_put; + } + + /* + * Note the client is created only once and then kept around + * even after a rmmod, just like the software-nodes. + */ + vcm_client = i2c_acpi_new_device_by_fwnode(acpi_fwnode_handle(adev), + 1, &data->board_info); + if (IS_ERR(vcm_client)) { + dev_err(data->sensor, "Error instantiating VCM client: %pe\n", + vcm_client); + goto out_pm_put; + } + + device_link_add(&vcm_client->dev, data->sensor, DL_FLAG_PM_RUNTIME); + + dev_info(data->sensor, "Instantiated %s VCM\n", data->board_info.type); + put_fwnode = false; /* Ownership has passed to the i2c-client */ + +out_pm_put: + pm_runtime_put(data->sensor); + put_device(data->sensor); + if (put_fwnode) + fwnode_handle_put(data->board_info.fwnode); + kfree(data); +} + +int ipu_bridge_instantiate_vcm(struct device *sensor) +{ + struct ipu_bridge_instantiate_vcm_work_data *data; + struct fwnode_handle *vcm_fwnode; + struct i2c_client *vcm_client; + struct acpi_device *adev; + char *sep; + + adev = ACPI_COMPANION(sensor); + if (!adev) + return 0; + + vcm_fwnode = fwnode_find_reference(dev_fwnode(sensor), "lens-focus", 0); + if (IS_ERR(vcm_fwnode)) + return 0; + + /* When reloading modules the client will already exist */ + vcm_client = i2c_find_device_by_fwnode(vcm_fwnode); + if (vcm_client) { + fwnode_handle_put(vcm_fwnode); + put_device(&vcm_client->dev); + return 0; + } + + data = kzalloc_obj(*data); + if (!data) { + fwnode_handle_put(vcm_fwnode); + return -ENOMEM; + } + + INIT_WORK(&data->work, ipu_bridge_instantiate_vcm_work); + data->sensor = get_device(sensor); + snprintf(data->name, sizeof(data->name), "%s-VCM", + acpi_dev_name(adev)); + data->board_info.dev_name = data->name; + data->board_info.fwnode = vcm_fwnode; + snprintf(data->board_info.type, sizeof(data->board_info.type), + "%pfwP", vcm_fwnode); + /* Strip "-" postfix */ + sep = strchrnul(data->board_info.type, '-'); + *sep = 0; + + queue_work(system_long_wq, &data->work); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu_bridge_instantiate_vcm, "INTEL_IPU_BRIDGE"); + +static int ipu_bridge_instantiate_ivsc(struct ipu_sensor *sensor) +{ + struct fwnode_handle *fwnode; + + if (!sensor->csi_dev) + return 0; + + fwnode = software_node_fwnode(&sensor->swnodes[SWNODE_IVSC_HID]); + if (!fwnode) + return -ENODEV; + + set_secondary_fwnode(sensor->csi_dev, fwnode); + + return 0; +} + +static void ipu_bridge_unregister_sensors(struct ipu_bridge *bridge) +{ + struct ipu_sensor *sensor; + unsigned int i; + + for (i = 0; i < bridge->n_sensors; i++) { + sensor = &bridge->sensors[i]; + software_node_unregister_node_group(sensor->group); + acpi_dev_put(sensor->adev); + put_device(sensor->csi_dev); + acpi_dev_put(sensor->ivsc_adev); + } +} + +static int ipu_bridge_connect_sensor(const struct ipu_sensor_config *cfg, + struct ipu_bridge *bridge) +{ + struct fwnode_handle *fwnode, *primary; + struct ipu_sensor *sensor; + struct acpi_device *adev = NULL; + int ret; + + for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) { + if (!ACPI_PTR(adev->status.enabled)) + continue; + + if (bridge->n_sensors >= IPU_MAX_PORTS) { + acpi_dev_put(adev); + dev_err(bridge->dev, "Exceeded available IPU ports\n"); + return -EINVAL; + } + + sensor = &bridge->sensors[bridge->n_sensors]; + + ret = bridge->parse_sensor_fwnode(adev, sensor); + if (ret) + goto err_put_adev; + + snprintf(sensor->name, sizeof(sensor->name), "%s-%u", + cfg->hid, sensor->link); + + ret = ipu_bridge_check_ivsc_dev(sensor, adev); + if (ret) + goto err_put_adev; + + ipu_bridge_create_fwnode_properties(sensor, bridge, cfg); + ipu_bridge_create_connection_swnodes(bridge, sensor); + + ret = software_node_register_node_group(sensor->group); + if (ret) + goto err_put_ivsc; + + fwnode = software_node_fwnode(&sensor->swnodes[ + SWNODE_SENSOR_HID]); + if (!fwnode) { + ret = -ENODEV; + goto err_free_swnodes; + } + + sensor->adev = ACPI_PTR(acpi_dev_get(adev)); + + primary = acpi_fwnode_handle(adev); + primary->secondary = fwnode; + + ret = ipu_bridge_instantiate_ivsc(sensor); + if (ret) + goto err_free_swnodes; + + dev_info(bridge->dev, "Found supported sensor %s\n", + acpi_dev_name(adev)); + + bridge->n_sensors++; + } + + return 0; + +err_free_swnodes: + software_node_unregister_node_group(sensor->group); +err_put_ivsc: + put_device(sensor->csi_dev); + acpi_dev_put(sensor->ivsc_adev); +err_put_adev: + acpi_dev_put(adev); + return ret; +} + +static int ipu_bridge_connect_sensors(struct ipu_bridge *bridge) +{ + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(ipu_supported_sensors); i++) { + const struct ipu_sensor_config *cfg = + &ipu_supported_sensors[i]; + + ret = ipu_bridge_connect_sensor(cfg, bridge); + if (ret) + goto err_unregister_sensors; + } + + return 0; + +err_unregister_sensors: + ipu_bridge_unregister_sensors(bridge); + return ret; +} + +static int ipu_bridge_ivsc_is_ready(void) +{ + struct acpi_device *sensor_adev, *adev; + struct device *csi_dev; + bool ready = true; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ipu_supported_sensors); i++) { + const struct ipu_sensor_config *cfg = + &ipu_supported_sensors[i]; + + for_each_acpi_dev_match(sensor_adev, cfg->hid, NULL, -1) { + if (!ACPI_PTR(sensor_adev->status.enabled)) + continue; + + adev = ipu_bridge_get_ivsc_acpi_dev(sensor_adev); + if (!adev) + continue; + + csi_dev = ipu_bridge_get_ivsc_csi_dev(adev); + if (!csi_dev) + ready = false; + + put_device(csi_dev); + acpi_dev_put(adev); + } + } + + return ready; +} + +static int ipu_bridge_check_fwnode_graph(struct fwnode_handle *fwnode) +{ + struct fwnode_handle *endpoint; + + if (IS_ERR_OR_NULL(fwnode)) + return -EINVAL; + + endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (endpoint) { + fwnode_handle_put(endpoint); + return 0; + } + + return ipu_bridge_check_fwnode_graph(fwnode->secondary); +} + +static DEFINE_MUTEX(ipu_bridge_mutex); + +int ipu_bridge_init(struct device *dev, + ipu_parse_sensor_fwnode_t parse_sensor_fwnode) +{ + struct fwnode_handle *fwnode; + struct ipu_bridge *bridge; + unsigned int i; + int ret; + + guard(mutex)(&ipu_bridge_mutex); + + if (!ipu_bridge_check_fwnode_graph(dev_fwnode(dev))) + return 0; + + if (!ipu_bridge_ivsc_is_ready()) + return dev_err_probe(dev, -EPROBE_DEFER, + "waiting for IVSC to become ready\n"); + + bridge = kzalloc_obj(*bridge); + if (!bridge) + return -ENOMEM; + + strscpy(bridge->ipu_node_name, IPU_HID, + sizeof(bridge->ipu_node_name)); + bridge->ipu_hid_node.name = bridge->ipu_node_name; + bridge->dev = dev; + bridge->parse_sensor_fwnode = parse_sensor_fwnode; + + ret = software_node_register(&bridge->ipu_hid_node); + if (ret < 0) { + dev_err(dev, "Failed to register the IPU HID node\n"); + goto err_free_bridge; + } + + /* + * Map the lane arrangement, which is fixed for the IPU3 (meaning we + * only need one, rather than one per sensor). We include it as a + * member of the struct ipu_bridge rather than a global variable so + * that it survives if the module is unloaded along with the rest of + * the struct. + */ + for (i = 0; i < IPU_MAX_LANES; i++) + bridge->data_lanes[i] = i + 1; + + ret = ipu_bridge_connect_sensors(bridge); + if (ret || bridge->n_sensors == 0) + goto err_unregister_ipu; + + dev_info(dev, "Connected %d cameras\n", bridge->n_sensors); + + fwnode = software_node_fwnode(&bridge->ipu_hid_node); + if (!fwnode) { + dev_err(dev, "Error getting fwnode from ipu software_node\n"); + ret = -ENODEV; + goto err_unregister_sensors; + } + + set_secondary_fwnode(dev, fwnode); + + return 0; + +err_unregister_sensors: + ipu_bridge_unregister_sensors(bridge); +err_unregister_ipu: + software_node_unregister(&bridge->ipu_hid_node); +err_free_bridge: + kfree(bridge); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(ipu_bridge_init, "INTEL_IPU_BRIDGE"); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Intel IPU Sensors Bridge driver"); diff --git a/drivers/media/pci/intel/ipu3/Kconfig b/drivers/media/pci/intel/ipu3/Kconfig new file mode 100644 index 000000000..c0a250daa --- /dev/null +++ b/drivers/media/pci/intel/ipu3/Kconfig @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_IPU3_CIO2 + tristate "Intel ipu3-cio2 driver" + depends on VIDEO_DEV && PCI + depends on IPU_BRIDGE || !IPU_BRIDGE + depends on ACPI || COMPILE_TEST + depends on X86 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + select VIDEOBUF2_DMA_SG + + help + This is the Intel IPU3 CIO2 CSI-2 receiver unit, found in Intel + Skylake and Kaby Lake SoCs and used for capturing images and + video from a camera sensor. + + Say Y or M here if you have a Skylake/Kaby Lake SoC with MIPI CSI-2 + connected camera. + The module will be called ipu3-cio2. diff --git a/drivers/media/pci/intel/ipu3/Makefile b/drivers/media/pci/intel/ipu3/Makefile new file mode 100644 index 000000000..98ddd5bea --- /dev/null +++ b/drivers/media/pci/intel/ipu3/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_VIDEO_IPU3_CIO2) += ipu3-cio2.o diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.c b/drivers/media/pci/intel/ipu3/ipu3-cio2.c new file mode 100644 index 000000000..eb1824ee8 --- /dev/null +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.c @@ -0,0 +1,1989 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017,2020 Intel Corporation + * + * Based partially on Intel IPU4 driver written by + * Sakari Ailus + * Samu Onkalo + * Jouni Högander + * Jouni Ukkonen + * Antti Laakso + * et al. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipu3-cio2.h" + +struct ipu3_cio2_fmt { + u32 mbus_code; + u32 fourcc; + u8 mipicode; + u8 bpp; +}; + +/* + * These are raw formats used in Intel's third generation of + * Image Processing Unit known as IPU3. + * 10bit raw bayer packed, 32 bytes for every 25 pixels, + * last LSB 6 bits unused. + */ +static const struct ipu3_cio2_fmt formats[] = { + { /* put default entry at beginning */ + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .fourcc = V4L2_PIX_FMT_IPU3_SGRBG10, + .mipicode = 0x2b, + .bpp = 10, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .fourcc = V4L2_PIX_FMT_IPU3_SGBRG10, + .mipicode = 0x2b, + .bpp = 10, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .fourcc = V4L2_PIX_FMT_IPU3_SBGGR10, + .mipicode = 0x2b, + .bpp = 10, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .fourcc = V4L2_PIX_FMT_IPU3_SRGGB10, + .mipicode = 0x2b, + .bpp = 10, + }, { + .mbus_code = MEDIA_BUS_FMT_Y10_1X10, + .fourcc = V4L2_PIX_FMT_IPU3_Y10, + .mipicode = 0x2b, + .bpp = 10, + }, +}; + +/* + * cio2_find_format - lookup color format by fourcc or/and media bus code + * @pixelformat: fourcc to match, ignored if null + * @mbus_code: media bus code to match, ignored if null + */ +static const struct ipu3_cio2_fmt *cio2_find_format(const u32 *pixelformat, + const u32 *mbus_code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (pixelformat && *pixelformat != formats[i].fourcc) + continue; + if (mbus_code && *mbus_code != formats[i].mbus_code) + continue; + + return &formats[i]; + } + + return NULL; +} + +static inline u32 cio2_bytesperline(const unsigned int width) +{ + /* + * 64 bytes for every 50 pixels, the line length + * in bytes is multiple of 64 (line end alignment). + */ + return DIV_ROUND_UP(width, 50) * 64; +} + +/**************** FBPT operations ****************/ + +static void cio2_fbpt_exit_dummy(struct cio2_device *cio2) +{ + struct device *dev = &cio2->pci_dev->dev; + + if (cio2->dummy_lop) { + dma_free_coherent(dev, PAGE_SIZE, cio2->dummy_lop, + cio2->dummy_lop_bus_addr); + cio2->dummy_lop = NULL; + } + if (cio2->dummy_page) { + dma_free_coherent(dev, PAGE_SIZE, cio2->dummy_page, + cio2->dummy_page_bus_addr); + cio2->dummy_page = NULL; + } +} + +static int cio2_fbpt_init_dummy(struct cio2_device *cio2) +{ + struct device *dev = &cio2->pci_dev->dev; + unsigned int i; + + cio2->dummy_page = dma_alloc_coherent(dev, PAGE_SIZE, + &cio2->dummy_page_bus_addr, + GFP_KERNEL); + cio2->dummy_lop = dma_alloc_coherent(dev, PAGE_SIZE, + &cio2->dummy_lop_bus_addr, + GFP_KERNEL); + if (!cio2->dummy_page || !cio2->dummy_lop) { + cio2_fbpt_exit_dummy(cio2); + return -ENOMEM; + } + /* + * List of Pointers(LOP) contains 1024x32b pointers to 4KB page each + * Initialize each entry to dummy_page bus base address. + */ + for (i = 0; i < CIO2_LOP_ENTRIES; i++) + cio2->dummy_lop[i] = PFN_DOWN(cio2->dummy_page_bus_addr); + + return 0; +} + +static void cio2_fbpt_entry_enable(struct cio2_device *cio2, + struct cio2_fbpt_entry entry[CIO2_MAX_LOPS]) +{ + /* + * The CPU first initializes some fields in fbpt, then sets + * the VALID bit, this barrier is to ensure that the DMA(device) + * does not see the VALID bit enabled before other fields are + * initialized; otherwise it could lead to havoc. + */ + dma_wmb(); + + /* + * Request interrupts for start and completion + * Valid bit is applicable only to 1st entry + */ + entry[0].first_entry.ctrl = CIO2_FBPT_CTRL_VALID | + CIO2_FBPT_CTRL_IOC | CIO2_FBPT_CTRL_IOS; +} + +/* Initialize fpbt entries to point to dummy frame */ +static void cio2_fbpt_entry_init_dummy(struct cio2_device *cio2, + struct cio2_fbpt_entry + entry[CIO2_MAX_LOPS]) +{ + unsigned int i; + + entry[0].first_entry.first_page_offset = 0; + entry[1].second_entry.num_of_pages = CIO2_LOP_ENTRIES * CIO2_MAX_LOPS; + entry[1].second_entry.last_page_available_bytes = PAGE_SIZE - 1; + + for (i = 0; i < CIO2_MAX_LOPS; i++) + entry[i].lop_page_addr = PFN_DOWN(cio2->dummy_lop_bus_addr); + + cio2_fbpt_entry_enable(cio2, entry); +} + +/* Initialize fpbt entries to point to a given buffer */ +static void cio2_fbpt_entry_init_buf(struct cio2_device *cio2, + struct cio2_buffer *b, + struct cio2_fbpt_entry + entry[CIO2_MAX_LOPS]) +{ + struct vb2_buffer *vb = &b->vbb.vb2_buf; + unsigned int length = vb->planes[0].length; + int remaining, i; + + entry[0].first_entry.first_page_offset = b->offset; + remaining = length + entry[0].first_entry.first_page_offset; + entry[1].second_entry.num_of_pages = PFN_UP(remaining); + /* + * last_page_available_bytes has the offset of the last byte in the + * last page which is still accessible by DMA. DMA cannot access + * beyond this point. Valid range for this is from 0 to 4095. + * 0 indicates 1st byte in the page is DMA accessible. + * 4095 (PAGE_SIZE - 1) means every single byte in the last page + * is available for DMA transfer. + */ + remaining = offset_in_page(remaining) ?: PAGE_SIZE; + entry[1].second_entry.last_page_available_bytes = remaining - 1; + /* Fill FBPT */ + remaining = length; + i = 0; + while (remaining > 0) { + entry->lop_page_addr = PFN_DOWN(b->lop_bus_addr[i]); + remaining -= CIO2_LOP_ENTRIES * PAGE_SIZE; + entry++; + i++; + } + + /* + * The first not meaningful FBPT entry should point to a valid LOP + */ + entry->lop_page_addr = PFN_DOWN(cio2->dummy_lop_bus_addr); + + cio2_fbpt_entry_enable(cio2, entry); +} + +static int cio2_fbpt_init(struct cio2_device *cio2, struct cio2_queue *q) +{ + struct device *dev = &cio2->pci_dev->dev; + + q->fbpt = dma_alloc_coherent(dev, CIO2_FBPT_SIZE, &q->fbpt_bus_addr, + GFP_KERNEL); + if (!q->fbpt) + return -ENOMEM; + + return 0; +} + +static void cio2_fbpt_exit(struct cio2_queue *q, struct device *dev) +{ + dma_free_coherent(dev, CIO2_FBPT_SIZE, q->fbpt, q->fbpt_bus_addr); +} + +/**************** CSI2 hardware setup ****************/ + +/* + * The CSI2 receiver has several parameters affecting + * the receiver timings. These depend on the MIPI bus frequency + * F in Hz (sensor transmitter rate) as follows: + * register value = (A/1e9 + B * UI) / COUNT_ACC + * where + * UI = 1 / (2 * F) in seconds + * COUNT_ACC = counter accuracy in seconds + * For IPU3 COUNT_ACC = 0.0625 + * + * A and B are coefficients from the table below, + * depending whether the register minimum or maximum value is + * calculated. + * Minimum Maximum + * Clock lane A B A B + * reg_rx_csi_dly_cnt_termen_clane 0 0 38 0 + * reg_rx_csi_dly_cnt_settle_clane 95 -8 300 -16 + * Data lanes + * reg_rx_csi_dly_cnt_termen_dlane0 0 0 35 4 + * reg_rx_csi_dly_cnt_settle_dlane0 85 -2 145 -6 + * reg_rx_csi_dly_cnt_termen_dlane1 0 0 35 4 + * reg_rx_csi_dly_cnt_settle_dlane1 85 -2 145 -6 + * reg_rx_csi_dly_cnt_termen_dlane2 0 0 35 4 + * reg_rx_csi_dly_cnt_settle_dlane2 85 -2 145 -6 + * reg_rx_csi_dly_cnt_termen_dlane3 0 0 35 4 + * reg_rx_csi_dly_cnt_settle_dlane3 85 -2 145 -6 + * + * We use the minimum values of both A and B. + */ + +/* + * shift for keeping value range suitable for 32-bit integer arithmetic + */ +#define LIMIT_SHIFT 8 + +static s32 cio2_rx_timing(s32 a, s32 b, s64 freq, int def) +{ + const u32 accinv = 16; /* invert of counter resolution */ + const u32 uiinv = 500000000; /* 1e9 / 2 */ + s32 r; + + freq >>= LIMIT_SHIFT; + + if (WARN_ON(freq <= 0 || freq > S32_MAX)) + return def; + /* + * b could be 0, -2 or -8, so |accinv * b| is always + * less than (1 << ds) and thus |r| < 500000000. + */ + r = accinv * b * (uiinv >> LIMIT_SHIFT); + r = r / (s32)freq; + /* max value of a is 95 */ + r += accinv * a; + + return r; +}; + +/* Calculate the delay value for termination enable of clock lane HS Rx */ +static int cio2_csi2_calc_timing(struct cio2_device *cio2, struct cio2_queue *q, + struct cio2_csi2_timing *timing, + unsigned int bpp, unsigned int lanes) +{ + struct device *dev = &cio2->pci_dev->dev; + struct media_pad *src_pad; + s64 freq; + + src_pad = media_entity_remote_source_pad_unique(&q->subdev.entity); + if (IS_ERR(src_pad)) { + dev_err(dev, "can't get source pad of %s (%pe)\n", + q->subdev.name, src_pad); + return PTR_ERR(src_pad); + } + + freq = v4l2_get_link_freq(src_pad, bpp, lanes * 2); + if (freq < 0) { + dev_err(dev, "error %lld, invalid link_freq\n", freq); + return freq; + } + + timing->clk_termen = cio2_rx_timing(CIO2_CSIRX_DLY_CNT_TERMEN_CLANE_A, + CIO2_CSIRX_DLY_CNT_TERMEN_CLANE_B, + freq, + CIO2_CSIRX_DLY_CNT_TERMEN_DEFAULT); + timing->clk_settle = cio2_rx_timing(CIO2_CSIRX_DLY_CNT_SETTLE_CLANE_A, + CIO2_CSIRX_DLY_CNT_SETTLE_CLANE_B, + freq, + CIO2_CSIRX_DLY_CNT_SETTLE_DEFAULT); + timing->dat_termen = cio2_rx_timing(CIO2_CSIRX_DLY_CNT_TERMEN_DLANE_A, + CIO2_CSIRX_DLY_CNT_TERMEN_DLANE_B, + freq, + CIO2_CSIRX_DLY_CNT_TERMEN_DEFAULT); + timing->dat_settle = cio2_rx_timing(CIO2_CSIRX_DLY_CNT_SETTLE_DLANE_A, + CIO2_CSIRX_DLY_CNT_SETTLE_DLANE_B, + freq, + CIO2_CSIRX_DLY_CNT_SETTLE_DEFAULT); + + dev_dbg(dev, "freq ct value is %d\n", timing->clk_termen); + dev_dbg(dev, "freq cs value is %d\n", timing->clk_settle); + dev_dbg(dev, "freq dt value is %d\n", timing->dat_termen); + dev_dbg(dev, "freq ds value is %d\n", timing->dat_settle); + + return 0; +}; + +static int cio2_hw_init(struct cio2_device *cio2, struct cio2_queue *q) +{ + static const int NUM_VCS = 4; + static const int SID; /* Stream id */ + static const int ENTRY; + static const int FBPT_WIDTH = DIV_ROUND_UP(CIO2_MAX_LOPS, + CIO2_FBPT_SUBENTRY_UNIT); + const u32 num_buffers1 = CIO2_MAX_BUFFERS - 1; + struct v4l2_subdev_state *state; + const struct v4l2_mbus_framefmt *format; + const struct ipu3_cio2_fmt *fmt; + void __iomem *const base = cio2->base; + u8 lanes, csi2bus = q->csi2.port; + u8 sensor_vc = SENSOR_VIR_CH_DFLT; + struct cio2_csi2_timing timing = { 0 }; + int i, r; + + state = v4l2_subdev_lock_and_get_active_state(&q->subdev); + format = v4l2_subdev_state_get_format(state, CIO2_PAD_SINK); + + fmt = cio2_find_format(NULL, &format->code); + + v4l2_subdev_unlock_state(state); + + if (!fmt) + return -EINVAL; + + lanes = q->csi2.lanes; + + r = cio2_csi2_calc_timing(cio2, q, &timing, fmt->bpp, lanes); + if (r) + return r; + + writel(timing.clk_termen, q->csi_rx_base + + CIO2_REG_CSIRX_DLY_CNT_TERMEN(CIO2_CSIRX_DLY_CNT_CLANE_IDX)); + writel(timing.clk_settle, q->csi_rx_base + + CIO2_REG_CSIRX_DLY_CNT_SETTLE(CIO2_CSIRX_DLY_CNT_CLANE_IDX)); + + for (i = 0; i < lanes; i++) { + writel(timing.dat_termen, q->csi_rx_base + + CIO2_REG_CSIRX_DLY_CNT_TERMEN(i)); + writel(timing.dat_settle, q->csi_rx_base + + CIO2_REG_CSIRX_DLY_CNT_SETTLE(i)); + } + + writel(CIO2_PBM_WMCTRL1_MIN_2CK | + CIO2_PBM_WMCTRL1_MID1_2CK | + CIO2_PBM_WMCTRL1_MID2_2CK, base + CIO2_REG_PBM_WMCTRL1); + writel(CIO2_PBM_WMCTRL2_HWM_2CK << CIO2_PBM_WMCTRL2_HWM_2CK_SHIFT | + CIO2_PBM_WMCTRL2_LWM_2CK << CIO2_PBM_WMCTRL2_LWM_2CK_SHIFT | + CIO2_PBM_WMCTRL2_OBFFWM_2CK << + CIO2_PBM_WMCTRL2_OBFFWM_2CK_SHIFT | + CIO2_PBM_WMCTRL2_TRANSDYN << CIO2_PBM_WMCTRL2_TRANSDYN_SHIFT | + CIO2_PBM_WMCTRL2_OBFF_MEM_EN, base + CIO2_REG_PBM_WMCTRL2); + writel(CIO2_PBM_ARB_CTRL_LANES_DIV << + CIO2_PBM_ARB_CTRL_LANES_DIV_SHIFT | + CIO2_PBM_ARB_CTRL_LE_EN | + CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN << + CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN_SHIFT | + CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP << + CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP_SHIFT, + base + CIO2_REG_PBM_ARB_CTRL); + writel(CIO2_CSIRX_STATUS_DLANE_HS_MASK, + q->csi_rx_base + CIO2_REG_CSIRX_STATUS_DLANE_HS); + writel(CIO2_CSIRX_STATUS_DLANE_LP_MASK, + q->csi_rx_base + CIO2_REG_CSIRX_STATUS_DLANE_LP); + + writel(CIO2_FB_HPLL_FREQ, base + CIO2_REG_FB_HPLL_FREQ); + writel(CIO2_ISCLK_RATIO, base + CIO2_REG_ISCLK_RATIO); + + /* Configure MIPI backend */ + for (i = 0; i < NUM_VCS; i++) + writel(1, q->csi_rx_base + CIO2_REG_MIPIBE_SP_LUT_ENTRY(i)); + + /* There are 16 short packet LUT entry */ + for (i = 0; i < 16; i++) + writel(CIO2_MIPIBE_LP_LUT_ENTRY_DISREGARD, + q->csi_rx_base + CIO2_REG_MIPIBE_LP_LUT_ENTRY(i)); + writel(CIO2_MIPIBE_GLOBAL_LUT_DISREGARD, + q->csi_rx_base + CIO2_REG_MIPIBE_GLOBAL_LUT_DISREGARD); + + writel(CIO2_INT_EN_EXT_IE_MASK, base + CIO2_REG_INT_EN_EXT_IE); + writel(CIO2_IRQCTRL_MASK, q->csi_rx_base + CIO2_REG_IRQCTRL_MASK); + writel(CIO2_IRQCTRL_MASK, q->csi_rx_base + CIO2_REG_IRQCTRL_ENABLE); + writel(0, q->csi_rx_base + CIO2_REG_IRQCTRL_EDGE); + writel(0, q->csi_rx_base + CIO2_REG_IRQCTRL_LEVEL_NOT_PULSE); + writel(CIO2_INT_EN_EXT_OE_MASK, base + CIO2_REG_INT_EN_EXT_OE); + + writel(CIO2_REG_INT_EN_IRQ | CIO2_INT_IOC(CIO2_DMA_CHAN) | + CIO2_REG_INT_EN_IOS(CIO2_DMA_CHAN), + base + CIO2_REG_INT_EN); + + writel((CIO2_PXM_PXF_FMT_CFG_BPP_10 | CIO2_PXM_PXF_FMT_CFG_PCK_64B) + << CIO2_PXM_PXF_FMT_CFG_SID0_SHIFT, + base + CIO2_REG_PXM_PXF_FMT_CFG0(csi2bus)); + writel(SID << CIO2_MIPIBE_LP_LUT_ENTRY_SID_SHIFT | + sensor_vc << CIO2_MIPIBE_LP_LUT_ENTRY_VC_SHIFT | + fmt->mipicode << CIO2_MIPIBE_LP_LUT_ENTRY_FORMAT_TYPE_SHIFT, + q->csi_rx_base + CIO2_REG_MIPIBE_LP_LUT_ENTRY(ENTRY)); + writel(0, q->csi_rx_base + CIO2_REG_MIPIBE_COMP_FORMAT(sensor_vc)); + writel(0, q->csi_rx_base + CIO2_REG_MIPIBE_FORCE_RAW8); + writel(0, base + CIO2_REG_PXM_SID2BID0(csi2bus)); + + writel(lanes, q->csi_rx_base + CIO2_REG_CSIRX_NOF_ENABLED_LANES); + writel(CIO2_CGC_PRIM_TGE | + CIO2_CGC_SIDE_TGE | + CIO2_CGC_XOSC_TGE | + CIO2_CGC_D3I3_TGE | + CIO2_CGC_CSI2_INTERFRAME_TGE | + CIO2_CGC_CSI2_PORT_DCGE | + CIO2_CGC_SIDE_DCGE | + CIO2_CGC_PRIM_DCGE | + CIO2_CGC_ROSC_DCGE | + CIO2_CGC_XOSC_DCGE | + CIO2_CGC_CLKGATE_HOLDOFF << CIO2_CGC_CLKGATE_HOLDOFF_SHIFT | + CIO2_CGC_CSI_CLKGATE_HOLDOFF + << CIO2_CGC_CSI_CLKGATE_HOLDOFF_SHIFT, base + CIO2_REG_CGC); + writel(CIO2_LTRCTRL_LTRDYNEN, base + CIO2_REG_LTRCTRL); + writel(CIO2_LTRVAL0_VAL << CIO2_LTRVAL02_VAL_SHIFT | + CIO2_LTRVAL0_SCALE << CIO2_LTRVAL02_SCALE_SHIFT | + CIO2_LTRVAL1_VAL << CIO2_LTRVAL13_VAL_SHIFT | + CIO2_LTRVAL1_SCALE << CIO2_LTRVAL13_SCALE_SHIFT, + base + CIO2_REG_LTRVAL01); + writel(CIO2_LTRVAL2_VAL << CIO2_LTRVAL02_VAL_SHIFT | + CIO2_LTRVAL2_SCALE << CIO2_LTRVAL02_SCALE_SHIFT | + CIO2_LTRVAL3_VAL << CIO2_LTRVAL13_VAL_SHIFT | + CIO2_LTRVAL3_SCALE << CIO2_LTRVAL13_SCALE_SHIFT, + base + CIO2_REG_LTRVAL23); + + for (i = 0; i < CIO2_NUM_DMA_CHAN; i++) { + writel(0, base + CIO2_REG_CDMABA(i)); + writel(0, base + CIO2_REG_CDMAC0(i)); + writel(0, base + CIO2_REG_CDMAC1(i)); + } + + /* Enable DMA */ + writel(PFN_DOWN(q->fbpt_bus_addr), base + CIO2_REG_CDMABA(CIO2_DMA_CHAN)); + + writel(num_buffers1 << CIO2_CDMAC0_FBPT_LEN_SHIFT | + FBPT_WIDTH << CIO2_CDMAC0_FBPT_WIDTH_SHIFT | + CIO2_CDMAC0_DMA_INTR_ON_FE | + CIO2_CDMAC0_FBPT_UPDATE_FIFO_FULL | + CIO2_CDMAC0_DMA_EN | + CIO2_CDMAC0_DMA_INTR_ON_FS | + CIO2_CDMAC0_DMA_HALTED, base + CIO2_REG_CDMAC0(CIO2_DMA_CHAN)); + + writel(1 << CIO2_CDMAC1_LINENUMUPDATE_SHIFT, + base + CIO2_REG_CDMAC1(CIO2_DMA_CHAN)); + + writel(0, base + CIO2_REG_PBM_FOPN_ABORT); + + writel(CIO2_PXM_FRF_CFG_CRC_TH << CIO2_PXM_FRF_CFG_CRC_TH_SHIFT | + CIO2_PXM_FRF_CFG_MSK_ECC_DPHY_NR | + CIO2_PXM_FRF_CFG_MSK_ECC_RE | + CIO2_PXM_FRF_CFG_MSK_ECC_DPHY_NE, + base + CIO2_REG_PXM_FRF_CFG(q->csi2.port)); + + /* Clear interrupts */ + writel(CIO2_IRQCTRL_MASK, q->csi_rx_base + CIO2_REG_IRQCTRL_CLEAR); + writel(~0, base + CIO2_REG_INT_STS_EXT_OE); + writel(~0, base + CIO2_REG_INT_STS_EXT_IE); + writel(~0, base + CIO2_REG_INT_STS); + + /* Enable devices, starting from the last device in the pipe */ + writel(1, q->csi_rx_base + CIO2_REG_MIPIBE_ENABLE); + writel(1, q->csi_rx_base + CIO2_REG_CSIRX_ENABLE); + + return 0; +} + +static void cio2_hw_exit(struct cio2_device *cio2, struct cio2_queue *q) +{ + struct device *dev = &cio2->pci_dev->dev; + void __iomem *const base = cio2->base; + unsigned int i; + u32 value; + int ret; + + /* Disable CSI receiver and MIPI backend devices */ + writel(0, q->csi_rx_base + CIO2_REG_IRQCTRL_MASK); + writel(0, q->csi_rx_base + CIO2_REG_IRQCTRL_ENABLE); + writel(0, q->csi_rx_base + CIO2_REG_CSIRX_ENABLE); + writel(0, q->csi_rx_base + CIO2_REG_MIPIBE_ENABLE); + + /* Halt DMA */ + writel(0, base + CIO2_REG_CDMAC0(CIO2_DMA_CHAN)); + ret = readl_poll_timeout(base + CIO2_REG_CDMAC0(CIO2_DMA_CHAN), + value, value & CIO2_CDMAC0_DMA_HALTED, + 4000, 2000000); + if (ret) + dev_err(dev, "DMA %i can not be halted\n", CIO2_DMA_CHAN); + + for (i = 0; i < CIO2_NUM_PORTS; i++) { + writel(readl(base + CIO2_REG_PXM_FRF_CFG(i)) | + CIO2_PXM_FRF_CFG_ABORT, base + CIO2_REG_PXM_FRF_CFG(i)); + writel(readl(base + CIO2_REG_PBM_FOPN_ABORT) | + CIO2_PBM_FOPN_ABORT(i), base + CIO2_REG_PBM_FOPN_ABORT); + } +} + +static void cio2_buffer_done(struct cio2_device *cio2, unsigned int dma_chan) +{ + struct device *dev = &cio2->pci_dev->dev; + struct cio2_queue *q = cio2->cur_queue; + struct cio2_fbpt_entry *entry; + u64 ns = ktime_get_ns(); + + if (dma_chan >= CIO2_QUEUES) { + dev_err(dev, "bad DMA channel %i\n", dma_chan); + return; + } + + entry = &q->fbpt[q->bufs_first * CIO2_MAX_LOPS]; + if (entry->first_entry.ctrl & CIO2_FBPT_CTRL_VALID) { + dev_warn(dev, "no ready buffers found on DMA channel %u\n", + dma_chan); + return; + } + + /* Find out which buffer(s) are ready */ + do { + struct cio2_buffer *b; + + b = q->bufs[q->bufs_first]; + if (b) { + unsigned int received = entry[1].second_entry.num_of_bytes; + unsigned long payload = + vb2_get_plane_payload(&b->vbb.vb2_buf, 0); + + q->bufs[q->bufs_first] = NULL; + atomic_dec(&q->bufs_queued); + dev_dbg(dev, "buffer %i done\n", b->vbb.vb2_buf.index); + + b->vbb.vb2_buf.timestamp = ns; + b->vbb.field = V4L2_FIELD_NONE; + b->vbb.sequence = atomic_read(&q->frame_sequence); + if (payload != received) + dev_warn(dev, + "payload length is %lu, received %u\n", + payload, received); + vb2_buffer_done(&b->vbb.vb2_buf, VB2_BUF_STATE_DONE); + } + atomic_inc(&q->frame_sequence); + cio2_fbpt_entry_init_dummy(cio2, entry); + q->bufs_first = (q->bufs_first + 1) % CIO2_MAX_BUFFERS; + entry = &q->fbpt[q->bufs_first * CIO2_MAX_LOPS]; + } while (!(entry->first_entry.ctrl & CIO2_FBPT_CTRL_VALID)); +} + +static void cio2_queue_event_sof(struct cio2_device *cio2, struct cio2_queue *q) +{ + /* + * For the user space camera control algorithms it is essential + * to know when the reception of a frame has begun. That's often + * the best timing information to get from the hardware. + */ + struct v4l2_event event = { + .type = V4L2_EVENT_FRAME_SYNC, + .u.frame_sync.frame_sequence = atomic_read(&q->frame_sequence), + }; + + v4l2_event_queue(q->subdev.devnode, &event); +} + +static const char *const cio2_irq_errs[] = { + "single packet header error corrected", + "multiple packet header errors detected", + "payload checksum (CRC) error", + "fifo overflow", + "reserved short packet data type detected", + "reserved long packet data type detected", + "incomplete long packet detected", + "frame sync error", + "line sync error", + "DPHY start of transmission error", + "DPHY synchronization error", + "escape mode error", + "escape mode trigger event", + "escape mode ultra-low power state for data lane(s)", + "escape mode ultra-low power state exit for clock lane", + "inter-frame short packet discarded", + "inter-frame long packet discarded", + "non-matching Long Packet stalled", +}; + +static void cio2_irq_log_irq_errs(struct device *dev, u8 port, u32 status) +{ + unsigned long csi2_status = status; + unsigned int i; + + for_each_set_bit(i, &csi2_status, ARRAY_SIZE(cio2_irq_errs)) + dev_err(dev, "CSI-2 receiver port %i: %s\n", + port, cio2_irq_errs[i]); + + if (fls_long(csi2_status) >= ARRAY_SIZE(cio2_irq_errs)) + dev_warn(dev, "unknown CSI2 error 0x%lx on port %i\n", + csi2_status, port); +} + +static const char *const cio2_port_errs[] = { + "ECC recoverable", + "DPHY not recoverable", + "ECC not recoverable", + "CRC error", + "INTERFRAMEDATA", + "PKT2SHORT", + "PKT2LONG", +}; + +static void cio2_irq_log_port_errs(struct device *dev, u8 port, u32 status) +{ + unsigned long port_status = status; + unsigned int i; + + for_each_set_bit(i, &port_status, ARRAY_SIZE(cio2_port_errs)) + dev_err(dev, "port %i error %s\n", port, cio2_port_errs[i]); +} + +static void cio2_irq_handle_once(struct cio2_device *cio2, u32 int_status) +{ + struct device *dev = &cio2->pci_dev->dev; + void __iomem *const base = cio2->base; + + if (int_status & CIO2_INT_IOOE) { + /* + * Interrupt on Output Error: + * 1) SRAM is full and FS received, or + * 2) An invalid bit detected by DMA. + */ + u32 oe_status, oe_clear; + + oe_clear = readl(base + CIO2_REG_INT_STS_EXT_OE); + oe_status = oe_clear; + + if (oe_status & CIO2_INT_EXT_OE_DMAOE_MASK) { + dev_err(dev, "DMA output error: 0x%x\n", + (oe_status & CIO2_INT_EXT_OE_DMAOE_MASK) + >> CIO2_INT_EXT_OE_DMAOE_SHIFT); + oe_status &= ~CIO2_INT_EXT_OE_DMAOE_MASK; + } + if (oe_status & CIO2_INT_EXT_OE_OES_MASK) { + dev_err(dev, "DMA output error on CSI2 buses: 0x%x\n", + (oe_status & CIO2_INT_EXT_OE_OES_MASK) + >> CIO2_INT_EXT_OE_OES_SHIFT); + oe_status &= ~CIO2_INT_EXT_OE_OES_MASK; + } + writel(oe_clear, base + CIO2_REG_INT_STS_EXT_OE); + if (oe_status) + dev_warn(dev, "unknown interrupt 0x%x on OE\n", + oe_status); + int_status &= ~CIO2_INT_IOOE; + } + + if (int_status & CIO2_INT_IOC_MASK) { + /* DMA IO done -- frame ready */ + u32 clr = 0; + unsigned int d; + + for (d = 0; d < CIO2_NUM_DMA_CHAN; d++) + if (int_status & CIO2_INT_IOC(d)) { + clr |= CIO2_INT_IOC(d); + cio2_buffer_done(cio2, d); + } + int_status &= ~clr; + } + + if (int_status & CIO2_INT_IOS_IOLN_MASK) { + /* DMA IO starts or reached specified line */ + u32 clr = 0; + unsigned int d; + + for (d = 0; d < CIO2_NUM_DMA_CHAN; d++) + if (int_status & CIO2_INT_IOS_IOLN(d)) { + clr |= CIO2_INT_IOS_IOLN(d); + if (d == CIO2_DMA_CHAN) + cio2_queue_event_sof(cio2, + cio2->cur_queue); + } + int_status &= ~clr; + } + + if (int_status & (CIO2_INT_IOIE | CIO2_INT_IOIRQ)) { + /* CSI2 receiver (error) interrupt */ + unsigned int port; + u32 ie_status; + + ie_status = readl(base + CIO2_REG_INT_STS_EXT_IE); + + for (port = 0; port < CIO2_NUM_PORTS; port++) { + u32 port_status = (ie_status >> (port * 8)) & 0xff; + + cio2_irq_log_port_errs(dev, port, port_status); + + if (ie_status & CIO2_INT_EXT_IE_IRQ(port)) { + void __iomem *csi_rx_base = + base + CIO2_REG_PIPE_BASE(port); + u32 csi2_status; + + csi2_status = readl(csi_rx_base + + CIO2_REG_IRQCTRL_STATUS); + + cio2_irq_log_irq_errs(dev, port, csi2_status); + + writel(csi2_status, + csi_rx_base + CIO2_REG_IRQCTRL_CLEAR); + } + } + + writel(ie_status, base + CIO2_REG_INT_STS_EXT_IE); + + int_status &= ~(CIO2_INT_IOIE | CIO2_INT_IOIRQ); + } + + if (int_status) + dev_warn(dev, "unknown interrupt 0x%x on INT\n", int_status); +} + +static irqreturn_t cio2_irq(int irq, void *cio2_ptr) +{ + struct cio2_device *cio2 = cio2_ptr; + void __iomem *const base = cio2->base; + struct device *dev = &cio2->pci_dev->dev; + u32 int_status; + + int_status = readl(base + CIO2_REG_INT_STS); + dev_dbg(dev, "isr enter - interrupt status 0x%x\n", int_status); + if (!int_status) + return IRQ_NONE; + + do { + writel(int_status, base + CIO2_REG_INT_STS); + cio2_irq_handle_once(cio2, int_status); + int_status = readl(base + CIO2_REG_INT_STS); + if (int_status) + dev_dbg(dev, "pending status 0x%x\n", int_status); + } while (int_status); + + return IRQ_HANDLED; +} + +/**************** Videobuf2 interface ****************/ + +static void cio2_vb2_return_all_buffers(struct cio2_queue *q, + enum vb2_buffer_state state) +{ + unsigned int i; + + for (i = 0; i < CIO2_MAX_BUFFERS; i++) { + if (q->bufs[i]) { + atomic_dec(&q->bufs_queued); + vb2_buffer_done(&q->bufs[i]->vbb.vb2_buf, + state); + q->bufs[i] = NULL; + } + } +} + +static int cio2_vb2_queue_setup(struct vb2_queue *vq, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct cio2_device *cio2 = vb2_get_drv_priv(vq); + struct device *dev = &cio2->pci_dev->dev; + struct cio2_queue *q = vb2q_to_cio2_queue(vq); + unsigned int i; + + if (*num_planes && *num_planes < q->format.num_planes) + return -EINVAL; + + for (i = 0; i < q->format.num_planes; ++i) { + if (*num_planes && sizes[i] < q->format.plane_fmt[i].sizeimage) + return -EINVAL; + sizes[i] = q->format.plane_fmt[i].sizeimage; + alloc_devs[i] = dev; + } + + *num_planes = q->format.num_planes; + *num_buffers = clamp_val(*num_buffers, 1, CIO2_MAX_BUFFERS); + + /* Initialize buffer queue */ + for (i = 0; i < CIO2_MAX_BUFFERS; i++) { + q->bufs[i] = NULL; + cio2_fbpt_entry_init_dummy(cio2, &q->fbpt[i * CIO2_MAX_LOPS]); + } + atomic_set(&q->bufs_queued, 0); + q->bufs_first = 0; + q->bufs_next = 0; + + return 0; +} + +/* Called after each buffer is allocated */ +static int cio2_vb2_buf_init(struct vb2_buffer *vb) +{ + struct cio2_device *cio2 = vb2_get_drv_priv(vb->vb2_queue); + struct device *dev = &cio2->pci_dev->dev; + struct cio2_buffer *b = to_cio2_buffer(vb); + unsigned int pages = PFN_UP(vb->planes[0].length); + unsigned int lops = DIV_ROUND_UP(pages + 1, CIO2_LOP_ENTRIES); + struct sg_table *sg; + struct sg_dma_page_iter sg_iter; + unsigned int i, j; + + if (lops <= 0 || lops > CIO2_MAX_LOPS) { + dev_err(dev, "%s: bad buffer size (%i)\n", __func__, + vb->planes[0].length); + return -ENOSPC; /* Should never happen */ + } + + memset(b->lop, 0, sizeof(b->lop)); + /* Allocate LOP table */ + for (i = 0; i < lops; i++) { + b->lop[i] = dma_alloc_coherent(dev, PAGE_SIZE, + &b->lop_bus_addr[i], GFP_KERNEL); + if (!b->lop[i]) + goto fail; + } + + /* Fill LOP */ + sg = vb2_dma_sg_plane_desc(vb, 0); + if (!sg) + return -ENOMEM; + + if (sg->nents && sg->sgl) + b->offset = sg->sgl->offset; + + i = j = 0; + for_each_sg_dma_page(sg->sgl, &sg_iter, sg->nents, 0) { + if (!pages--) + break; + b->lop[i][j] = PFN_DOWN(sg_page_iter_dma_address(&sg_iter)); + j++; + if (j == CIO2_LOP_ENTRIES) { + i++; + j = 0; + } + } + + b->lop[i][j] = PFN_DOWN(cio2->dummy_page_bus_addr); + return 0; +fail: + while (i--) + dma_free_coherent(dev, PAGE_SIZE, b->lop[i], b->lop_bus_addr[i]); + return -ENOMEM; +} + +/* Transfer buffer ownership to cio2 */ +static void cio2_vb2_buf_queue(struct vb2_buffer *vb) +{ + struct cio2_device *cio2 = vb2_get_drv_priv(vb->vb2_queue); + struct device *dev = &cio2->pci_dev->dev; + struct cio2_queue *q = + container_of(vb->vb2_queue, struct cio2_queue, vbq); + struct cio2_buffer *b = to_cio2_buffer(vb); + struct cio2_fbpt_entry *entry; + unsigned long flags; + unsigned int i, j, next = q->bufs_next; + int bufs_queued = atomic_inc_return(&q->bufs_queued); + u32 fbpt_rp; + + dev_dbg(dev, "queue buffer %d\n", vb->index); + + /* + * This code queues the buffer to the CIO2 DMA engine, which starts + * running once streaming has started. It is possible that this code + * gets pre-empted due to increased CPU load. Upon this, the driver + * does not get an opportunity to queue new buffers to the CIO2 DMA + * engine. When the DMA engine encounters an FBPT entry without the + * VALID bit set, the DMA engine halts, which requires a restart of + * the DMA engine and sensor, to continue streaming. + * This is not desired and is highly unlikely given that there are + * 32 FBPT entries that the DMA engine needs to process, to run into + * an FBPT entry, without the VALID bit set. We try to mitigate this + * by disabling interrupts for the duration of this queueing. + */ + local_irq_save(flags); + + fbpt_rp = (readl(cio2->base + CIO2_REG_CDMARI(CIO2_DMA_CHAN)) + >> CIO2_CDMARI_FBPT_RP_SHIFT) + & CIO2_CDMARI_FBPT_RP_MASK; + + /* + * fbpt_rp is the fbpt entry that the dma is currently working + * on, but since it could jump to next entry at any time, + * assume that we might already be there. + */ + fbpt_rp = (fbpt_rp + 1) % CIO2_MAX_BUFFERS; + + if (bufs_queued <= 1 || fbpt_rp == next) + /* Buffers were drained */ + next = (fbpt_rp + 1) % CIO2_MAX_BUFFERS; + + for (i = 0; i < CIO2_MAX_BUFFERS; i++) { + /* + * We have allocated CIO2_MAX_BUFFERS circularly for the + * hw, the user has requested N buffer queue. The driver + * ensures N <= CIO2_MAX_BUFFERS and guarantees that whenever + * user queues a buffer, there necessarily is a free buffer. + */ + if (!q->bufs[next]) { + q->bufs[next] = b; + entry = &q->fbpt[next * CIO2_MAX_LOPS]; + cio2_fbpt_entry_init_buf(cio2, b, entry); + local_irq_restore(flags); + q->bufs_next = (next + 1) % CIO2_MAX_BUFFERS; + for (j = 0; j < vb->num_planes; j++) + vb2_set_plane_payload(vb, j, + q->format.plane_fmt[j].sizeimage); + return; + } + + dev_dbg(dev, "entry %i was full!\n", next); + next = (next + 1) % CIO2_MAX_BUFFERS; + } + + local_irq_restore(flags); + dev_err(dev, "error: all cio2 entries were full!\n"); + atomic_dec(&q->bufs_queued); + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); +} + +/* Called when each buffer is freed */ +static void cio2_vb2_buf_cleanup(struct vb2_buffer *vb) +{ + struct cio2_device *cio2 = vb2_get_drv_priv(vb->vb2_queue); + struct device *dev = &cio2->pci_dev->dev; + struct cio2_buffer *b = to_cio2_buffer(vb); + unsigned int i; + + /* Free LOP table */ + for (i = 0; i < CIO2_MAX_LOPS; i++) { + if (b->lop[i]) + dma_free_coherent(dev, PAGE_SIZE, + b->lop[i], b->lop_bus_addr[i]); + } +} + +static int cio2_vb2_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct cio2_queue *q = vb2q_to_cio2_queue(vq); + struct cio2_device *cio2 = vb2_get_drv_priv(vq); + struct device *dev = &cio2->pci_dev->dev; + int r; + + cio2->cur_queue = q; + atomic_set(&q->frame_sequence, 0); + + r = pm_runtime_resume_and_get(dev); + if (r < 0) { + dev_info(dev, "failed to set power %d\n", r); + return r; + } + + r = video_device_pipeline_start(&q->vdev, &q->pipe); + if (r) + goto fail_pipeline; + + r = cio2_hw_init(cio2, q); + if (r) + goto fail_hw; + + /* Start streaming on sensor */ + r = v4l2_subdev_call(q->sensor, video, s_stream, 1); + if (r) + goto fail_csi2_subdev; + + cio2->streaming = true; + + return 0; + +fail_csi2_subdev: + cio2_hw_exit(cio2, q); +fail_hw: + video_device_pipeline_stop(&q->vdev); +fail_pipeline: + dev_dbg(dev, "failed to start streaming (%d)\n", r); + cio2_vb2_return_all_buffers(q, VB2_BUF_STATE_QUEUED); + pm_runtime_put(dev); + + return r; +} + +static void cio2_vb2_stop_streaming(struct vb2_queue *vq) +{ + struct cio2_queue *q = vb2q_to_cio2_queue(vq); + struct cio2_device *cio2 = vb2_get_drv_priv(vq); + struct device *dev = &cio2->pci_dev->dev; + + if (v4l2_subdev_call(q->sensor, video, s_stream, 0)) + dev_err(dev, "failed to stop sensor streaming\n"); + + cio2_hw_exit(cio2, q); + synchronize_irq(cio2->pci_dev->irq); + cio2_vb2_return_all_buffers(q, VB2_BUF_STATE_ERROR); + video_device_pipeline_stop(&q->vdev); + pm_runtime_put(dev); + cio2->streaming = false; +} + +static const struct vb2_ops cio2_vb2_ops = { + .buf_init = cio2_vb2_buf_init, + .buf_queue = cio2_vb2_buf_queue, + .buf_cleanup = cio2_vb2_buf_cleanup, + .queue_setup = cio2_vb2_queue_setup, + .start_streaming = cio2_vb2_start_streaming, + .stop_streaming = cio2_vb2_stop_streaming, +}; + +/**************** V4L2 interface ****************/ + +static int cio2_v4l2_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, CIO2_NAME, sizeof(cap->driver)); + strscpy(cap->card, CIO2_DEVICE_NAME, sizeof(cap->card)); + + return 0; +} + +static int cio2_v4l2_enum_fmt(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + if (f->index >= ARRAY_SIZE(formats)) + return -EINVAL; + + f->pixelformat = formats[f->index].fourcc; + + return 0; +} + +/* The format is validated in cio2_video_link_validate() */ +static int cio2_v4l2_g_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct cio2_queue *q = file_to_cio2_queue(file); + + f->fmt.pix_mp = q->format; + + return 0; +} + +static int cio2_v4l2_try_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + const struct ipu3_cio2_fmt *fmt; + struct v4l2_pix_format_mplane *mpix = &f->fmt.pix_mp; + + fmt = cio2_find_format(&mpix->pixelformat, NULL); + if (!fmt) + fmt = &formats[0]; + + /* Only supports up to 4224x3136 */ + if (mpix->width > CIO2_IMAGE_MAX_WIDTH) + mpix->width = CIO2_IMAGE_MAX_WIDTH; + if (mpix->height > CIO2_IMAGE_MAX_HEIGHT) + mpix->height = CIO2_IMAGE_MAX_HEIGHT; + + mpix->num_planes = 1; + mpix->pixelformat = fmt->fourcc; + mpix->colorspace = V4L2_COLORSPACE_RAW; + mpix->field = V4L2_FIELD_NONE; + mpix->plane_fmt[0].bytesperline = cio2_bytesperline(mpix->width); + mpix->plane_fmt[0].sizeimage = mpix->plane_fmt[0].bytesperline * + mpix->height; + + /* use default */ + mpix->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + mpix->quantization = V4L2_QUANTIZATION_DEFAULT; + mpix->xfer_func = V4L2_XFER_FUNC_DEFAULT; + + return 0; +} + +static int cio2_v4l2_s_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct cio2_queue *q = file_to_cio2_queue(file); + + cio2_v4l2_try_fmt(file, fh, f); + q->format = f->fmt.pix_mp; + + return 0; +} + +static int +cio2_video_enum_input(struct file *file, void *fh, struct v4l2_input *input) +{ + if (input->index > 0) + return -EINVAL; + + strscpy(input->name, "camera", sizeof(input->name)); + input->type = V4L2_INPUT_TYPE_CAMERA; + + return 0; +} + +static int +cio2_video_g_input(struct file *file, void *fh, unsigned int *input) +{ + *input = 0; + + return 0; +} + +static int +cio2_video_s_input(struct file *file, void *fh, unsigned int input) +{ + return input == 0 ? 0 : -EINVAL; +} + +static const struct v4l2_file_operations cio2_v4l2_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, +}; + +static const struct v4l2_ioctl_ops cio2_v4l2_ioctl_ops = { + .vidioc_querycap = cio2_v4l2_querycap, + .vidioc_enum_fmt_vid_cap = cio2_v4l2_enum_fmt, + .vidioc_g_fmt_vid_cap_mplane = cio2_v4l2_g_fmt, + .vidioc_s_fmt_vid_cap_mplane = cio2_v4l2_s_fmt, + .vidioc_try_fmt_vid_cap_mplane = cio2_v4l2_try_fmt, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_enum_input = cio2_video_enum_input, + .vidioc_g_input = cio2_video_g_input, + .vidioc_s_input = cio2_video_s_input, +}; + +static int cio2_subdev_subscribe_event(struct v4l2_subdev *sd, + struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + if (sub->type != V4L2_EVENT_FRAME_SYNC) + return -EINVAL; + + /* Line number. For now only zero accepted. */ + if (sub->id != 0) + return -EINVAL; + + return v4l2_event_subscribe(fh, sub, 0, NULL); +} + +static int cio2_subdev_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + const struct v4l2_mbus_framefmt fmt_default = { + .width = 1936, + .height = 1096, + .code = formats[0].mbus_code, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_RAW, + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, + .quantization = V4L2_QUANTIZATION_DEFAULT, + .xfer_func = V4L2_XFER_FUNC_DEFAULT, + }; + struct v4l2_mbus_framefmt *format; + + /* Initialize the format on the sink and source pads. */ + format = v4l2_subdev_state_get_format(state, CIO2_PAD_SINK); + *format = fmt_default; + + /* same as sink */ + format = v4l2_subdev_state_get_format(state, CIO2_PAD_SOURCE); + *format = fmt_default; + + return 0; +} + +static int cio2_subdev_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *mbus; + u32 mbus_code = fmt->format.code; + unsigned int i; + + /* + * Only allow setting sink pad format; + * source always propagates from sink + */ + if (fmt->pad == CIO2_PAD_SOURCE) + return v4l2_subdev_get_fmt(sd, sd_state, fmt); + + fmt->format.code = formats[0].mbus_code; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].mbus_code == mbus_code) { + fmt->format.code = mbus_code; + break; + } + } + + fmt->format.width = min(fmt->format.width, CIO2_IMAGE_MAX_WIDTH); + fmt->format.height = min(fmt->format.height, CIO2_IMAGE_MAX_HEIGHT); + fmt->format.field = V4L2_FIELD_NONE; + + mbus = v4l2_subdev_state_get_format(sd_state, CIO2_PAD_SINK); + *mbus = fmt->format; + + /* Propagate the format to the source pad. */ + mbus = v4l2_subdev_state_get_format(sd_state, CIO2_PAD_SOURCE); + *mbus = fmt->format; + + return 0; +} + +static int cio2_subdev_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(formats)) + return -EINVAL; + + code->code = formats[code->index].mbus_code; + return 0; +} + +static int cio2_subdev_link_validate_get_format(struct media_pad *pad, + struct v4l2_subdev_format *fmt) +{ + if (is_media_entity_v4l2_subdev(pad->entity)) { + struct v4l2_subdev *sd = + media_entity_to_v4l2_subdev(pad->entity); + + memset(fmt, 0, sizeof(*fmt)); + fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE; + fmt->pad = pad->index; + return v4l2_subdev_call(sd, pad, get_fmt, NULL, fmt); + } + + return -EINVAL; +} + +static int cio2_video_link_validate(struct media_link *link) +{ + struct media_entity *entity = link->sink->entity; + struct video_device *vd = media_entity_to_video_device(entity); + struct cio2_queue *q = container_of(vd, struct cio2_queue, vdev); + struct cio2_device *cio2 = video_get_drvdata(vd); + struct device *dev = &cio2->pci_dev->dev; + struct v4l2_subdev_format source_fmt; + int ret; + + if (!media_pad_remote_pad_first(entity->pads)) { + dev_info(dev, "video node %s pad not connected\n", vd->name); + return -ENOTCONN; + } + + ret = cio2_subdev_link_validate_get_format(link->source, &source_fmt); + if (ret < 0) + return 0; + + if (source_fmt.format.width != q->format.width || + source_fmt.format.height != q->format.height) { + dev_err(dev, "Wrong width or height %ux%u (%ux%u expected)\n", + q->format.width, q->format.height, + source_fmt.format.width, source_fmt.format.height); + return -EINVAL; + } + + if (!cio2_find_format(&q->format.pixelformat, &source_fmt.format.code)) + return -EINVAL; + + return 0; +} + +static const struct v4l2_subdev_core_ops cio2_subdev_core_ops = { + .subscribe_event = cio2_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_internal_ops cio2_subdev_internal_ops = { + .init_state = cio2_subdev_init_state, +}; + +static const struct v4l2_subdev_pad_ops cio2_subdev_pad_ops = { + .link_validate = v4l2_subdev_link_validate_default, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = cio2_subdev_set_fmt, + .enum_mbus_code = cio2_subdev_enum_mbus_code, +}; + +static const struct v4l2_subdev_ops cio2_subdev_ops = { + .core = &cio2_subdev_core_ops, + .pad = &cio2_subdev_pad_ops, +}; + +/******* V4L2 sub-device asynchronous registration callbacks***********/ + +struct sensor_async_subdev { + struct v4l2_async_connection asd; + struct csi2_bus_info csi2; +}; + +#define to_sensor_asd(__asd) \ + container_of_const(__asd, struct sensor_async_subdev, asd) + +/* The .bound() notifier callback when a match is found */ +static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asd) +{ + struct cio2_device *cio2 = to_cio2_device(notifier); + struct sensor_async_subdev *s_asd = to_sensor_asd(asd); + struct cio2_queue *q; + int ret; + + if (cio2->queue[s_asd->csi2.port].sensor) + return -EBUSY; + + ret = ipu_bridge_instantiate_vcm(sd->dev); + if (ret) + return ret; + + q = &cio2->queue[s_asd->csi2.port]; + + q->csi2 = s_asd->csi2; + q->sensor = sd; + q->csi_rx_base = cio2->base + CIO2_REG_PIPE_BASE(q->csi2.port); + + return 0; +} + +/* The .unbind callback */ +static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asd) +{ + struct cio2_device *cio2 = to_cio2_device(notifier); + struct sensor_async_subdev *s_asd = to_sensor_asd(asd); + + cio2->queue[s_asd->csi2.port].sensor = NULL; +} + +/* .complete() is called after all subdevices have been located */ +static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct cio2_device *cio2 = to_cio2_device(notifier); + struct sensor_async_subdev *s_asd; + struct v4l2_async_connection *asd; + struct cio2_queue *q; + int ret; + + list_for_each_entry(asd, &cio2->notifier.done_list, asc_entry) { + s_asd = to_sensor_asd(asd); + q = &cio2->queue[s_asd->csi2.port]; + + ret = v4l2_create_fwnode_links_to_pad(asd->sd, + &q->subdev_pads[CIO2_PAD_SINK], 0); + if (ret) + return ret; + } + + return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); +} + +static const struct v4l2_async_notifier_operations cio2_async_ops = { + .bound = cio2_notifier_bound, + .unbind = cio2_notifier_unbind, + .complete = cio2_notifier_complete, +}; + +static int cio2_parse_firmware(struct cio2_device *cio2) +{ + struct device *dev = &cio2->pci_dev->dev; + unsigned int i; + int ret; + + for (i = 0; i < CIO2_NUM_PORTS; i++) { + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct sensor_async_subdev *s_asd; + struct fwnode_handle *ep; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), i, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + continue; + + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret) + goto err_parse; + + s_asd = v4l2_async_nf_add_fwnode_remote(&cio2->notifier, ep, + struct + sensor_async_subdev); + if (IS_ERR(s_asd)) { + ret = PTR_ERR(s_asd); + goto err_parse; + } + + s_asd->csi2.port = vep.base.port; + s_asd->csi2.lanes = vep.bus.mipi_csi2.num_data_lanes; + + fwnode_handle_put(ep); + + continue; + +err_parse: + fwnode_handle_put(ep); + return ret; + } + + /* + * Proceed even without sensors connected to allow the device to + * suspend. + */ + cio2->notifier.ops = &cio2_async_ops; + ret = v4l2_async_nf_register(&cio2->notifier); + if (ret) + dev_err(dev, "failed to register async notifier : %d\n", ret); + + return ret; +} + +/**************** Queue initialization ****************/ +static const struct media_entity_operations cio2_media_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct media_entity_operations cio2_video_entity_ops = { + .link_validate = cio2_video_link_validate, +}; + +static int cio2_queue_init(struct cio2_device *cio2, struct cio2_queue *q) +{ + static const u32 default_width = 1936; + static const u32 default_height = 1096; + struct device *dev = &cio2->pci_dev->dev; + struct video_device *vdev = &q->vdev; + struct vb2_queue *vbq = &q->vbq; + struct v4l2_subdev *subdev = &q->subdev; + int r; + + /* Initialize miscellaneous variables */ + mutex_init(&q->lock); + + q->format.width = default_width; + q->format.height = default_height; + q->format.pixelformat = formats[0].fourcc; + q->format.colorspace = V4L2_COLORSPACE_RAW; + q->format.field = V4L2_FIELD_NONE; + q->format.num_planes = 1; + q->format.plane_fmt[0].bytesperline = + cio2_bytesperline(q->format.width); + q->format.plane_fmt[0].sizeimage = q->format.plane_fmt[0].bytesperline * + q->format.height; + + /* Initialize fbpt */ + r = cio2_fbpt_init(cio2, q); + if (r) + goto fail_fbpt; + + /* Initialize media entities */ + q->subdev_pads[CIO2_PAD_SINK].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + q->subdev_pads[CIO2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + subdev->entity.ops = &cio2_media_ops; + subdev->internal_ops = &cio2_subdev_internal_ops; + r = media_entity_pads_init(&subdev->entity, CIO2_PADS, q->subdev_pads); + if (r) { + dev_err(dev, "failed initialize subdev media entity (%d)\n", r); + goto fail_subdev_media_entity; + } + + q->vdev_pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; + vdev->entity.ops = &cio2_video_entity_ops; + r = media_entity_pads_init(&vdev->entity, 1, &q->vdev_pad); + if (r) { + dev_err(dev, "failed initialize videodev media entity (%d)\n", + r); + goto fail_vdev_media_entity; + } + + /* Initialize subdev */ + v4l2_subdev_init(subdev, &cio2_subdev_ops); + subdev->flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + subdev->owner = THIS_MODULE; + subdev->dev = dev; + snprintf(subdev->name, sizeof(subdev->name), + CIO2_ENTITY_NAME " %td", q - cio2->queue); + subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + v4l2_set_subdevdata(subdev, cio2); + + r = v4l2_subdev_init_finalize(subdev); + if (r) { + dev_err(dev, "failed to initialize subdev (%d)\n", r); + goto fail_subdev; + } + + r = v4l2_device_register_subdev(&cio2->v4l2_dev, subdev); + if (r) { + dev_err(dev, "failed to register subdev (%d)\n", r); + goto fail_subdev; + } + + /* Initialize vbq */ + vbq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + vbq->io_modes = VB2_USERPTR | VB2_MMAP | VB2_DMABUF; + vbq->ops = &cio2_vb2_ops; + vbq->mem_ops = &vb2_dma_sg_memops; + vbq->buf_struct_size = sizeof(struct cio2_buffer); + vbq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vbq->min_queued_buffers = 1; + vbq->drv_priv = cio2; + vbq->lock = &q->lock; + r = vb2_queue_init(vbq); + if (r) { + dev_err(dev, "failed to initialize videobuf2 queue (%d)\n", r); + goto fail_subdev; + } + + /* Initialize vdev */ + snprintf(vdev->name, sizeof(vdev->name), + "%s %td", CIO2_NAME, q - cio2->queue); + vdev->release = video_device_release_empty; + vdev->fops = &cio2_v4l2_fops; + vdev->ioctl_ops = &cio2_v4l2_ioctl_ops; + vdev->lock = &cio2->lock; + vdev->v4l2_dev = &cio2->v4l2_dev; + vdev->queue = &q->vbq; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING; + video_set_drvdata(vdev, cio2); + r = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (r) { + dev_err(dev, "failed to register video device (%d)\n", r); + goto fail_vdev; + } + + /* Create link from CIO2 subdev to output node */ + r = media_create_pad_link( + &subdev->entity, CIO2_PAD_SOURCE, &vdev->entity, 0, + MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); + if (r) + goto fail_link; + + return 0; + +fail_link: + vb2_video_unregister_device(&q->vdev); +fail_vdev: + v4l2_device_unregister_subdev(subdev); +fail_subdev: + media_entity_cleanup(&vdev->entity); +fail_vdev_media_entity: + media_entity_cleanup(&subdev->entity); +fail_subdev_media_entity: + cio2_fbpt_exit(q, dev); +fail_fbpt: + mutex_destroy(&q->lock); + + return r; +} + +static void cio2_queue_exit(struct cio2_device *cio2, struct cio2_queue *q) +{ + vb2_video_unregister_device(&q->vdev); + media_entity_cleanup(&q->vdev.entity); + v4l2_device_unregister_subdev(&q->subdev); + media_entity_cleanup(&q->subdev.entity); + cio2_fbpt_exit(q, &cio2->pci_dev->dev); + mutex_destroy(&q->lock); +} + +static int cio2_queues_init(struct cio2_device *cio2) +{ + int i, r; + + for (i = 0; i < CIO2_QUEUES; i++) { + r = cio2_queue_init(cio2, &cio2->queue[i]); + if (r) + break; + } + + if (i == CIO2_QUEUES) + return 0; + + for (i--; i >= 0; i--) + cio2_queue_exit(cio2, &cio2->queue[i]); + + return r; +} + +static void cio2_queues_exit(struct cio2_device *cio2) +{ + unsigned int i; + + for (i = 0; i < CIO2_QUEUES; i++) + cio2_queue_exit(cio2, &cio2->queue[i]); +} + +/**************** PCI interface ****************/ + +static int cio2_pci_probe(struct pci_dev *pci_dev, + const struct pci_device_id *id) +{ + struct device *dev = &pci_dev->dev; + struct cio2_device *cio2; + int r; + + /* + * On some platforms no connections to sensors are defined in firmware, + * if the device has no endpoints then we can try to build those as + * software_nodes parsed from SSDB. + */ + r = ipu_bridge_init(dev, ipu_bridge_parse_ssdb); + if (r) + return r; + + cio2 = devm_kzalloc(dev, sizeof(*cio2), GFP_KERNEL); + if (!cio2) + return -ENOMEM; + cio2->pci_dev = pci_dev; + + r = pcim_enable_device(pci_dev); + if (r) { + dev_err(dev, "failed to enable device (%d)\n", r); + return r; + } + + dev_info(dev, "device 0x%x (rev: 0x%x)\n", + pci_dev->device, pci_dev->revision); + + cio2->base = pcim_iomap_region(pci_dev, CIO2_PCI_BAR, CIO2_NAME); + r = PTR_ERR_OR_ZERO(cio2->base); + if (r) { + dev_err(dev, "failed to remap I/O memory (%d)\n", r); + return -ENODEV; + } + + pci_set_drvdata(pci_dev, cio2); + + pci_set_master(pci_dev); + + r = dma_set_mask(&pci_dev->dev, CIO2_DMA_MASK); + if (r) { + dev_err(dev, "failed to set DMA mask (%d)\n", r); + return -ENODEV; + } + + r = pci_enable_msi(pci_dev); + if (r) { + dev_err(dev, "failed to enable MSI (%d)\n", r); + return r; + } + + r = cio2_fbpt_init_dummy(cio2); + if (r) + return r; + + mutex_init(&cio2->lock); + + cio2->media_dev.dev = dev; + strscpy(cio2->media_dev.model, CIO2_DEVICE_NAME, + sizeof(cio2->media_dev.model)); + cio2->media_dev.hw_revision = 0; + + media_device_init(&cio2->media_dev); + r = media_device_register(&cio2->media_dev); + if (r < 0) + goto fail_mutex_destroy; + + cio2->v4l2_dev.mdev = &cio2->media_dev; + r = v4l2_device_register(dev, &cio2->v4l2_dev); + if (r) { + dev_err(dev, "failed to register V4L2 device (%d)\n", r); + goto fail_media_device_unregister; + } + + r = cio2_queues_init(cio2); + if (r) + goto fail_v4l2_device_unregister; + + v4l2_async_nf_init(&cio2->notifier, &cio2->v4l2_dev); + + r = devm_request_irq(dev, pci_dev->irq, cio2_irq, IRQF_SHARED, + CIO2_NAME, cio2); + if (r) { + dev_err(dev, "failed to request IRQ (%d)\n", r); + goto fail_clean_notifier; + } + + /* Register notifier for subdevices we care */ + r = cio2_parse_firmware(cio2); + if (r) + goto fail_clean_notifier; + + pm_runtime_put_noidle(dev); + pm_runtime_allow(dev); + + return 0; + +fail_clean_notifier: + v4l2_async_nf_unregister(&cio2->notifier); + v4l2_async_nf_cleanup(&cio2->notifier); + cio2_queues_exit(cio2); +fail_v4l2_device_unregister: + v4l2_device_unregister(&cio2->v4l2_dev); +fail_media_device_unregister: + media_device_unregister(&cio2->media_dev); + media_device_cleanup(&cio2->media_dev); +fail_mutex_destroy: + mutex_destroy(&cio2->lock); + cio2_fbpt_exit_dummy(cio2); + + return r; +} + +static void cio2_pci_remove(struct pci_dev *pci_dev) +{ + struct cio2_device *cio2 = pci_get_drvdata(pci_dev); + + media_device_unregister(&cio2->media_dev); + v4l2_async_nf_unregister(&cio2->notifier); + v4l2_async_nf_cleanup(&cio2->notifier); + cio2_queues_exit(cio2); + cio2_fbpt_exit_dummy(cio2); + v4l2_device_unregister(&cio2->v4l2_dev); + media_device_cleanup(&cio2->media_dev); + mutex_destroy(&cio2->lock); + + pm_runtime_forbid(&pci_dev->dev); + pm_runtime_get_noresume(&pci_dev->dev); +} + +static int __maybe_unused cio2_runtime_suspend(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct cio2_device *cio2 = pci_get_drvdata(pci_dev); + void __iomem *const base = cio2->base; + + writel(CIO2_D0I3C_I3, base + CIO2_REG_D0I3C); + dev_dbg(dev, "cio2 runtime suspend.\n"); + + return 0; +} + +static int __maybe_unused cio2_runtime_resume(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct cio2_device *cio2 = pci_get_drvdata(pci_dev); + void __iomem *const base = cio2->base; + + writel(CIO2_D0I3C_RR, base + CIO2_REG_D0I3C); + dev_dbg(dev, "cio2 runtime resume.\n"); + + return 0; +} + +/* + * Helper function to advance all the elements of a circular buffer by "start" + * positions + */ +static void arrange(void *ptr, size_t elem_size, size_t elems, size_t start) +{ + struct { + size_t begin, end; + } arr[2] = { + { 0, start - 1 }, + { start, elems - 1 }, + }; + +#define CHUNK_SIZE(a) ((a)->end - (a)->begin + 1) + + /* Loop as long as we have out-of-place entries */ + while (CHUNK_SIZE(&arr[0]) && CHUNK_SIZE(&arr[1])) { + size_t size0, i; + + /* + * Find the number of entries that can be arranged on this + * iteration. + */ + size0 = min(CHUNK_SIZE(&arr[0]), CHUNK_SIZE(&arr[1])); + + /* Swap the entries in two parts of the array. */ + for (i = 0; i < size0; i++) { + u8 *d = ptr + elem_size * (arr[1].begin + i); + u8 *s = ptr + elem_size * (arr[0].begin + i); + size_t j; + + for (j = 0; j < elem_size; j++) + swap(d[j], s[j]); + } + + if (CHUNK_SIZE(&arr[0]) > CHUNK_SIZE(&arr[1])) { + /* The end of the first array remains unarranged. */ + arr[0].begin += size0; + } else { + /* + * The first array is fully arranged so we proceed + * handling the next one. + */ + arr[0].begin = arr[1].begin; + arr[0].end = arr[1].begin + size0 - 1; + arr[1].begin += size0; + } + } +} + +static void cio2_fbpt_rearrange(struct cio2_device *cio2, struct cio2_queue *q) +{ + unsigned int i, j; + + for (i = 0, j = q->bufs_first; i < CIO2_MAX_BUFFERS; + i++, j = (j + 1) % CIO2_MAX_BUFFERS) + if (q->bufs[j]) + break; + + if (i == CIO2_MAX_BUFFERS) + return; + + if (j) { + arrange(q->fbpt, sizeof(struct cio2_fbpt_entry) * CIO2_MAX_LOPS, + CIO2_MAX_BUFFERS, j); + arrange(q->bufs, sizeof(struct cio2_buffer *), + CIO2_MAX_BUFFERS, j); + } + + /* + * DMA clears the valid bit when accessing the buffer. + * When stopping stream in suspend callback, some of the buffers + * may be in invalid state. After resume, when DMA meets the invalid + * buffer, it will halt and stop receiving new data. + * To avoid DMA halting, set the valid bit for all buffers in FBPT. + */ + for (i = 0; i < CIO2_MAX_BUFFERS; i++) + cio2_fbpt_entry_enable(cio2, q->fbpt + i * CIO2_MAX_LOPS); +} + +static int __maybe_unused cio2_suspend(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct cio2_device *cio2 = pci_get_drvdata(pci_dev); + struct cio2_queue *q = cio2->cur_queue; + int r; + + dev_dbg(dev, "cio2 suspend\n"); + if (!cio2->streaming) + return 0; + + /* Stop stream */ + r = v4l2_subdev_call(q->sensor, video, s_stream, 0); + if (r) { + dev_err(dev, "failed to stop sensor streaming\n"); + return r; + } + + cio2_hw_exit(cio2, q); + synchronize_irq(pci_dev->irq); + + pm_runtime_force_suspend(dev); + + /* + * Upon resume, hw starts to process the fbpt entries from beginning, + * so relocate the queued buffs to the fbpt head before suspend. + */ + cio2_fbpt_rearrange(cio2, q); + q->bufs_first = 0; + q->bufs_next = 0; + + return 0; +} + +static int __maybe_unused cio2_resume(struct device *dev) +{ + struct cio2_device *cio2 = dev_get_drvdata(dev); + struct cio2_queue *q = cio2->cur_queue; + int r; + + dev_dbg(dev, "cio2 resume\n"); + if (!cio2->streaming) + return 0; + /* Start stream */ + r = pm_runtime_force_resume(dev); + if (r < 0) { + dev_err(dev, "failed to set power %d\n", r); + return r; + } + + r = cio2_hw_init(cio2, q); + if (r) { + dev_err(dev, "fail to init cio2 hw\n"); + return r; + } + + r = v4l2_subdev_call(q->sensor, video, s_stream, 1); + if (r) { + dev_err(dev, "fail to start sensor streaming\n"); + cio2_hw_exit(cio2, q); + } + + return r; +} + +static const struct dev_pm_ops cio2_pm_ops = { + SET_RUNTIME_PM_OPS(&cio2_runtime_suspend, &cio2_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(&cio2_suspend, &cio2_resume) +}; + +static const struct pci_device_id cio2_pci_id_table[] = { + { PCI_VDEVICE(INTEL, CIO2_PCI_ID) }, + { } +}; + +MODULE_DEVICE_TABLE(pci, cio2_pci_id_table); + +static struct pci_driver cio2_pci_driver = { + .name = CIO2_NAME, + .id_table = cio2_pci_id_table, + .probe = cio2_pci_probe, + .remove = cio2_pci_remove, + .driver = { + .pm = &cio2_pm_ops, + }, +}; + +module_pci_driver(cio2_pci_driver); + +MODULE_AUTHOR("Tuukka Toivonen"); +MODULE_AUTHOR("Tianshu Qiu "); +MODULE_AUTHOR("Jian Xu Zheng"); +MODULE_AUTHOR("Yuning Pu"); +MODULE_AUTHOR("Yong Zhi "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("IPU3 CIO2 driver"); +MODULE_IMPORT_NS("INTEL_IPU_BRIDGE"); diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.h b/drivers/media/pci/intel/ipu3/ipu3-cio2.h new file mode 100644 index 000000000..192581909 --- /dev/null +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.h @@ -0,0 +1,456 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2017 Intel Corporation */ + +#ifndef __IPU3_CIO2_H +#define __IPU3_CIO2_H + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +struct cio2_fbpt_entry; /* defined here, after the first usage */ +struct pci_dev; + +#define CIO2_NAME "ipu3-cio2" +#define CIO2_DEVICE_NAME "Intel IPU3 CIO2" +#define CIO2_ENTITY_NAME "ipu3-csi2" +#define CIO2_PCI_ID 0x9d32 +#define CIO2_PCI_BAR 0 +#define CIO2_DMA_MASK DMA_BIT_MASK(39) + +#define CIO2_IMAGE_MAX_WIDTH 4224U +#define CIO2_IMAGE_MAX_HEIGHT 3136U + +/* 32MB = 8xFBPT_entry */ +#define CIO2_MAX_LOPS 8 +#define CIO2_MAX_BUFFERS (PAGE_SIZE / 16 / CIO2_MAX_LOPS) +#define CIO2_LOP_ENTRIES (PAGE_SIZE / sizeof(u32)) + +#define CIO2_PAD_SINK 0U +#define CIO2_PAD_SOURCE 1U +#define CIO2_PADS 2U + +#define CIO2_NUM_DMA_CHAN 20U +#define CIO2_NUM_PORTS 4U /* DPHYs */ + +/* 1 for each sensor */ +#define CIO2_QUEUES CIO2_NUM_PORTS + +/* Register and bit field definitions */ +#define CIO2_REG_PIPE_BASE(n) ((n) * 0x0400) /* n = 0..3 */ +#define CIO2_REG_CSIRX_BASE 0x000 +#define CIO2_REG_MIPIBE_BASE 0x100 +#define CIO2_REG_PIXELGEN_BAS 0x200 +#define CIO2_REG_IRQCTRL_BASE 0x300 +#define CIO2_REG_GPREG_BASE 0x1000 + +/* base register: CIO2_REG_PIPE_BASE(pipe) * CIO2_REG_CSIRX_BASE */ +#define CIO2_REG_CSIRX_ENABLE (CIO2_REG_CSIRX_BASE + 0x0) +#define CIO2_REG_CSIRX_NOF_ENABLED_LANES (CIO2_REG_CSIRX_BASE + 0x4) +#define CIO2_REG_CSIRX_SP_IF_CONFIG (CIO2_REG_CSIRX_BASE + 0x10) +#define CIO2_REG_CSIRX_LP_IF_CONFIG (CIO2_REG_CSIRX_BASE + 0x14) +#define CIO2_CSIRX_IF_CONFIG_FILTEROUT 0x00 +#define CIO2_CSIRX_IF_CONFIG_FILTEROUT_VC_INACTIVE 0x01 +#define CIO2_CSIRX_IF_CONFIG_PASS 0x02 +#define CIO2_CSIRX_IF_CONFIG_FLAG_ERROR BIT(2) +#define CIO2_REG_CSIRX_STATUS (CIO2_REG_CSIRX_BASE + 0x18) +#define CIO2_REG_CSIRX_STATUS_DLANE_HS (CIO2_REG_CSIRX_BASE + 0x1c) +#define CIO2_CSIRX_STATUS_DLANE_HS_MASK 0xff +#define CIO2_REG_CSIRX_STATUS_DLANE_LP (CIO2_REG_CSIRX_BASE + 0x20) +#define CIO2_CSIRX_STATUS_DLANE_LP_MASK 0xffffff +/* Termination enable and settle in 0.0625ns units, lane=0..3 or -1 for clock */ +#define CIO2_REG_CSIRX_DLY_CNT_TERMEN(lane) \ + (CIO2_REG_CSIRX_BASE + 0x2c + 8 * (lane)) +#define CIO2_REG_CSIRX_DLY_CNT_SETTLE(lane) \ + (CIO2_REG_CSIRX_BASE + 0x30 + 8 * (lane)) +/* base register: CIO2_REG_PIPE_BASE(pipe) * CIO2_REG_MIPIBE_BASE */ +#define CIO2_REG_MIPIBE_ENABLE (CIO2_REG_MIPIBE_BASE + 0x0) +#define CIO2_REG_MIPIBE_STATUS (CIO2_REG_MIPIBE_BASE + 0x4) +#define CIO2_REG_MIPIBE_COMP_FORMAT(vc) \ + (CIO2_REG_MIPIBE_BASE + 0x8 + 0x4 * (vc)) +#define CIO2_REG_MIPIBE_FORCE_RAW8 (CIO2_REG_MIPIBE_BASE + 0x20) +#define CIO2_REG_MIPIBE_FORCE_RAW8_ENABLE BIT(0) +#define CIO2_REG_MIPIBE_FORCE_RAW8_USE_TYPEID BIT(1) +#define CIO2_REG_MIPIBE_FORCE_RAW8_TYPEID_SHIFT 2U + +#define CIO2_REG_MIPIBE_IRQ_STATUS (CIO2_REG_MIPIBE_BASE + 0x24) +#define CIO2_REG_MIPIBE_IRQ_CLEAR (CIO2_REG_MIPIBE_BASE + 0x28) +#define CIO2_REG_MIPIBE_GLOBAL_LUT_DISREGARD (CIO2_REG_MIPIBE_BASE + 0x68) +#define CIO2_MIPIBE_GLOBAL_LUT_DISREGARD 1U +#define CIO2_REG_MIPIBE_PKT_STALL_STATUS (CIO2_REG_MIPIBE_BASE + 0x6c) +#define CIO2_REG_MIPIBE_PARSE_GSP_THROUGH_LP_LUT_REG_IDX \ + (CIO2_REG_MIPIBE_BASE + 0x70) +#define CIO2_REG_MIPIBE_SP_LUT_ENTRY(vc) \ + (CIO2_REG_MIPIBE_BASE + 0x74 + 4 * (vc)) +#define CIO2_REG_MIPIBE_LP_LUT_ENTRY(m) /* m = 0..15 */ \ + (CIO2_REG_MIPIBE_BASE + 0x84 + 4 * (m)) +#define CIO2_MIPIBE_LP_LUT_ENTRY_DISREGARD 1U +#define CIO2_MIPIBE_LP_LUT_ENTRY_SID_SHIFT 1U +#define CIO2_MIPIBE_LP_LUT_ENTRY_VC_SHIFT 5U +#define CIO2_MIPIBE_LP_LUT_ENTRY_FORMAT_TYPE_SHIFT 7U + +/* base register: CIO2_REG_PIPE_BASE(pipe) * CIO2_REG_IRQCTRL_BASE */ +/* IRQ registers are 18-bit wide, see cio2_irq_error for bit definitions */ +#define CIO2_REG_IRQCTRL_EDGE (CIO2_REG_IRQCTRL_BASE + 0x00) +#define CIO2_REG_IRQCTRL_MASK (CIO2_REG_IRQCTRL_BASE + 0x04) +#define CIO2_REG_IRQCTRL_STATUS (CIO2_REG_IRQCTRL_BASE + 0x08) +#define CIO2_REG_IRQCTRL_CLEAR (CIO2_REG_IRQCTRL_BASE + 0x0c) +#define CIO2_REG_IRQCTRL_ENABLE (CIO2_REG_IRQCTRL_BASE + 0x10) +#define CIO2_REG_IRQCTRL_LEVEL_NOT_PULSE (CIO2_REG_IRQCTRL_BASE + 0x14) + +#define CIO2_REG_GPREG_SRST (CIO2_REG_GPREG_BASE + 0x0) +#define CIO2_GPREG_SRST_ALL 0xffff /* Reset all */ +#define CIO2_REG_FB_HPLL_FREQ (CIO2_REG_GPREG_BASE + 0x08) +#define CIO2_REG_ISCLK_RATIO (CIO2_REG_GPREG_BASE + 0xc) + +#define CIO2_REG_CGC 0x1400 +#define CIO2_CGC_CSI2_TGE BIT(0) +#define CIO2_CGC_PRIM_TGE BIT(1) +#define CIO2_CGC_SIDE_TGE BIT(2) +#define CIO2_CGC_XOSC_TGE BIT(3) +#define CIO2_CGC_MPLL_SHUTDOWN_EN BIT(4) +#define CIO2_CGC_D3I3_TGE BIT(5) +#define CIO2_CGC_CSI2_INTERFRAME_TGE BIT(6) +#define CIO2_CGC_CSI2_PORT_DCGE BIT(8) +#define CIO2_CGC_CSI2_DCGE BIT(9) +#define CIO2_CGC_SIDE_DCGE BIT(10) +#define CIO2_CGC_PRIM_DCGE BIT(11) +#define CIO2_CGC_ROSC_DCGE BIT(12) +#define CIO2_CGC_XOSC_DCGE BIT(13) +#define CIO2_CGC_FLIS_DCGE BIT(14) +#define CIO2_CGC_CLKGATE_HOLDOFF_SHIFT 20U +#define CIO2_CGC_CSI_CLKGATE_HOLDOFF_SHIFT 24U +#define CIO2_REG_D0I3C 0x1408 +#define CIO2_D0I3C_I3 BIT(2) /* Set D0I3 */ +#define CIO2_D0I3C_RR BIT(3) /* Restore? */ +#define CIO2_REG_SWRESET 0x140c +#define CIO2_SWRESET_SWRESET 1U +#define CIO2_REG_SENSOR_ACTIVE 0x1410 +#define CIO2_REG_INT_STS 0x1414 +#define CIO2_REG_INT_STS_EXT_OE 0x1418 +#define CIO2_INT_EXT_OE_DMAOE_SHIFT 0U +#define CIO2_INT_EXT_OE_DMAOE_MASK 0x7ffff +#define CIO2_INT_EXT_OE_OES_SHIFT 24U +#define CIO2_INT_EXT_OE_OES_MASK (0xf << CIO2_INT_EXT_OE_OES_SHIFT) +#define CIO2_REG_INT_EN 0x1420 +#define CIO2_REG_INT_EN_IRQ (1 << 24) +#define CIO2_REG_INT_EN_IOS(dma) (1U << (((dma) >> 1U) + 12U)) +/* + * Interrupt on completion bit, Eg. DMA 0-3 maps to bit 0-3, + * DMA4 & DMA5 map to bit 4 ... DMA18 & DMA19 map to bit 11 Et cetera + */ +#define CIO2_INT_IOC(dma) (1U << ((dma) < 4U ? (dma) : ((dma) >> 1U) + 2U)) +#define CIO2_INT_IOC_SHIFT 0 +#define CIO2_INT_IOC_MASK (0x7ff << CIO2_INT_IOC_SHIFT) +#define CIO2_INT_IOS_IOLN(dma) (1U << (((dma) >> 1U) + 12U)) +#define CIO2_INT_IOS_IOLN_SHIFT 12 +#define CIO2_INT_IOS_IOLN_MASK (0x3ff << CIO2_INT_IOS_IOLN_SHIFT) +#define CIO2_INT_IOIE BIT(22) +#define CIO2_INT_IOOE BIT(23) +#define CIO2_INT_IOIRQ BIT(24) +#define CIO2_REG_INT_EN_EXT_OE 0x1424 +#define CIO2_REG_DMA_DBG 0x1448 +#define CIO2_REG_DMA_DBG_DMA_INDEX_SHIFT 0U +#define CIO2_REG_PBM_ARB_CTRL 0x1460 +#define CIO2_PBM_ARB_CTRL_LANES_DIV 0U /* 4-4-2-2 lanes */ +#define CIO2_PBM_ARB_CTRL_LANES_DIV_SHIFT 0U +#define CIO2_PBM_ARB_CTRL_LE_EN BIT(7) +#define CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN 2U +#define CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN_SHIFT 8U +#define CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP 480U +#define CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP_SHIFT 16U +#define CIO2_REG_PBM_WMCTRL1 0x1464 +#define CIO2_PBM_WMCTRL1_MIN_2CK_SHIFT 0U +#define CIO2_PBM_WMCTRL1_MID1_2CK_SHIFT 8U +#define CIO2_PBM_WMCTRL1_MID2_2CK_SHIFT 16U +#define CIO2_PBM_WMCTRL1_TS_COUNT_DISABLE BIT(31) +#define CIO2_PBM_WMCTRL1_MIN_2CK (4 << CIO2_PBM_WMCTRL1_MIN_2CK_SHIFT) +#define CIO2_PBM_WMCTRL1_MID1_2CK (16 << CIO2_PBM_WMCTRL1_MID1_2CK_SHIFT) +#define CIO2_PBM_WMCTRL1_MID2_2CK (21 << CIO2_PBM_WMCTRL1_MID2_2CK_SHIFT) +#define CIO2_REG_PBM_WMCTRL2 0x1468 +#define CIO2_PBM_WMCTRL2_HWM_2CK 40U +#define CIO2_PBM_WMCTRL2_HWM_2CK_SHIFT 0U +#define CIO2_PBM_WMCTRL2_LWM_2CK 22U +#define CIO2_PBM_WMCTRL2_LWM_2CK_SHIFT 8U +#define CIO2_PBM_WMCTRL2_OBFFWM_2CK 2U +#define CIO2_PBM_WMCTRL2_OBFFWM_2CK_SHIFT 16U +#define CIO2_PBM_WMCTRL2_TRANSDYN 1U +#define CIO2_PBM_WMCTRL2_TRANSDYN_SHIFT 24U +#define CIO2_PBM_WMCTRL2_DYNWMEN BIT(28) +#define CIO2_PBM_WMCTRL2_OBFF_MEM_EN BIT(29) +#define CIO2_PBM_WMCTRL2_OBFF_CPU_EN BIT(30) +#define CIO2_PBM_WMCTRL2_DRAINNOW BIT(31) +#define CIO2_REG_PBM_TS_COUNT 0x146c +#define CIO2_REG_PBM_FOPN_ABORT 0x1474 +/* below n = 0..3 */ +#define CIO2_PBM_FOPN_ABORT(n) (0x1 << 8U * (n)) +#define CIO2_PBM_FOPN_FORCE_ABORT(n) (0x2 << 8U * (n)) +#define CIO2_PBM_FOPN_FRAMEOPEN(n) (0x8 << 8U * (n)) +#define CIO2_REG_LTRCTRL 0x1480 +#define CIO2_LTRCTRL_LTRDYNEN BIT(16) +#define CIO2_LTRCTRL_LTRSTABLETIME_SHIFT 8U +#define CIO2_LTRCTRL_LTRSTABLETIME_MASK 0xff +#define CIO2_LTRCTRL_LTRSEL1S3 BIT(7) +#define CIO2_LTRCTRL_LTRSEL1S2 BIT(6) +#define CIO2_LTRCTRL_LTRSEL1S1 BIT(5) +#define CIO2_LTRCTRL_LTRSEL1S0 BIT(4) +#define CIO2_LTRCTRL_LTRSEL2S3 BIT(3) +#define CIO2_LTRCTRL_LTRSEL2S2 BIT(2) +#define CIO2_LTRCTRL_LTRSEL2S1 BIT(1) +#define CIO2_LTRCTRL_LTRSEL2S0 BIT(0) +#define CIO2_REG_LTRVAL23 0x1484 +#define CIO2_REG_LTRVAL01 0x1488 +#define CIO2_LTRVAL02_VAL_SHIFT 0U +#define CIO2_LTRVAL02_SCALE_SHIFT 10U +#define CIO2_LTRVAL13_VAL_SHIFT 16U +#define CIO2_LTRVAL13_SCALE_SHIFT 26U + +#define CIO2_LTRVAL0_VAL 175U +/* Value times 1024 ns */ +#define CIO2_LTRVAL0_SCALE 2U +#define CIO2_LTRVAL1_VAL 90U +#define CIO2_LTRVAL1_SCALE 2U +#define CIO2_LTRVAL2_VAL 90U +#define CIO2_LTRVAL2_SCALE 2U +#define CIO2_LTRVAL3_VAL 90U +#define CIO2_LTRVAL3_SCALE 2U + +#define CIO2_REG_CDMABA(n) (0x1500 + 0x10 * (n)) /* n = 0..19 */ +#define CIO2_REG_CDMARI(n) (0x1504 + 0x10 * (n)) +#define CIO2_CDMARI_FBPT_RP_SHIFT 0U +#define CIO2_CDMARI_FBPT_RP_MASK 0xff +#define CIO2_REG_CDMAC0(n) (0x1508 + 0x10 * (n)) +#define CIO2_CDMAC0_FBPT_LEN_SHIFT 0U +#define CIO2_CDMAC0_FBPT_WIDTH_SHIFT 8U +#define CIO2_CDMAC0_FBPT_NS BIT(25) +#define CIO2_CDMAC0_DMA_INTR_ON_FS BIT(26) +#define CIO2_CDMAC0_DMA_INTR_ON_FE BIT(27) +#define CIO2_CDMAC0_FBPT_UPDATE_FIFO_FULL BIT(28) +#define CIO2_CDMAC0_FBPT_FIFO_FULL_FIX_DIS BIT(29) +#define CIO2_CDMAC0_DMA_EN BIT(30) +#define CIO2_CDMAC0_DMA_HALTED BIT(31) +#define CIO2_REG_CDMAC1(n) (0x150c + 0x10 * (n)) +#define CIO2_CDMAC1_LINENUMINT_SHIFT 0U +#define CIO2_CDMAC1_LINENUMUPDATE_SHIFT 16U +/* n = 0..3 */ +#define CIO2_REG_PXM_PXF_FMT_CFG0(n) (0x1700 + 0x30 * (n)) +#define CIO2_PXM_PXF_FMT_CFG_SID0_SHIFT 0U +#define CIO2_PXM_PXF_FMT_CFG_SID1_SHIFT 16U +#define CIO2_PXM_PXF_FMT_CFG_PCK_64B (0 << 0) +#define CIO2_PXM_PXF_FMT_CFG_PCK_32B (1 << 0) +#define CIO2_PXM_PXF_FMT_CFG_BPP_08 (0 << 2) +#define CIO2_PXM_PXF_FMT_CFG_BPP_10 (1 << 2) +#define CIO2_PXM_PXF_FMT_CFG_BPP_12 (2 << 2) +#define CIO2_PXM_PXF_FMT_CFG_BPP_14 (3 << 2) +#define CIO2_PXM_PXF_FMT_CFG_SPEC_4PPC (0 << 4) +#define CIO2_PXM_PXF_FMT_CFG_SPEC_3PPC_RGBA (1 << 4) +#define CIO2_PXM_PXF_FMT_CFG_SPEC_3PPC_ARGB (2 << 4) +#define CIO2_PXM_PXF_FMT_CFG_SPEC_PLANAR2 (3 << 4) +#define CIO2_PXM_PXF_FMT_CFG_SPEC_PLANAR3 (4 << 4) +#define CIO2_PXM_PXF_FMT_CFG_SPEC_NV16 (5 << 4) +#define CIO2_PXM_PXF_FMT_CFG_PSWAP4_1ST_AB (1 << 7) +#define CIO2_PXM_PXF_FMT_CFG_PSWAP4_1ST_CD (1 << 8) +#define CIO2_PXM_PXF_FMT_CFG_PSWAP4_2ND_AC (1 << 9) +#define CIO2_PXM_PXF_FMT_CFG_PSWAP4_2ND_BD (1 << 10) +#define CIO2_REG_INT_STS_EXT_IE 0x17e4 +#define CIO2_REG_INT_EN_EXT_IE 0x17e8 +#define CIO2_INT_EXT_IE_ECC_RE(n) (0x01 << (8U * (n))) +#define CIO2_INT_EXT_IE_DPHY_NR(n) (0x02 << (8U * (n))) +#define CIO2_INT_EXT_IE_ECC_NR(n) (0x04 << (8U * (n))) +#define CIO2_INT_EXT_IE_CRCERR(n) (0x08 << (8U * (n))) +#define CIO2_INT_EXT_IE_INTERFRAMEDATA(n) (0x10 << (8U * (n))) +#define CIO2_INT_EXT_IE_PKT2SHORT(n) (0x20 << (8U * (n))) +#define CIO2_INT_EXT_IE_PKT2LONG(n) (0x40 << (8U * (n))) +#define CIO2_INT_EXT_IE_IRQ(n) (0x80 << (8U * (n))) +#define CIO2_REG_PXM_FRF_CFG(n) (0x1720 + 0x30 * (n)) +#define CIO2_PXM_FRF_CFG_FNSEL BIT(0) +#define CIO2_PXM_FRF_CFG_FN_RST BIT(1) +#define CIO2_PXM_FRF_CFG_ABORT BIT(2) +#define CIO2_PXM_FRF_CFG_CRC_TH_SHIFT 3U +#define CIO2_PXM_FRF_CFG_MSK_ECC_DPHY_NR BIT(8) +#define CIO2_PXM_FRF_CFG_MSK_ECC_RE BIT(9) +#define CIO2_PXM_FRF_CFG_MSK_ECC_DPHY_NE BIT(10) +#define CIO2_PXM_FRF_CFG_EVEN_ODD_MODE_SHIFT 11U +#define CIO2_PXM_FRF_CFG_MASK_CRC_THRES BIT(13) +#define CIO2_PXM_FRF_CFG_MASK_CSI_ACCEPT BIT(14) +#define CIO2_PXM_FRF_CFG_CIOHC_FS_MODE BIT(15) +#define CIO2_PXM_FRF_CFG_CIOHC_FRST_FRM_SHIFT 16U +#define CIO2_REG_PXM_SID2BID0(n) (0x1724 + 0x30 * (n)) +#define CIO2_FB_HPLL_FREQ 0x2 +#define CIO2_ISCLK_RATIO 0xc + +#define CIO2_IRQCTRL_MASK 0x3ffff + +#define CIO2_INT_EN_EXT_OE_MASK 0x8f0fffff + +#define CIO2_CGC_CLKGATE_HOLDOFF 3U +#define CIO2_CGC_CSI_CLKGATE_HOLDOFF 5U + +#define CIO2_PXM_FRF_CFG_CRC_TH 16 + +#define CIO2_INT_EN_EXT_IE_MASK 0xffffffff + +#define CIO2_DMA_CHAN 0U + +#define CIO2_CSIRX_DLY_CNT_CLANE_IDX -1 + +#define CIO2_CSIRX_DLY_CNT_TERMEN_CLANE_A 0 +#define CIO2_CSIRX_DLY_CNT_TERMEN_CLANE_B 0 +#define CIO2_CSIRX_DLY_CNT_SETTLE_CLANE_A 95 +#define CIO2_CSIRX_DLY_CNT_SETTLE_CLANE_B -8 + +#define CIO2_CSIRX_DLY_CNT_TERMEN_DLANE_A 0 +#define CIO2_CSIRX_DLY_CNT_TERMEN_DLANE_B 0 +#define CIO2_CSIRX_DLY_CNT_SETTLE_DLANE_A 85 +#define CIO2_CSIRX_DLY_CNT_SETTLE_DLANE_B -2 + +#define CIO2_CSIRX_DLY_CNT_TERMEN_DEFAULT 0x4 +#define CIO2_CSIRX_DLY_CNT_SETTLE_DEFAULT 0x570 + +struct cio2_csi2_timing { + s32 clk_termen; + s32 clk_settle; + s32 dat_termen; + s32 dat_settle; +}; + +struct cio2_buffer { + struct vb2_v4l2_buffer vbb; + u32 *lop[CIO2_MAX_LOPS]; + dma_addr_t lop_bus_addr[CIO2_MAX_LOPS]; + unsigned int offset; +}; + +#define to_cio2_buffer(vb) container_of(vb, struct cio2_buffer, vbb.vb2_buf) + +struct csi2_bus_info { + u32 port; + u32 lanes; +}; + +struct cio2_queue { + /* mutex to be used by vb2_queue */ + struct mutex lock; + struct media_pipeline pipe; + struct csi2_bus_info csi2; + struct v4l2_subdev *sensor; + void __iomem *csi_rx_base; + + /* Subdev, /dev/v4l-subdevX */ + struct v4l2_subdev subdev; + struct media_pad subdev_pads[CIO2_PADS]; + atomic_t frame_sequence; + + /* Video device, /dev/videoX */ + struct video_device vdev; + struct media_pad vdev_pad; + struct v4l2_pix_format_mplane format; + struct vb2_queue vbq; + + /* Buffer queue handling */ + struct cio2_fbpt_entry *fbpt; /* Frame buffer pointer table */ + dma_addr_t fbpt_bus_addr; + struct cio2_buffer *bufs[CIO2_MAX_BUFFERS]; + unsigned int bufs_first; /* Index of the first used entry */ + unsigned int bufs_next; /* Index of the first unused entry */ + atomic_t bufs_queued; +}; + +struct cio2_device { + struct pci_dev *pci_dev; + void __iomem *base; + struct v4l2_device v4l2_dev; + struct cio2_queue queue[CIO2_QUEUES]; + struct cio2_queue *cur_queue; + /* mutex to be used by video_device */ + struct mutex lock; + + bool streaming; + struct v4l2_async_notifier notifier; + struct media_device media_dev; + + /* + * Safety net to catch DMA fetch ahead + * when reaching the end of LOP + */ + void *dummy_page; + /* DMA handle of dummy_page */ + dma_addr_t dummy_page_bus_addr; + /* single List of Pointers (LOP) page */ + u32 *dummy_lop; + /* DMA handle of dummy_lop */ + dma_addr_t dummy_lop_bus_addr; +}; + +#define to_cio2_device(n) container_of(n, struct cio2_device, notifier) + +/**************** Virtual channel ****************/ +/* + * This should come from sensor driver. No + * driver interface nor requirement yet. + */ +#define SENSOR_VIR_CH_DFLT 0 + +/**************** FBPT operations ****************/ +#define CIO2_FBPT_SIZE (CIO2_MAX_BUFFERS * CIO2_MAX_LOPS * \ + sizeof(struct cio2_fbpt_entry)) + +#define CIO2_FBPT_SUBENTRY_UNIT 4 + +/* cio2 fbpt first_entry ctrl status */ +#define CIO2_FBPT_CTRL_VALID BIT(0) +#define CIO2_FBPT_CTRL_IOC BIT(1) +#define CIO2_FBPT_CTRL_IOS BIT(2) +#define CIO2_FBPT_CTRL_SUCCXFAIL BIT(3) +#define CIO2_FBPT_CTRL_CMPLCODE_SHIFT 4 + +/* + * Frame Buffer Pointer Table(FBPT) entry + * each entry describe an output buffer and consists of + * several sub-entries + */ +struct __packed cio2_fbpt_entry { + union { + struct __packed { + u32 ctrl; /* status ctrl */ + u16 cur_line_num; /* current line # written to DDR */ + u16 frame_num; /* updated by DMA upon FE */ + u32 first_page_offset; /* offset for 1st page in LOP */ + } first_entry; + /* Second entry per buffer */ + struct __packed { + u32 timestamp; + u32 num_of_bytes; + /* the number of bytes for write on last page */ + u16 last_page_available_bytes; + /* the number of pages allocated for this buf */ + u16 num_of_pages; + } second_entry; + }; + u32 lop_page_addr; /* Points to list of pointers (LOP) table */ +}; + +static inline struct cio2_queue *file_to_cio2_queue(struct file *file) +{ + return container_of(video_devdata(file), struct cio2_queue, vdev); +} + +static inline struct cio2_queue *vb2q_to_cio2_queue(struct vb2_queue *vq) +{ + return container_of(vq, struct cio2_queue, vbq); +} + +#endif diff --git a/drivers/media/pci/intel/ipu6/Kconfig b/drivers/media/pci/intel/ipu6/Kconfig new file mode 100644 index 000000000..1129e2beb --- /dev/null +++ b/drivers/media/pci/intel/ipu6/Kconfig @@ -0,0 +1,18 @@ +config VIDEO_INTEL_IPU6 + tristate "Intel IPU6 driver" + depends on ACPI || COMPILE_TEST + depends on VIDEO_DEV + depends on X86 && HAS_DMA + depends on IPU_BRIDGE || !IPU_BRIDGE + select AUXILIARY_BUS + select IOMMU_IOVA + select VIDEO_V4L2_SUBDEV_API + select MEDIA_CONTROLLER + select VIDEOBUF2_DMA_SG + select V4L2_FWNODE + help + This is the 6th Gen Intel Image Processing Unit, found in Intel SoCs + and used for capturing images and video from camera sensors. + + To compile this driver, say Y here! It contains 2 modules - + intel_ipu6 and intel_ipu6_isys. diff --git a/drivers/media/pci/intel/ipu6/Makefile b/drivers/media/pci/intel/ipu6/Makefile new file mode 100644 index 000000000..a821b0a15 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/Makefile @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: GPL-2.0-only + +intel-ipu6-y := ipu6.o \ + ipu6-bus.o \ + ipu6-dma.o \ + ipu6-mmu.o \ + ipu6-buttress.o \ + ipu6-cpd.o \ + ipu6-fw-com.o + +obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6.o + +intel-ipu6-isys-y := ipu6-isys.o \ + ipu6-isys-csi2.o \ + ipu6-fw-isys.o \ + ipu6-isys-video.o \ + ipu6-isys-queue.o \ + ipu6-isys-subdev.o \ + ipu6-isys-mcd-phy.o \ + ipu6-isys-jsl-phy.o \ + ipu6-isys-dwc-phy.o + +obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6-isys.o diff --git a/drivers/media/pci/intel/ipu6/ipu6-bus.c b/drivers/media/pci/intel/ipu6/ipu6-bus.c new file mode 100644 index 000000000..173616381 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-bus.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2024 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipu6.h" +#include "ipu6-bus.h" +#include "ipu6-buttress.h" +#include "ipu6-dma.h" + +static int bus_pm_runtime_suspend(struct device *dev) +{ + struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); + int ret; + + ret = pm_generic_runtime_suspend(dev); + if (ret) + return ret; + + ret = ipu6_buttress_power(dev, adev->ctrl, false); + if (!ret) + return 0; + + dev_err(dev, "power down failed!\n"); + + /* Powering down failed, attempt to resume device now */ + ret = pm_generic_runtime_resume(dev); + if (!ret) + return -EBUSY; + + return -EIO; +} + +static int bus_pm_runtime_resume(struct device *dev) +{ + struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); + int ret; + + ret = ipu6_buttress_power(dev, adev->ctrl, true); + if (ret) + return ret; + + ret = pm_generic_runtime_resume(dev); + if (ret) + goto out_err; + + return 0; + +out_err: + ipu6_buttress_power(dev, adev->ctrl, false); + + return -EBUSY; +} + +static struct dev_pm_domain ipu6_bus_pm_domain = { + .ops = { + .runtime_suspend = bus_pm_runtime_suspend, + .runtime_resume = bus_pm_runtime_resume, + }, +}; + +static DEFINE_MUTEX(ipu6_bus_mutex); + +static void ipu6_bus_release(struct device *dev) +{ + struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); + + kfree(adev->pdata); + kfree(adev); +} + +struct ipu6_bus_device * +ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent, + void *pdata, const struct ipu6_buttress_ctrl *ctrl, + char *name) +{ + struct auxiliary_device *auxdev; + struct ipu6_bus_device *adev; + struct ipu6_device *isp = pci_get_drvdata(pdev); + int ret; + + adev = kzalloc_obj(*adev); + if (!adev) + return ERR_PTR(-ENOMEM); + + adev->isp = isp; + adev->ctrl = ctrl; + adev->pdata = pdata; + auxdev = &adev->auxdev; + auxdev->name = name; + auxdev->id = (pci_domain_nr(pdev->bus) << 16) | + PCI_DEVID(pdev->bus->number, pdev->devfn); + + auxdev->dev.parent = parent; + auxdev->dev.release = ipu6_bus_release; + + ret = auxiliary_device_init(auxdev); + if (ret < 0) { + dev_err(&isp->pdev->dev, "auxiliary device init failed (%d)\n", + ret); + kfree(adev); + return ERR_PTR(ret); + } + + dev_pm_domain_set(&auxdev->dev, &ipu6_bus_pm_domain); + + pm_runtime_forbid(&adev->auxdev.dev); + pm_runtime_enable(&adev->auxdev.dev); + + return adev; +} + +int ipu6_bus_add_device(struct ipu6_bus_device *adev) +{ + struct auxiliary_device *auxdev = &adev->auxdev; + int ret; + + ret = auxiliary_device_add(auxdev); + if (ret) { + auxiliary_device_uninit(auxdev); + return ret; + } + + mutex_lock(&ipu6_bus_mutex); + list_add(&adev->list, &adev->isp->devices); + mutex_unlock(&ipu6_bus_mutex); + + pm_runtime_allow(&auxdev->dev); + + return 0; +} + +void ipu6_bus_del_devices(struct pci_dev *pdev) +{ + struct ipu6_device *isp = pci_get_drvdata(pdev); + struct ipu6_bus_device *adev, *save; + + mutex_lock(&ipu6_bus_mutex); + + list_for_each_entry_safe(adev, save, &isp->devices, list) { + pm_runtime_disable(&adev->auxdev.dev); + list_del(&adev->list); + auxiliary_device_delete(&adev->auxdev); + auxiliary_device_uninit(&adev->auxdev); + } + + mutex_unlock(&ipu6_bus_mutex); +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-bus.h b/drivers/media/pci/intel/ipu6/ipu6-bus.h new file mode 100644 index 000000000..a08c5468d --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-bus.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013 - 2024 Intel Corporation */ + +#ifndef IPU6_BUS_H +#define IPU6_BUS_H + +#include +#include +#include +#include +#include +#include +#include + +struct firmware; +struct pci_dev; + +struct ipu6_buttress_ctrl; + +struct ipu6_bus_device { + struct auxiliary_device auxdev; + const struct auxiliary_driver *auxdrv; + const struct ipu6_auxdrv_data *auxdrv_data; + struct list_head list; + void *pdata; + struct ipu6_mmu *mmu; + struct ipu6_device *isp; + const struct ipu6_buttress_ctrl *ctrl; + const struct firmware *fw; + struct sg_table fw_sgt; + u64 *pkg_dir; + dma_addr_t pkg_dir_dma_addr; + unsigned int pkg_dir_size; +}; + +struct ipu6_auxdrv_data { + irqreturn_t (*isr)(struct ipu6_bus_device *adev); + irqreturn_t (*isr_threaded)(struct ipu6_bus_device *adev); + bool wake_isr_thread; +}; + +#define to_ipu6_bus_device(_dev) \ + container_of(to_auxiliary_dev(_dev), struct ipu6_bus_device, auxdev) +#define auxdev_to_adev(_auxdev) \ + container_of(_auxdev, struct ipu6_bus_device, auxdev) +#define ipu6_bus_get_drvdata(adev) dev_get_drvdata(&(adev)->auxdev.dev) + +struct ipu6_bus_device * +ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent, + void *pdata, const struct ipu6_buttress_ctrl *ctrl, + char *name); +int ipu6_bus_add_device(struct ipu6_bus_device *adev); +void ipu6_bus_del_devices(struct pci_dev *pdev); + +#endif diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.c b/drivers/media/pci/intel/ipu6/ipu6-buttress.c new file mode 100644 index 000000000..e0ecb4c8b --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.c @@ -0,0 +1,910 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipu6.h" +#include "ipu6-bus.h" +#include "ipu6-dma.h" +#include "ipu6-buttress.h" +#include "ipu6-platform-buttress-regs.h" + +#define BOOTLOADER_STATUS_OFFSET 0x15c + +#define BOOTLOADER_MAGIC_KEY 0xb00710ad + +#define ENTRY BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE1 +#define EXIT BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE2 +#define QUERY BUTTRESS_IU2CSECSR_IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE + +#define BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX 10 + +#define BUTTRESS_POWER_TIMEOUT_US (200 * USEC_PER_MSEC) + +#define BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US (5 * USEC_PER_SEC) +#define BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US (10 * USEC_PER_SEC) +#define BUTTRESS_CSE_FWRESET_TIMEOUT_US (100 * USEC_PER_MSEC) + +#define BUTTRESS_IPC_TX_TIMEOUT_MS MSEC_PER_SEC +#define BUTTRESS_IPC_RX_TIMEOUT_MS MSEC_PER_SEC +#define BUTTRESS_IPC_VALIDITY_TIMEOUT_US (1 * USEC_PER_SEC) +#define BUTTRESS_TSC_SYNC_TIMEOUT_US (5 * USEC_PER_MSEC) + +#define BUTTRESS_IPC_RESET_RETRY 2000 +#define BUTTRESS_CSE_IPC_RESET_RETRY 4 +#define BUTTRESS_IPC_CMD_SEND_RETRY 1 + +#define BUTTRESS_MAX_CONSECUTIVE_IRQS 100 + +static const u32 ipu6_adev_irq_mask[2] = { + BUTTRESS_ISR_IS_IRQ, + BUTTRESS_ISR_PS_IRQ +}; + +int ipu6_buttress_ipc_reset(struct ipu6_device *isp, + struct ipu6_buttress_ipc *ipc) +{ + unsigned int retries = BUTTRESS_IPC_RESET_RETRY; + struct ipu6_buttress *b = &isp->buttress; + u32 val = 0, csr_in_clr; + + if (!isp->secure_mode) { + dev_dbg(&isp->pdev->dev, "Skip IPC reset for non-secure mode"); + return 0; + } + + mutex_lock(&b->ipc_mutex); + + /* Clear-by-1 CSR (all bits), corresponding internal states. */ + val = readl(isp->base + ipc->csr_in); + writel(val, isp->base + ipc->csr_in); + + /* Set peer CSR bit IPC_PEER_COMP_ACTIONS_RST_PHASE1 */ + writel(ENTRY, isp->base + ipc->csr_out); + /* + * Clear-by-1 all CSR bits EXCEPT following + * bits: + * A. IPC_PEER_COMP_ACTIONS_RST_PHASE1. + * B. IPC_PEER_COMP_ACTIONS_RST_PHASE2. + * C. Possibly custom bits, depending on + * their role. + */ + csr_in_clr = BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ | + BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID | + BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ | QUERY; + + do { + usleep_range(400, 500); + val = readl(isp->base + ipc->csr_in); + switch (val) { + case ENTRY | EXIT: + case ENTRY | EXIT | QUERY: + /* + * 1) Clear-by-1 CSR bits + * (IPC_PEER_COMP_ACTIONS_RST_PHASE1, + * IPC_PEER_COMP_ACTIONS_RST_PHASE2). + * 2) Set peer CSR bit + * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE. + */ + writel(ENTRY | EXIT, isp->base + ipc->csr_in); + writel(QUERY, isp->base + ipc->csr_out); + break; + case ENTRY: + case ENTRY | QUERY: + /* + * 1) Clear-by-1 CSR bits + * (IPC_PEER_COMP_ACTIONS_RST_PHASE1, + * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE). + * 2) Set peer CSR bit + * IPC_PEER_COMP_ACTIONS_RST_PHASE1. + */ + writel(ENTRY | QUERY, isp->base + ipc->csr_in); + writel(ENTRY, isp->base + ipc->csr_out); + break; + case EXIT: + case EXIT | QUERY: + /* + * Clear-by-1 CSR bit + * IPC_PEER_COMP_ACTIONS_RST_PHASE2. + * 1) Clear incoming doorbell. + * 2) Clear-by-1 all CSR bits EXCEPT following + * bits: + * A. IPC_PEER_COMP_ACTIONS_RST_PHASE1. + * B. IPC_PEER_COMP_ACTIONS_RST_PHASE2. + * C. Possibly custom bits, depending on + * their role. + * 3) Set peer CSR bit + * IPC_PEER_COMP_ACTIONS_RST_PHASE2. + */ + writel(EXIT, isp->base + ipc->csr_in); + writel(0, isp->base + ipc->db0_in); + writel(csr_in_clr, isp->base + ipc->csr_in); + writel(EXIT, isp->base + ipc->csr_out); + + /* + * Read csr_in again to make sure if RST_PHASE2 is done. + * If csr_in is QUERY, it should be handled again. + */ + usleep_range(200, 300); + val = readl(isp->base + ipc->csr_in); + if (val & QUERY) { + dev_dbg(&isp->pdev->dev, + "RST_PHASE2 retry csr_in = %x\n", val); + break; + } + mutex_unlock(&b->ipc_mutex); + return 0; + case QUERY: + /* + * 1) Clear-by-1 CSR bit + * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE. + * 2) Set peer CSR bit + * IPC_PEER_COMP_ACTIONS_RST_PHASE1 + */ + writel(QUERY, isp->base + ipc->csr_in); + writel(ENTRY, isp->base + ipc->csr_out); + break; + default: + dev_dbg_ratelimited(&isp->pdev->dev, + "Unexpected CSR 0x%x\n", val); + break; + } + } while (retries--); + + mutex_unlock(&b->ipc_mutex); + dev_err(&isp->pdev->dev, "Timed out while waiting for CSE\n"); + + return -ETIMEDOUT; +} + +static void ipu6_buttress_ipc_validity_close(struct ipu6_device *isp, + struct ipu6_buttress_ipc *ipc) +{ + writel(BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ, + isp->base + ipc->csr_out); +} + +static int +ipu6_buttress_ipc_validity_open(struct ipu6_device *isp, + struct ipu6_buttress_ipc *ipc) +{ + unsigned int mask = BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID; + void __iomem *addr; + int ret; + u32 val; + + writel(BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ, + isp->base + ipc->csr_out); + + addr = isp->base + ipc->csr_in; + ret = readl_poll_timeout(addr, val, val & mask, 200, + BUTTRESS_IPC_VALIDITY_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, "CSE validity timeout 0x%x\n", val); + ipu6_buttress_ipc_validity_close(isp, ipc); + } + + return ret; +} + +static void ipu6_buttress_ipc_recv(struct ipu6_device *isp, + struct ipu6_buttress_ipc *ipc, u32 *ipc_msg) +{ + if (ipc_msg) + *ipc_msg = readl(isp->base + ipc->data0_in); + writel(0, isp->base + ipc->db0_in); +} + +static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, + struct ipu6_ipc_buttress_bulk_msg *msgs, + u32 size) +{ + unsigned long tx_timeout_jiffies, rx_timeout_jiffies; + unsigned int i, retry = BUTTRESS_IPC_CMD_SEND_RETRY; + struct ipu6_buttress *b = &isp->buttress; + struct ipu6_buttress_ipc *ipc = &b->cse; + u32 val; + int ret; + int tout; + + mutex_lock(&b->ipc_mutex); + + ret = ipu6_buttress_ipc_validity_open(isp, ipc); + if (ret) { + dev_err(&isp->pdev->dev, "IPC validity open failed\n"); + goto out; + } + + tx_timeout_jiffies = msecs_to_jiffies(BUTTRESS_IPC_TX_TIMEOUT_MS); + rx_timeout_jiffies = msecs_to_jiffies(BUTTRESS_IPC_RX_TIMEOUT_MS); + + for (i = 0; i < size; i++) { + reinit_completion(&ipc->send_complete); + if (msgs[i].require_resp) + reinit_completion(&ipc->recv_complete); + + dev_dbg(&isp->pdev->dev, "bulk IPC command: 0x%x\n", + msgs[i].cmd); + writel(msgs[i].cmd, isp->base + ipc->data0_out); + val = BUTTRESS_IU2CSEDB0_BUSY | msgs[i].cmd_size; + writel(val, isp->base + ipc->db0_out); + + tout = wait_for_completion_timeout(&ipc->send_complete, + tx_timeout_jiffies); + if (!tout) { + dev_err(&isp->pdev->dev, "send IPC response timeout\n"); + if (!retry--) { + ret = -ETIMEDOUT; + goto out; + } + + /* Try again if CSE is not responding on first try */ + writel(0, isp->base + ipc->db0_out); + i--; + continue; + } + + retry = BUTTRESS_IPC_CMD_SEND_RETRY; + + if (!msgs[i].require_resp) + continue; + + tout = wait_for_completion_timeout(&ipc->recv_complete, + rx_timeout_jiffies); + if (!tout) { + dev_err(&isp->pdev->dev, "recv IPC response timeout\n"); + ret = -ETIMEDOUT; + goto out; + } + + if (ipc->nack_mask && + (ipc->recv_data & ipc->nack_mask) == ipc->nack) { + dev_err(&isp->pdev->dev, + "IPC NACK for cmd 0x%x\n", msgs[i].cmd); + ret = -EIO; + goto out; + } + + if (ipc->recv_data != msgs[i].expected_resp) { + dev_err(&isp->pdev->dev, + "expected resp: 0x%x, IPC response: 0x%x ", + msgs[i].expected_resp, ipc->recv_data); + ret = -EIO; + goto out; + } + } + + dev_dbg(&isp->pdev->dev, "bulk IPC commands done\n"); + +out: + ipu6_buttress_ipc_validity_close(isp, ipc); + mutex_unlock(&b->ipc_mutex); + return ret; +} + +static int +ipu6_buttress_ipc_send(struct ipu6_device *isp, + u32 ipc_msg, u32 size, bool require_resp, + u32 expected_resp) +{ + struct ipu6_ipc_buttress_bulk_msg msg = { + .cmd = ipc_msg, + .cmd_size = size, + .require_resp = require_resp, + .expected_resp = expected_resp, + }; + + return ipu6_buttress_ipc_send_bulk(isp, &msg, 1); +} + +static irqreturn_t ipu6_buttress_call_isr(struct ipu6_bus_device *adev) +{ + irqreturn_t ret = IRQ_WAKE_THREAD; + + if (!adev || !adev->auxdrv || !adev->auxdrv_data) + return IRQ_NONE; + + if (adev->auxdrv_data->isr) + ret = adev->auxdrv_data->isr(adev); + + if (ret == IRQ_WAKE_THREAD && !adev->auxdrv_data->isr_threaded) + ret = IRQ_NONE; + + return ret; +} + +irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) +{ + struct ipu6_device *isp = isp_ptr; + struct ipu6_bus_device *adev[] = { isp->isys, isp->psys }; + struct ipu6_buttress *b = &isp->buttress; + u32 reg_irq_sts = BUTTRESS_REG_ISR_STATUS; + irqreturn_t ret = IRQ_NONE; + u32 disable_irqs = 0; + u32 irq_status; + u32 i, count = 0; + int active; + + active = pm_runtime_get_if_active(&isp->pdev->dev); + if (!active) + return IRQ_NONE; + + irq_status = readl(isp->base + reg_irq_sts); + if (irq_status == 0 || WARN_ON_ONCE(irq_status == 0xffffffffu)) { + if (active > 0) + pm_runtime_put_noidle(&isp->pdev->dev); + return IRQ_NONE; + } + + do { + writel(irq_status, isp->base + BUTTRESS_REG_ISR_CLEAR); + + for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask); i++) { + irqreturn_t r = ipu6_buttress_call_isr(adev[i]); + + if (!(irq_status & ipu6_adev_irq_mask[i])) + continue; + + if (r == IRQ_WAKE_THREAD) { + ret = IRQ_WAKE_THREAD; + disable_irqs |= ipu6_adev_irq_mask[i]; + } else if (ret == IRQ_NONE && r == IRQ_HANDLED) { + ret = IRQ_HANDLED; + } + } + + if ((irq_status & BUTTRESS_EVENT) && ret == IRQ_NONE) + ret = IRQ_HANDLED; + + if (irq_status & BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING) { + dev_dbg(&isp->pdev->dev, + "BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING\n"); + ipu6_buttress_ipc_recv(isp, &b->cse, &b->cse.recv_data); + complete(&b->cse.recv_complete); + } + + if (irq_status & BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE) { + dev_dbg(&isp->pdev->dev, + "BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE\n"); + complete(&b->cse.send_complete); + } + + if (irq_status & BUTTRESS_ISR_SAI_VIOLATION && + ipu6_buttress_get_secure_mode(isp)) + dev_err(&isp->pdev->dev, + "BUTTRESS_ISR_SAI_VIOLATION\n"); + + if (irq_status & (BUTTRESS_ISR_IS_FATAL_MEM_ERR | + BUTTRESS_ISR_PS_FATAL_MEM_ERR)) + dev_err(&isp->pdev->dev, + "BUTTRESS_ISR_FATAL_MEM_ERR\n"); + + if (irq_status & BUTTRESS_ISR_UFI_ERROR) + dev_err(&isp->pdev->dev, "BUTTRESS_ISR_UFI_ERROR\n"); + + if (++count == BUTTRESS_MAX_CONSECUTIVE_IRQS) { + dev_err(&isp->pdev->dev, "too many consecutive IRQs\n"); + ret = IRQ_NONE; + break; + } + + irq_status = readl(isp->base + reg_irq_sts); + } while (irq_status); + + if (disable_irqs) + writel(BUTTRESS_IRQS & ~disable_irqs, + isp->base + BUTTRESS_REG_ISR_ENABLE); + + if (active > 0) + pm_runtime_put(&isp->pdev->dev); + + return ret; +} + +irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr) +{ + struct ipu6_device *isp = isp_ptr; + struct ipu6_bus_device *adev[] = { isp->isys, isp->psys }; + const struct ipu6_auxdrv_data *drv_data = NULL; + irqreturn_t ret = IRQ_NONE; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask) && adev[i]; i++) { + drv_data = adev[i]->auxdrv_data; + if (!drv_data) + continue; + + if (drv_data->wake_isr_thread && + drv_data->isr_threaded(adev[i]) == IRQ_HANDLED) + ret = IRQ_HANDLED; + } + + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); + + return ret; +} + +int ipu6_buttress_power(struct device *dev, + const struct ipu6_buttress_ctrl *ctrl, bool on) +{ + struct ipu6_device *isp = to_ipu6_bus_device(dev)->isp; + u32 pwr_sts, val; + int ret; + + if (!ctrl) + return 0; + + mutex_lock(&isp->buttress.power_mutex); + + if (!on) { + val = 0; + pwr_sts = ctrl->pwr_sts_off << ctrl->pwr_sts_shift; + } else { + val = BUTTRESS_FREQ_CTL_START | + FIELD_PREP(BUTTRESS_FREQ_CTL_RATIO_MASK, + ctrl->ratio) | + FIELD_PREP(BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK, + ctrl->qos_floor) | + BUTTRESS_FREQ_CTL_ICCMAX_LEVEL; + + pwr_sts = ctrl->pwr_sts_on << ctrl->pwr_sts_shift; + } + + writel(val, isp->base + ctrl->freq_ctl); + + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE, + val, (val & ctrl->pwr_sts_mask) == pwr_sts, + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) + dev_err(&isp->pdev->dev, + "Change power status timeout with 0x%x\n", val); + + mutex_unlock(&isp->buttress.power_mutex); + + return ret; +} + +bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp) +{ + u32 val; + + val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL); + + return val & BUTTRESS_SECURITY_CTL_FW_SECURE_MODE; +} + +bool ipu6_buttress_auth_done(struct ipu6_device *isp) +{ + u32 val; + + if (!isp->secure_mode) + return true; + + val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL); + val = FIELD_GET(BUTTRESS_SECURITY_CTL_FW_SETUP_MASK, val); + + return val == BUTTRESS_SECURITY_CTL_AUTH_DONE; +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_auth_done, "INTEL_IPU6"); + +int ipu6_buttress_reset_authentication(struct ipu6_device *isp) +{ + int ret; + u32 val; + + if (!isp->secure_mode) { + dev_dbg(&isp->pdev->dev, "Skip auth for non-secure mode\n"); + return 0; + } + + writel(BUTTRESS_FW_RESET_CTL_START, isp->base + + BUTTRESS_REG_FW_RESET_CTL); + + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_FW_RESET_CTL, val, + val & BUTTRESS_FW_RESET_CTL_DONE, 500, + BUTTRESS_CSE_FWRESET_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, + "Time out while resetting authentication state\n"); + return ret; + } + + dev_dbg(&isp->pdev->dev, "FW reset for authentication done\n"); + writel(0, isp->base + BUTTRESS_REG_FW_RESET_CTL); + /* leave some time for HW restore */ + usleep_range(800, 1000); + + return 0; +} + +int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys, + const struct firmware *fw, struct sg_table *sgt) +{ + bool is_vmalloc = is_vmalloc_addr(fw->data); + struct pci_dev *pdev = sys->isp->pdev; + struct page **pages; + const void *addr; + unsigned long n_pages; + unsigned int i; + int ret; + + if (!is_vmalloc && !virt_addr_valid(fw->data)) + return -EDOM; + + n_pages = PFN_UP(fw->size); + + pages = kmalloc_objs(*pages, n_pages); + if (!pages) + return -ENOMEM; + + addr = fw->data; + for (i = 0; i < n_pages; i++) { + struct page *p = is_vmalloc ? + vmalloc_to_page(addr) : virt_to_page(addr); + + if (!p) { + ret = -ENOMEM; + goto out; + } + pages[i] = p; + addr += PAGE_SIZE; + } + + ret = sg_alloc_table_from_pages(sgt, pages, n_pages, 0, fw->size, + GFP_KERNEL); + if (ret) { + ret = -ENOMEM; + goto out; + } + + ret = dma_map_sgtable(&pdev->dev, sgt, DMA_TO_DEVICE, 0); + if (ret) { + sg_free_table(sgt); + goto out; + } + + ret = ipu6_dma_map_sgtable(sys, sgt, DMA_TO_DEVICE, 0); + if (ret) { + dma_unmap_sgtable(&pdev->dev, sgt, DMA_TO_DEVICE, 0); + sg_free_table(sgt); + goto out; + } + + ipu6_dma_sync_sgtable(sys, sgt); + +out: + kfree(pages); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_map_fw_image, "INTEL_IPU6"); + +void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys, + struct sg_table *sgt) +{ + struct pci_dev *pdev = sys->isp->pdev; + + ipu6_dma_unmap_sgtable(sys, sgt, DMA_TO_DEVICE, 0); + dma_unmap_sgtable(&pdev->dev, sgt, DMA_TO_DEVICE, 0); + sg_free_table(sgt); +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_unmap_fw_image, "INTEL_IPU6"); + +int ipu6_buttress_authenticate(struct ipu6_device *isp) +{ + struct ipu6_buttress *b = &isp->buttress; + struct ipu6_psys_pdata *psys_pdata; + u32 data, mask, done, fail; + int ret; + + if (!isp->secure_mode) { + dev_dbg(&isp->pdev->dev, "Skip auth for non-secure mode\n"); + return 0; + } + + psys_pdata = isp->psys->pdata; + + mutex_lock(&b->auth_mutex); + + if (ipu6_buttress_auth_done(isp)) { + ret = 0; + goto out_unlock; + } + + /* + * Write address of FIT table to FW_SOURCE register + * Let's use fw address. I.e. not using FIT table yet + */ + data = lower_32_bits(isp->psys->pkg_dir_dma_addr); + writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_LO); + + data = upper_32_bits(isp->psys->pkg_dir_dma_addr); + writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_HI); + + /* + * Write boot_load into IU2CSEDATA0 + * Write sizeof(boot_load) | 0x2 << CLIENT_ID to + * IU2CSEDB.IU2CSECMD and set IU2CSEDB.IU2CSEBUSY as + */ + dev_info(&isp->pdev->dev, "Sending BOOT_LOAD to CSE\n"); + + ret = ipu6_buttress_ipc_send(isp, + BUTTRESS_IU2CSEDATA0_IPC_BOOT_LOAD, + 1, true, + BUTTRESS_CSE2IUDATA0_IPC_BOOT_LOAD_DONE); + if (ret) { + dev_err(&isp->pdev->dev, "CSE boot_load failed\n"); + goto out_unlock; + } + + mask = BUTTRESS_SECURITY_CTL_FW_SETUP_MASK; + done = BUTTRESS_SECURITY_CTL_FW_SETUP_DONE; + fail = BUTTRESS_SECURITY_CTL_AUTH_FAILED; + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data, + ((data & mask) == done || + (data & mask) == fail), 500, + BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, "CSE boot_load timeout\n"); + goto out_unlock; + } + + if ((data & mask) == fail) { + dev_err(&isp->pdev->dev, "CSE auth failed\n"); + ret = -EINVAL; + goto out_unlock; + } + + ret = readl_poll_timeout(psys_pdata->base + BOOTLOADER_STATUS_OFFSET, + data, data == BOOTLOADER_MAGIC_KEY, 500, + BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, "Unexpected magic number 0x%x\n", + data); + goto out_unlock; + } + + /* + * Write authenticate_run into IU2CSEDATA0 + * Write sizeof(boot_load) | 0x2 << CLIENT_ID to + * IU2CSEDB.IU2CSECMD and set IU2CSEDB.IU2CSEBUSY as + */ + dev_info(&isp->pdev->dev, "Sending AUTHENTICATE_RUN to CSE\n"); + ret = ipu6_buttress_ipc_send(isp, + BUTTRESS_IU2CSEDATA0_IPC_AUTH_RUN, + 1, true, + BUTTRESS_CSE2IUDATA0_IPC_AUTH_RUN_DONE); + if (ret) { + dev_err(&isp->pdev->dev, "CSE authenticate_run failed\n"); + goto out_unlock; + } + + done = BUTTRESS_SECURITY_CTL_AUTH_DONE; + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data, + ((data & mask) == done || + (data & mask) == fail), 500, + BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, "CSE authenticate timeout\n"); + goto out_unlock; + } + + if ((data & mask) == fail) { + dev_err(&isp->pdev->dev, "CSE boot_load failed\n"); + ret = -EINVAL; + goto out_unlock; + } + + dev_info(&isp->pdev->dev, "CSE authenticate_run done\n"); + +out_unlock: + mutex_unlock(&b->auth_mutex); + + return ret; +} + +static int ipu6_buttress_send_tsc_request(struct ipu6_device *isp) +{ + u32 val, mask, done; + int ret; + + mask = BUTTRESS_PWR_STATE_HH_STATUS_MASK; + + writel(BUTTRESS_FABRIC_CMD_START_TSC_SYNC, + isp->base + BUTTRESS_REG_FABRIC_CMD); + + val = readl(isp->base + BUTTRESS_REG_PWR_STATE); + val = FIELD_GET(mask, val); + if (val == BUTTRESS_PWR_STATE_HH_STATE_ERR) { + dev_err(&isp->pdev->dev, "Start tsc sync failed\n"); + return -EINVAL; + } + + done = BUTTRESS_PWR_STATE_HH_STATE_DONE; + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE, val, + FIELD_GET(mask, val) == done, 500, + BUTTRESS_TSC_SYNC_TIMEOUT_US); + if (ret) + dev_err(&isp->pdev->dev, "Start tsc sync timeout\n"); + + return ret; +} + +int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp) +{ + unsigned int i; + + for (i = 0; i < BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX; i++) { + u32 val; + int ret; + + ret = ipu6_buttress_send_tsc_request(isp); + if (ret != -ETIMEDOUT) + return ret; + + val = readl(isp->base + BUTTRESS_REG_TSW_CTL); + val = val | BUTTRESS_TSW_CTL_SOFT_RESET; + writel(val, isp->base + BUTTRESS_REG_TSW_CTL); + val = val & ~BUTTRESS_TSW_CTL_SOFT_RESET; + writel(val, isp->base + BUTTRESS_REG_TSW_CTL); + } + + dev_err(&isp->pdev->dev, "TSC sync failed (timeout)\n"); + + return -ETIMEDOUT; +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_start_tsc_sync, "INTEL_IPU6"); + +void ipu6_buttress_tsc_read(struct ipu6_device *isp, u64 *val) +{ + u32 tsc_hi_1, tsc_hi_2, tsc_lo; + unsigned long flags; + + local_irq_save(flags); + tsc_hi_1 = readl(isp->base + BUTTRESS_REG_TSC_HI); + tsc_lo = readl(isp->base + BUTTRESS_REG_TSC_LO); + tsc_hi_2 = readl(isp->base + BUTTRESS_REG_TSC_HI); + if (tsc_hi_1 == tsc_hi_2) { + *val = (u64)tsc_hi_1 << 32 | tsc_lo; + } else { + /* Check if TSC has rolled over */ + if (tsc_lo & BIT(31)) + *val = (u64)tsc_hi_1 << 32 | tsc_lo; + else + *val = (u64)tsc_hi_2 << 32 | tsc_lo; + } + local_irq_restore(flags); +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_read, "INTEL_IPU6"); + +u64 ipu6_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu6_device *isp) +{ + u64 ns = ticks * 10000; + + /* + * converting TSC tick count to ns is calculated by: + * Example (TSC clock frequency is 19.2MHz): + * ns = ticks * 1000 000 000 / 19.2Mhz + * = ticks * 1000 000 000 / 19200000Hz + * = ticks * 10000 / 192 ns + */ + return div_u64(ns, isp->buttress.ref_clk); +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_ticks_to_ns, "INTEL_IPU6"); + +void ipu6_buttress_restore(struct ipu6_device *isp) +{ + struct ipu6_buttress *b = &isp->buttress; + + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR); + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); + writel(b->wdt_cached_value, isp->base + BUTTRESS_REG_WDT); +} + +int ipu6_buttress_init(struct ipu6_device *isp) +{ + int ret, ipc_reset_retry = BUTTRESS_CSE_IPC_RESET_RETRY; + struct ipu6_buttress *b = &isp->buttress; + u32 val; + + mutex_init(&b->power_mutex); + mutex_init(&b->auth_mutex); + mutex_init(&b->cons_mutex); + mutex_init(&b->ipc_mutex); + init_completion(&b->cse.send_complete); + init_completion(&b->cse.recv_complete); + + b->cse.nack = BUTTRESS_CSE2IUDATA0_IPC_NACK; + b->cse.nack_mask = BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK; + b->cse.csr_in = BUTTRESS_REG_CSE2IUCSR; + b->cse.csr_out = BUTTRESS_REG_IU2CSECSR; + b->cse.db0_in = BUTTRESS_REG_CSE2IUDB0; + b->cse.db0_out = BUTTRESS_REG_IU2CSEDB0; + b->cse.data0_in = BUTTRESS_REG_CSE2IUDATA0; + b->cse.data0_out = BUTTRESS_REG_IU2CSEDATA0; + + INIT_LIST_HEAD(&b->constraints); + + isp->secure_mode = ipu6_buttress_get_secure_mode(isp); + dev_dbg(&isp->pdev->dev, "IPU6 in %s mode touch 0x%x mask 0x%x\n", + isp->secure_mode ? "secure" : "non-secure", + readl(isp->base + BUTTRESS_REG_SECURITY_TOUCH), + readl(isp->base + BUTTRESS_REG_CAMERA_MASK)); + + b->wdt_cached_value = readl(isp->base + BUTTRESS_REG_WDT); + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR); + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); + + /* get ref_clk frequency by reading the indication in btrs control */ + val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL); + val = FIELD_GET(BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND, val); + + switch (val) { + case 0x0: + b->ref_clk = 240; + break; + case 0x1: + b->ref_clk = 192; + break; + case 0x2: + b->ref_clk = 384; + break; + default: + dev_warn(&isp->pdev->dev, + "Unsupported ref clock, use 19.2Mhz by default.\n"); + b->ref_clk = 192; + break; + } + + /* Retry couple of times in case of CSE initialization is delayed */ + do { + ret = ipu6_buttress_ipc_reset(isp, &b->cse); + if (ret) { + dev_warn(&isp->pdev->dev, + "IPC reset protocol failed, retrying\n"); + } else { + dev_dbg(&isp->pdev->dev, "IPC reset done\n"); + return 0; + } + } while (ipc_reset_retry--); + + dev_err(&isp->pdev->dev, "IPC reset protocol failed\n"); + + mutex_destroy(&b->power_mutex); + mutex_destroy(&b->auth_mutex); + mutex_destroy(&b->cons_mutex); + mutex_destroy(&b->ipc_mutex); + + return ret; +} + +void ipu6_buttress_exit(struct ipu6_device *isp) +{ + struct ipu6_buttress *b = &isp->buttress; + + writel(0, isp->base + BUTTRESS_REG_ISR_ENABLE); + + mutex_destroy(&b->power_mutex); + mutex_destroy(&b->auth_mutex); + mutex_destroy(&b->cons_mutex); + mutex_destroy(&b->ipc_mutex); +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.h b/drivers/media/pci/intel/ipu6/ipu6-buttress.h new file mode 100644 index 000000000..51e5ad48d --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013--2024 Intel Corporation */ + +#ifndef IPU6_BUTTRESS_H +#define IPU6_BUTTRESS_H + +#include +#include +#include +#include + +struct device; +struct firmware; +struct ipu6_device; +struct ipu6_bus_device; + +#define BUTTRESS_PS_FREQ_STEP 25U +#define BUTTRESS_MIN_FORCE_PS_FREQ (BUTTRESS_PS_FREQ_STEP * 8) +#define BUTTRESS_MAX_FORCE_PS_FREQ (BUTTRESS_PS_FREQ_STEP * 32) + +#define BUTTRESS_IS_FREQ_STEP 25U +#define BUTTRESS_MIN_FORCE_IS_FREQ (BUTTRESS_IS_FREQ_STEP * 8) +#define BUTTRESS_MAX_FORCE_IS_FREQ (BUTTRESS_IS_FREQ_STEP * 22) + +struct ipu6_buttress_ctrl { + u32 freq_ctl, pwr_sts_shift, pwr_sts_mask, pwr_sts_on, pwr_sts_off; + unsigned int ratio; + unsigned int qos_floor; +}; + +struct ipu6_buttress_ipc { + struct completion send_complete; + struct completion recv_complete; + u32 nack; + u32 nack_mask; + u32 recv_data; + u32 csr_out; + u32 csr_in; + u32 db0_in; + u32 db0_out; + u32 data0_out; + u32 data0_in; +}; + +struct ipu6_buttress { + struct mutex power_mutex, auth_mutex, cons_mutex, ipc_mutex; + struct ipu6_buttress_ipc cse; + struct list_head constraints; + u32 wdt_cached_value; + bool force_suspend; + u32 ref_clk; +}; + +struct ipu6_ipc_buttress_bulk_msg { + u32 cmd; + u32 expected_resp; + bool require_resp; + u8 cmd_size; +}; + +int ipu6_buttress_ipc_reset(struct ipu6_device *isp, + struct ipu6_buttress_ipc *ipc); +int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys, + const struct firmware *fw, + struct sg_table *sgt); +void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys, + struct sg_table *sgt); +int ipu6_buttress_power(struct device *dev, + const struct ipu6_buttress_ctrl *ctrl, bool on); +bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp); +int ipu6_buttress_authenticate(struct ipu6_device *isp); +int ipu6_buttress_reset_authentication(struct ipu6_device *isp); +bool ipu6_buttress_auth_done(struct ipu6_device *isp); +int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp); +void ipu6_buttress_tsc_read(struct ipu6_device *isp, u64 *val); +u64 ipu6_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu6_device *isp); + +irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr); +irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr); +int ipu6_buttress_init(struct ipu6_device *isp); +void ipu6_buttress_exit(struct ipu6_device *isp); +void ipu6_buttress_csi_port_config(struct ipu6_device *isp, + u32 legacy, u32 combo); +void ipu6_buttress_restore(struct ipu6_device *isp); +#endif /* IPU6_BUTTRESS_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-cpd.c b/drivers/media/pci/intel/ipu6/ipu6-cpd.c new file mode 100644 index 000000000..b7013f652 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-cpd.c @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipu6.h" +#include "ipu6-bus.h" +#include "ipu6-cpd.h" +#include "ipu6-dma.h" + +/* 15 entries + header*/ +#define MAX_PKG_DIR_ENT_CNT 16 +/* 2 qword per entry/header */ +#define PKG_DIR_ENT_LEN 2 +/* PKG_DIR size in bytes */ +#define PKG_DIR_SIZE ((MAX_PKG_DIR_ENT_CNT) * \ + (PKG_DIR_ENT_LEN) * sizeof(u64)) +/* _IUPKDR_ */ +#define PKG_DIR_HDR_MARK 0x5f4955504b44525fULL + +/* $CPD */ +#define CPD_HDR_MARK 0x44504324 + +#define MAX_MANIFEST_SIZE (SZ_2K * sizeof(u32)) +#define MAX_METADATA_SIZE SZ_64K + +#define MAX_COMPONENT_ID 127 +#define MAX_COMPONENT_VERSION 0xffff + +#define MANIFEST_IDX 0 +#define METADATA_IDX 1 +#define MODULEDATA_IDX 2 +/* + * PKG_DIR Entry (type == id) + * 63:56 55 54:48 47:32 31:24 23:0 + * Rsvd Rsvd Type Version Rsvd Size + */ +#define PKG_DIR_SIZE_MASK GENMASK_ULL(23, 0) +#define PKG_DIR_VERSION_MASK GENMASK_ULL(47, 32) +#define PKG_DIR_TYPE_MASK GENMASK_ULL(54, 48) + +static inline const struct ipu6_cpd_ent *ipu6_cpd_get_entry(const void *cpd, + u8 idx) +{ + const struct ipu6_cpd_hdr *cpd_hdr = cpd; + const struct ipu6_cpd_ent *ent; + + ent = (const struct ipu6_cpd_ent *)((const u8 *)cpd + cpd_hdr->hdr_len); + return ent + idx; +} + +#define ipu6_cpd_get_manifest(cpd) ipu6_cpd_get_entry(cpd, MANIFEST_IDX) +#define ipu6_cpd_get_metadata(cpd) ipu6_cpd_get_entry(cpd, METADATA_IDX) +#define ipu6_cpd_get_moduledata(cpd) ipu6_cpd_get_entry(cpd, MODULEDATA_IDX) + +static const struct ipu6_cpd_metadata_cmpnt_hdr * +ipu6_cpd_metadata_get_cmpnt(struct ipu6_device *isp, const void *metadata, + unsigned int metadata_size, u8 idx) +{ + size_t extn_size = sizeof(struct ipu6_cpd_metadata_extn); + size_t cmpnt_count = metadata_size - extn_size; + + cmpnt_count = div_u64(cmpnt_count, isp->cpd_metadata_cmpnt_size); + + if (idx > MAX_COMPONENT_ID || idx >= cmpnt_count) { + dev_err(&isp->pdev->dev, "Component index out of range (%d)\n", + idx); + return ERR_PTR(-EINVAL); + } + + return metadata + extn_size + idx * isp->cpd_metadata_cmpnt_size; +} + +static u32 ipu6_cpd_metadata_cmpnt_version(struct ipu6_device *isp, + const void *metadata, + unsigned int metadata_size, u8 idx) +{ + const struct ipu6_cpd_metadata_cmpnt_hdr *cmpnt; + + cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx); + if (IS_ERR(cmpnt)) + return PTR_ERR(cmpnt); + + return cmpnt->ver; +} + +static int ipu6_cpd_metadata_get_cmpnt_id(struct ipu6_device *isp, + const void *metadata, + unsigned int metadata_size, u8 idx) +{ + const struct ipu6_cpd_metadata_cmpnt_hdr *cmpnt; + + cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx); + if (IS_ERR(cmpnt)) + return PTR_ERR(cmpnt); + + return cmpnt->id; +} + +static int ipu6_cpd_parse_module_data(struct ipu6_device *isp, + const void *module_data, + unsigned int module_data_size, + dma_addr_t dma_addr_module_data, + u64 *pkg_dir, const void *metadata, + unsigned int metadata_size) +{ + const struct ipu6_cpd_module_data_hdr *module_data_hdr; + const struct ipu6_cpd_hdr *dir_hdr; + const struct ipu6_cpd_ent *dir_ent; + unsigned int i; + u8 len; + + if (!module_data) + return -EINVAL; + + module_data_hdr = module_data; + dir_hdr = module_data + module_data_hdr->hdr_len; + len = dir_hdr->hdr_len; + dir_ent = (const struct ipu6_cpd_ent *)(((u8 *)dir_hdr) + len); + + pkg_dir[0] = PKG_DIR_HDR_MARK; + /* pkg_dir entry count = component count + pkg_dir header */ + pkg_dir[1] = dir_hdr->ent_cnt + 1; + + for (i = 0; i < dir_hdr->ent_cnt; i++, dir_ent++) { + u64 *p = &pkg_dir[PKG_DIR_ENT_LEN * (1 + i)]; + int ver, id; + + *p++ = dma_addr_module_data + dir_ent->offset; + id = ipu6_cpd_metadata_get_cmpnt_id(isp, metadata, + metadata_size, i); + if (id < 0 || id > MAX_COMPONENT_ID) { + dev_err(&isp->pdev->dev, "Invalid CPD component id\n"); + return -EINVAL; + } + + ver = ipu6_cpd_metadata_cmpnt_version(isp, metadata, + metadata_size, i); + if (ver < 0 || ver > MAX_COMPONENT_VERSION) { + dev_err(&isp->pdev->dev, + "Invalid CPD component version\n"); + return -EINVAL; + } + + *p = FIELD_PREP(PKG_DIR_SIZE_MASK, dir_ent->len) | + FIELD_PREP(PKG_DIR_TYPE_MASK, id) | + FIELD_PREP(PKG_DIR_VERSION_MASK, ver); + } + + return 0; +} + +int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src) +{ + dma_addr_t dma_addr_src = sg_dma_address(adev->fw_sgt.sgl); + const struct ipu6_cpd_ent *ent, *man_ent, *met_ent; + struct ipu6_device *isp = adev->isp; + unsigned int man_sz, met_sz; + void *pkg_dir_pos; + int ret; + + man_ent = ipu6_cpd_get_manifest(src); + man_sz = man_ent->len; + + met_ent = ipu6_cpd_get_metadata(src); + met_sz = met_ent->len; + + adev->pkg_dir_size = PKG_DIR_SIZE + man_sz + met_sz; + adev->pkg_dir = ipu6_dma_alloc(adev, adev->pkg_dir_size, + &adev->pkg_dir_dma_addr, GFP_KERNEL, 0); + if (!adev->pkg_dir) + return -ENOMEM; + + /* + * pkg_dir entry/header: + * qword | 63:56 | 55 | 54:48 | 47:32 | 31:24 | 23:0 + * N Address/Offset/"_IUPKDR_" + * N + 1 | rsvd | rsvd | type | ver | rsvd | size + * + * We can ignore other fields that size in N + 1 qword as they + * are 0 anyway. Just setting size for now. + */ + + ent = ipu6_cpd_get_moduledata(src); + + ret = ipu6_cpd_parse_module_data(isp, src + ent->offset, + ent->len, dma_addr_src + ent->offset, + adev->pkg_dir, src + met_ent->offset, + met_ent->len); + if (ret) { + dev_err(&isp->pdev->dev, "Failed to parse module data\n"); + ipu6_dma_free(adev, adev->pkg_dir_size, + adev->pkg_dir, adev->pkg_dir_dma_addr, 0); + return ret; + } + + /* Copy manifest after pkg_dir */ + pkg_dir_pos = adev->pkg_dir + PKG_DIR_ENT_LEN * MAX_PKG_DIR_ENT_CNT; + memcpy(pkg_dir_pos, src + man_ent->offset, man_sz); + + /* Copy metadata after manifest */ + pkg_dir_pos += man_sz; + memcpy(pkg_dir_pos, src + met_ent->offset, met_sz); + + ipu6_dma_sync_single(adev, adev->pkg_dir_dma_addr, + adev->pkg_dir_size); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu6_cpd_create_pkg_dir, "INTEL_IPU6"); + +void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev) +{ + ipu6_dma_free(adev, adev->pkg_dir_size, adev->pkg_dir, + adev->pkg_dir_dma_addr, 0); +} +EXPORT_SYMBOL_NS_GPL(ipu6_cpd_free_pkg_dir, "INTEL_IPU6"); + +static int ipu6_cpd_validate_cpd(struct ipu6_device *isp, const void *cpd, + unsigned long cpd_size, + unsigned long data_size) +{ + const struct ipu6_cpd_hdr *cpd_hdr = cpd; + const struct ipu6_cpd_ent *ent; + unsigned int i; + u8 len; + + len = cpd_hdr->hdr_len; + + /* Ensure cpd hdr is within moduledata */ + if (cpd_size < len) { + dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n"); + return -EINVAL; + } + + /* Sanity check for CPD header */ + if ((cpd_size - len) / sizeof(*ent) < cpd_hdr->ent_cnt) { + dev_err(&isp->pdev->dev, "Invalid CPD header\n"); + return -EINVAL; + } + + /* Ensure that all entries are within moduledata */ + ent = (const struct ipu6_cpd_ent *)(((const u8 *)cpd_hdr) + len); + for (i = 0; i < cpd_hdr->ent_cnt; i++, ent++) { + if (data_size < ent->offset || + data_size - ent->offset < ent->len) { + dev_err(&isp->pdev->dev, "Invalid CPD entry (%d)\n", i); + return -EINVAL; + } + } + + return 0; +} + +static int ipu6_cpd_validate_moduledata(struct ipu6_device *isp, + const void *moduledata, + u32 moduledata_size) +{ + const struct ipu6_cpd_module_data_hdr *mod_hdr = moduledata; + int ret; + + /* Ensure moduledata hdr is within moduledata */ + if (moduledata_size < sizeof(*mod_hdr) || + moduledata_size < mod_hdr->hdr_len) { + dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n"); + return -EINVAL; + } + + dev_dbg(&isp->pdev->dev, "FW version: %x\n", mod_hdr->fw_pkg_date); + ret = ipu6_cpd_validate_cpd(isp, moduledata + mod_hdr->hdr_len, + moduledata_size - mod_hdr->hdr_len, + moduledata_size); + if (ret) { + dev_err(&isp->pdev->dev, "Invalid CPD in moduledata\n"); + return ret; + } + + return 0; +} + +static int ipu6_cpd_validate_metadata(struct ipu6_device *isp, + const void *metadata, u32 meta_size) +{ + const struct ipu6_cpd_metadata_extn *extn = metadata; + + /* Sanity check for metadata size */ + if (meta_size < sizeof(*extn) || meta_size > MAX_METADATA_SIZE) { + dev_err(&isp->pdev->dev, "Invalid CPD metadata\n"); + return -EINVAL; + } + + /* Validate extension and image types */ + if (extn->extn_type != IPU6_CPD_METADATA_EXTN_TYPE_IUNIT || + extn->img_type != IPU6_CPD_METADATA_IMAGE_TYPE_MAIN_FIRMWARE) { + dev_err(&isp->pdev->dev, + "Invalid CPD metadata descriptor img_type (%d)\n", + extn->img_type); + return -EINVAL; + } + + /* Validate metadata size multiple of metadata components */ + if ((meta_size - sizeof(*extn)) % isp->cpd_metadata_cmpnt_size) { + dev_err(&isp->pdev->dev, "Invalid CPD metadata size\n"); + return -EINVAL; + } + + return 0; +} + +int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, + unsigned long cpd_file_size) +{ + const struct ipu6_cpd_hdr *hdr = cpd_file; + const struct ipu6_cpd_ent *ent; + int ret; + + ret = ipu6_cpd_validate_cpd(isp, cpd_file, cpd_file_size, + cpd_file_size); + if (ret) { + dev_err(&isp->pdev->dev, "Invalid CPD in file\n"); + return ret; + } + + /* Check for CPD file marker */ + if (hdr->hdr_mark != CPD_HDR_MARK) { + dev_err(&isp->pdev->dev, "Invalid CPD header\n"); + return -EINVAL; + } + + /* Sanity check for manifest size */ + ent = ipu6_cpd_get_manifest(cpd_file); + if (ent->len > MAX_MANIFEST_SIZE) { + dev_err(&isp->pdev->dev, "Invalid CPD manifest size\n"); + return -EINVAL; + } + + /* Validate metadata */ + ent = ipu6_cpd_get_metadata(cpd_file); + ret = ipu6_cpd_validate_metadata(isp, cpd_file + ent->offset, ent->len); + if (ret) { + dev_err(&isp->pdev->dev, "Invalid CPD metadata\n"); + return ret; + } + + /* Validate moduledata */ + ent = ipu6_cpd_get_moduledata(cpd_file); + ret = ipu6_cpd_validate_moduledata(isp, cpd_file + ent->offset, + ent->len); + if (ret) + dev_err(&isp->pdev->dev, "Invalid CPD moduledata\n"); + + return ret; +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-cpd.h b/drivers/media/pci/intel/ipu6/ipu6-cpd.h new file mode 100644 index 000000000..e0e4fdeca --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-cpd.h @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2015--2024 Intel Corporation */ + +#ifndef IPU6_CPD_H +#define IPU6_CPD_H + +struct ipu6_device; +struct ipu6_bus_device; + +#define IPU6_CPD_SIZE_OF_FW_ARCH_VERSION 7 +#define IPU6_CPD_SIZE_OF_SYSTEM_VERSION 11 +#define IPU6_CPD_SIZE_OF_COMPONENT_NAME 12 + +#define IPU6_CPD_METADATA_EXTN_TYPE_IUNIT 0x10 + +#define IPU6_CPD_METADATA_IMAGE_TYPE_RESERVED 0 +#define IPU6_CPD_METADATA_IMAGE_TYPE_BOOTLOADER 1 +#define IPU6_CPD_METADATA_IMAGE_TYPE_MAIN_FIRMWARE 2 + +#define IPU6_CPD_PKG_DIR_PSYS_SERVER_IDX 0 +#define IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX 1 + +#define IPU6_CPD_PKG_DIR_CLIENT_PG_TYPE 3 + +#define IPU6_CPD_METADATA_HASH_KEY_SIZE 48 +#define IPU6SE_CPD_METADATA_HASH_KEY_SIZE 32 + +struct ipu6_cpd_module_data_hdr { + u32 hdr_len; + u32 endian; + u32 fw_pkg_date; + u32 hive_sdk_date; + u32 compiler_date; + u32 target_platform_type; + u8 sys_ver[IPU6_CPD_SIZE_OF_SYSTEM_VERSION]; + u8 fw_arch_ver[IPU6_CPD_SIZE_OF_FW_ARCH_VERSION]; + u8 rsvd[2]; +} __packed; + +/* + * ipu6_cpd_hdr structure updated as the chksum and + * sub_partition_name is unused on host side + * CSE layout version 1.6 for IPU6SE (hdr_len = 0x10) + * CSE layout version 1.7 for IPU6 (hdr_len = 0x14) + */ +struct ipu6_cpd_hdr { + u32 hdr_mark; + u32 ent_cnt; + u8 hdr_ver; + u8 ent_ver; + u8 hdr_len; +} __packed; + +struct ipu6_cpd_ent { + u8 name[IPU6_CPD_SIZE_OF_COMPONENT_NAME]; + u32 offset; + u32 len; + u8 rsvd[4]; +} __packed; + +struct ipu6_cpd_metadata_cmpnt_hdr { + u32 id; + u32 size; + u32 ver; +} __packed; + +struct ipu6_cpd_metadata_cmpnt { + struct ipu6_cpd_metadata_cmpnt_hdr hdr; + u8 sha2_hash[IPU6_CPD_METADATA_HASH_KEY_SIZE]; + u32 entry_point; + u32 icache_base_offs; + u8 attrs[16]; +} __packed; + +struct ipu6se_cpd_metadata_cmpnt { + struct ipu6_cpd_metadata_cmpnt_hdr hdr; + u8 sha2_hash[IPU6SE_CPD_METADATA_HASH_KEY_SIZE]; + u32 entry_point; + u32 icache_base_offs; + u8 attrs[16]; +} __packed; + +struct ipu6_cpd_metadata_extn { + u32 extn_type; + u32 len; + u32 img_type; + u8 rsvd[16]; +} __packed; + +struct ipu6_cpd_client_pkg_hdr { + u32 prog_list_offs; + u32 prog_list_size; + u32 prog_desc_offs; + u32 prog_desc_size; + u32 pg_manifest_offs; + u32 pg_manifest_size; + u32 prog_bin_offs; + u32 prog_bin_size; +} __packed; + +int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src); +void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev); +int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, + unsigned long cpd_file_size); +#endif /* IPU6_CPD_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-dma.c b/drivers/media/pci/intel/ipu6/ipu6-dma.c new file mode 100644 index 000000000..fdcdb15b0 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-dma.c @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipu6.h" +#include "ipu6-bus.h" +#include "ipu6-dma.h" +#include "ipu6-mmu.h" + +struct vm_info { + struct list_head list; + struct page **pages; + dma_addr_t ipu6_iova; + void *vaddr; + unsigned long size; +}; + +static struct vm_info *get_vm_info(struct ipu6_mmu *mmu, dma_addr_t iova) +{ + struct vm_info *info, *save; + + list_for_each_entry_safe(info, save, &mmu->vma_list, list) { + if (iova >= info->ipu6_iova && + iova < (info->ipu6_iova + info->size)) + return info; + } + + return NULL; +} + +static void __clear_buffer(struct page *page, size_t size, unsigned long attrs) +{ + void *ptr; + + if (!page) + return; + /* + * Ensure that the allocated pages are zeroed, and that any data + * lurking in the kernel direct-mapped region is invalidated. + */ + ptr = page_address(page); + memset(ptr, 0, size); + if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0) + clflush_cache_range(ptr, size); +} + +static struct page **__alloc_buffer(size_t size, gfp_t gfp, unsigned long attrs) +{ + int count = PHYS_PFN(size); + int array_size = count * sizeof(struct page *); + struct page **pages; + int i = 0; + + pages = kvzalloc(array_size, GFP_KERNEL); + if (!pages) + return NULL; + + gfp |= __GFP_NOWARN; + + while (count) { + int j, order = __fls(count); + + pages[i] = alloc_pages(gfp, order); + while (!pages[i] && order) + pages[i] = alloc_pages(gfp, --order); + if (!pages[i]) + goto error; + + if (order) { + split_page(pages[i], order); + j = 1 << order; + while (j--) + pages[i + j] = pages[i] + j; + } + + __clear_buffer(pages[i], PAGE_SIZE << order, attrs); + i += 1 << order; + count -= 1 << order; + } + + return pages; +error: + while (i--) + if (pages[i]) + __free_pages(pages[i], 0); + kvfree(pages); + return NULL; +} + +static void __free_buffer(struct page **pages, size_t size, unsigned long attrs) +{ + int count = PHYS_PFN(size); + unsigned int i; + + for (i = 0; i < count && pages[i]; i++) { + __clear_buffer(pages[i], PAGE_SIZE, attrs); + __free_pages(pages[i], 0); + } + + kvfree(pages); +} + +void ipu6_dma_sync_single(struct ipu6_bus_device *sys, dma_addr_t dma_handle, + size_t size) +{ + void *vaddr; + u32 offset; + struct vm_info *info; + struct ipu6_mmu *mmu = sys->mmu; + + info = get_vm_info(mmu, dma_handle); + if (WARN_ON(!info)) + return; + + offset = dma_handle - info->ipu6_iova; + if (WARN_ON(size > (info->size - offset))) + return; + + vaddr = info->vaddr + offset; + clflush_cache_range(vaddr, size); +} +EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_single, "INTEL_IPU6"); + +void ipu6_dma_sync_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist, + int nents) +{ + struct scatterlist *sg; + int i; + + for_each_sg(sglist, sg, nents, i) + clflush_cache_range(sg_virt(sg), sg->length); +} +EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_sg, "INTEL_IPU6"); + +void ipu6_dma_sync_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt) +{ + ipu6_dma_sync_sg(sys, sgt->sgl, sgt->orig_nents); +} +EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_sgtable, "INTEL_IPU6"); + +void *ipu6_dma_alloc(struct ipu6_bus_device *sys, size_t size, + dma_addr_t *dma_handle, gfp_t gfp, + unsigned long attrs) +{ + struct device *dev = &sys->auxdev.dev; + struct pci_dev *pdev = sys->isp->pdev; + dma_addr_t pci_dma_addr, ipu6_iova; + struct ipu6_mmu *mmu = sys->mmu; + struct vm_info *info; + unsigned long count; + struct page **pages; + struct iova *iova; + unsigned int i; + int ret; + + info = kzalloc_obj(*info); + if (!info) + return NULL; + + size = PAGE_ALIGN(size); + count = PHYS_PFN(size); + + iova = alloc_iova(&mmu->dmap->iovad, count, + PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0); + if (!iova) + goto out_kfree; + + pages = __alloc_buffer(size, gfp, attrs); + if (!pages) + goto out_free_iova; + + dev_dbg(dev, "dma_alloc: size %zu iova low pfn %lu, high pfn %lu\n", + size, iova->pfn_lo, iova->pfn_hi); + for (i = 0; iova->pfn_lo + i <= iova->pfn_hi; i++) { + pci_dma_addr = dma_map_page_attrs(&pdev->dev, pages[i], 0, + PAGE_SIZE, DMA_BIDIRECTIONAL, + attrs); + dev_dbg(dev, "dma_alloc: mapped pci_dma_addr %pad\n", + &pci_dma_addr); + if (dma_mapping_error(&pdev->dev, pci_dma_addr)) { + dev_err(dev, "pci_dma_mapping for page[%d] failed", i); + goto out_unmap; + } + + ret = ipu6_mmu_map(mmu->dmap->mmu_info, + PFN_PHYS(iova->pfn_lo + i), pci_dma_addr, + PAGE_SIZE); + if (ret) { + dev_err(dev, "ipu6_mmu_map for pci_dma[%d] %pad failed", + i, &pci_dma_addr); + dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, + PAGE_SIZE, DMA_BIDIRECTIONAL, + attrs); + goto out_unmap; + } + } + + info->vaddr = vmap(pages, count, VM_USERMAP, PAGE_KERNEL); + if (!info->vaddr) + goto out_unmap; + + *dma_handle = PFN_PHYS(iova->pfn_lo); + + info->pages = pages; + info->ipu6_iova = *dma_handle; + info->size = size; + list_add(&info->list, &mmu->vma_list); + + return info->vaddr; + +out_unmap: + while (i--) { + ipu6_iova = PFN_PHYS(iova->pfn_lo + i); + pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info, + ipu6_iova); + dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, PAGE_SIZE, + DMA_BIDIRECTIONAL, attrs); + + ipu6_mmu_unmap(mmu->dmap->mmu_info, ipu6_iova, PAGE_SIZE); + } + + __free_buffer(pages, size, attrs); + +out_free_iova: + __free_iova(&mmu->dmap->iovad, iova); +out_kfree: + kfree(info); + + return NULL; +} +EXPORT_SYMBOL_NS_GPL(ipu6_dma_alloc, "INTEL_IPU6"); + +void ipu6_dma_free(struct ipu6_bus_device *sys, size_t size, void *vaddr, + dma_addr_t dma_handle, unsigned long attrs) +{ + struct ipu6_mmu *mmu = sys->mmu; + struct pci_dev *pdev = sys->isp->pdev; + struct iova *iova = find_iova(&mmu->dmap->iovad, PHYS_PFN(dma_handle)); + dma_addr_t pci_dma_addr, ipu6_iova; + struct vm_info *info; + struct page **pages; + unsigned int i; + + if (WARN_ON(!iova)) + return; + + info = get_vm_info(mmu, dma_handle); + if (WARN_ON(!info)) + return; + + if (WARN_ON(!info->vaddr)) + return; + + if (WARN_ON(!info->pages)) + return; + + list_del(&info->list); + + size = PAGE_ALIGN(size); + + pages = info->pages; + + vunmap(vaddr); + + for (i = 0; i < PHYS_PFN(size); i++) { + ipu6_iova = PFN_PHYS(iova->pfn_lo + i); + pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info, + ipu6_iova); + dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, PAGE_SIZE, + DMA_BIDIRECTIONAL, attrs); + } + + ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo), + PFN_PHYS(iova_size(iova))); + + __free_buffer(pages, size, attrs); + + mmu->tlb_invalidate(mmu); + + __free_iova(&mmu->dmap->iovad, iova); + + kfree(info); +} +EXPORT_SYMBOL_NS_GPL(ipu6_dma_free, "INTEL_IPU6"); + +int ipu6_dma_mmap(struct ipu6_bus_device *sys, struct vm_area_struct *vma, + void *addr, dma_addr_t iova, size_t size, + unsigned long attrs) +{ + struct ipu6_mmu *mmu = sys->mmu; + size_t count = PFN_UP(size); + struct vm_info *info; + size_t i; + int ret; + + info = get_vm_info(mmu, iova); + if (!info) + return -EFAULT; + + if (!info->vaddr) + return -EFAULT; + + if (vma->vm_start & ~PAGE_MASK) + return -EINVAL; + + if (size > info->size) + return -EFAULT; + + for (i = 0; i < count; i++) { + ret = vm_insert_page(vma, vma->vm_start + PFN_PHYS(i), + info->pages[i]); + if (ret < 0) + return ret; + } + + return 0; +} + +void ipu6_dma_unmap_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist, + int nents, enum dma_data_direction dir, + unsigned long attrs) +{ + struct device *dev = &sys->auxdev.dev; + struct ipu6_mmu *mmu = sys->mmu; + struct iova *iova = find_iova(&mmu->dmap->iovad, + PHYS_PFN(sg_dma_address(sglist))); + struct scatterlist *sg; + dma_addr_t pci_dma_addr; + unsigned int i; + + if (!nents) + return; + + if (WARN_ON(!iova)) + return; + + /* + * Before IPU6 mmu unmap, return the pci dma address back to sg + * assume the nents is less than orig_nents as the least granule + * is 1 SZ_4K page + */ + dev_dbg(dev, "trying to unmap concatenated %u ents\n", nents); + for_each_sg(sglist, sg, nents, i) { + dev_dbg(dev, "unmap sg[%d] %pad size %u\n", i, + &sg_dma_address(sg), sg_dma_len(sg)); + pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info, + sg_dma_address(sg)); + dev_dbg(dev, "return pci_dma_addr %pad back to sg[%d]\n", + &pci_dma_addr, i); + sg_dma_address(sg) = pci_dma_addr; + } + + dev_dbg(dev, "ipu6_mmu_unmap low pfn %lu high pfn %lu\n", + iova->pfn_lo, iova->pfn_hi); + ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo), + PFN_PHYS(iova_size(iova))); + + mmu->tlb_invalidate(mmu); + __free_iova(&mmu->dmap->iovad, iova); +} +EXPORT_SYMBOL_NS_GPL(ipu6_dma_unmap_sg, "INTEL_IPU6"); + +int ipu6_dma_map_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist, + int nents, enum dma_data_direction dir, + unsigned long attrs) +{ + struct device *dev = &sys->auxdev.dev; + struct ipu6_mmu *mmu = sys->mmu; + struct scatterlist *sg; + struct iova *iova; + size_t npages = 0; + unsigned long iova_addr; + int i; + + for_each_sg(sglist, sg, nents, i) { + if (sg->offset) { + dev_err(dev, "Unsupported non-zero sg[%d].offset %x\n", + i, sg->offset); + return -EFAULT; + } + } + + for_each_sg(sglist, sg, nents, i) + npages += PFN_UP(sg_dma_len(sg)); + + dev_dbg(dev, "dmamap trying to map %d ents %zu pages\n", + nents, npages); + + iova = alloc_iova(&mmu->dmap->iovad, npages, + PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0); + if (!iova) + return 0; + + dev_dbg(dev, "dmamap: iova low pfn %lu, high pfn %lu\n", iova->pfn_lo, + iova->pfn_hi); + + iova_addr = iova->pfn_lo; + for_each_sg(sglist, sg, nents, i) { + phys_addr_t iova_pa; + int ret; + + iova_pa = PFN_PHYS(iova_addr); + dev_dbg(dev, "mapping entry %d: iova %pap phy %pap size %d\n", + i, &iova_pa, &sg_dma_address(sg), sg_dma_len(sg)); + + ret = ipu6_mmu_map(mmu->dmap->mmu_info, PFN_PHYS(iova_addr), + sg_dma_address(sg), + PAGE_ALIGN(sg_dma_len(sg))); + if (ret) + goto out_fail; + + sg_dma_address(sg) = PFN_PHYS(iova_addr); + + iova_addr += PFN_UP(sg_dma_len(sg)); + } + + dev_dbg(dev, "dmamap %d ents %zu pages mapped\n", nents, npages); + + return nents; + +out_fail: + ipu6_dma_unmap_sg(sys, sglist, i, dir, attrs); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu6_dma_map_sg, "INTEL_IPU6"); + +int ipu6_dma_map_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt, + enum dma_data_direction dir, unsigned long attrs) +{ + int nents; + + nents = ipu6_dma_map_sg(sys, sgt->sgl, sgt->nents, dir, attrs); + if (nents < 0) + return nents; + + sgt->nents = nents; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu6_dma_map_sgtable, "INTEL_IPU6"); + +void ipu6_dma_unmap_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt, + enum dma_data_direction dir, unsigned long attrs) +{ + ipu6_dma_unmap_sg(sys, sgt->sgl, sgt->nents, dir, attrs); +} +EXPORT_SYMBOL_NS_GPL(ipu6_dma_unmap_sgtable, "INTEL_IPU6"); diff --git a/drivers/media/pci/intel/ipu6/ipu6-dma.h b/drivers/media/pci/intel/ipu6/ipu6-dma.h new file mode 100644 index 000000000..ae9b9a5df --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-dma.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013--2024 Intel Corporation */ + +#ifndef IPU6_DMA_H +#define IPU6_DMA_H + +#include +#include +#include + +#include "ipu6-bus.h" + +struct ipu6_mmu_info; + +struct ipu6_dma_mapping { + struct ipu6_mmu_info *mmu_info; + struct iova_domain iovad; +}; + +void ipu6_dma_sync_single(struct ipu6_bus_device *sys, dma_addr_t dma_handle, + size_t size); +void ipu6_dma_sync_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist, + int nents); +void ipu6_dma_sync_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt); +void *ipu6_dma_alloc(struct ipu6_bus_device *sys, size_t size, + dma_addr_t *dma_handle, gfp_t gfp, + unsigned long attrs); +void ipu6_dma_free(struct ipu6_bus_device *sys, size_t size, void *vaddr, + dma_addr_t dma_handle, unsigned long attrs); +int ipu6_dma_mmap(struct ipu6_bus_device *sys, struct vm_area_struct *vma, + void *addr, dma_addr_t iova, size_t size, + unsigned long attrs); +int ipu6_dma_map_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist, + int nents, enum dma_data_direction dir, + unsigned long attrs); +void ipu6_dma_unmap_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist, + int nents, enum dma_data_direction dir, + unsigned long attrs); +int ipu6_dma_map_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt, + enum dma_data_direction dir, unsigned long attrs); +void ipu6_dma_unmap_sgtable(struct ipu6_bus_device *sys, struct sg_table *sgt, + enum dma_data_direction dir, unsigned long attrs); +#endif /* IPU6_DMA_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-com.c b/drivers/media/pci/intel/ipu6/ipu6-fw-com.c new file mode 100644 index 000000000..51567c5fc --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-com.c @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ipu6-bus.h" +#include "ipu6-dma.h" +#include "ipu6-fw-com.h" + +/* + * FWCOM layer is a shared resource between FW and driver. It consist + * of token queues to both send and receive directions. Queue is simply + * an array of structures with read and write indexes to the queue. + * There are 1...n queues to both directions. Queues locates in + * system RAM and are mapped to ISP MMU so that both CPU and ISP can + * see the same buffer. Indexes are located in ISP DMEM so that FW code + * can poll those with very low latency and cost. CPU access to indexes is + * more costly but that happens only at message sending time and + * interrupt triggered message handling. CPU doesn't need to poll indexes. + * wr_reg / rd_reg are offsets to those dmem location. They are not + * the indexes itself. + */ + +/* Shared structure between driver and FW - do not modify */ +struct ipu6_fw_sys_queue { + u64 host_address; + u32 vied_address; + u32 size; + u32 token_size; + u32 wr_reg; /* reg number in subsystem's regmem */ + u32 rd_reg; + u32 _align; +} __packed; + +struct ipu6_fw_sys_queue_res { + u64 host_address; + u32 vied_address; + u32 reg; +} __packed; + +enum syscom_state { + /* Program load or explicit host setting should init to this */ + SYSCOM_STATE_UNINIT = 0x57a7e000, + /* SP Syscom sets this when it is ready for use */ + SYSCOM_STATE_READY = 0x57a7e001, + /* SP Syscom sets this when no more syscom accesses will happen */ + SYSCOM_STATE_INACTIVE = 0x57a7e002, +}; + +enum syscom_cmd { + /* Program load or explicit host setting should init to this */ + SYSCOM_COMMAND_UNINIT = 0x57a7f000, + /* Host Syscom requests syscom to become inactive */ + SYSCOM_COMMAND_INACTIVE = 0x57a7f001, +}; + +/* firmware config: data that sent from the host to SP via DDR */ +/* Cell copies data into a context */ + +struct ipu6_fw_syscom_config { + u32 firmware_address; + + u32 num_input_queues; + u32 num_output_queues; + + /* ISP pointers to an array of ipu6_fw_sys_queue structures */ + u32 input_queue; + u32 output_queue; + + /* ISYS / PSYS private data */ + u32 specific_addr; + u32 specific_size; +}; + +struct ipu6_fw_com_context { + struct ipu6_bus_device *adev; + void __iomem *dmem_addr; + int (*cell_ready)(struct ipu6_bus_device *adev); + void (*cell_start)(struct ipu6_bus_device *adev); + + void *dma_buffer; + dma_addr_t dma_addr; + unsigned int dma_size; + + struct ipu6_fw_sys_queue *input_queue; /* array of host to SP queues */ + struct ipu6_fw_sys_queue *output_queue; /* array of SP to host */ + + u32 config_vied_addr; + + unsigned int buttress_boot_offset; + void __iomem *base_addr; +}; + +#define FW_COM_WR_REG 0 +#define FW_COM_RD_REG 4 + +#define REGMEM_OFFSET 0 +#define TUNIT_MAGIC_PATTERN 0x5a5a5a5a + +enum regmem_id { + /* pass pkg_dir address to SPC in non-secure mode */ + PKG_DIR_ADDR_REG = 0, + /* Tunit CFG blob for secure - provided by host.*/ + TUNIT_CFG_DWR_REG = 1, + /* syscom commands - modified by the host */ + SYSCOM_COMMAND_REG = 2, + /* Store interrupt status - updated by SP */ + SYSCOM_IRQ_REG = 3, + /* first syscom queue pointer register */ + SYSCOM_QPR_BASE_REG = 4 +}; + +#define BUTTRESS_FW_BOOT_PARAMS_0 0x4000 +#define BUTTRESS_FW_BOOT_PARAM_REG(base, offset, id) \ + ((base) + BUTTRESS_FW_BOOT_PARAMS_0 + ((offset) + (id)) * 4) + +enum buttress_syscom_id { + /* pass syscom configuration to SPC */ + SYSCOM_CONFIG_ID = 0, + /* syscom state - modified by SP */ + SYSCOM_STATE_ID = 1, + /* syscom vtl0 addr mask */ + SYSCOM_VTL0_ADDR_MASK_ID = 2, + SYSCOM_ID_MAX +}; + +static void ipu6_sys_queue_init(struct ipu6_fw_sys_queue *q, unsigned int size, + unsigned int token_size, + struct ipu6_fw_sys_queue_res *res) +{ + unsigned int buf_size = (size + 1) * token_size; + + q->size = size + 1; + q->token_size = token_size; + + /* acquire the shared buffer space */ + q->host_address = res->host_address; + res->host_address += buf_size; + q->vied_address = res->vied_address; + res->vied_address += buf_size; + + /* acquire the shared read and writer pointers */ + q->wr_reg = res->reg; + res->reg++; + q->rd_reg = res->reg; + res->reg++; +} + +void *ipu6_fw_com_prepare(struct ipu6_fw_com_cfg *cfg, + struct ipu6_bus_device *adev, void __iomem *base) +{ + size_t conf_size, inq_size, outq_size, specific_size; + struct ipu6_fw_syscom_config *config_host_addr; + unsigned int sizeinput = 0, sizeoutput = 0; + struct ipu6_fw_sys_queue_res res; + struct ipu6_fw_com_context *ctx; + struct device *dev = &adev->auxdev.dev; + size_t sizeall, offset; + void *specific_host_addr; + unsigned int i; + + if (!cfg || !cfg->cell_start || !cfg->cell_ready) + return NULL; + + ctx = kzalloc_obj(*ctx); + if (!ctx) + return NULL; + ctx->dmem_addr = base + cfg->dmem_addr + REGMEM_OFFSET; + ctx->adev = adev; + ctx->cell_start = cfg->cell_start; + ctx->cell_ready = cfg->cell_ready; + ctx->buttress_boot_offset = cfg->buttress_boot_offset; + ctx->base_addr = base; + + /* + * Allocate DMA mapped memory. Allocate one big chunk. + */ + /* Base cfg for FW */ + conf_size = roundup(sizeof(struct ipu6_fw_syscom_config), 8); + /* Descriptions of the queues */ + inq_size = size_mul(cfg->num_input_queues, + sizeof(struct ipu6_fw_sys_queue)); + outq_size = size_mul(cfg->num_output_queues, + sizeof(struct ipu6_fw_sys_queue)); + /* FW specific information structure */ + specific_size = roundup(cfg->specific_size, 8); + + sizeall = conf_size + inq_size + outq_size + specific_size; + + for (i = 0; i < cfg->num_input_queues; i++) + sizeinput += size_mul(cfg->input[i].queue_size + 1, + cfg->input[i].token_size); + + for (i = 0; i < cfg->num_output_queues; i++) + sizeoutput += size_mul(cfg->output[i].queue_size + 1, + cfg->output[i].token_size); + + sizeall += sizeinput + sizeoutput; + + ctx->dma_buffer = ipu6_dma_alloc(adev, sizeall, &ctx->dma_addr, + GFP_KERNEL, 0); + if (!ctx->dma_buffer) { + dev_err(dev, "failed to allocate dma memory\n"); + kfree(ctx); + return NULL; + } + + ctx->dma_size = sizeall; + + config_host_addr = ctx->dma_buffer; + ctx->config_vied_addr = ctx->dma_addr; + + offset = conf_size; + ctx->input_queue = ctx->dma_buffer + offset; + config_host_addr->input_queue = ctx->dma_addr + offset; + config_host_addr->num_input_queues = cfg->num_input_queues; + + offset += inq_size; + ctx->output_queue = ctx->dma_buffer + offset; + config_host_addr->output_queue = ctx->dma_addr + offset; + config_host_addr->num_output_queues = cfg->num_output_queues; + + /* copy firmware specific data */ + offset += outq_size; + specific_host_addr = ctx->dma_buffer + offset; + config_host_addr->specific_addr = ctx->dma_addr + offset; + config_host_addr->specific_size = cfg->specific_size; + if (cfg->specific_addr && cfg->specific_size) + memcpy(specific_host_addr, cfg->specific_addr, + cfg->specific_size); + + ipu6_dma_sync_single(adev, ctx->config_vied_addr, sizeall); + + /* initialize input queues */ + offset += specific_size; + res.reg = SYSCOM_QPR_BASE_REG; + res.host_address = (uintptr_t)(ctx->dma_buffer + offset); + res.vied_address = ctx->dma_addr + offset; + for (i = 0; i < cfg->num_input_queues; i++) + ipu6_sys_queue_init(ctx->input_queue + i, + cfg->input[i].queue_size, + cfg->input[i].token_size, &res); + + /* initialize output queues */ + offset += sizeinput; + res.host_address = (uintptr_t)(ctx->dma_buffer + offset); + res.vied_address = ctx->dma_addr + offset; + for (i = 0; i < cfg->num_output_queues; i++) { + ipu6_sys_queue_init(ctx->output_queue + i, + cfg->output[i].queue_size, + cfg->output[i].token_size, &res); + } + + return ctx; +} +EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_prepare, "INTEL_IPU6"); + +int ipu6_fw_com_open(struct ipu6_fw_com_context *ctx) +{ + /* write magic pattern to disable the tunit trace */ + writel(TUNIT_MAGIC_PATTERN, ctx->dmem_addr + TUNIT_CFG_DWR_REG * 4); + /* Check if SP is in valid state */ + if (!ctx->cell_ready(ctx->adev)) + return -EIO; + + /* store syscom uninitialized command */ + writel(SYSCOM_COMMAND_UNINIT, ctx->dmem_addr + SYSCOM_COMMAND_REG * 4); + + /* store syscom uninitialized state */ + writel(SYSCOM_STATE_UNINIT, + BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr, + ctx->buttress_boot_offset, + SYSCOM_STATE_ID)); + + /* store firmware configuration address */ + writel(ctx->config_vied_addr, + BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr, + ctx->buttress_boot_offset, + SYSCOM_CONFIG_ID)); + ctx->cell_start(ctx->adev); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_open, "INTEL_IPU6"); + +int ipu6_fw_com_close(struct ipu6_fw_com_context *ctx) +{ + int state; + + state = readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr, + ctx->buttress_boot_offset, + SYSCOM_STATE_ID)); + if (state != SYSCOM_STATE_READY) + return -EBUSY; + + /* set close request flag */ + writel(SYSCOM_COMMAND_INACTIVE, ctx->dmem_addr + + SYSCOM_COMMAND_REG * 4); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_close, "INTEL_IPU6"); + +int ipu6_fw_com_release(struct ipu6_fw_com_context *ctx, unsigned int force) +{ + /* check if release is forced, an verify cell state if it is not */ + if (!force && !ctx->cell_ready(ctx->adev)) + return -EBUSY; + + ipu6_dma_free(ctx->adev, ctx->dma_size, + ctx->dma_buffer, ctx->dma_addr, 0); + kfree(ctx); + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_release, "INTEL_IPU6"); + +bool ipu6_fw_com_ready(struct ipu6_fw_com_context *ctx) +{ + int state; + + state = readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr, + ctx->buttress_boot_offset, + SYSCOM_STATE_ID)); + + return state == SYSCOM_STATE_READY; +} +EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_ready, "INTEL_IPU6"); + +void *ipu6_send_get_token(struct ipu6_fw_com_context *ctx, int q_nbr) +{ + struct ipu6_fw_sys_queue *q = &ctx->input_queue[q_nbr]; + void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4; + unsigned int wr, rd; + unsigned int packets; + unsigned int index; + + wr = readl(q_dmem + FW_COM_WR_REG); + rd = readl(q_dmem + FW_COM_RD_REG); + + if (WARN_ON_ONCE(wr >= q->size || rd >= q->size)) + return NULL; + + if (wr < rd) + packets = rd - wr - 1; + else + packets = q->size - (wr - rd + 1); + + if (!packets) + return NULL; + + index = readl(q_dmem + FW_COM_WR_REG); + + return (void *)((uintptr_t)q->host_address + index * q->token_size); +} +EXPORT_SYMBOL_NS_GPL(ipu6_send_get_token, "INTEL_IPU6"); + +void ipu6_send_put_token(struct ipu6_fw_com_context *ctx, int q_nbr) +{ + struct ipu6_fw_sys_queue *q = &ctx->input_queue[q_nbr]; + void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4; + unsigned int wr = readl(q_dmem + FW_COM_WR_REG) + 1; + + if (wr >= q->size) + wr = 0; + + writel(wr, q_dmem + FW_COM_WR_REG); +} +EXPORT_SYMBOL_NS_GPL(ipu6_send_put_token, "INTEL_IPU6"); + +void *ipu6_recv_get_token(struct ipu6_fw_com_context *ctx, int q_nbr) +{ + struct ipu6_fw_sys_queue *q = &ctx->output_queue[q_nbr]; + void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4; + unsigned int wr, rd; + unsigned int packets; + + wr = readl(q_dmem + FW_COM_WR_REG); + rd = readl(q_dmem + FW_COM_RD_REG); + + if (WARN_ON_ONCE(wr >= q->size || rd >= q->size)) + return NULL; + + if (wr < rd) + wr += q->size; + + packets = wr - rd; + if (!packets) + return NULL; + + return (void *)((uintptr_t)q->host_address + rd * q->token_size); +} +EXPORT_SYMBOL_NS_GPL(ipu6_recv_get_token, "INTEL_IPU6"); + +void ipu6_recv_put_token(struct ipu6_fw_com_context *ctx, int q_nbr) +{ + struct ipu6_fw_sys_queue *q = &ctx->output_queue[q_nbr]; + void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4; + unsigned int rd = readl(q_dmem + FW_COM_RD_REG) + 1; + + if (rd >= q->size) + rd = 0; + + writel(rd, q_dmem + FW_COM_RD_REG); +} +EXPORT_SYMBOL_NS_GPL(ipu6_recv_put_token, "INTEL_IPU6"); diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-com.h b/drivers/media/pci/intel/ipu6/ipu6-fw-com.h new file mode 100644 index 000000000..b02285a3e --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-com.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013--2024 Intel Corporation */ + +#ifndef IPU6_FW_COM_H +#define IPU6_FW_COM_H + +struct ipu6_fw_com_context; +struct ipu6_bus_device; + +struct ipu6_fw_syscom_queue_config { + unsigned int queue_size; /* tokens per queue */ + unsigned int token_size; /* bytes per token */ +}; + +#define SYSCOM_BUTTRESS_FW_PARAMS_ISYS_OFFSET 0 + +struct ipu6_fw_com_cfg { + unsigned int num_input_queues; + unsigned int num_output_queues; + struct ipu6_fw_syscom_queue_config *input; + struct ipu6_fw_syscom_queue_config *output; + + unsigned int dmem_addr; + + /* firmware-specific configuration data */ + void *specific_addr; + unsigned int specific_size; + int (*cell_ready)(struct ipu6_bus_device *adev); + void (*cell_start)(struct ipu6_bus_device *adev); + + unsigned int buttress_boot_offset; +}; + +void *ipu6_fw_com_prepare(struct ipu6_fw_com_cfg *cfg, + struct ipu6_bus_device *adev, void __iomem *base); + +int ipu6_fw_com_open(struct ipu6_fw_com_context *ctx); +bool ipu6_fw_com_ready(struct ipu6_fw_com_context *ctx); +int ipu6_fw_com_close(struct ipu6_fw_com_context *ctx); +int ipu6_fw_com_release(struct ipu6_fw_com_context *ctx, unsigned int force); + +void *ipu6_recv_get_token(struct ipu6_fw_com_context *ctx, int q_nbr); +void ipu6_recv_put_token(struct ipu6_fw_com_context *ctx, int q_nbr); +void *ipu6_send_get_token(struct ipu6_fw_com_context *ctx, int q_nbr); +void ipu6_send_put_token(struct ipu6_fw_com_context *ctx, int q_nbr); + +#endif diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c new file mode 100644 index 000000000..62ed92ff1 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c @@ -0,0 +1,487 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include + +#include "ipu6-bus.h" +#include "ipu6-fw-com.h" +#include "ipu6-isys.h" +#include "ipu6-platform-isys-csi2-reg.h" +#include "ipu6-platform-regs.h" + +static const char send_msg_types[N_IPU6_FW_ISYS_SEND_TYPE][32] = { + "STREAM_OPEN", + "STREAM_START", + "STREAM_START_AND_CAPTURE", + "STREAM_CAPTURE", + "STREAM_STOP", + "STREAM_FLUSH", + "STREAM_CLOSE" +}; + +static int handle_proxy_response(struct ipu6_isys *isys, unsigned int req_id) +{ + struct device *dev = &isys->adev->auxdev.dev; + struct ipu6_fw_isys_proxy_resp_info_abi *resp; + int ret; + + resp = ipu6_recv_get_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES); + if (!resp) + return 1; + + dev_dbg(dev, "Proxy response: id %u, error %u, details %u\n", + resp->request_id, resp->error_info.error, + resp->error_info.error_details); + + ret = req_id == resp->request_id ? 0 : -EIO; + + ipu6_recv_put_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES); + + return ret; +} + +int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, + unsigned int req_id, + unsigned int index, + unsigned int offset, u32 value) +{ + struct ipu6_fw_com_context *ctx = isys->fwcom; + struct device *dev = &isys->adev->auxdev.dev; + struct ipu6_fw_proxy_send_queue_token *token; + unsigned int timeout = 1000; + int ret; + + dev_dbg(dev, + "proxy send: req_id 0x%x, index %d, offset 0x%x, value 0x%x\n", + req_id, index, offset, value); + + token = ipu6_send_get_token(ctx, IPU6_BASE_PROXY_SEND_QUEUES); + if (!token) + return -EBUSY; + + token->request_id = req_id; + token->region_index = index; + token->offset = offset; + token->value = value; + ipu6_send_put_token(ctx, IPU6_BASE_PROXY_SEND_QUEUES); + + do { + usleep_range(100, 110); + ret = handle_proxy_response(isys, req_id); + if (!ret) + break; + if (ret == -EIO) { + dev_err(dev, "Proxy respond with unexpected id\n"); + break; + } + timeout--; + } while (ret && timeout); + + if (!timeout) + dev_err(dev, "Proxy response timed out\n"); + + return ret; +} + +int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, + const unsigned int stream_handle, + void *cpu_mapped_buf, + dma_addr_t dma_mapped_buf, + size_t size, u16 send_type) +{ + struct ipu6_fw_com_context *ctx = isys->fwcom; + struct device *dev = &isys->adev->auxdev.dev; + struct ipu6_fw_send_queue_token *token; + + if (send_type >= N_IPU6_FW_ISYS_SEND_TYPE) + return -EINVAL; + + dev_dbg(dev, "send_token: %s\n", send_msg_types[send_type]); + + /* + * Time to flush cache in case we have some payload. Not all messages + * have that + */ + if (cpu_mapped_buf) + clflush_cache_range(cpu_mapped_buf, size); + + token = ipu6_send_get_token(ctx, + stream_handle + IPU6_BASE_MSG_SEND_QUEUES); + if (!token) + return -EBUSY; + + token->payload = dma_mapped_buf; + token->buf_handle = (unsigned long)cpu_mapped_buf; + token->send_type = send_type; + + ipu6_send_put_token(ctx, stream_handle + IPU6_BASE_MSG_SEND_QUEUES); + + return 0; +} + +int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys, + const unsigned int stream_handle, u16 send_type) +{ + return ipu6_fw_isys_complex_cmd(isys, stream_handle, NULL, 0, 0, + send_type); +} + +int ipu6_fw_isys_close(struct ipu6_isys *isys) +{ + struct device *dev = &isys->adev->auxdev.dev; + int retry = IPU6_ISYS_CLOSE_RETRY; + unsigned long flags; + void *fwcom; + int ret; + + /* + * Stop the isys fw. Actual close takes + * some time as the FW must stop its actions including code fetch + * to SP icache. + * spinlock to wait the interrupt handler to be finished + */ + spin_lock_irqsave(&isys->power_lock, flags); + ret = ipu6_fw_com_close(isys->fwcom); + fwcom = isys->fwcom; + isys->fwcom = NULL; + spin_unlock_irqrestore(&isys->power_lock, flags); + if (ret) + dev_err(dev, "Device close failure: %d\n", ret); + + /* release probably fails if the close failed. Let's try still */ + do { + usleep_range(400, 500); + ret = ipu6_fw_com_release(fwcom, 0); + retry--; + } while (ret && retry); + + if (ret) { + dev_err(dev, "Device release time out %d\n", ret); + spin_lock_irqsave(&isys->power_lock, flags); + isys->fwcom = fwcom; + spin_unlock_irqrestore(&isys->power_lock, flags); + } + + return ret; +} + +void ipu6_fw_isys_cleanup(struct ipu6_isys *isys) +{ + int ret; + + ret = ipu6_fw_com_release(isys->fwcom, 1); + if (ret < 0) + dev_warn(&isys->adev->auxdev.dev, + "Device busy, fw_com release failed."); + isys->fwcom = NULL; +} + +static void start_sp(struct ipu6_bus_device *adev) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + void __iomem *spc_regs_base = isys->pdata->base + + isys->pdata->ipdata->hw_variant.spc_offset; + u32 val = IPU6_ISYS_SPC_STATUS_START | + IPU6_ISYS_SPC_STATUS_RUN | + IPU6_ISYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE; + + val |= isys->icache_prefetch ? IPU6_ISYS_SPC_STATUS_ICACHE_PREFETCH : 0; + + writel(val, spc_regs_base + IPU6_ISYS_REG_SPC_STATUS_CTRL); +} + +static int query_sp(struct ipu6_bus_device *adev) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + void __iomem *spc_regs_base = isys->pdata->base + + isys->pdata->ipdata->hw_variant.spc_offset; + u32 val; + + val = readl(spc_regs_base + IPU6_ISYS_REG_SPC_STATUS_CTRL); + /* return true when READY == 1, START == 0 */ + val &= IPU6_ISYS_SPC_STATUS_READY | IPU6_ISYS_SPC_STATUS_START; + + return val == IPU6_ISYS_SPC_STATUS_READY; +} + +static int ipu6_isys_fwcom_cfg_init(struct ipu6_isys *isys, + struct ipu6_fw_com_cfg *fwcom, + unsigned int num_streams) +{ + unsigned int max_send_queues, max_sram_blocks, max_devq_size; + struct ipu6_fw_syscom_queue_config *input_queue_cfg; + struct ipu6_fw_syscom_queue_config *output_queue_cfg; + struct device *dev = &isys->adev->auxdev.dev; + int type_proxy = IPU6_FW_ISYS_QUEUE_TYPE_PROXY; + int type_dev = IPU6_FW_ISYS_QUEUE_TYPE_DEV; + int type_msg = IPU6_FW_ISYS_QUEUE_TYPE_MSG; + int base_dev_send = IPU6_BASE_DEV_SEND_QUEUES; + int base_msg_send = IPU6_BASE_MSG_SEND_QUEUES; + int base_msg_recv = IPU6_BASE_MSG_RECV_QUEUES; + struct ipu6_fw_isys_fw_config *isys_fw_cfg; + u32 num_in_message_queues; + unsigned int max_streams; + unsigned int size; + unsigned int i; + + max_streams = isys->pdata->ipdata->max_streams; + max_send_queues = isys->pdata->ipdata->max_send_queues; + max_sram_blocks = isys->pdata->ipdata->max_sram_blocks; + max_devq_size = isys->pdata->ipdata->max_devq_size; + num_in_message_queues = clamp(num_streams, 1U, max_streams); + isys_fw_cfg = devm_kzalloc(dev, sizeof(*isys_fw_cfg), GFP_KERNEL); + if (!isys_fw_cfg) + return -ENOMEM; + + isys_fw_cfg->num_send_queues[type_proxy] = IPU6_N_MAX_PROXY_SEND_QUEUES; + isys_fw_cfg->num_send_queues[type_dev] = IPU6_N_MAX_DEV_SEND_QUEUES; + isys_fw_cfg->num_send_queues[type_msg] = num_in_message_queues; + isys_fw_cfg->num_recv_queues[type_proxy] = IPU6_N_MAX_PROXY_RECV_QUEUES; + /* Common msg/dev return queue */ + isys_fw_cfg->num_recv_queues[type_dev] = 0; + isys_fw_cfg->num_recv_queues[type_msg] = 1; + + size = sizeof(*input_queue_cfg) * max_send_queues; + input_queue_cfg = devm_kzalloc(dev, size, GFP_KERNEL); + if (!input_queue_cfg) + return -ENOMEM; + + size = sizeof(*output_queue_cfg) * IPU6_N_MAX_RECV_QUEUES; + output_queue_cfg = devm_kzalloc(dev, size, GFP_KERNEL); + if (!output_queue_cfg) + return -ENOMEM; + + fwcom->input = input_queue_cfg; + fwcom->output = output_queue_cfg; + + fwcom->num_input_queues = isys_fw_cfg->num_send_queues[type_proxy] + + isys_fw_cfg->num_send_queues[type_dev] + + isys_fw_cfg->num_send_queues[type_msg]; + + fwcom->num_output_queues = isys_fw_cfg->num_recv_queues[type_proxy] + + isys_fw_cfg->num_recv_queues[type_dev] + + isys_fw_cfg->num_recv_queues[type_msg]; + + /* SRAM partitioning. Equal partitioning is set. */ + for (i = 0; i < max_sram_blocks; i++) { + if (i < num_in_message_queues) + isys_fw_cfg->buffer_partition.num_gda_pages[i] = + (IPU6_DEVICE_GDA_NR_PAGES * + IPU6_DEVICE_GDA_VIRT_FACTOR) / + num_in_message_queues; + else + isys_fw_cfg->buffer_partition.num_gda_pages[i] = 0; + } + + /* FW assumes proxy interface at fwcom queue 0 */ + for (i = 0; i < isys_fw_cfg->num_send_queues[type_proxy]; i++) { + input_queue_cfg[i].token_size = + sizeof(struct ipu6_fw_proxy_send_queue_token); + input_queue_cfg[i].queue_size = IPU6_ISYS_SIZE_PROXY_SEND_QUEUE; + } + + for (i = 0; i < isys_fw_cfg->num_send_queues[type_dev]; i++) { + input_queue_cfg[base_dev_send + i].token_size = + sizeof(struct ipu6_fw_send_queue_token); + input_queue_cfg[base_dev_send + i].queue_size = max_devq_size; + } + + for (i = 0; i < isys_fw_cfg->num_send_queues[type_msg]; i++) { + input_queue_cfg[base_msg_send + i].token_size = + sizeof(struct ipu6_fw_send_queue_token); + input_queue_cfg[base_msg_send + i].queue_size = + IPU6_ISYS_SIZE_SEND_QUEUE; + } + + for (i = 0; i < isys_fw_cfg->num_recv_queues[type_proxy]; i++) { + output_queue_cfg[i].token_size = + sizeof(struct ipu6_fw_proxy_resp_queue_token); + output_queue_cfg[i].queue_size = + IPU6_ISYS_SIZE_PROXY_RECV_QUEUE; + } + /* There is no recv DEV queue */ + for (i = 0; i < isys_fw_cfg->num_recv_queues[type_msg]; i++) { + output_queue_cfg[base_msg_recv + i].token_size = + sizeof(struct ipu6_fw_resp_queue_token); + output_queue_cfg[base_msg_recv + i].queue_size = + IPU6_ISYS_SIZE_RECV_QUEUE; + } + + fwcom->dmem_addr = isys->pdata->ipdata->hw_variant.dmem_offset; + fwcom->specific_addr = isys_fw_cfg; + fwcom->specific_size = sizeof(*isys_fw_cfg); + + return 0; +} + +int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) +{ + struct device *dev = &isys->adev->auxdev.dev; + int retry = IPU6_ISYS_OPEN_RETRY; + struct ipu6_fw_com_cfg fwcom = { + .cell_start = start_sp, + .cell_ready = query_sp, + .buttress_boot_offset = SYSCOM_BUTTRESS_FW_PARAMS_ISYS_OFFSET, + }; + int ret; + + ipu6_isys_fwcom_cfg_init(isys, &fwcom, num_streams); + + isys->fwcom = ipu6_fw_com_prepare(&fwcom, isys->adev, + isys->pdata->base); + if (!isys->fwcom) { + dev_err(dev, "isys fw com prepare failed\n"); + return -EIO; + } + + ret = ipu6_fw_com_open(isys->fwcom); + if (ret) { + dev_err(dev, "isys fw com open failed %d\n", ret); + return ret; + } + + do { + usleep_range(400, 500); + if (ipu6_fw_com_ready(isys->fwcom)) + break; + retry--; + } while (retry > 0); + + if (!retry) { + dev_err(dev, "isys port open ready failed %d\n", ret); + ipu6_fw_isys_close(isys); + ret = -EIO; + } + + return ret; +} + +struct ipu6_fw_isys_resp_info_abi * +ipu6_fw_isys_get_resp(void *context, unsigned int queue) +{ + return ipu6_recv_get_token(context, queue); +} + +void ipu6_fw_isys_put_resp(void *context, unsigned int queue) +{ + ipu6_recv_put_token(context, queue); +} + +void ipu6_fw_isys_dump_stream_cfg(struct device *dev, + struct ipu6_fw_isys_stream_cfg_data_abi *cfg) +{ + unsigned int i; + + dev_dbg(dev, "-----------------------------------------------------\n"); + dev_dbg(dev, "IPU6_FW_ISYS_STREAM_CFG_DATA\n"); + + dev_dbg(dev, "compfmt = %d\n", cfg->vc); + dev_dbg(dev, "src = %d\n", cfg->src); + dev_dbg(dev, "vc = %d\n", cfg->vc); + dev_dbg(dev, "isl_use = %d\n", cfg->isl_use); + dev_dbg(dev, "sensor_type = %d\n", cfg->sensor_type); + + dev_dbg(dev, "send_irq_sof_discarded = %d\n", + cfg->send_irq_sof_discarded); + dev_dbg(dev, "send_irq_eof_discarded = %d\n", + cfg->send_irq_eof_discarded); + dev_dbg(dev, "send_resp_sof_discarded = %d\n", + cfg->send_resp_sof_discarded); + dev_dbg(dev, "send_resp_eof_discarded = %d\n", + cfg->send_resp_eof_discarded); + + dev_dbg(dev, "crop:\n"); + dev_dbg(dev, "\t.left_top = [%d, %d]\n", cfg->crop.left_offset, + cfg->crop.top_offset); + dev_dbg(dev, "\t.right_bottom = [%d, %d]\n", cfg->crop.right_offset, + cfg->crop.bottom_offset); + + dev_dbg(dev, "nof_input_pins = %d\n", cfg->nof_input_pins); + for (i = 0; i < cfg->nof_input_pins; i++) { + dev_dbg(dev, "input pin[%d]:\n", i); + dev_dbg(dev, "\t.dt = 0x%0x\n", cfg->input_pins[i].dt); + dev_dbg(dev, "\t.mipi_store_mode = %d\n", + cfg->input_pins[i].mipi_store_mode); + dev_dbg(dev, "\t.bits_per_pix = %d\n", + cfg->input_pins[i].bits_per_pix); + dev_dbg(dev, "\t.mapped_dt = 0x%0x\n", + cfg->input_pins[i].mapped_dt); + dev_dbg(dev, "\t.input_res = %dx%d\n", + cfg->input_pins[i].input_res.width, + cfg->input_pins[i].input_res.height); + dev_dbg(dev, "\t.mipi_decompression = %d\n", + cfg->input_pins[i].mipi_decompression); + dev_dbg(dev, "\t.capture_mode = %d\n", + cfg->input_pins[i].capture_mode); + } + + dev_dbg(dev, "nof_output_pins = %d\n", cfg->nof_output_pins); + for (i = 0; i < cfg->nof_output_pins; i++) { + dev_dbg(dev, "output_pin[%d]:\n", i); + dev_dbg(dev, "\t.input_pin_id = %d\n", + cfg->output_pins[i].input_pin_id); + dev_dbg(dev, "\t.output_res = %dx%d\n", + cfg->output_pins[i].output_res.width, + cfg->output_pins[i].output_res.height); + dev_dbg(dev, "\t.stride = %d\n", cfg->output_pins[i].stride); + dev_dbg(dev, "\t.pt = %d\n", cfg->output_pins[i].pt); + dev_dbg(dev, "\t.payload_buf_size = %d\n", + cfg->output_pins[i].payload_buf_size); + dev_dbg(dev, "\t.ft = %d\n", cfg->output_pins[i].ft); + dev_dbg(dev, "\t.watermark_in_lines = %d\n", + cfg->output_pins[i].watermark_in_lines); + dev_dbg(dev, "\t.send_irq = %d\n", + cfg->output_pins[i].send_irq); + dev_dbg(dev, "\t.reserve_compression = %d\n", + cfg->output_pins[i].reserve_compression); + dev_dbg(dev, "\t.snoopable = %d\n", + cfg->output_pins[i].snoopable); + dev_dbg(dev, "\t.error_handling_enable = %d\n", + cfg->output_pins[i].error_handling_enable); + dev_dbg(dev, "\t.sensor_type = %d\n", + cfg->output_pins[i].sensor_type); + } + dev_dbg(dev, "-----------------------------------------------------\n"); +} + +void +ipu6_fw_isys_dump_frame_buff_set(struct device *dev, + struct ipu6_fw_isys_frame_buff_set_abi *buf, + unsigned int outputs) +{ + unsigned int i; + + dev_dbg(dev, "-----------------------------------------------------\n"); + dev_dbg(dev, "IPU6_FW_ISYS_FRAME_BUFF_SET\n"); + + for (i = 0; i < outputs; i++) { + dev_dbg(dev, "output_pin[%d]:\n", i); + dev_dbg(dev, "\t.out_buf_id = %llu\n", + buf->output_pins[i].out_buf_id); + dev_dbg(dev, "\t.addr = 0x%x\n", buf->output_pins[i].addr); + dev_dbg(dev, "\t.compress = %d\n", + buf->output_pins[i].compress); + } + + dev_dbg(dev, "send_irq_sof = 0x%x\n", buf->send_irq_sof); + dev_dbg(dev, "send_irq_eof = 0x%x\n", buf->send_irq_eof); + dev_dbg(dev, "send_resp_sof = 0x%x\n", buf->send_resp_sof); + dev_dbg(dev, "send_resp_eof = 0x%x\n", buf->send_resp_eof); + dev_dbg(dev, "send_irq_capture_ack = 0x%x\n", + buf->send_irq_capture_ack); + dev_dbg(dev, "send_irq_capture_done = 0x%x\n", + buf->send_irq_capture_done); + dev_dbg(dev, "send_resp_capture_ack = 0x%x\n", + buf->send_resp_capture_ack); + dev_dbg(dev, "send_resp_capture_done = 0x%x\n", + buf->send_resp_capture_done); + + dev_dbg(dev, "-----------------------------------------------------\n"); +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h new file mode 100644 index 000000000..b60f02076 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h @@ -0,0 +1,596 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013--2024 Intel Corporation */ + +#ifndef IPU6_FW_ISYS_H +#define IPU6_FW_ISYS_H + +#include + +struct device; +struct ipu6_isys; + +/* Max number of Input/Output Pins */ +#define IPU6_MAX_IPINS 4 + +#define IPU6_MAX_OPINS ((IPU6_MAX_IPINS) + 1) + +#define IPU6_STREAM_ID_MAX 16 +#define IPU6_NONSECURE_STREAM_ID_MAX 12 +#define IPU6_DEV_SEND_QUEUE_SIZE (IPU6_STREAM_ID_MAX) +#define IPU6_NOF_SRAM_BLOCKS_MAX (IPU6_STREAM_ID_MAX) +#define IPU6_N_MAX_MSG_SEND_QUEUES (IPU6_STREAM_ID_MAX) +#define IPU6SE_STREAM_ID_MAX 8 +#define IPU6SE_NONSECURE_STREAM_ID_MAX 4 +#define IPU6SE_DEV_SEND_QUEUE_SIZE (IPU6SE_STREAM_ID_MAX) +#define IPU6SE_NOF_SRAM_BLOCKS_MAX (IPU6SE_STREAM_ID_MAX) +#define IPU6SE_N_MAX_MSG_SEND_QUEUES (IPU6SE_STREAM_ID_MAX) + +/* Single return queue for all streams/commands type */ +#define IPU6_N_MAX_MSG_RECV_QUEUES 1 +/* Single device queue for high priority commands (bypass in-order queue) */ +#define IPU6_N_MAX_DEV_SEND_QUEUES 1 +/* Single dedicated send queue for proxy interface */ +#define IPU6_N_MAX_PROXY_SEND_QUEUES 1 +/* Single dedicated recv queue for proxy interface */ +#define IPU6_N_MAX_PROXY_RECV_QUEUES 1 +/* Send queues layout */ +#define IPU6_BASE_PROXY_SEND_QUEUES 0 +#define IPU6_BASE_DEV_SEND_QUEUES \ + (IPU6_BASE_PROXY_SEND_QUEUES + IPU6_N_MAX_PROXY_SEND_QUEUES) +#define IPU6_BASE_MSG_SEND_QUEUES \ + (IPU6_BASE_DEV_SEND_QUEUES + IPU6_N_MAX_DEV_SEND_QUEUES) +/* Recv queues layout */ +#define IPU6_BASE_PROXY_RECV_QUEUES 0 +#define IPU6_BASE_MSG_RECV_QUEUES \ + (IPU6_BASE_PROXY_RECV_QUEUES + IPU6_N_MAX_PROXY_RECV_QUEUES) +#define IPU6_N_MAX_RECV_QUEUES \ + (IPU6_BASE_MSG_RECV_QUEUES + IPU6_N_MAX_MSG_RECV_QUEUES) + +#define IPU6_N_MAX_SEND_QUEUES \ + (IPU6_BASE_MSG_SEND_QUEUES + IPU6_N_MAX_MSG_SEND_QUEUES) +#define IPU6SE_N_MAX_SEND_QUEUES \ + (IPU6_BASE_MSG_SEND_QUEUES + IPU6SE_N_MAX_MSG_SEND_QUEUES) + +/* Max number of planes for frame formats supported by the FW */ +#define IPU6_PIN_PLANES_MAX 4 + +#define IPU6_FW_ISYS_SENSOR_TYPE_START 14 +#define IPU6_FW_ISYS_SENSOR_TYPE_END 19 +#define IPU6SE_FW_ISYS_SENSOR_TYPE_START 6 +#define IPU6SE_FW_ISYS_SENSOR_TYPE_END 11 +/* + * Device close takes some time from last ack message to actual stopping + * of the SP processor. As long as the SP processor runs we can't proceed with + * clean up of resources. + */ +#define IPU6_ISYS_OPEN_RETRY 2000 +#define IPU6_ISYS_CLOSE_RETRY 2000 +#define IPU6_FW_CALL_TIMEOUT_JIFFIES msecs_to_jiffies(2000) + +enum ipu6_fw_isys_resp_type { + IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE = 0, + IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK, + IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK, + IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK, + IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK, + IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK, + IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK, + IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY, + IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_WATERMARK, + IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF, + IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF, + IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE, + IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE, + IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_SKIPPED, + IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_SKIPPED, + IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF_DISCARDED, + IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF_DISCARDED, + IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY, + N_IPU6_FW_ISYS_RESP_TYPE +}; + +enum ipu6_fw_isys_send_type { + IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN = 0, + IPU6_FW_ISYS_SEND_TYPE_STREAM_START, + IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE, + IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE, + IPU6_FW_ISYS_SEND_TYPE_STREAM_STOP, + IPU6_FW_ISYS_SEND_TYPE_STREAM_FLUSH, + IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE, + N_IPU6_FW_ISYS_SEND_TYPE +}; + +enum ipu6_fw_isys_queue_type { + IPU6_FW_ISYS_QUEUE_TYPE_PROXY = 0, + IPU6_FW_ISYS_QUEUE_TYPE_DEV, + IPU6_FW_ISYS_QUEUE_TYPE_MSG, + N_IPU6_FW_ISYS_QUEUE_TYPE +}; + +enum ipu6_fw_isys_stream_source { + IPU6_FW_ISYS_STREAM_SRC_PORT_0 = 0, + IPU6_FW_ISYS_STREAM_SRC_PORT_1, + IPU6_FW_ISYS_STREAM_SRC_PORT_2, + IPU6_FW_ISYS_STREAM_SRC_PORT_3, + IPU6_FW_ISYS_STREAM_SRC_PORT_4, + IPU6_FW_ISYS_STREAM_SRC_PORT_5, + IPU6_FW_ISYS_STREAM_SRC_PORT_6, + IPU6_FW_ISYS_STREAM_SRC_PORT_7, + IPU6_FW_ISYS_STREAM_SRC_PORT_8, + IPU6_FW_ISYS_STREAM_SRC_PORT_9, + IPU6_FW_ISYS_STREAM_SRC_PORT_10, + IPU6_FW_ISYS_STREAM_SRC_PORT_11, + IPU6_FW_ISYS_STREAM_SRC_PORT_12, + IPU6_FW_ISYS_STREAM_SRC_PORT_13, + IPU6_FW_ISYS_STREAM_SRC_PORT_14, + IPU6_FW_ISYS_STREAM_SRC_PORT_15, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_0, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_1, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_2, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_3, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_4, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_5, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_6, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_7, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_8, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_9, + N_IPU6_FW_ISYS_STREAM_SRC +}; + +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT0 IPU6_FW_ISYS_STREAM_SRC_PORT_0 +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT1 IPU6_FW_ISYS_STREAM_SRC_PORT_1 +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT2 IPU6_FW_ISYS_STREAM_SRC_PORT_2 +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT3 IPU6_FW_ISYS_STREAM_SRC_PORT_3 + +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_PORTA IPU6_FW_ISYS_STREAM_SRC_PORT_4 +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_PORTB IPU6_FW_ISYS_STREAM_SRC_PORT_5 +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT0 \ + IPU6_FW_ISYS_STREAM_SRC_PORT_6 +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT1 \ + IPU6_FW_ISYS_STREAM_SRC_PORT_7 +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT2 \ + IPU6_FW_ISYS_STREAM_SRC_PORT_8 +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT3 \ + IPU6_FW_ISYS_STREAM_SRC_PORT_9 + +#define IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_PORT0 IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_0 +#define IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_PORT1 IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_1 + +/* + * enum ipu6_fw_isys_mipi_vc: MIPI csi2 spec + * supports up to 4 virtual per physical channel + */ +enum ipu6_fw_isys_mipi_vc { + IPU6_FW_ISYS_MIPI_VC_0 = 0, + IPU6_FW_ISYS_MIPI_VC_1, + IPU6_FW_ISYS_MIPI_VC_2, + IPU6_FW_ISYS_MIPI_VC_3, + N_IPU6_FW_ISYS_MIPI_VC +}; + +enum ipu6_fw_isys_frame_format_type { + IPU6_FW_ISYS_FRAME_FORMAT_NV11 = 0, /* 12 bit YUV 411, Y, UV plane */ + IPU6_FW_ISYS_FRAME_FORMAT_NV12, /* 12 bit YUV 420, Y, UV plane */ + IPU6_FW_ISYS_FRAME_FORMAT_NV12_16, /* 16 bit YUV 420, Y, UV plane */ + /* 12 bit YUV 420, Intel proprietary tiled format */ + IPU6_FW_ISYS_FRAME_FORMAT_NV12_TILEY, + + IPU6_FW_ISYS_FRAME_FORMAT_NV16, /* 16 bit YUV 422, Y, UV plane */ + IPU6_FW_ISYS_FRAME_FORMAT_NV21, /* 12 bit YUV 420, Y, VU plane */ + IPU6_FW_ISYS_FRAME_FORMAT_NV61, /* 16 bit YUV 422, Y, VU plane */ + IPU6_FW_ISYS_FRAME_FORMAT_YV12, /* 12 bit YUV 420, Y, V, U plane */ + IPU6_FW_ISYS_FRAME_FORMAT_YV16, /* 16 bit YUV 422, Y, V, U plane */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV420, /* 12 bit YUV 420, Y, U, V plane */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV420_10, /* yuv420, 10 bits per subpixel */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV420_12, /* yuv420, 12 bits per subpixel */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV420_14, /* yuv420, 14 bits per subpixel */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV420_16, /* yuv420, 16 bits per subpixel */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV422, /* 16 bit YUV 422, Y, U, V plane */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV422_16, /* yuv422, 16 bits per subpixel */ + IPU6_FW_ISYS_FRAME_FORMAT_UYVY, /* 16 bit YUV 422, UYVY interleaved */ + IPU6_FW_ISYS_FRAME_FORMAT_YUYV, /* 16 bit YUV 422, YUYV interleaved */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV444, /* 24 bit YUV 444, Y, U, V plane */ + /* Internal format, 2 y lines followed by a uvinterleaved line */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV_LINE, + IPU6_FW_ISYS_FRAME_FORMAT_RAW8, /* RAW8, 1 plane */ + IPU6_FW_ISYS_FRAME_FORMAT_RAW10, /* RAW10, 1 plane */ + IPU6_FW_ISYS_FRAME_FORMAT_RAW12, /* RAW12, 1 plane */ + IPU6_FW_ISYS_FRAME_FORMAT_RAW14, /* RAW14, 1 plane */ + IPU6_FW_ISYS_FRAME_FORMAT_RAW16, /* RAW16, 1 plane */ + /** + * 16 bit RGB, 1 plane. Each 3 sub pixels are packed into one 16 bit + * value, 5 bits for R, 6 bits for G and 5 bits for B. + */ + IPU6_FW_ISYS_FRAME_FORMAT_RGB565, + IPU6_FW_ISYS_FRAME_FORMAT_PLANAR_RGB888, /* 24 bit RGB, 3 planes */ + IPU6_FW_ISYS_FRAME_FORMAT_RGBA888, /* 32 bit RGBA, 1 plane, A=Alpha */ + IPU6_FW_ISYS_FRAME_FORMAT_QPLANE6, /* Internal, for advanced ISP */ + IPU6_FW_ISYS_FRAME_FORMAT_BINARY_8, /* byte stream, used for jpeg. */ + N_IPU6_FW_ISYS_FRAME_FORMAT +}; + +enum ipu6_fw_isys_pin_type { + /* captured as MIPI packets */ + IPU6_FW_ISYS_PIN_TYPE_MIPI = 0, + /* captured through the SoC path */ + IPU6_FW_ISYS_PIN_TYPE_RAW_SOC = 3, +}; + +/* + * enum ipu6_fw_isys_mipi_store_mode. Describes if long MIPI packets reach + * MIPI SRAM with the long packet header or + * if not, then only option is to capture it with pin type MIPI. + */ +enum ipu6_fw_isys_mipi_store_mode { + IPU6_FW_ISYS_MIPI_STORE_MODE_NORMAL = 0, + IPU6_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER, + N_IPU6_FW_ISYS_MIPI_STORE_MODE +}; + +enum ipu6_fw_isys_capture_mode { + IPU6_FW_ISYS_CAPTURE_MODE_REGULAR = 0, + IPU6_FW_ISYS_CAPTURE_MODE_BURST, + N_IPU6_FW_ISYS_CAPTURE_MODE, +}; + +enum ipu6_fw_isys_sensor_mode { + IPU6_FW_ISYS_SENSOR_MODE_NORMAL = 0, + IPU6_FW_ISYS_SENSOR_MODE_TOBII, + N_IPU6_FW_ISYS_SENSOR_MODE, +}; + +enum ipu6_fw_isys_error { + IPU6_FW_ISYS_ERROR_NONE = 0, + IPU6_FW_ISYS_ERROR_FW_INTERNAL_CONSISTENCY, + IPU6_FW_ISYS_ERROR_HW_CONSISTENCY, + IPU6_FW_ISYS_ERROR_DRIVER_INVALID_COMMAND_SEQUENCE, + IPU6_FW_ISYS_ERROR_DRIVER_INVALID_DEVICE_CONFIGURATION, + IPU6_FW_ISYS_ERROR_DRIVER_INVALID_STREAM_CONFIGURATION, + IPU6_FW_ISYS_ERROR_DRIVER_INVALID_FRAME_CONFIGURATION, + IPU6_FW_ISYS_ERROR_INSUFFICIENT_RESOURCES, + IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO, + IPU6_FW_ISYS_ERROR_HW_REPORTED_SIG2CIO, + IPU6_FW_ISYS_ERROR_SENSOR_FW_SYNC, + IPU6_FW_ISYS_ERROR_STREAM_IN_SUSPENSION, + IPU6_FW_ISYS_ERROR_RESPONSE_QUEUE_FULL, + N_IPU6_FW_ISYS_ERROR +}; + +enum ipu6_fw_proxy_error { + IPU6_FW_PROXY_ERROR_NONE = 0, + IPU6_FW_PROXY_ERROR_INVALID_WRITE_REGION, + IPU6_FW_PROXY_ERROR_INVALID_WRITE_OFFSET, + N_IPU6_FW_PROXY_ERROR +}; + +/* firmware ABI structure below are aligned in firmware, no need pack */ +struct ipu6_fw_isys_buffer_partition_abi { + u32 num_gda_pages[IPU6_STREAM_ID_MAX]; +}; + +struct ipu6_fw_isys_fw_config { + struct ipu6_fw_isys_buffer_partition_abi buffer_partition; + u32 num_send_queues[N_IPU6_FW_ISYS_QUEUE_TYPE]; + u32 num_recv_queues[N_IPU6_FW_ISYS_QUEUE_TYPE]; +}; + +/* + * struct ipu6_fw_isys_resolution_abi: Generic resolution structure. + */ +struct ipu6_fw_isys_resolution_abi { + u32 width; + u32 height; +}; + +/** + * struct ipu6_fw_isys_output_pin_payload_abi - ISYS output pin buffer + * @out_buf_id: Points to output pin buffer - buffer identifier + * @addr: Points to output pin buffer - CSS Virtual Address + * @compress: Request frame compression (1), or not (0) + */ +struct ipu6_fw_isys_output_pin_payload_abi { + u64 out_buf_id; + u32 addr; + u32 compress; +}; + +/** + * struct ipu6_fw_isys_output_pin_info_abi - ISYS output pin info + * @output_res: output pin resolution + * @stride: output stride in Bytes (not valid for statistics) + * @watermark_in_lines: pin watermark level in lines + * @payload_buf_size: minimum size in Bytes of all buffers that will be + * supplied for capture on this pin + * @ts_offsets: ts_offsets + * @s2m_pixel_soc_pixel_remapping: pixel soc remapping (see the definition of + * S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING) + * @csi_be_soc_pixel_remapping: see s2m_pixel_soc_pixel_remapping + * @send_irq: assert if pin event should trigger irq + * @input_pin_id: related input pin id + * @pt: pin type -real format "enum ipu6_fw_isys_pin_type" + * @ft: frame format type -real format "enum ipu6_fw_isys_frame_format_type" + * @reserved: a reserved field + * @reserve_compression: reserve compression resources for pin + * @snoopable: snoopable + * @error_handling_enable: enable error handling + * @sensor_type: sensor_type + */ +struct ipu6_fw_isys_output_pin_info_abi { + struct ipu6_fw_isys_resolution_abi output_res; + u32 stride; + u32 watermark_in_lines; + u32 payload_buf_size; + u32 ts_offsets[IPU6_PIN_PLANES_MAX]; + u32 s2m_pixel_soc_pixel_remapping; + u32 csi_be_soc_pixel_remapping; + u8 send_irq; + u8 input_pin_id; + u8 pt; + u8 ft; + u8 reserved; + u8 reserve_compression; + u8 snoopable; + u8 error_handling_enable; + u32 sensor_type; +}; + +/** + * struct ipu6_fw_isys_input_pin_info_abi - ISYS input pin info + * @input_res: input resolution + * @dt: mipi data type ((enum ipu6_fw_isys_mipi_data_type) + * @mipi_store_mode: defines if legacy long packet header will be stored or + * discarded if discarded, output pin type for this + * input pin can only be MIPI + * (enum ipu6_fw_isys_mipi_store_mode) + * @bits_per_pix: native bits per pixel + * @mapped_dt: actual data type from sensor + * @mipi_decompression: defines which compression will be in mipi backend + * @crop_first_and_last_lines: Control whether to crop the first and last line + * of the input image. Crop done by HW device. + * @capture_mode: mode of capture, regular or burst, default value is regular + * @reserved: a reserved field + */ +struct ipu6_fw_isys_input_pin_info_abi { + struct ipu6_fw_isys_resolution_abi input_res; + u8 dt; + u8 mipi_store_mode; + u8 bits_per_pix; + u8 mapped_dt; + u8 mipi_decompression; + u8 crop_first_and_last_lines; + u8 capture_mode; + u8 reserved; +}; + +/** + * struct ipu6_fw_isys_cropping_abi - ISYS cropping coordinates + * @top_offset: Top offset + * @left_offset: Left offset + * @bottom_offset: Bottom offset + * @right_offset: Right offset + */ +struct ipu6_fw_isys_cropping_abi { + s32 top_offset; + s32 left_offset; + s32 bottom_offset; + s32 right_offset; +}; + +/** + * struct ipu6_fw_isys_stream_cfg_data_abi - ISYS stream configuration data + * ISYS stream configuration data structure + * @crop: for extended use and is not used in FW currently + * @input_pins: input pin descriptors + * @output_pins: output pin descriptors + * @compfmt: de-compression setting for User Defined Data + * @nof_input_pins: number of input pins + * @nof_output_pins: number of output pins + * @send_irq_sof_discarded: send irq on discarded frame sof response + * - if '1' it will override the send_resp_sof_discarded + * and send the response + * - if '0' the send_resp_sof_discarded will determine + * whether to send the response + * @send_irq_eof_discarded: send irq on discarded frame eof response + * - if '1' it will override the send_resp_eof_discarded + * and send the response + * - if '0' the send_resp_eof_discarded will determine + * whether to send the response + * @send_resp_sof_discarded: send response for discarded frame sof detected, + * used only when send_irq_sof_discarded is '0' + * @send_resp_eof_discarded: send response for discarded frame eof detected, + * used only when send_irq_eof_discarded is '0' + * @src: Stream source index e.g. MIPI_generator_0, CSI2-rx_1 + * @vc: MIPI Virtual Channel (up to 4 virtual per physical channel) + * @isl_use: indicates whether stream requires ISL and how + * @sensor_type: type of connected sensor, tobii or others, default is 0 + * @reserved: a reserved field + * @reserved2: a reserved field + */ +struct ipu6_fw_isys_stream_cfg_data_abi { + struct ipu6_fw_isys_cropping_abi crop; + struct ipu6_fw_isys_input_pin_info_abi input_pins[IPU6_MAX_IPINS]; + struct ipu6_fw_isys_output_pin_info_abi output_pins[IPU6_MAX_OPINS]; + u32 compfmt; + u8 nof_input_pins; + u8 nof_output_pins; + u8 send_irq_sof_discarded; + u8 send_irq_eof_discarded; + u8 send_resp_sof_discarded; + u8 send_resp_eof_discarded; + u8 src; + u8 vc; + u8 isl_use; + u8 sensor_type; + u8 reserved; + u8 reserved2; +}; + +/** + * struct ipu6_fw_isys_frame_buff_set_abi - ISYS frame buffer set (request) + * @output_pins: output pin addresses + * @send_irq_sof: send irq on frame sof response + * - if '1' it will override the send_resp_sof and + * send the response + * - if '0' the send_resp_sof will determine whether to + * send the response + * @send_irq_eof: send irq on frame eof response + * - if '1' it will override the send_resp_eof and + * send the response + * - if '0' the send_resp_eof will determine whether to + * send the response + * @send_irq_capture_ack: send irq on capture ack + * @send_irq_capture_done: send irq on capture done + * @send_resp_sof: send response for frame sof detected, + * used only when send_irq_sof is '0' + * @send_resp_eof: send response for frame eof detected, + * used only when send_irq_eof is '0' + * @send_resp_capture_ack: send response for capture ack event + * @send_resp_capture_done: send response for capture done event + * @reserved: a reserved field + */ +struct ipu6_fw_isys_frame_buff_set_abi { + struct ipu6_fw_isys_output_pin_payload_abi output_pins[IPU6_MAX_OPINS]; + u8 send_irq_sof; + u8 send_irq_eof; + u8 send_irq_capture_ack; + u8 send_irq_capture_done; + u8 send_resp_sof; + u8 send_resp_eof; + u8 send_resp_capture_ack; + u8 send_resp_capture_done; + u8 reserved[8]; +}; + +/** + * struct ipu6_fw_isys_error_info_abi - ISYS error information + * @error: error code if something went wrong + * @error_details: depending on error code, it may contain additional error info + */ +struct ipu6_fw_isys_error_info_abi { + u32 error; + u32 error_details; +}; + +/** + * struct ipu6_fw_isys_resp_info_abi - ISYS firmware response + * @buf_id: buffer ID + * @pin: this var is only valid for pin event related responses, + * contains pin addresses + * @error_info: error information from the FW + * @timestamp: Time information for event if available + * @stream_handle: stream id the response corresponds to + * @type: response type (enum ipu6_fw_isys_resp_type) + * @pin_id: pin id that the pin payload corresponds to + * @reserved: a reserved field + * @reserved2: a reserved field + */ +struct ipu6_fw_isys_resp_info_abi { + u64 buf_id; + struct ipu6_fw_isys_output_pin_payload_abi pin; + struct ipu6_fw_isys_error_info_abi error_info; + u32 timestamp[2]; + u8 stream_handle; + u8 type; + u8 pin_id; + u8 reserved; + u32 reserved2; +}; + +/** + * struct ipu6_fw_isys_proxy_error_info_abi - ISYS proxy error + * @error: error code if something went wrong + * @error_details: depending on error code, it may contain additional error info + */ +struct ipu6_fw_isys_proxy_error_info_abi { + u32 error; + u32 error_details; +}; + +struct ipu6_fw_isys_proxy_resp_info_abi { + u32 request_id; + struct ipu6_fw_isys_proxy_error_info_abi error_info; +}; + +/** + * struct ipu6_fw_proxy_write_queue_token - ISYS proxy write queue token + * @request_id: update id for the specific proxy write request + * @region_index: Region id for the proxy write request + * @offset: Offset of the write request according to the base address + * of the region + * @value: Value that is requested to be written with the proxy write request + */ +struct ipu6_fw_proxy_write_queue_token { + u32 request_id; + u32 region_index; + u32 offset; + u32 value; +}; + +/** + * struct ipu6_fw_resp_queue_token - ISYS response queue token + * @resp_info: response info + */ +struct ipu6_fw_resp_queue_token { + struct ipu6_fw_isys_resp_info_abi resp_info; +}; + +/** + * struct ipu6_fw_send_queue_token - ISYS send queue token + * @buf_handle: buffer handle + * @payload: payload + * @send_type: send_type + * @stream_id: stream_id + */ +struct ipu6_fw_send_queue_token { + u64 buf_handle; + u32 payload; + u16 send_type; + u16 stream_id; +}; + +/** + * struct ipu6_fw_proxy_resp_queue_token - ISYS proxy response queue token + * @proxy_resp_info: proxy response info + */ +struct ipu6_fw_proxy_resp_queue_token { + struct ipu6_fw_isys_proxy_resp_info_abi proxy_resp_info; +}; + +/** + * struct ipu6_fw_proxy_send_queue_token - SYS proxy send queue token + * @request_id: request_id + * @region_index: region_index + * @offset: offset + * @value: value + */ +struct ipu6_fw_proxy_send_queue_token { + u32 request_id; + u32 region_index; + u32 offset; + u32 value; +}; + +void +ipu6_fw_isys_dump_stream_cfg(struct device *dev, + struct ipu6_fw_isys_stream_cfg_data_abi *cfg); +void +ipu6_fw_isys_dump_frame_buff_set(struct device *dev, + struct ipu6_fw_isys_frame_buff_set_abi *buf, + unsigned int outputs); +int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams); +int ipu6_fw_isys_close(struct ipu6_isys *isys); +int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys, + const unsigned int stream_handle, u16 send_type); +int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, + const unsigned int stream_handle, + void *cpu_mapped_buf, dma_addr_t dma_mapped_buf, + size_t size, u16 send_type); +int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, + unsigned int req_id, + unsigned int index, + unsigned int offset, u32 value); +void ipu6_fw_isys_cleanup(struct ipu6_isys *isys); +struct ipu6_fw_isys_resp_info_abi * +ipu6_fw_isys_get_resp(void *context, unsigned int queue); +void ipu6_fw_isys_put_resp(void *context, unsigned int queue); +#endif diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c new file mode 100644 index 000000000..7e539a0c6 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c @@ -0,0 +1,647 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ipu6-bus.h" +#include "ipu6-isys.h" +#include "ipu6-isys-csi2.h" +#include "ipu6-isys-subdev.h" +#include "ipu6-platform-isys-csi2-reg.h" + +static const u32 csi2_supported_codes[] = { + MEDIA_BUS_FMT_RGB565_1X16, + MEDIA_BUS_FMT_RGB888_1X24, + MEDIA_BUS_FMT_UYVY8_1X16, + MEDIA_BUS_FMT_YUYV8_1X16, + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SBGGR12_1X12, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SRGGB12_1X12, + MEDIA_BUS_FMT_SBGGR8_1X8, + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, + MEDIA_BUS_FMT_SRGGB8_1X8, + MEDIA_BUS_FMT_Y8_1X8, + MEDIA_BUS_FMT_Y10_1X10, + MEDIA_BUS_FMT_Y12_1X12, + MEDIA_BUS_FMT_Y16_1X16, + MEDIA_BUS_FMT_META_8, + MEDIA_BUS_FMT_META_10, + MEDIA_BUS_FMT_META_12, + MEDIA_BUS_FMT_META_16, + MEDIA_BUS_FMT_META_24, + 0 +}; + +/* + * Strings corresponding to CSI-2 receiver errors are here. + * Corresponding macros are defined in the header file. + */ +static const struct ipu6_csi2_error dphy_rx_errors[] = { + { "Single packet header error corrected", true }, + { "Multiple packet header errors detected", true }, + { "Payload checksum (CRC) error", true }, + { "Transfer FIFO overflow", false }, + { "Reserved short packet data type detected", true }, + { "Reserved long packet data type detected", true }, + { "Incomplete long packet detected", false }, + { "Frame sync error", false }, + { "Line sync error", false }, + { "DPHY recoverable synchronization error", true }, + { "DPHY fatal error", false }, + { "DPHY elastic FIFO overflow", false }, + { "Inter-frame short packet discarded", true }, + { "Inter-frame long packet discarded", true }, + { "MIPI pktgen overflow", false }, + { "MIPI pktgen data loss", false }, + { "FIFO overflow", false }, + { "Lane deskew", false }, + { "SOT sync error", false }, + { "HSIDLE detected", false } +}; + +s64 ipu6_isys_csi2_get_link_freq(struct ipu6_isys_csi2 *csi2) +{ + struct media_pad *src_pad; + + if (!csi2) + return -EINVAL; + + src_pad = media_pad_remote_pad_unique(&csi2->asd.sd.entity.pads[CSI2_PAD_SINK]); + if (IS_ERR(src_pad)) { + dev_err(&csi2->isys->adev->auxdev.dev, + "can't get source pad of %s (%pe)\n", + csi2->asd.sd.name, src_pad); + return PTR_ERR(src_pad); + } + + return v4l2_get_link_freq(src_pad, 0, 0); +} + +static int csi2_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); + struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd); + struct device *dev = &csi2->isys->adev->auxdev.dev; + + dev_dbg(dev, "csi2 subscribe event(type %u id %u)\n", + sub->type, sub->id); + + switch (sub->type) { + case V4L2_EVENT_FRAME_SYNC: + return v4l2_event_subscribe(fh, sub, 10, NULL); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); + default: + return -EINVAL; + } +} + +static const struct v4l2_subdev_core_ops csi2_sd_core_ops = { + .subscribe_event = csi2_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +/* + * The input system CSI2+ receiver has several + * parameters affecting the receiver timings. These depend + * on the MIPI bus frequency F in Hz (sensor transmitter rate) + * as follows: + * register value = (A/1e9 + B * UI) / COUNT_ACC + * where + * UI = 1 / (2 * F) in seconds + * COUNT_ACC = counter accuracy in seconds + * COUNT_ACC = 0.125 ns = 1 / 8 ns, ACCINV = 8. + * + * A and B are coefficients from the table below, + * depending whether the register minimum or maximum value is + * calculated. + * Minimum Maximum + * Clock lane A B A B + * reg_rx_csi_dly_cnt_termen_clane 0 0 38 0 + * reg_rx_csi_dly_cnt_settle_clane 95 -8 300 -16 + * Data lanes + * reg_rx_csi_dly_cnt_termen_dlane0 0 0 35 4 + * reg_rx_csi_dly_cnt_settle_dlane0 85 -2 145 -6 + * reg_rx_csi_dly_cnt_termen_dlane1 0 0 35 4 + * reg_rx_csi_dly_cnt_settle_dlane1 85 -2 145 -6 + * reg_rx_csi_dly_cnt_termen_dlane2 0 0 35 4 + * reg_rx_csi_dly_cnt_settle_dlane2 85 -2 145 -6 + * reg_rx_csi_dly_cnt_termen_dlane3 0 0 35 4 + * reg_rx_csi_dly_cnt_settle_dlane3 85 -2 145 -6 + * + * We use the minimum values of both A and B. + */ + +#define DIV_SHIFT 8 +#define CSI2_ACCINV 8 + +static u32 calc_timing(s32 a, s32 b, s64 link_freq, s32 accinv) +{ + return accinv * a + (accinv * b * (500000000 >> DIV_SHIFT) + / (s32)(link_freq >> DIV_SHIFT)); +} + +static int +ipu6_isys_csi2_calc_timing(struct ipu6_isys_csi2 *csi2, + struct ipu6_isys_csi2_timing *timing, s32 accinv) +{ + struct device *dev = &csi2->isys->adev->auxdev.dev; + s64 link_freq; + + link_freq = ipu6_isys_csi2_get_link_freq(csi2); + if (link_freq < 0) + return link_freq; + + timing->ctermen = calc_timing(CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_A, + CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_B, + link_freq, accinv); + timing->csettle = calc_timing(CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_A, + CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_B, + link_freq, accinv); + timing->dtermen = calc_timing(CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_A, + CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_B, + link_freq, accinv); + timing->dsettle = calc_timing(CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_A, + CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_B, + link_freq, accinv); + + dev_dbg(dev, "ctermen %u csettle %u dtermen %u dsettle %u\n", + timing->ctermen, timing->csettle, + timing->dtermen, timing->dsettle); + + return 0; +} + +void ipu6_isys_register_errors(struct ipu6_isys_csi2 *csi2) +{ + u32 irq = readl(csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + + CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); + struct ipu6_isys *isys = csi2->isys; + u32 mask; + + mask = isys->pdata->ipdata->csi2.irq_mask; + writel(irq & mask, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + + CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); + csi2->receiver_errors |= irq & mask; +} + +void ipu6_isys_csi2_error(struct ipu6_isys_csi2 *csi2) +{ + struct device *dev = &csi2->isys->adev->auxdev.dev; + const struct ipu6_csi2_error *errors; + u32 status; + u32 i; + + /* register errors once more in case of interrupts are disabled */ + ipu6_isys_register_errors(csi2); + status = csi2->receiver_errors; + csi2->receiver_errors = 0; + errors = dphy_rx_errors; + + for (i = 0; i < CSI_RX_NUM_ERRORS_IN_IRQ; i++) { + if (status & BIT(i)) + dev_err_ratelimited(dev, "csi2-%i error: %s\n", + csi2->port, errors[i].error_string); + } +} + +static int ipu6_isys_csi2_set_stream(struct v4l2_subdev *sd, + const struct ipu6_isys_csi2_timing *timing, + unsigned int nlanes, int enable) +{ + struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); + struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd); + struct ipu6_isys *isys = csi2->isys; + struct device *dev = &isys->adev->auxdev.dev; + struct ipu6_isys_csi2_config cfg; + unsigned int nports; + int ret = 0; + u32 mask = 0; + u32 i; + + dev_dbg(dev, "stream %s CSI2-%u with %u lanes\n", enable ? "on" : "off", + csi2->port, nlanes); + + cfg.port = csi2->port; + cfg.nlanes = nlanes; + + mask = isys->pdata->ipdata->csi2.irq_mask; + nports = isys->pdata->ipdata->csi2.nports; + + if (!enable) { + writel(0, csi2->base + CSI_REG_CSI_FE_ENABLE); + writel(0, csi2->base + CSI_REG_PPI2CSI_ENABLE); + + writel(0, + csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + + CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); + writel(mask, + csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + + CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); + writel(0, + csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); + writel(0xffffffff, + csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); + + isys->phy_set_power(isys, &cfg, timing, false); + + writel(0, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT + (isys->pdata->ipdata->csi2.fw_access_port_ofs, + csi2->port)); + writel(0, isys->pdata->base + + CSI_REG_HUB_DRV_ACCESS_PORT(csi2->port)); + + return ret; + } + + /* reset port reset */ + writel(0x1, csi2->base + CSI_REG_PORT_GPREG_SRST); + usleep_range(100, 200); + writel(0x0, csi2->base + CSI_REG_PORT_GPREG_SRST); + + /* enable port clock */ + for (i = 0; i < nports; i++) { + writel(1, isys->pdata->base + CSI_REG_HUB_DRV_ACCESS_PORT(i)); + writel(1, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT + (isys->pdata->ipdata->csi2.fw_access_port_ofs, i)); + } + + /* enable all error related irq */ + writel(mask, + csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + + CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); + writel(mask, + csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + + CSI_PORT_REG_BASE_IRQ_MASK_OFFSET); + writel(mask, + csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + + CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); + writel(mask, + csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + + CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET); + writel(mask, + csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + + CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); + + /* + * Using event from firmware instead of irq to handle CSI2 sync event + * which can reduce system wakeups. If CSI2 sync irq enabled, we need + * disable the firmware CSI2 sync event to avoid duplicate handling. + */ + writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); + writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + CSI_PORT_REG_BASE_IRQ_MASK_OFFSET); + writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); + writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET); + writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); + + /* configure to enable FE and PPI2CSI */ + writel(0, csi2->base + CSI_REG_CSI_FE_MODE); + writel(CSI_SENSOR_INPUT, csi2->base + CSI_REG_CSI_FE_MUX_CTRL); + writel(CSI_CNTR_SENSOR_LINE_ID | CSI_CNTR_SENSOR_FRAME_ID, + csi2->base + CSI_REG_CSI_FE_SYNC_CNTR_SEL); + writel(FIELD_PREP(PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK, nlanes - 1), + csi2->base + CSI_REG_PPI2CSI_CONFIG_PPI_INTF); + + writel(1, csi2->base + CSI_REG_PPI2CSI_ENABLE); + writel(1, csi2->base + CSI_REG_CSI_FE_ENABLE); + + ret = isys->phy_set_power(isys, &cfg, timing, true); + if (ret) + dev_err(dev, "csi-%d phy power up failed %d\n", csi2->port, + ret); + + return ret; +} + +static int ipu6_isys_csi2_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); + struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd); + struct ipu6_isys_csi2_timing timing = { }; + struct v4l2_subdev *remote_sd; + struct media_pad *remote_pad; + u64 sink_streams; + int ret; + + remote_pad = media_pad_remote_pad_first(&sd->entity.pads[CSI2_PAD_SINK]); + remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); + + sink_streams = + v4l2_subdev_state_xlate_streams(state, pad, CSI2_PAD_SINK, + &streams_mask); + + ret = ipu6_isys_csi2_calc_timing(csi2, &timing, CSI2_ACCINV); + if (ret) + return ret; + + ret = ipu6_isys_csi2_set_stream(sd, &timing, csi2->nlanes, true); + if (ret) + return ret; + + ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index, + sink_streams); + if (ret) { + ipu6_isys_csi2_set_stream(sd, NULL, 0, false); + return ret; + } + + return 0; +} + +static int ipu6_isys_csi2_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct v4l2_subdev *remote_sd; + struct media_pad *remote_pad; + u64 sink_streams; + + sink_streams = + v4l2_subdev_state_xlate_streams(state, pad, CSI2_PAD_SINK, + &streams_mask); + + remote_pad = media_pad_remote_pad_first(&sd->entity.pads[CSI2_PAD_SINK]); + remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); + + ipu6_isys_csi2_set_stream(sd, NULL, 0, false); + + v4l2_subdev_disable_streams(remote_sd, remote_pad->index, sink_streams); + + return 0; +} + +static int ipu6_isys_csi2_set_sel(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); + struct device *dev = &asd->isys->adev->auxdev.dev; + struct v4l2_mbus_framefmt *sink_ffmt; + struct v4l2_mbus_framefmt *src_ffmt; + struct v4l2_rect *crop; + + if (sel->pad == CSI2_PAD_SINK || sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + sink_ffmt = v4l2_subdev_state_get_opposite_stream_format(state, + sel->pad, + sel->stream); + if (!sink_ffmt) + return -EINVAL; + + src_ffmt = v4l2_subdev_state_get_format(state, sel->pad, sel->stream); + if (!src_ffmt) + return -EINVAL; + + crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream); + if (!crop) + return -EINVAL; + + /* Only vertical cropping is supported */ + sel->r.left = 0; + sel->r.width = sink_ffmt->width; + /* Non-bayer formats can't be single line cropped */ + if (!ipu6_isys_is_bayer_format(sink_ffmt->code)) + sel->r.top &= ~1; + sel->r.height = clamp(sel->r.height & ~1, IPU6_ISYS_MIN_HEIGHT, + sink_ffmt->height - sel->r.top); + *crop = sel->r; + + /* update source pad format */ + src_ffmt->width = sel->r.width; + src_ffmt->height = sel->r.height; + if (ipu6_isys_is_bayer_format(sink_ffmt->code)) + src_ffmt->code = ipu6_isys_convert_bayer_order(sink_ffmt->code, + sel->r.left, + sel->r.top); + dev_dbg(dev, "set crop for %s sel: %d,%d,%d,%d code: 0x%x\n", + sd->name, sel->r.left, sel->r.top, sel->r.width, sel->r.height, + src_ffmt->code); + + return 0; +} + +static int ipu6_isys_csi2_get_sel(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + struct v4l2_mbus_framefmt *sink_ffmt; + struct v4l2_rect *crop; + int ret = 0; + + if (sd->entity.pads[sel->pad].flags & MEDIA_PAD_FL_SINK) + return -EINVAL; + + sink_ffmt = v4l2_subdev_state_get_opposite_stream_format(state, + sel->pad, + sel->stream); + if (!sink_ffmt) + return -EINVAL; + + crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream); + if (!crop) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = sink_ffmt->width; + sel->r.height = sink_ffmt->height; + break; + case V4L2_SEL_TGT_CROP: + sel->r = *crop; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct v4l2_subdev_pad_ops csi2_sd_pad_ops = { + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = ipu6_isys_subdev_set_fmt, + .get_selection = ipu6_isys_csi2_get_sel, + .set_selection = ipu6_isys_csi2_set_sel, + .enum_mbus_code = ipu6_isys_subdev_enum_mbus_code, + .set_routing = ipu6_isys_subdev_set_routing, + .enable_streams = ipu6_isys_csi2_enable_streams, + .disable_streams = ipu6_isys_csi2_disable_streams, +}; + +static const struct v4l2_subdev_ops csi2_sd_ops = { + .core = &csi2_sd_core_ops, + .pad = &csi2_sd_pad_ops, +}; + +static const struct media_entity_operations csi2_entity_ops = { + .link_validate = v4l2_subdev_link_validate, + .has_pad_interdep = v4l2_subdev_has_pad_interdep, +}; + +void ipu6_isys_csi2_cleanup(struct ipu6_isys_csi2 *csi2) +{ + if (!csi2->isys) + return; + + v4l2_device_unregister_subdev(&csi2->asd.sd); + v4l2_subdev_cleanup(&csi2->asd.sd); + ipu6_isys_subdev_cleanup(&csi2->asd); + csi2->isys = NULL; +} + +int ipu6_isys_csi2_init(struct ipu6_isys_csi2 *csi2, + struct ipu6_isys *isys, + void __iomem *base, unsigned int index) +{ + struct device *dev = &isys->adev->auxdev.dev; + int ret; + + csi2->isys = isys; + csi2->base = base; + csi2->port = index; + + csi2->asd.sd.entity.ops = &csi2_entity_ops; + csi2->asd.isys = isys; + ret = ipu6_isys_subdev_init(&csi2->asd, &csi2_sd_ops, 0, + NR_OF_CSI2_SINK_PADS, NR_OF_CSI2_SRC_PADS); + if (ret) + goto fail; + + csi2->asd.source = IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT0 + index; + csi2->asd.supported_codes = csi2_supported_codes; + snprintf(csi2->asd.sd.name, sizeof(csi2->asd.sd.name), + IPU6_ISYS_ENTITY_PREFIX " CSI2 %u", index); + v4l2_set_subdevdata(&csi2->asd.sd, &csi2->asd); + ret = v4l2_subdev_init_finalize(&csi2->asd.sd); + if (ret) { + dev_err(dev, "failed to init v4l2 subdev\n"); + goto fail; + } + + ret = v4l2_device_register_subdev(&isys->v4l2_dev, &csi2->asd.sd); + if (ret) { + dev_err(dev, "failed to register v4l2 subdev\n"); + goto fail; + } + + return 0; + +fail: + ipu6_isys_csi2_cleanup(csi2); + + return ret; +} + +void ipu6_isys_csi2_sof_event_by_stream(struct ipu6_isys_stream *stream) +{ + struct video_device *vdev = stream->asd->sd.devnode; + struct device *dev = &stream->isys->adev->auxdev.dev; + struct ipu6_isys_csi2 *csi2 = ipu6_isys_subdev_to_csi2(stream->asd); + struct v4l2_event ev = { + .type = V4L2_EVENT_FRAME_SYNC, + }; + + ev.u.frame_sync.frame_sequence = atomic_fetch_inc(&stream->sequence); + v4l2_event_queue(vdev, &ev); + + dev_dbg(dev, "sof_event::csi2-%i sequence: %i, vc: %d\n", + csi2->port, ev.u.frame_sync.frame_sequence, stream->vc); +} + +void ipu6_isys_csi2_eof_event_by_stream(struct ipu6_isys_stream *stream) +{ + struct device *dev = &stream->isys->adev->auxdev.dev; + struct ipu6_isys_csi2 *csi2 = ipu6_isys_subdev_to_csi2(stream->asd); + u32 frame_sequence = atomic_read(&stream->sequence); + + dev_dbg(dev, "eof_event::csi2-%i sequence: %i\n", + csi2->port, frame_sequence); +} + +int ipu6_isys_csi2_get_remote_desc(u32 source_stream, + struct ipu6_isys_csi2 *csi2, + struct media_entity *source_entity, + struct v4l2_mbus_frame_desc_entry *entry) +{ + struct v4l2_mbus_frame_desc_entry *desc_entry = NULL; + struct device *dev = &csi2->isys->adev->auxdev.dev; + struct v4l2_mbus_frame_desc desc; + struct v4l2_subdev *source; + struct media_pad *pad; + unsigned int i; + int ret; + + source = media_entity_to_v4l2_subdev(source_entity); + if (!source) + return -EPIPE; + + pad = media_pad_remote_pad_first(&csi2->asd.pad[CSI2_PAD_SINK]); + if (!pad) + return -EPIPE; + + ret = v4l2_subdev_call(source, pad, get_frame_desc, pad->index, &desc); + if (ret) + return ret; + + if (desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) { + dev_err(dev, "Unsupported frame descriptor type\n"); + return -EINVAL; + } + + for (i = 0; i < desc.num_entries; i++) { + if (source_stream == desc.entry[i].stream) { + desc_entry = &desc.entry[i]; + break; + } + } + + if (!desc_entry) { + dev_err(dev, "Failed to find stream %u from remote subdev\n", + source_stream); + return -EINVAL; + } + + if (desc_entry->bus.csi2.vc >= NR_OF_CSI2_VC) { + dev_err(dev, "invalid vc %d\n", desc_entry->bus.csi2.vc); + return -EINVAL; + } + + *entry = *desc_entry; + + return 0; +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h new file mode 100644 index 000000000..ce8eed910 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013--2024 Intel Corporation */ + +#ifndef IPU6_ISYS_CSI2_H +#define IPU6_ISYS_CSI2_H + +#include + +#include "ipu6-isys-subdev.h" +#include "ipu6-isys-video.h" + +struct media_entity; +struct v4l2_mbus_frame_desc_entry; + +struct ipu6_isys_video; +struct ipu6_isys; +struct ipu6_isys_stream; + +#define NR_OF_CSI2_VC 16 +#define INVALID_VC_ID -1 +#define NR_OF_CSI2_SINK_PADS 1 +#define CSI2_PAD_SINK 0 +#define NR_OF_CSI2_SRC_PADS 8 +#define CSI2_PAD_SRC 1 +#define NR_OF_CSI2_PADS (NR_OF_CSI2_SINK_PADS + NR_OF_CSI2_SRC_PADS) + +#define CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_A 0 +#define CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_B 0 +#define CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_A 95 +#define CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_B -8 + +#define CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_A 0 +#define CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_B 0 +#define CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_A 85 +#define CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_B -2 + +struct ipu6_isys_csi2 { + struct ipu6_isys_subdev asd; + struct ipu6_isys *isys; + struct ipu6_isys_video av[NR_OF_CSI2_SRC_PADS]; + + void __iomem *base; + u32 receiver_errors; + unsigned int nlanes; + unsigned int port; +}; + +struct ipu6_isys_csi2_timing { + u32 ctermen; + u32 csettle; + u32 dtermen; + u32 dsettle; +}; + +struct ipu6_csi2_error { + const char *error_string; + bool is_info_only; +}; + +#define ipu6_isys_subdev_to_csi2(__sd) \ + container_of(__sd, struct ipu6_isys_csi2, asd) + +#define to_ipu6_isys_csi2(__asd) container_of(__asd, struct ipu6_isys_csi2, asd) + +s64 ipu6_isys_csi2_get_link_freq(struct ipu6_isys_csi2 *csi2); +int ipu6_isys_csi2_init(struct ipu6_isys_csi2 *csi2, struct ipu6_isys *isys, + void __iomem *base, unsigned int index); +void ipu6_isys_csi2_cleanup(struct ipu6_isys_csi2 *csi2); +void ipu6_isys_csi2_sof_event_by_stream(struct ipu6_isys_stream *stream); +void ipu6_isys_csi2_eof_event_by_stream(struct ipu6_isys_stream *stream); +void ipu6_isys_register_errors(struct ipu6_isys_csi2 *csi2); +void ipu6_isys_csi2_error(struct ipu6_isys_csi2 *csi2); +int ipu6_isys_csi2_get_remote_desc(u32 source_stream, + struct ipu6_isys_csi2 *csi2, + struct media_entity *source_entity, + struct v4l2_mbus_frame_desc_entry *entry); + +#endif /* IPU6_ISYS_CSI2_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c b/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c new file mode 100644 index 000000000..237906cb1 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c @@ -0,0 +1,548 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include + +#include "ipu6-bus.h" +#include "ipu6-isys.h" +#include "ipu6-platform-isys-csi2-reg.h" + +#define IPU6_DWC_DPHY_BASE(i) (0x238038 + 0x34 * (i)) +#define IPU6_DWC_DPHY_RSTZ 0x00 +#define IPU6_DWC_DPHY_SHUTDOWNZ 0x04 +#define IPU6_DWC_DPHY_HSFREQRANGE 0x08 +#define IPU6_DWC_DPHY_CFGCLKFREQRANGE 0x0c +#define IPU6_DWC_DPHY_TEST_IFC_ACCESS_MODE 0x10 +#define IPU6_DWC_DPHY_TEST_IFC_REQ 0x14 +#define IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION 0x18 +#define IPU6_DWC_DPHY_DFT_CTRL0 0x28 +#define IPU6_DWC_DPHY_DFT_CTRL1 0x2c +#define IPU6_DWC_DPHY_DFT_CTRL2 0x30 + +/* + * test IFC request definition: + * - req: 0 for read, 1 for write + * - 12 bits address + * - 8bits data (will ignore for read) + * --24----16------4-----0 + * --|-data-|-addr-|-req-| + */ +#define IFC_REQ(req, addr, data) (FIELD_PREP(GENMASK(23, 16), data) | \ + FIELD_PREP(GENMASK(15, 4), addr) | \ + FIELD_PREP(GENMASK(1, 0), req)) + +#define TEST_IFC_REQ_READ 0 +#define TEST_IFC_REQ_WRITE 1 +#define TEST_IFC_REQ_RESET 2 + +#define TEST_IFC_ACCESS_MODE_FSM 0 +#define TEST_IFC_ACCESS_MODE_IFC_CTL 1 + +enum phy_fsm_state { + PHY_FSM_STATE_POWERON = 0, + PHY_FSM_STATE_BGPON = 1, + PHY_FSM_STATE_CAL_TYPE = 2, + PHY_FSM_STATE_BURNIN_CAL = 3, + PHY_FSM_STATE_TERMCAL = 4, + PHY_FSM_STATE_OFFSETCAL = 5, + PHY_FSM_STATE_OFFSET_LANE = 6, + PHY_FSM_STATE_IDLE = 7, + PHY_FSM_STATE_ULP = 8, + PHY_FSM_STATE_DDLTUNNING = 9, + PHY_FSM_STATE_SKEW_BACKWARD = 10, + PHY_FSM_STATE_INVALID, +}; + +static void dwc_dphy_write(struct ipu6_isys *isys, u32 phy_id, u32 addr, + u32 data) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id); + + dev_dbg(dev, "write: reg 0x%zx = data 0x%x", base + addr - isys_base, + data); + writel(data, base + addr); +} + +static u32 dwc_dphy_read(struct ipu6_isys *isys, u32 phy_id, u32 addr) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id); + u32 data; + + data = readl(base + addr); + dev_dbg(dev, "read: reg 0x%zx = data 0x%x", base + addr - isys_base, + data); + + return data; +} + +static void dwc_dphy_write_mask(struct ipu6_isys *isys, u32 phy_id, u32 addr, + u32 data, u8 shift, u8 width) +{ + u32 temp; + u32 mask; + + mask = (1 << width) - 1; + temp = dwc_dphy_read(isys, phy_id, addr); + temp &= ~(mask << shift); + temp |= (data & mask) << shift; + dwc_dphy_write(isys, phy_id, addr, temp); +} + +static u32 __maybe_unused dwc_dphy_read_mask(struct ipu6_isys *isys, u32 phy_id, + u32 addr, u8 shift, u8 width) +{ + u32 val; + + val = dwc_dphy_read(isys, phy_id, addr) >> shift; + return val & ((1 << width) - 1); +} + +#define DWC_DPHY_TIMEOUT (5 * USEC_PER_SEC) +static int dwc_dphy_ifc_read(struct ipu6_isys *isys, u32 phy_id, u32 addr, + u32 *val) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id); + void __iomem *reg; + u32 completion; + int ret; + + dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ, + IFC_REQ(TEST_IFC_REQ_READ, addr, 0)); + reg = base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION; + ret = readl_poll_timeout(reg, completion, !(completion & BIT(0)), + 10, DWC_DPHY_TIMEOUT); + if (ret) { + dev_err(dev, "DWC ifc request read timeout\n"); + return ret; + } + + *val = completion >> 8 & 0xff; + *val = FIELD_GET(GENMASK(15, 8), completion); + dev_dbg(dev, "DWC ifc read 0x%x = 0x%x", addr, *val); + + return 0; +} + +static int dwc_dphy_ifc_write(struct ipu6_isys *isys, u32 phy_id, u32 addr, + u32 data) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id); + void __iomem *reg; + u32 completion; + int ret; + + dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ, + IFC_REQ(TEST_IFC_REQ_WRITE, addr, data)); + completion = readl(base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION); + reg = base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION; + ret = readl_poll_timeout(reg, completion, !(completion & BIT(0)), + 10, DWC_DPHY_TIMEOUT); + if (ret) + dev_err(dev, "DWC ifc request write timeout\n"); + + return ret; +} + +static void dwc_dphy_ifc_write_mask(struct ipu6_isys *isys, u32 phy_id, + u32 addr, u32 data, u8 shift, u8 width) +{ + u32 temp, mask; + int ret; + + ret = dwc_dphy_ifc_read(isys, phy_id, addr, &temp); + if (ret) + return; + + mask = (1 << width) - 1; + temp &= ~(mask << shift); + temp |= (data & mask) << shift; + dwc_dphy_ifc_write(isys, phy_id, addr, temp); +} + +static u32 dwc_dphy_ifc_read_mask(struct ipu6_isys *isys, u32 phy_id, u32 addr, + u8 shift, u8 width) +{ + int ret; + u32 val; + + ret = dwc_dphy_ifc_read(isys, phy_id, addr, &val); + if (ret) + return 0; + + return ((val >> shift) & ((1 << width) - 1)); +} + +static int dwc_dphy_pwr_up(struct ipu6_isys *isys, u32 phy_id) +{ + struct device *dev = &isys->adev->auxdev.dev; + u32 fsm_state; + int ret; + + dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_RSTZ, 1); + usleep_range(10, 20); + dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_SHUTDOWNZ, 1); + + ret = read_poll_timeout(dwc_dphy_ifc_read_mask, fsm_state, + (fsm_state == PHY_FSM_STATE_IDLE || + fsm_state == PHY_FSM_STATE_ULP), + 100, DWC_DPHY_TIMEOUT, false, isys, + phy_id, 0x1e, 0, 4); + + if (ret) + dev_err(dev, "Dphy %d power up failed, state 0x%x", phy_id, + fsm_state); + + return ret; +} + +struct dwc_dphy_freq_range { + u8 hsfreq; + u16 min; + u16 max; + u16 default_mbps; + u16 osc_freq_target; +}; + +#define DPHY_FREQ_RANGE_NUM (63) +#define DPHY_FREQ_RANGE_INVALID_INDEX (0xff) +static const struct dwc_dphy_freq_range freqranges[DPHY_FREQ_RANGE_NUM] = { + {0x00, 80, 97, 80, 335}, + {0x10, 80, 107, 90, 335}, + {0x20, 84, 118, 100, 335}, + {0x30, 93, 128, 110, 335}, + {0x01, 103, 139, 120, 335}, + {0x11, 112, 149, 130, 335}, + {0x21, 122, 160, 140, 335}, + {0x31, 131, 170, 150, 335}, + {0x02, 141, 181, 160, 335}, + {0x12, 150, 191, 170, 335}, + {0x22, 160, 202, 180, 335}, + {0x32, 169, 212, 190, 335}, + {0x03, 183, 228, 205, 335}, + {0x13, 198, 244, 220, 335}, + {0x23, 212, 259, 235, 335}, + {0x33, 226, 275, 250, 335}, + {0x04, 250, 301, 275, 335}, + {0x14, 274, 328, 300, 335}, + {0x25, 297, 354, 325, 335}, + {0x35, 321, 380, 350, 335}, + {0x05, 369, 433, 400, 335}, + {0x16, 416, 485, 450, 335}, + {0x26, 464, 538, 500, 335}, + {0x37, 511, 590, 550, 335}, + {0x07, 559, 643, 600, 335}, + {0x18, 606, 695, 650, 335}, + {0x28, 654, 748, 700, 335}, + {0x39, 701, 800, 750, 335}, + {0x09, 749, 853, 800, 335}, + {0x19, 796, 905, 850, 335}, + {0x29, 844, 958, 900, 335}, + {0x3a, 891, 1010, 950, 335}, + {0x0a, 939, 1063, 1000, 335}, + {0x1a, 986, 1115, 1050, 335}, + {0x2a, 1034, 1168, 1100, 335}, + {0x3b, 1081, 1220, 1150, 335}, + {0x0b, 1129, 1273, 1200, 335}, + {0x1b, 1176, 1325, 1250, 335}, + {0x2b, 1224, 1378, 1300, 335}, + {0x3c, 1271, 1430, 1350, 335}, + {0x0c, 1319, 1483, 1400, 335}, + {0x1c, 1366, 1535, 1450, 335}, + {0x2c, 1414, 1588, 1500, 335}, + {0x3d, 1461, 1640, 1550, 208}, + {0x0d, 1509, 1693, 1600, 214}, + {0x1d, 1556, 1745, 1650, 221}, + {0x2e, 1604, 1798, 1700, 228}, + {0x3e, 1651, 1850, 1750, 234}, + {0x0e, 1699, 1903, 1800, 241}, + {0x1e, 1746, 1955, 1850, 248}, + {0x2f, 1794, 2008, 1900, 255}, + {0x3f, 1841, 2060, 1950, 261}, + {0x0f, 1889, 2113, 2000, 268}, + {0x40, 1936, 2165, 2050, 275}, + {0x41, 1984, 2218, 2100, 281}, + {0x42, 2031, 2270, 2150, 288}, + {0x43, 2079, 2323, 2200, 294}, + {0x44, 2126, 2375, 2250, 302}, + {0x45, 2174, 2428, 2300, 308}, + {0x46, 2221, 2480, 2350, 315}, + {0x47, 2269, 2500, 2400, 321}, + {0x48, 2316, 2500, 2450, 328}, + {0x49, 2364, 2500, 2500, 335} +}; + +static u16 get_hsfreq_by_mbps(u32 mbps) +{ + u16 best = DPHY_FREQ_RANGE_INVALID_INDEX; + unsigned int i; + + for (i = 0; i < DPHY_FREQ_RANGE_NUM; i++) { + if (mbps > freqranges[i].max) + continue; + + if (mbps < freqranges[i].min) + break; + + if (best == DPHY_FREQ_RANGE_INVALID_INDEX || + freqranges[i].osc_freq_target > + freqranges[best].osc_freq_target || + (freqranges[i].osc_freq_target == + freqranges[best].osc_freq_target && + abs((int)mbps - (int)freqranges[i].default_mbps) < + abs((int)mbps - (int)freqranges[best].default_mbps))) + best = i; + } + + return best; +} + +static int ipu6_isys_dwc_phy_config(struct ipu6_isys *isys, + u32 phy_id, u32 mbps) +{ + struct ipu6_bus_device *adev = isys->adev; + struct device *dev = &adev->auxdev.dev; + struct ipu6_device *isp = adev->isp; + u32 cfg_clk_freqrange; + u32 osc_freq_target; + u32 index; + + dev_dbg(dev, "config Dphy %u with %u mbps", phy_id, mbps); + + index = get_hsfreq_by_mbps(mbps); + if (index == DPHY_FREQ_RANGE_INVALID_INDEX) { + dev_err(dev, "link freq not found for mbps %u", mbps); + return -EINVAL; + } + + dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_HSFREQRANGE, + freqranges[index].hsfreq, 0, 7); + + /* Force termination Calibration */ + if (isys->phy_termcal_val) { + dwc_dphy_ifc_write_mask(isys, phy_id, 0x20a, 0x1, 0, 1); + dwc_dphy_ifc_write_mask(isys, phy_id, 0x209, 0x3, 0, 2); + dwc_dphy_ifc_write_mask(isys, phy_id, 0x209, + isys->phy_termcal_val, 4, 4); + } + + /* + * Enable override to configure the DDL target oscillation + * frequency on bit 0 of register 0xe4 + */ + dwc_dphy_ifc_write_mask(isys, phy_id, 0xe4, 0x1, 0, 1); + /* + * configure registers 0xe2, 0xe3 with the + * appropriate DDL target oscillation frequency + * 0x1cc(460) + */ + osc_freq_target = freqranges[index].osc_freq_target; + dwc_dphy_ifc_write_mask(isys, phy_id, 0xe2, + osc_freq_target & 0xff, 0, 8); + dwc_dphy_ifc_write_mask(isys, phy_id, 0xe3, + (osc_freq_target >> 8) & 0xf, 0, 4); + + if (mbps < 1500) { + /* deskew_polarity_rw, for < 1.5Gbps */ + dwc_dphy_ifc_write_mask(isys, phy_id, 0x8, 0x1, 5, 1); + } + + /* + * Set cfgclkfreqrange[5:0] = round[(Fcfg_clk(MHz)-17)*4] + * (38.4 - 17) * 4 = ~85 (0x55) + */ + cfg_clk_freqrange = (isp->buttress.ref_clk - 170) * 4 / 10; + dev_dbg(dev, "ref_clk = %u clk_freqrange = %u", + isp->buttress.ref_clk, cfg_clk_freqrange); + dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_CFGCLKFREQRANGE, + cfg_clk_freqrange, 0, 8); + + dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0x1, 4, 1); + dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0x1, 8, 1); + + return 0; +} + +static void ipu6_isys_dwc_phy_aggr_setup(struct ipu6_isys *isys, u32 master, + u32 slave, u32 mbps) +{ + /* Config mastermacro */ + dwc_dphy_ifc_write_mask(isys, master, 0x133, 0x1, 0, 1); + dwc_dphy_ifc_write_mask(isys, slave, 0x133, 0x0, 0, 1); + + /* Config master PHY clk lane to drive long channel clk */ + dwc_dphy_ifc_write_mask(isys, master, 0x307, 0x1, 2, 1); + dwc_dphy_ifc_write_mask(isys, slave, 0x307, 0x0, 2, 1); + + /* Config both PHYs data lanes to get clk from long channel */ + dwc_dphy_ifc_write_mask(isys, master, 0x508, 0x1, 5, 1); + dwc_dphy_ifc_write_mask(isys, slave, 0x508, 0x1, 5, 1); + dwc_dphy_ifc_write_mask(isys, master, 0x708, 0x1, 5, 1); + dwc_dphy_ifc_write_mask(isys, slave, 0x708, 0x1, 5, 1); + + /* Config slave PHY clk lane to bypass long channel clk to DDR clk */ + dwc_dphy_ifc_write_mask(isys, master, 0x308, 0x0, 3, 1); + dwc_dphy_ifc_write_mask(isys, slave, 0x308, 0x1, 3, 1); + + /* Override slave PHY clk lane enable (DPHYRXCLK_CLL_demux module) */ + dwc_dphy_ifc_write_mask(isys, slave, 0xe0, 0x3, 0, 2); + + /* Override slave PHY DDR clk lane enable (DPHYHSRX_div124 module) */ + dwc_dphy_ifc_write_mask(isys, slave, 0xe1, 0x1, 1, 1); + dwc_dphy_ifc_write_mask(isys, slave, 0x307, 0x1, 3, 1); + + /* Turn off slave PHY LP-RX clk lane */ + dwc_dphy_ifc_write_mask(isys, slave, 0x304, 0x1, 7, 1); + dwc_dphy_ifc_write_mask(isys, slave, 0x305, 0xa, 0, 5); +} + +#define PHY_E 4 +static int ipu6_isys_dwc_phy_powerup_ack(struct ipu6_isys *isys, u32 phy_id) +{ + struct device *dev = &isys->adev->auxdev.dev; + u32 rescal_done; + int ret; + + ret = dwc_dphy_pwr_up(isys, phy_id); + if (ret != 0) { + dev_err(dev, "Dphy %u power up failed(%d)", phy_id, ret); + return ret; + } + + /* reset forcerxmode */ + dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0, 4, 1); + dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0, 8, 1); + + dev_dbg(dev, "Dphy %u is ready!", phy_id); + + if (phy_id != PHY_E || isys->phy_termcal_val) + return 0; + + usleep_range(100, 200); + rescal_done = dwc_dphy_ifc_read_mask(isys, phy_id, 0x221, 7, 1); + if (rescal_done) { + isys->phy_termcal_val = dwc_dphy_ifc_read_mask(isys, phy_id, + 0x220, 2, 4); + dev_dbg(dev, "termcal done with value = %u", + isys->phy_termcal_val); + } + + return 0; +} + +static void ipu6_isys_dwc_phy_reset(struct ipu6_isys *isys, u32 phy_id) +{ + dev_dbg(&isys->adev->auxdev.dev, "Reset phy %u", phy_id); + + dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_SHUTDOWNZ, 0); + dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_RSTZ, 0); + dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_ACCESS_MODE, + TEST_IFC_ACCESS_MODE_FSM); + dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ, + TEST_IFC_REQ_RESET); +} + +int ipu6_isys_dwc_phy_set_power(struct ipu6_isys *isys, + struct ipu6_isys_csi2_config *cfg, + const struct ipu6_isys_csi2_timing *timing, + bool on) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + u32 phy_id, primary, secondary; + u32 nlanes, port, mbps; + s64 link_freq; + int ret; + + port = cfg->port; + + if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) { + dev_warn(dev, "invalid port ID %d\n", port); + return -EINVAL; + } + + nlanes = cfg->nlanes; + /* only port 0, 2 and 4 support 4 lanes */ + if (nlanes == 4 && port % 2) { + dev_err(dev, "invalid csi-port %u with %u lanes\n", port, + nlanes); + return -EINVAL; + } + + link_freq = ipu6_isys_csi2_get_link_freq(&isys->csi2[port]); + if (link_freq < 0) { + dev_err(dev, "get link freq failed(%lld).\n", link_freq); + return link_freq; + } + + mbps = div_u64(link_freq, 500000); + + phy_id = port; + primary = port & ~1; + secondary = primary + 1; + if (on) { + if (nlanes == 4) { + dev_dbg(dev, "config phy %u and %u in aggr mode\n", + primary, secondary); + + ipu6_isys_dwc_phy_reset(isys, primary); + ipu6_isys_dwc_phy_reset(isys, secondary); + ipu6_isys_dwc_phy_aggr_setup(isys, primary, + secondary, mbps); + + ret = ipu6_isys_dwc_phy_config(isys, primary, mbps); + if (ret) + return ret; + ret = ipu6_isys_dwc_phy_config(isys, secondary, mbps); + if (ret) + return ret; + + ret = ipu6_isys_dwc_phy_powerup_ack(isys, primary); + if (ret) + return ret; + + ret = ipu6_isys_dwc_phy_powerup_ack(isys, secondary); + return ret; + } + + dev_dbg(dev, "config phy %u with %u lanes in non-aggr mode\n", + phy_id, nlanes); + + ipu6_isys_dwc_phy_reset(isys, phy_id); + ret = ipu6_isys_dwc_phy_config(isys, phy_id, mbps); + if (ret) + return ret; + + ret = ipu6_isys_dwc_phy_powerup_ack(isys, phy_id); + return ret; + } + + if (nlanes == 4) { + dev_dbg(dev, "Power down phy %u and phy %u for port %u\n", + primary, secondary, port); + ipu6_isys_dwc_phy_reset(isys, secondary); + ipu6_isys_dwc_phy_reset(isys, primary); + + return 0; + } + + dev_dbg(dev, "Powerdown phy %u with %u lanes\n", phy_id, nlanes); + + ipu6_isys_dwc_phy_reset(isys, phy_id); + + return 0; +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c b/drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c new file mode 100644 index 000000000..c804291cf --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ + +#include +#include +#include +#include + +#include "ipu6-bus.h" +#include "ipu6-isys.h" +#include "ipu6-isys-csi2.h" +#include "ipu6-platform-isys-csi2-reg.h" + +/* only use BB0, BB2, BB4, and BB6 on PHY0 */ +#define IPU6SE_ISYS_PHY_BB_NUM 4 +#define IPU6SE_ISYS_PHY_0_BASE 0x10000 + +#define PHY_CPHY_DLL_OVRD(x) (0x100 + 0x100 * (x)) +#define PHY_CPHY_RX_CONTROL1(x) (0x110 + 0x100 * (x)) +#define PHY_DPHY_CFG(x) (0x148 + 0x100 * (x)) +#define PHY_BB_AFE_CONFIG(x) (0x174 + 0x100 * (x)) + +/* + * use port_cfg to configure that which data lanes used + * +---------+ +------+ +-----+ + * | port0 x4<-----| | | | + * | | | port | | | + * | port1 x2<-----| | | | + * | | | <-| PHY | + * | port2 x4<-----| | | | + * | | |config| | | + * | port3 x2<-----| | | | + * +---------+ +------+ +-----+ + */ +static const unsigned int csi2_port_cfg[][3] = { + {0, 0, 0x1f}, /* no link */ + {4, 0, 0x10}, /* x4 + x4 config */ + {2, 0, 0x12}, /* x2 + x2 config */ + {1, 0, 0x13}, /* x1 + x1 config */ + {2, 1, 0x15}, /* x2x1 + x2x1 config */ + {1, 1, 0x16}, /* x1x1 + x1x1 config */ + {2, 2, 0x18}, /* x2x2 + x2x2 config */ + {1, 2, 0x19} /* x1x2 + x1x2 config */ +}; + +/* port, nlanes, bbindex, portcfg */ +static const unsigned int phy_port_cfg[][4] = { + /* sip0 */ + {0, 1, 0, 0x15}, + {0, 2, 0, 0x15}, + {0, 4, 0, 0x15}, + {0, 4, 2, 0x22}, + /* sip1 */ + {2, 1, 4, 0x15}, + {2, 2, 4, 0x15}, + {2, 4, 4, 0x15}, + {2, 4, 6, 0x22} +}; + +static void ipu6_isys_csi2_phy_config_by_port(struct ipu6_isys *isys, + unsigned int port, + unsigned int nlanes) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *base = isys->adev->isp->base; + unsigned int bbnum; + u32 val, reg, i; + + dev_dbg(dev, "port %u with %u lanes", port, nlanes); + + /* only support <1.5Gbps */ + for (i = 0; i < IPU6SE_ISYS_PHY_BB_NUM; i++) { + /* cphy_dll_ovrd.crcdc_fsm_dlane0 = 13 */ + reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i); + val = readl(base + reg); + val |= FIELD_PREP(GENMASK(6, 1), 13); + writel(val, base + reg); + + /* cphy_rx_control1.en_crc1 = 1 */ + reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_RX_CONTROL1(i); + val = readl(base + reg); + val |= BIT(31); + writel(val, base + reg); + + /* dphy_cfg.reserved = 1, .lden_from_dll_ovrd_0 = 1 */ + reg = IPU6SE_ISYS_PHY_0_BASE + PHY_DPHY_CFG(i); + val = readl(base + reg); + val |= BIT(25) | BIT(26); + writel(val, base + reg); + + /* cphy_dll_ovrd.lden_crcdc_fsm_dlane0 = 1 */ + reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i); + val = readl(base + reg); + val |= BIT(0); + writel(val, base + reg); + } + + /* Front end config, use minimal channel loss */ + for (i = 0; i < ARRAY_SIZE(phy_port_cfg); i++) { + if (phy_port_cfg[i][0] == port && + phy_port_cfg[i][1] == nlanes) { + bbnum = phy_port_cfg[i][2] / 2; + reg = IPU6SE_ISYS_PHY_0_BASE + PHY_BB_AFE_CONFIG(bbnum); + val = readl(base + reg); + val |= phy_port_cfg[i][3]; + writel(val, base + reg); + } + } +} + +static void ipu6_isys_csi2_rx_control(struct ipu6_isys *isys) +{ + void __iomem *base = isys->adev->isp->base; + u32 val, reg; + + reg = CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL; + val = readl(base + reg); + val |= BIT(0); + writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL); + + reg = CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL; + val = readl(base + reg); + val |= BIT(0); + writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL); + + reg = CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL; + val = readl(base + reg); + val |= BIT(0); + writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL); + + reg = CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL; + val = readl(base + reg); + val |= BIT(0); + writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL); +} + +static int ipu6_isys_csi2_set_port_cfg(struct ipu6_isys *isys, + unsigned int port, unsigned int nlanes) +{ + struct device *dev = &isys->adev->auxdev.dev; + unsigned int sip = port / 2; + unsigned int index; + + switch (nlanes) { + case 1: + index = 5; + break; + case 2: + index = 6; + break; + case 4: + index = 1; + break; + default: + dev_err(dev, "lanes nr %u is unsupported\n", nlanes); + return -EINVAL; + } + + dev_dbg(dev, "port config for port %u with %u lanes\n", port, nlanes); + + writel(csi2_port_cfg[index][2], + isys->pdata->base + CSI2_HUB_GPREG_SIP_FB_PORT_CFG(sip)); + + return 0; +} + +static void +ipu6_isys_csi2_set_timing(struct ipu6_isys *isys, + const struct ipu6_isys_csi2_timing *timing, + unsigned int port, unsigned int nlanes) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *reg; + u32 port_base; + u32 i; + + port_base = (port % 2) ? CSI2_SIP_TOP_CSI_RX_PORT_BASE_1(port) : + CSI2_SIP_TOP_CSI_RX_PORT_BASE_0(port); + + dev_dbg(dev, "set timing for port %u with %u lanes\n", port, nlanes); + + reg = isys->pdata->base + port_base; + reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_CLANE; + + writel(timing->ctermen, reg); + + reg = isys->pdata->base + port_base; + reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_CLANE; + writel(timing->csettle, reg); + + for (i = 0; i < nlanes; i++) { + reg = isys->pdata->base + port_base; + reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_DLANE(i); + writel(timing->dtermen, reg); + + reg = isys->pdata->base + port_base; + reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_DLANE(i); + writel(timing->dsettle, reg); + } +} + +#define DPHY_TIMER_INCR 0x28 +int ipu6_isys_jsl_phy_set_power(struct ipu6_isys *isys, + struct ipu6_isys_csi2_config *cfg, + const struct ipu6_isys_csi2_timing *timing, + bool on) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + int ret = 0; + u32 nlanes; + u32 port; + + if (!on) + return 0; + + port = cfg->port; + nlanes = cfg->nlanes; + + if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) { + dev_warn(dev, "invalid port ID %d\n", port); + return -EINVAL; + } + + ipu6_isys_csi2_phy_config_by_port(isys, port, nlanes); + + writel(DPHY_TIMER_INCR, + isys->pdata->base + CSI2_HUB_GPREG_DPHY_TIMER_INCR); + + /* set port cfg and rx timing */ + ipu6_isys_csi2_set_timing(isys, timing, port, nlanes); + + ret = ipu6_isys_csi2_set_port_cfg(isys, port, nlanes); + if (ret) + return ret; + + ipu6_isys_csi2_rx_control(isys); + + return 0; +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c b/drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c new file mode 100644 index 000000000..71aa50095 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c @@ -0,0 +1,720 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ipu6.h" +#include "ipu6-bus.h" +#include "ipu6-isys.h" +#include "ipu6-isys-csi2.h" +#include "ipu6-platform-isys-csi2-reg.h" + +#define CSI_REG_HUB_GPREG_PHY_CTL(id) (CSI_REG_BASE + 0x18008 + (id) * 0x8) +#define CSI_REG_HUB_GPREG_PHY_CTL_RESET BIT(4) +#define CSI_REG_HUB_GPREG_PHY_CTL_PWR_EN BIT(0) +#define CSI_REG_HUB_GPREG_PHY_STATUS(id) (CSI_REG_BASE + 0x1800c + (id) * 0x8) +#define CSI_REG_HUB_GPREG_PHY_POWER_ACK BIT(0) +#define CSI_REG_HUB_GPREG_PHY_READY BIT(4) + +#define MCD_PHY_POWER_STATUS_TIMEOUT (200 * USEC_PER_MSEC) + +/* + * bridge to phy in buttress reg map, each phy has 16 kbytes + * only 2 phys for TGL U and Y + */ +#define IPU6_ISYS_MCD_PHY_BASE(i) (0x10000 + (i) * 0x4000) + +/* + * There are 2 MCD DPHY instances on TGL and 1 MCD DPHY instance on ADL. + * Each MCD PHY has 12-lanes which has 8 data lanes and 4 clock lanes. + * CSI port 1, 3 (5, 7) can support max 2 data lanes. + * CSI port 0, 2 (4, 6) can support max 4 data lanes. + * PHY configurations are PPI based instead of port. + * Left: + * +---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | PPI | PPI5 | PPI4 | PPI3 | PPI2 | PPI1 | PPI0 | + * +---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x4 | unused | D3 | D2 | C0 | D0 | D1 | + * |---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x2x2 | C1 | D0 | D1 | C0 | D0 | D1 | + * ----------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x2x1 | C1 | D0 | unused | C0 | D0 | D1 | + * +---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x1x1 | C1 | D0 | unused | C0 | D0 | unused | + * +---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x1x2 | C1 | D0 | D1 | C0 | D0 | unused | + * +---------+---------+---------+---------+--------+---------+----------+ + * + * Right: + * +---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | PPI | PPI6 | PPI7 | PPI8 | PPI9 | PPI10 | PPI11 | + * +---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x4 | D1 | D0 | C2 | D2 | D3 | unused | + * |---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x2x2 | D1 | D0 | C2 | D1 | D0 | C3 | + * ----------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x2x1 | D1 | D0 | C2 | unused | D0 | C3 | + * +---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x1x1 | unused | D0 | C2 | unused | D0 | C3 | + * +---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x1x2 | unused | D0 | C2 | D1 | D0 | C3 | + * +---------+---------+---------+---------+--------+---------+----------+ + * + * ppi mapping per phy : + * + * x4 + x4: + * Left : port0 - PPI range {0, 1, 2, 3, 4} + * Right: port2 - PPI range {6, 7, 8, 9, 10} + * + * x4 + x2x2: + * Left: port0 - PPI range {0, 1, 2, 3, 4} + * Right: port2 - PPI range {6, 7, 8}, port3 - PPI range {9, 10, 11} + * + * x2x2 + x4: + * Left: port0 - PPI range {0, 1, 2}, port1 - PPI range {3, 4, 5} + * Right: port2 - PPI range {6, 7, 8, 9, 10} + * + * x2x2 + x2x2: + * Left : port0 - PPI range {0, 1, 2}, port1 - PPI range {3, 4, 5} + * Right: port2 - PPI range {6, 7, 8}, port3 - PPI range {9, 10, 11} + */ + +struct phy_reg { + u32 reg; + u32 val; +}; + +static const struct phy_reg common_init_regs[] = { + /* for TGL-U, use 0x80000000 */ + {0x00000040, 0x80000000}, + {0x00000044, 0x00a80880}, + {0x00000044, 0x00b80880}, + {0x00000010, 0x0000078c}, + {0x00000344, 0x2f4401e2}, + {0x00000544, 0x924401e2}, + {0x00000744, 0x594401e2}, + {0x00000944, 0x624401e2}, + {0x00000b44, 0xfc4401e2}, + {0x00000d44, 0xc54401e2}, + {0x00000f44, 0x034401e2}, + {0x00001144, 0x8f4401e2}, + {0x00001344, 0x754401e2}, + {0x00001544, 0xe94401e2}, + {0x00001744, 0xcb4401e2}, + {0x00001944, 0xfa4401e2} +}; + +static const struct phy_reg x1_port0_config_regs[] = { + {0x00000694, 0xc80060fa}, + {0x00000680, 0x3d4f78ea}, + {0x00000690, 0x10a0140b}, + {0x000006a8, 0xdf04010a}, + {0x00000700, 0x57050060}, + {0x00000710, 0x0030001c}, + {0x00000738, 0x5f004444}, + {0x0000073c, 0x78464204}, + {0x00000748, 0x7821f940}, + {0x0000074c, 0xb2000433}, + {0x00000494, 0xfe6030fa}, + {0x00000480, 0x29ef5ed0}, + {0x00000490, 0x10a0540b}, + {0x000004a8, 0x7a01010a}, + {0x00000500, 0xef053460}, + {0x00000510, 0xe030101c}, + {0x00000538, 0xdf808444}, + {0x0000053c, 0xc8422204}, + {0x00000540, 0x0180088c}, + {0x00000574, 0x00000000}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x1_port1_config_regs[] = { + {0x00000c94, 0xc80060fa}, + {0x00000c80, 0xcf47abea}, + {0x00000c90, 0x10a0840b}, + {0x00000ca8, 0xdf04010a}, + {0x00000d00, 0x57050060}, + {0x00000d10, 0x0030001c}, + {0x00000d38, 0x5f004444}, + {0x00000d3c, 0x78464204}, + {0x00000d48, 0x7821f940}, + {0x00000d4c, 0xb2000433}, + {0x00000a94, 0xc91030fa}, + {0x00000a80, 0x5a166ed0}, + {0x00000a90, 0x10a0540b}, + {0x00000aa8, 0x5d060100}, + {0x00000b00, 0xef053460}, + {0x00000b10, 0xa030101c}, + {0x00000b38, 0xdf808444}, + {0x00000b3c, 0xc8422204}, + {0x00000b40, 0x0180088c}, + {0x00000b74, 0x00000000}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x1_port2_config_regs[] = { + {0x00001294, 0x28f000fa}, + {0x00001280, 0x08130cea}, + {0x00001290, 0x10a0140b}, + {0x000012a8, 0xd704010a}, + {0x00001300, 0x8d050060}, + {0x00001310, 0x0030001c}, + {0x00001338, 0xdf008444}, + {0x0000133c, 0x78422204}, + {0x00001348, 0x7821f940}, + {0x0000134c, 0x5a000433}, + {0x00001094, 0x2d20b0fa}, + {0x00001080, 0xade75dd0}, + {0x00001090, 0x10a0540b}, + {0x000010a8, 0xb101010a}, + {0x00001100, 0x33053460}, + {0x00001110, 0x0030101c}, + {0x00001138, 0xdf808444}, + {0x0000113c, 0xc8422204}, + {0x00001140, 0x8180088c}, + {0x00001174, 0x00000000}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x1_port3_config_regs[] = { + {0x00001894, 0xc80060fa}, + {0x00001880, 0x0f90fd6a}, + {0x00001890, 0x10a0840b}, + {0x000018a8, 0xdf04010a}, + {0x00001900, 0x57050060}, + {0x00001910, 0x0030001c}, + {0x00001938, 0x5f004444}, + {0x0000193c, 0x78464204}, + {0x00001948, 0x7821f940}, + {0x0000194c, 0xb2000433}, + {0x00001694, 0x3050d0fa}, + {0x00001680, 0x0ef6d050}, + {0x00001690, 0x10a0540b}, + {0x000016a8, 0xe301010a}, + {0x00001700, 0x69053460}, + {0x00001710, 0xa030101c}, + {0x00001738, 0xdf808444}, + {0x0000173c, 0xc8422204}, + {0x00001740, 0x0180088c}, + {0x00001774, 0x00000000}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x2_port0_config_regs[] = { + {0x00000694, 0xc80060fa}, + {0x00000680, 0x3d4f78ea}, + {0x00000690, 0x10a0140b}, + {0x000006a8, 0xdf04010a}, + {0x00000700, 0x57050060}, + {0x00000710, 0x0030001c}, + {0x00000738, 0x5f004444}, + {0x0000073c, 0x78464204}, + {0x00000748, 0x7821f940}, + {0x0000074c, 0xb2000433}, + {0x00000494, 0xc80060fa}, + {0x00000480, 0x29ef5ed8}, + {0x00000490, 0x10a0540b}, + {0x000004a8, 0x7a01010a}, + {0x00000500, 0xef053460}, + {0x00000510, 0xe030101c}, + {0x00000538, 0xdf808444}, + {0x0000053c, 0xc8422204}, + {0x00000540, 0x0180088c}, + {0x00000574, 0x00000000}, + {0x00000294, 0xc80060fa}, + {0x00000280, 0xcb45b950}, + {0x00000290, 0x10a0540b}, + {0x000002a8, 0x8c01010a}, + {0x00000300, 0xef053460}, + {0x00000310, 0x8030101c}, + {0x00000338, 0x41808444}, + {0x0000033c, 0x32422204}, + {0x00000340, 0x0180088c}, + {0x00000374, 0x00000000}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x2_port1_config_regs[] = { + {0x00000c94, 0xc80060fa}, + {0x00000c80, 0xcf47abea}, + {0x00000c90, 0x10a0840b}, + {0x00000ca8, 0xdf04010a}, + {0x00000d00, 0x57050060}, + {0x00000d10, 0x0030001c}, + {0x00000d38, 0x5f004444}, + {0x00000d3c, 0x78464204}, + {0x00000d48, 0x7821f940}, + {0x00000d4c, 0xb2000433}, + {0x00000a94, 0xc80060fa}, + {0x00000a80, 0x5a166ed8}, + {0x00000a90, 0x10a0540b}, + {0x00000aa8, 0x7a01010a}, + {0x00000b00, 0xef053460}, + {0x00000b10, 0xa030101c}, + {0x00000b38, 0xdf808444}, + {0x00000b3c, 0xc8422204}, + {0x00000b40, 0x0180088c}, + {0x00000b74, 0x00000000}, + {0x00000894, 0xc80060fa}, + {0x00000880, 0x4d4f21d0}, + {0x00000890, 0x10a0540b}, + {0x000008a8, 0x5601010a}, + {0x00000900, 0xef053460}, + {0x00000910, 0x8030101c}, + {0x00000938, 0xdf808444}, + {0x0000093c, 0xc8422204}, + {0x00000940, 0x0180088c}, + {0x00000974, 0x00000000}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x2_port2_config_regs[] = { + {0x00001294, 0xc80060fa}, + {0x00001280, 0x08130cea}, + {0x00001290, 0x10a0140b}, + {0x000012a8, 0xd704010a}, + {0x00001300, 0x8d050060}, + {0x00001310, 0x0030001c}, + {0x00001338, 0xdf008444}, + {0x0000133c, 0x78422204}, + {0x00001348, 0x7821f940}, + {0x0000134c, 0x5a000433}, + {0x00001094, 0xc80060fa}, + {0x00001080, 0xade75dd8}, + {0x00001090, 0x10a0540b}, + {0x000010a8, 0xb101010a}, + {0x00001100, 0x33053460}, + {0x00001110, 0x0030101c}, + {0x00001138, 0xdf808444}, + {0x0000113c, 0xc8422204}, + {0x00001140, 0x8180088c}, + {0x00001174, 0x00000000}, + {0x00000e94, 0xc80060fa}, + {0x00000e80, 0x0fbf16d0}, + {0x00000e90, 0x10a0540b}, + {0x00000ea8, 0x7a01010a}, + {0x00000f00, 0xf5053460}, + {0x00000f10, 0xc030101c}, + {0x00000f38, 0xdf808444}, + {0x00000f3c, 0xc8422204}, + {0x00000f40, 0x8180088c}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x2_port3_config_regs[] = { + {0x00001894, 0xc80060fa}, + {0x00001880, 0x0f90fd6a}, + {0x00001890, 0x10a0840b}, + {0x000018a8, 0xdf04010a}, + {0x00001900, 0x57050060}, + {0x00001910, 0x0030001c}, + {0x00001938, 0x5f004444}, + {0x0000193c, 0x78464204}, + {0x00001948, 0x7821f940}, + {0x0000194c, 0xb2000433}, + {0x00001694, 0xc80060fa}, + {0x00001680, 0x0ef6d058}, + {0x00001690, 0x10a0540b}, + {0x000016a8, 0x7a01010a}, + {0x00001700, 0x69053460}, + {0x00001710, 0xa030101c}, + {0x00001738, 0xdf808444}, + {0x0000173c, 0xc8422204}, + {0x00001740, 0x0180088c}, + {0x00001774, 0x00000000}, + {0x00001494, 0xc80060fa}, + {0x00001480, 0xf9d34bd0}, + {0x00001490, 0x10a0540b}, + {0x000014a8, 0x7a01010a}, + {0x00001500, 0x1b053460}, + {0x00001510, 0x0030101c}, + {0x00001538, 0xdf808444}, + {0x0000153c, 0xc8422204}, + {0x00001540, 0x8180088c}, + {0x00001574, 0x00000000}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x4_port0_config_regs[] = { + {0x00000694, 0xc80060fa}, + {0x00000680, 0x3d4f78fa}, + {0x00000690, 0x10a0140b}, + {0x000006a8, 0xdf04010a}, + {0x00000700, 0x57050060}, + {0x00000710, 0x0030001c}, + {0x00000738, 0x5f004444}, + {0x0000073c, 0x78464204}, + {0x00000748, 0x7821f940}, + {0x0000074c, 0xb2000433}, + {0x00000494, 0xfe6030fa}, + {0x00000480, 0x29ef5ed8}, + {0x00000490, 0x10a0540b}, + {0x000004a8, 0x7a01010a}, + {0x00000500, 0xef053460}, + {0x00000510, 0xe030101c}, + {0x00000538, 0xdf808444}, + {0x0000053c, 0xc8422204}, + {0x00000540, 0x0180088c}, + {0x00000574, 0x00000004}, + {0x00000294, 0x23e030fa}, + {0x00000280, 0xcb45b950}, + {0x00000290, 0x10a0540b}, + {0x000002a8, 0x8c01010a}, + {0x00000300, 0xef053460}, + {0x00000310, 0x8030101c}, + {0x00000338, 0x41808444}, + {0x0000033c, 0x32422204}, + {0x00000340, 0x0180088c}, + {0x00000374, 0x00000004}, + {0x00000894, 0x5620b0fa}, + {0x00000880, 0x4d4f21dc}, + {0x00000890, 0x10a0540b}, + {0x000008a8, 0x5601010a}, + {0x00000900, 0xef053460}, + {0x00000910, 0x8030101c}, + {0x00000938, 0xdf808444}, + {0x0000093c, 0xc8422204}, + {0x00000940, 0x0180088c}, + {0x00000974, 0x00000004}, + {0x00000a94, 0xc91030fa}, + {0x00000a80, 0x5a166ecc}, + {0x00000a90, 0x10a0540b}, + {0x00000aa8, 0x5d01010a}, + {0x00000b00, 0xef053460}, + {0x00000b10, 0xa030101c}, + {0x00000b38, 0xdf808444}, + {0x00000b3c, 0xc8422204}, + {0x00000b40, 0x0180088c}, + {0x00000b74, 0x00000004}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x4_port1_config_regs[] = { + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x4_port2_config_regs[] = { + {0x00001294, 0x28f000fa}, + {0x00001280, 0x08130cfa}, + {0x00001290, 0x10c0140b}, + {0x000012a8, 0xd704010a}, + {0x00001300, 0x8d050060}, + {0x00001310, 0x0030001c}, + {0x00001338, 0xdf008444}, + {0x0000133c, 0x78422204}, + {0x00001348, 0x7821f940}, + {0x0000134c, 0x5a000433}, + {0x00001094, 0x2d20b0fa}, + {0x00001080, 0xade75dd8}, + {0x00001090, 0x10a0540b}, + {0x000010a8, 0xb101010a}, + {0x00001100, 0x33053460}, + {0x00001110, 0x0030101c}, + {0x00001138, 0xdf808444}, + {0x0000113c, 0xc8422204}, + {0x00001140, 0x8180088c}, + {0x00001174, 0x00000004}, + {0x00000e94, 0xd308d0fa}, + {0x00000e80, 0x0fbf16d0}, + {0x00000e90, 0x10a0540b}, + {0x00000ea8, 0x2c01010a}, + {0x00000f00, 0xf5053460}, + {0x00000f10, 0xc030101c}, + {0x00000f38, 0xdf808444}, + {0x00000f3c, 0xc8422204}, + {0x00000f40, 0x8180088c}, + {0x00000f74, 0x00000004}, + {0x00001494, 0x136850fa}, + {0x00001480, 0xf9d34bdc}, + {0x00001490, 0x10a0540b}, + {0x000014a8, 0x5a01010a}, + {0x00001500, 0x1b053460}, + {0x00001510, 0x0030101c}, + {0x00001538, 0xdf808444}, + {0x0000153c, 0xc8422204}, + {0x00001540, 0x8180088c}, + {0x00001574, 0x00000004}, + {0x00001694, 0x3050d0fa}, + {0x00001680, 0x0ef6d04c}, + {0x00001690, 0x10a0540b}, + {0x000016a8, 0xe301010a}, + {0x00001700, 0x69053460}, + {0x00001710, 0xa030101c}, + {0x00001738, 0xdf808444}, + {0x0000173c, 0xc8422204}, + {0x00001740, 0x0180088c}, + {0x00001774, 0x00000004}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x4_port3_config_regs[] = { + {0x00000000, 0x00000000} +}; + +static const struct phy_reg *x1_config_regs[4] = { + x1_port0_config_regs, + x1_port1_config_regs, + x1_port2_config_regs, + x1_port3_config_regs +}; + +static const struct phy_reg *x2_config_regs[4] = { + x2_port0_config_regs, + x2_port1_config_regs, + x2_port2_config_regs, + x2_port3_config_regs +}; + +static const struct phy_reg *x4_config_regs[4] = { + x4_port0_config_regs, + x4_port1_config_regs, + x4_port2_config_regs, + x4_port3_config_regs +}; + +static const struct phy_reg **config_regs[3] = { + x1_config_regs, + x2_config_regs, + x4_config_regs +}; + +static int ipu6_isys_mcd_phy_powerup_ack(struct ipu6_isys *isys, u8 id) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + u32 val; + int ret; + + val = readl(isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); + val |= CSI_REG_HUB_GPREG_PHY_CTL_PWR_EN; + writel(val, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); + + ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id), + val, val & CSI_REG_HUB_GPREG_PHY_POWER_ACK, + 200, MCD_PHY_POWER_STATUS_TIMEOUT); + if (ret) + dev_err(dev, "PHY%d powerup ack timeout", id); + + return ret; +} + +static int ipu6_isys_mcd_phy_powerdown_ack(struct ipu6_isys *isys, u8 id) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + u32 val; + int ret; + + writel(0, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); + ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id), + val, !(val & CSI_REG_HUB_GPREG_PHY_POWER_ACK), + 200, MCD_PHY_POWER_STATUS_TIMEOUT); + if (ret) + dev_err(dev, "PHY%d powerdown ack timeout", id); + + return ret; +} + +static void ipu6_isys_mcd_phy_reset(struct ipu6_isys *isys, u8 id, bool assert) +{ + void __iomem *isys_base = isys->pdata->base; + u32 val; + + val = readl(isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); + if (assert) + val |= CSI_REG_HUB_GPREG_PHY_CTL_RESET; + else + val &= ~(CSI_REG_HUB_GPREG_PHY_CTL_RESET); + + writel(val, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); +} + +static int ipu6_isys_mcd_phy_ready(struct ipu6_isys *isys, u8 id) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + u32 val; + int ret; + + ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id), + val, val & CSI_REG_HUB_GPREG_PHY_READY, + 200, MCD_PHY_POWER_STATUS_TIMEOUT); + if (ret) + dev_err(dev, "PHY%d ready ack timeout", id); + + return ret; +} + +static void ipu6_isys_mcd_phy_common_init(struct ipu6_isys *isys) +{ + struct ipu6_bus_device *adev = isys->adev; + struct ipu6_device *isp = adev->isp; + void __iomem *isp_base = isp->base; + struct sensor_async_sd *s_asd; + struct v4l2_async_connection *asc; + void __iomem *phy_base; + unsigned int i; + u8 phy_id; + + list_for_each_entry(asc, &isys->notifier.done_list, asc_entry) { + s_asd = container_of(asc, struct sensor_async_sd, asc); + phy_id = s_asd->csi2.port / 4; + phy_base = isp_base + IPU6_ISYS_MCD_PHY_BASE(phy_id); + + for (i = 0; i < ARRAY_SIZE(common_init_regs); i++) + writel(common_init_regs[i].val, + phy_base + common_init_regs[i].reg); + } +} + +static int ipu6_isys_driver_port_to_phy_port(struct ipu6_isys_csi2_config *cfg) +{ + int phy_port; + int ret; + + if (!(cfg->nlanes == 4 || cfg->nlanes == 2 || cfg->nlanes == 1)) + return -EINVAL; + + /* B,F -> C0 A,E -> C1 C,G -> C2 D,H -> C4 */ + /* normalize driver port number */ + phy_port = cfg->port % 4; + + /* swap port number only for A and B */ + if (phy_port == 0) + phy_port = 1; + else if (phy_port == 1) + phy_port = 0; + + ret = phy_port; + + /* check validity per lane configuration */ + if (cfg->nlanes == 4 && !(phy_port == 0 || phy_port == 2)) + ret = -EINVAL; + else if ((cfg->nlanes == 2 || cfg->nlanes == 1) && + !(phy_port >= 0 && phy_port <= 3)) + ret = -EINVAL; + + return ret; +} + +static int ipu6_isys_mcd_phy_config(struct ipu6_isys *isys) +{ + struct device *dev = &isys->adev->auxdev.dev; + struct ipu6_bus_device *adev = isys->adev; + const struct phy_reg **phy_config_regs; + struct ipu6_device *isp = adev->isp; + void __iomem *isp_base = isp->base; + struct sensor_async_sd *s_asd; + struct ipu6_isys_csi2_config cfg; + struct v4l2_async_connection *asc; + int phy_port, phy_id; + unsigned int i; + void __iomem *phy_base; + + list_for_each_entry(asc, &isys->notifier.done_list, asc_entry) { + s_asd = container_of(asc, struct sensor_async_sd, asc); + cfg.port = s_asd->csi2.port; + cfg.nlanes = s_asd->csi2.nlanes; + phy_port = ipu6_isys_driver_port_to_phy_port(&cfg); + if (phy_port < 0) { + dev_err(dev, "invalid port %d for lane %d", cfg.port, + cfg.nlanes); + return -ENXIO; + } + + phy_id = cfg.port / 4; + phy_base = isp_base + IPU6_ISYS_MCD_PHY_BASE(phy_id); + dev_dbg(dev, "port%d PHY%u lanes %u\n", cfg.port, phy_id, + cfg.nlanes); + + phy_config_regs = config_regs[cfg.nlanes / 2]; + cfg.port = phy_port; + for (i = 0; phy_config_regs[cfg.port][i].reg; i++) + writel(phy_config_regs[cfg.port][i].val, + phy_base + phy_config_regs[cfg.port][i].reg); + } + + return 0; +} + +#define CSI_MCD_PHY_NUM 2 +static refcount_t phy_power_ref_count[CSI_MCD_PHY_NUM]; + +int ipu6_isys_mcd_phy_set_power(struct ipu6_isys *isys, + struct ipu6_isys_csi2_config *cfg, + const struct ipu6_isys_csi2_timing *timing, + bool on) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + u8 port, phy_id; + refcount_t *ref; + int ret; + + port = cfg->port; + phy_id = port / 4; + + ref = &phy_power_ref_count[phy_id]; + + dev_dbg(dev, "for phy %d port %d, lanes: %d\n", phy_id, port, + cfg->nlanes); + + if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) { + dev_warn(dev, "invalid port ID %d\n", port); + return -EINVAL; + } + + if (on) { + if (refcount_read(ref)) { + dev_dbg(dev, "for phy %d is already UP", phy_id); + refcount_inc(ref); + return 0; + } + + ret = ipu6_isys_mcd_phy_powerup_ack(isys, phy_id); + if (ret) + return ret; + + ipu6_isys_mcd_phy_reset(isys, phy_id, 0); + ipu6_isys_mcd_phy_common_init(isys); + + ret = ipu6_isys_mcd_phy_config(isys); + if (ret) + return ret; + + ipu6_isys_mcd_phy_reset(isys, phy_id, 1); + ret = ipu6_isys_mcd_phy_ready(isys, phy_id); + if (ret) + return ret; + + refcount_set(ref, 1); + return 0; + } + + if (!refcount_dec_and_test(ref)) + return 0; + + return ipu6_isys_mcd_phy_powerdown_ack(isys, phy_id); +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c new file mode 100644 index 000000000..fabaed63d --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c @@ -0,0 +1,857 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ipu6-bus.h" +#include "ipu6-dma.h" +#include "ipu6-fw-isys.h" +#include "ipu6-isys.h" +#include "ipu6-isys-video.h" + +static int ipu6_isys_buf_init(struct vb2_buffer *vb) +{ + struct ipu6_isys *isys = vb2_get_drv_priv(vb->vb2_queue); + struct sg_table *sg = vb2_dma_sg_plane_desc(vb, 0); + struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); + struct ipu6_isys_video_buffer *ivb = + vb2_buffer_to_ipu6_isys_video_buffer(vvb); + int ret; + + ret = ipu6_dma_map_sgtable(isys->adev, sg, DMA_TO_DEVICE, 0); + if (ret) + return ret; + + ivb->dma_addr = sg_dma_address(sg->sgl); + + return 0; +} + +static void ipu6_isys_buf_cleanup(struct vb2_buffer *vb) +{ + struct ipu6_isys *isys = vb2_get_drv_priv(vb->vb2_queue); + struct sg_table *sg = vb2_dma_sg_plane_desc(vb, 0); + struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); + struct ipu6_isys_video_buffer *ivb = + vb2_buffer_to_ipu6_isys_video_buffer(vvb); + + ivb->dma_addr = 0; + ipu6_dma_unmap_sgtable(isys->adev, sg, DMA_TO_DEVICE, 0); +} + +static int ipu6_isys_queue_setup(struct vb2_queue *q, unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + u32 size = ipu6_isys_get_data_size(av); + + /* num_planes == 0: we're being called through VIDIOC_REQBUFS */ + if (!*num_planes) { + sizes[0] = size; + } else if (sizes[0] < size) { + dev_dbg(dev, "%s: queue setup: size %u < %u\n", + av->vdev.name, sizes[0], size); + return -EINVAL; + } + + *num_planes = 1; + + return 0; +} + +static int ipu6_isys_buf_prepare(struct vb2_buffer *vb) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + u32 bytesperline = ipu6_isys_get_bytes_per_line(av); + u32 height = ipu6_isys_get_frame_height(av); + u32 size = ipu6_isys_get_data_size(av); + + dev_dbg(dev, "buffer: %s: configured size %u, buffer size %lu\n", + av->vdev.name, size, vb2_plane_size(vb, 0)); + + if (size > vb2_plane_size(vb, 0)) + return -EINVAL; + + vb2_set_plane_payload(vb, 0, bytesperline * height); + + return 0; +} + +/* + * Queue a buffer list back to incoming or active queues. The buffers + * are removed from the buffer list. + */ +void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl, + unsigned long op_flags, + enum vb2_buffer_state state) +{ + struct ipu6_isys_buffer *ib, *ib_safe; + unsigned long flags; + bool first = true; + + if (!bl) + return; + + WARN_ON_ONCE(!bl->nbufs); + WARN_ON_ONCE(op_flags & IPU6_ISYS_BUFFER_LIST_FL_ACTIVE && + op_flags & IPU6_ISYS_BUFFER_LIST_FL_INCOMING); + + list_for_each_entry_safe(ib, ib_safe, &bl->head, head) { + struct ipu6_isys_video *av; + struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); + struct ipu6_isys_queue *aq = + vb2_queue_to_isys_queue(vb->vb2_queue); + struct device *dev; + + av = ipu6_isys_queue_to_video(aq); + dev = &av->isys->adev->auxdev.dev; + spin_lock_irqsave(&aq->lock, flags); + list_del(&ib->head); + if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_ACTIVE) + list_add(&ib->head, &aq->active); + else if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_INCOMING) + list_add_tail(&ib->head, &aq->incoming); + spin_unlock_irqrestore(&aq->lock, flags); + + if (first) { + dev_dbg(dev, + "queue buf list %p flags %lx, s %d, %d bufs\n", + bl, op_flags, state, bl->nbufs); + first = false; + } + + bl->nbufs--; + } + + WARN_ON(bl->nbufs); +} + +/* + * flush_firmware_streamon_fail() - Flush in cases where requests may + * have been queued to firmware and the *firmware streamon fails for a + * reason or another. + */ +static void flush_firmware_streamon_fail(struct ipu6_isys_stream *stream) +{ + struct device *dev = &stream->isys->adev->auxdev.dev; + struct ipu6_isys_queue *aq; + unsigned long flags; + + lockdep_assert_held(&stream->mutex); + + list_for_each_entry(aq, &stream->queues, node) { + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct ipu6_isys_buffer *ib, *ib_safe; + + spin_lock_irqsave(&aq->lock, flags); + list_for_each_entry_safe(ib, ib_safe, &aq->active, head) { + struct vb2_buffer *vb = + ipu6_isys_buffer_to_vb2_buffer(ib); + + list_del(&ib->head); + if (av->streaming) { + dev_dbg(dev, + "%s: queue buffer %u back to incoming\n", + av->vdev.name, vb->index); + /* Queue already streaming, return to driver. */ + list_add(&ib->head, &aq->incoming); + continue; + } + /* Queue not yet streaming, return to user. */ + dev_dbg(dev, "%s: return %u back to videobuf2\n", + av->vdev.name, vb->index); + vb2_buffer_done(ipu6_isys_buffer_to_vb2_buffer(ib), + VB2_BUF_STATE_QUEUED); + } + spin_unlock_irqrestore(&aq->lock, flags); + } +} + +/* + * Attempt obtaining a buffer list from the incoming queues, a list of buffers + * that contains one entry from each video buffer queue. If a buffer can't be + * obtained from every queue, the buffers are returned back to the queue. + */ +static int buffer_list_get(struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl) +{ + struct device *dev = &stream->isys->adev->auxdev.dev; + struct ipu6_isys_queue *aq; + unsigned long flags; + unsigned long buf_flag = IPU6_ISYS_BUFFER_LIST_FL_INCOMING; + + lockdep_assert_held(&stream->mutex); + + bl->nbufs = 0; + INIT_LIST_HEAD(&bl->head); + + list_for_each_entry(aq, &stream->queues, node) { + struct ipu6_isys_buffer *ib; + + spin_lock_irqsave(&aq->lock, flags); + if (list_empty(&aq->incoming)) { + spin_unlock_irqrestore(&aq->lock, flags); + if (!list_empty(&bl->head)) + ipu6_isys_buffer_list_queue(bl, buf_flag, 0); + return -ENODATA; + } + + ib = list_last_entry(&aq->incoming, + struct ipu6_isys_buffer, head); + + dev_dbg(dev, "buffer: %s: buffer %u\n", + ipu6_isys_queue_to_video(aq)->vdev.name, + ipu6_isys_buffer_to_vb2_buffer(ib)->index); + list_del(&ib->head); + list_add(&ib->head, &bl->head); + spin_unlock_irqrestore(&aq->lock, flags); + + bl->nbufs++; + } + + dev_dbg(dev, "get buffer list %p, %u buffers\n", bl, bl->nbufs); + + return 0; +} + +static void +ipu6_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb, + struct ipu6_fw_isys_frame_buff_set_abi *set) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); + struct ipu6_isys_video_buffer *ivb = + vb2_buffer_to_ipu6_isys_video_buffer(vvb); + + set->output_pins[aq->fw_output].addr = ivb->dma_addr; + set->output_pins[aq->fw_output].out_buf_id = vb->index + 1; +} + +/* + * Convert a buffer list to a isys fw ABI framebuffer set. The + * buffer list is not modified. + */ +#define IPU6_ISYS_FRAME_NUM_THRESHOLD (30) +void +ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set, + struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl) +{ + struct ipu6_isys_buffer *ib; + + WARN_ON(!bl->nbufs); + + set->send_irq_sof = 1; + set->send_resp_sof = 1; + set->send_irq_eof = 0; + set->send_resp_eof = 0; + + if (stream->streaming) + set->send_irq_capture_ack = 0; + else + set->send_irq_capture_ack = 1; + set->send_irq_capture_done = 0; + + set->send_resp_capture_ack = 1; + set->send_resp_capture_done = 1; + if (atomic_read(&stream->sequence) >= IPU6_ISYS_FRAME_NUM_THRESHOLD) { + set->send_resp_capture_ack = 0; + set->send_resp_capture_done = 0; + } + + list_for_each_entry(ib, &bl->head, head) { + struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); + + ipu6_isys_buf_to_fw_frame_buf_pin(vb, set); + } +} + +/* Start streaming for real. The buffer list must be available. */ +static int ipu6_isys_stream_start(struct ipu6_isys_video *av, + struct ipu6_isys_buffer_list *bl) +{ + struct ipu6_isys_stream *stream = av->stream; + struct device *dev = &stream->isys->adev->auxdev.dev; + struct ipu6_isys_buffer_list __bl; + int ret; + + guard(mutex)(&stream->isys->stream_mutex); + ret = ipu6_isys_video_set_streaming(av, 1, bl); + if (ret) + goto out_requeue; + + stream->streaming = 1; + + bl = &__bl; + + do { + struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; + struct isys_fw_msgs *msg; + u16 send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE; + + ret = buffer_list_get(stream, bl); + if (ret < 0) + break; + + msg = ipu6_get_fw_msg_buf(stream); + if (!msg) + return -ENOMEM; + + buf = &msg->fw_msg.frame; + ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl); + ipu6_fw_isys_dump_frame_buff_set(dev, buf, + stream->nr_output_pins); + ipu6_isys_buffer_list_queue(bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, + 0); + ret = ipu6_fw_isys_complex_cmd(stream->isys, + stream->stream_handle, buf, + msg->dma_addr, sizeof(*buf), + send_type); + } while (!WARN_ON(ret)); + + return 0; + +out_requeue: + if (bl && bl->nbufs) + ipu6_isys_buffer_list_queue(bl, + IPU6_ISYS_BUFFER_LIST_FL_INCOMING, + VB2_BUF_STATE_QUEUED); + flush_firmware_streamon_fail(stream); + + return ret; +} + +static void buf_queue(struct vb2_buffer *vb) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); + struct ipu6_isys_video_buffer *ivb = + vb2_buffer_to_ipu6_isys_video_buffer(vvb); + struct ipu6_isys_buffer *ib = &ivb->ib; + struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; + struct ipu6_isys_stream *stream = av->stream; + struct ipu6_isys_buffer_list bl; + struct isys_fw_msgs *msg; + unsigned long flags; + dma_addr_t dma; + int ret; + + dev_dbg(dev, "queue buffer %u for %s\n", vb->index, av->vdev.name); + + dma = ivb->dma_addr; + dev_dbg(dev, "iova: iova %pad\n", &dma); + + spin_lock_irqsave(&aq->lock, flags); + list_add(&ib->head, &aq->incoming); + spin_unlock_irqrestore(&aq->lock, flags); + + if (!vb2_start_streaming_called(vb->vb2_queue)) { + dev_dbg(dev, "start_streaming hasn't been called yet on %s\n", + av->vdev.name); + return; + } + + mutex_lock(&stream->mutex); + + if (stream->nr_streaming != stream->nr_queues) { + dev_dbg(dev, "not streaming yet, adding to incoming\n"); + goto out; + } + + /* + * We just put one buffer to the incoming list of this queue + * (above). Let's see whether all queues in the pipeline would + * have a buffer. + */ + ret = buffer_list_get(stream, &bl); + if (ret < 0) { + dev_dbg(dev, "No buffers available\n"); + goto out; + } + + msg = ipu6_get_fw_msg_buf(stream); + if (!msg) { + ret = -ENOMEM; + goto out; + } + + buf = &msg->fw_msg.frame; + ipu6_isys_buf_to_fw_frame_buf(buf, stream, &bl); + ipu6_fw_isys_dump_frame_buff_set(dev, buf, stream->nr_output_pins); + + /* + * We must queue the buffers in the buffer list to the + * appropriate video buffer queues BEFORE passing them to the + * firmware since we could get a buffer event back before we + * have queued them ourselves to the active queue. + */ + ipu6_isys_buffer_list_queue(&bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); + + ret = ipu6_fw_isys_complex_cmd(stream->isys, stream->stream_handle, + buf, msg->dma_addr, sizeof(*buf), + IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE); + if (ret < 0) + dev_err(dev, "send stream capture failed\n"); + +out: + mutex_unlock(&stream->mutex); +} + +static int ipu6_isys_link_fmt_validate(struct ipu6_isys_queue *aq) +{ + struct v4l2_mbus_framefmt format, *__format; + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + struct media_pad *remote_pad = + media_pad_remote_pad_first(av->vdev.entity.pads); + struct v4l2_subdev *sd; + u32 r_stream, code; + + if (!remote_pad) + return -ENOTCONN; + + sd = media_entity_to_v4l2_subdev(remote_pad->entity); + r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, remote_pad->index); + + struct v4l2_subdev_state *state = + v4l2_subdev_lock_and_get_active_state(sd); + + __format = v4l2_subdev_state_get_format(state, remote_pad->index, + r_stream); + if (__format) + format = *__format; + + v4l2_subdev_unlock_state(state); + + if (!__format) { + dev_dbg(dev, "failed to get %s: pad %d, stream:%d format\n", + sd->entity.name, remote_pad->index, r_stream); + return -EPIPE; + } + + if (format.width != ipu6_isys_get_frame_width(av) || + format.height != ipu6_isys_get_frame_height(av)) { + dev_dbg(dev, "wrong width or height %ux%u (%ux%u expected)\n", + ipu6_isys_get_frame_width(av), + ipu6_isys_get_frame_height(av), format.width, + format.height); + return -EINVAL; + } + + code = ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0)->code; + if (format.code != code) { + dev_dbg(dev, "wrong mbus code 0x%8.8x (0x%8.8x expected)\n", + code, format.code); + return -EINVAL; + } + + return 0; +} + +static void return_buffers(struct ipu6_isys_queue *aq, + enum vb2_buffer_state state) +{ + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct ipu6_isys_buffer *ib; + bool need_reset = false; + unsigned long flags; + + spin_lock_irqsave(&aq->lock, flags); + while (!list_empty(&aq->incoming)) { + struct vb2_buffer *vb; + + ib = list_first_entry(&aq->incoming, struct ipu6_isys_buffer, + head); + vb = ipu6_isys_buffer_to_vb2_buffer(ib); + list_del(&ib->head); + spin_unlock_irqrestore(&aq->lock, flags); + + vb2_buffer_done(vb, state); + + spin_lock_irqsave(&aq->lock, flags); + } + + /* + * Something went wrong (FW crash / HW hang / not all buffers + * returned from isys) if there are still buffers queued in active + * queue. We have to clean up places a bit. + */ + while (!list_empty(&aq->active)) { + struct vb2_buffer *vb; + + ib = list_first_entry(&aq->active, struct ipu6_isys_buffer, + head); + vb = ipu6_isys_buffer_to_vb2_buffer(ib); + + list_del(&ib->head); + spin_unlock_irqrestore(&aq->lock, flags); + + vb2_buffer_done(vb, state); + + spin_lock_irqsave(&aq->lock, flags); + need_reset = true; + } + + spin_unlock_irqrestore(&aq->lock, flags); + + if (need_reset) { + mutex_lock(&av->isys->mutex); + av->isys->need_reset = true; + mutex_unlock(&av->isys->mutex); + } +} + +static void ipu6_isys_stream_cleanup(struct ipu6_isys_video *av) +{ + video_device_pipeline_stop(&av->vdev); + ipu6_isys_put_stream(av->stream); + av->stream = NULL; +} + +static int start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + const struct ipu6_isys_pixelformat *pfmt = + ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0); + struct ipu6_isys_buffer_list __bl, *bl = NULL; + struct ipu6_isys_stream *stream; + struct media_pad *source_pad, *remote_pad; + int nr_queues, ret; + + dev_dbg(dev, "stream: %s: width %u, height %u, css pixelformat %u\n", + av->vdev.name, ipu6_isys_get_frame_width(av), + ipu6_isys_get_frame_height(av), pfmt->css_pixelformat); + + remote_pad = media_pad_remote_pad_unique(&av->pad); + if (IS_ERR(remote_pad)) { + dev_dbg(dev, "failed to get remote pad\n"); + ret = PTR_ERR(remote_pad); + goto out_return_buffers; + } + + source_pad = media_pad_remote_pad_unique(&remote_pad->entity->pads[0]); + if (IS_ERR(source_pad)) { + dev_dbg(dev, "No external source entity\n"); + ret = PTR_ERR(source_pad); + goto out_return_buffers; + } + + ret = ipu6_isys_setup_video(av, remote_pad, source_pad, &nr_queues); + if (ret < 0) { + dev_dbg(dev, "failed to setup video\n"); + goto out_return_buffers; + } + + ret = ipu6_isys_link_fmt_validate(aq); + if (ret) { + dev_dbg(dev, + "%s: link format validation failed (%d)\n", + av->vdev.name, ret); + goto out_pipeline_stop; + } + + ret = ipu6_isys_fw_open(av->isys); + if (ret) + goto out_pipeline_stop; + + stream = av->stream; + mutex_lock(&stream->mutex); + if (!stream->nr_streaming) { + ret = ipu6_isys_video_prepare_stream(av, source_pad->entity, + nr_queues); + if (ret) + goto out_fw_close; + } + + stream->nr_streaming++; + dev_dbg(dev, "queue %u of %u\n", stream->nr_streaming, + stream->nr_queues); + + list_add(&aq->node, &stream->queues); + ipu6_isys_configure_stream_watermark(av, source_pad->entity); + ipu6_isys_update_stream_watermark(av, true); + + if (stream->nr_streaming != stream->nr_queues) + goto out; + + bl = &__bl; + ret = buffer_list_get(stream, bl); + if (ret < 0) { + dev_warn(dev, "no buffer available, DRIVER BUG?\n"); + goto out; + } + + ret = ipu6_isys_stream_start(av, bl); + if (ret) + goto out_stream_start; + +out: + mutex_unlock(&stream->mutex); + + return 0; + +out_stream_start: + ipu6_isys_update_stream_watermark(av, false); + list_del(&aq->node); + stream->nr_streaming--; + +out_fw_close: + mutex_unlock(&stream->mutex); + ipu6_isys_fw_close(av->isys); + +out_pipeline_stop: + ipu6_isys_stream_cleanup(av); + +out_return_buffers: + return_buffers(aq, VB2_BUF_STATE_QUEUED); + + return ret; +} + +static void stop_streaming(struct vb2_queue *q) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct ipu6_isys_stream *stream = av->stream; + + mutex_lock(&stream->mutex); + + ipu6_isys_update_stream_watermark(av, false); + + mutex_lock(&av->isys->stream_mutex); + if (stream->nr_streaming == stream->nr_queues && stream->streaming) + ipu6_isys_video_set_streaming(av, 0, NULL); + list_del(&aq->node); + mutex_unlock(&av->isys->stream_mutex); + + stream->nr_streaming--; + stream->streaming = 0; + mutex_unlock(&stream->mutex); + + ipu6_isys_stream_cleanup(av); + + return_buffers(aq, VB2_BUF_STATE_ERROR); + + ipu6_isys_fw_close(av->isys); +} + +static unsigned int +get_sof_sequence_by_timestamp(struct ipu6_isys_stream *stream, u64 time) +{ + struct ipu6_isys *isys = stream->isys; + struct device *dev = &isys->adev->auxdev.dev; + unsigned int i; + + /* + * The timestamp is invalid as no TSC in some FPGA platform, + * so get the sequence from pipeline directly in this case. + */ + if (time == 0) + return atomic_read(&stream->sequence) - 1; + + for (i = 0; i < IPU6_ISYS_MAX_PARALLEL_SOF; i++) + if (time == stream->seq[i].timestamp) { + dev_dbg(dev, "sof: using seq nr %u for ts %llu\n", + stream->seq[i].sequence, time); + return stream->seq[i].sequence; + } + + for (i = 0; i < IPU6_ISYS_MAX_PARALLEL_SOF; i++) + dev_dbg(dev, "sof: sequence %u, timestamp value %llu\n", + stream->seq[i].sequence, stream->seq[i].timestamp); + + return 0; +} + +static u64 get_sof_ns_delta(struct ipu6_isys_video *av, u64 timestamp) +{ + struct ipu6_bus_device *adev = av->isys->adev; + struct ipu6_device *isp = adev->isp; + u64 delta, tsc_now; + + ipu6_buttress_tsc_read(isp, &tsc_now); + if (!tsc_now) + return 0; + + delta = tsc_now - timestamp; + + return ipu6_buttress_tsc_ticks_to_ns(delta, isp); +} + +static void +ipu6_isys_buf_calc_sequence_time(struct ipu6_isys_buffer *ib, u64 time) +{ + struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu6_isys_stream *stream = av->stream; + u64 ns; + u32 sequence; + + ns = ktime_get_ns() - get_sof_ns_delta(av, time); + sequence = get_sof_sequence_by_timestamp(stream, time); + + vbuf->vb2_buf.timestamp = ns; + vbuf->sequence = sequence; + + dev_dbg(dev, "buf: %s: buffer done, CPU-timestamp:%lld, sequence:%d\n", + av->vdev.name, ktime_get_ns(), sequence); + dev_dbg(dev, "index:%d, vbuf timestamp:%lld\n", vb->index, + vbuf->vb2_buf.timestamp); +} + +static void ipu6_isys_queue_buf_done(struct ipu6_isys_buffer *ib) +{ + struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); + + if (atomic_read(&ib->str2mmio_flag)) { + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + /* + * Operation on buffer is ended with error and will be reported + * to the userspace when it is de-queued + */ + atomic_set(&ib->str2mmio_flag, 0); + } else { + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + } +} + +static void +ipu6_stream_buf_ready(struct ipu6_isys_stream *stream, u8 pin_id, u32 pin_addr, + u64 time, bool error_check) +{ + struct ipu6_isys_queue *aq = stream->output_pins_queue[pin_id]; + struct ipu6_isys *isys = stream->isys; + struct device *dev = &isys->adev->auxdev.dev; + struct ipu6_isys_buffer *ib; + struct vb2_buffer *vb; + unsigned long flags; + bool first = true; + struct vb2_v4l2_buffer *buf; + + spin_lock_irqsave(&aq->lock, flags); + if (list_empty(&aq->active)) { + spin_unlock_irqrestore(&aq->lock, flags); + dev_err(dev, "active queue empty\n"); + return; + } + + list_for_each_entry_reverse(ib, &aq->active, head) { + struct ipu6_isys_video_buffer *ivb; + struct vb2_v4l2_buffer *vvb; + dma_addr_t addr; + + vb = ipu6_isys_buffer_to_vb2_buffer(ib); + vvb = to_vb2_v4l2_buffer(vb); + ivb = vb2_buffer_to_ipu6_isys_video_buffer(vvb); + addr = ivb->dma_addr; + + if (pin_addr != addr) { + if (first) + dev_err(dev, "Unexpected buffer address %pad\n", + &addr); + first = false; + continue; + } + + if (error_check) { + /* + * Check for error message: + * 'IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO' + */ + atomic_set(&ib->str2mmio_flag, 1); + } + dev_dbg(dev, "buffer: found buffer %pad\n", &addr); + + buf = to_vb2_v4l2_buffer(vb); + buf->field = V4L2_FIELD_NONE; + + list_del(&ib->head); + spin_unlock_irqrestore(&aq->lock, flags); + + ipu6_isys_buf_calc_sequence_time(ib, time); + + ipu6_isys_queue_buf_done(ib); + + return; + } + + dev_err(dev, "Failed to find a matching video buffer\n"); + + spin_unlock_irqrestore(&aq->lock, flags); +} + +void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream, + struct ipu6_fw_isys_resp_info_abi *info) +{ + u64 time = (u64)info->timestamp[1] << 32 | info->timestamp[0]; + bool err = info->error_info.error == IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO; + + ipu6_stream_buf_ready(stream, info->pin_id, info->pin.addr, time, err); +} + +static const struct vb2_ops ipu6_isys_queue_ops = { + .queue_setup = ipu6_isys_queue_setup, + .buf_init = ipu6_isys_buf_init, + .buf_prepare = ipu6_isys_buf_prepare, + .buf_cleanup = ipu6_isys_buf_cleanup, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, + .buf_queue = buf_queue, +}; + +int ipu6_isys_queue_init(struct ipu6_isys_queue *aq) +{ + struct ipu6_isys *isys = ipu6_isys_queue_to_video(aq)->isys; + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct ipu6_bus_device *adev = isys->adev; + int ret; + + /* no support for userptr */ + if (!aq->vbq.io_modes) + aq->vbq.io_modes = VB2_MMAP | VB2_DMABUF; + + aq->vbq.drv_priv = isys; + aq->vbq.ops = &ipu6_isys_queue_ops; + aq->vbq.lock = &av->mutex; + aq->vbq.mem_ops = &vb2_dma_sg_memops; + aq->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + aq->vbq.min_queued_buffers = 1; + aq->vbq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + + ret = vb2_queue_init(&aq->vbq); + if (ret) + return ret; + + aq->vbq.dev = &adev->isp->pdev->dev; + spin_lock_init(&aq->lock); + INIT_LIST_HEAD(&aq->active); + INIT_LIST_HEAD(&aq->incoming); + + return 0; +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h new file mode 100644 index 000000000..dec1fed44 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013--2024 Intel Corporation */ + +#ifndef IPU6_ISYS_QUEUE_H +#define IPU6_ISYS_QUEUE_H + +#include +#include +#include +#include +#include + +#include + +#include "ipu6-fw-isys.h" +#include "ipu6-isys-video.h" + +struct ipu6_isys_stream; + +struct ipu6_isys_queue { + struct vb2_queue vbq; + struct list_head node; + spinlock_t lock; /* Protects active and incoming lists */ + struct list_head active; + struct list_head incoming; + unsigned int fw_output; +}; + +struct ipu6_isys_buffer { + struct list_head head; + atomic_t str2mmio_flag; +}; + +struct ipu6_isys_video_buffer { + struct vb2_v4l2_buffer vb_v4l2; + struct ipu6_isys_buffer ib; + dma_addr_t dma_addr; +}; + +#define IPU6_ISYS_BUFFER_LIST_FL_INCOMING BIT(0) +#define IPU6_ISYS_BUFFER_LIST_FL_ACTIVE BIT(1) + +struct ipu6_isys_buffer_list { + struct list_head head; + unsigned int nbufs; +}; + +#define vb2_queue_to_isys_queue(__vb2) \ + container_of(__vb2, struct ipu6_isys_queue, vbq) + +#define ipu6_isys_to_isys_video_buffer(__ib) \ + container_of(__ib, struct ipu6_isys_video_buffer, ib) + +#define vb2_buffer_to_ipu6_isys_video_buffer(__vvb) \ + container_of(__vvb, struct ipu6_isys_video_buffer, vb_v4l2) + +#define ipu6_isys_buffer_to_vb2_buffer(__ib) \ + (&ipu6_isys_to_isys_video_buffer(__ib)->vb_v4l2.vb2_buf) + +void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl, + unsigned long op_flags, + enum vb2_buffer_state state); +void +ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set, + struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl); +void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream, + struct ipu6_fw_isys_resp_info_abi *info); +int ipu6_isys_queue_init(struct ipu6_isys_queue *aq); +#endif /* IPU6_ISYS_QUEUE_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c new file mode 100644 index 000000000..dbd6f76a0 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "ipu6-bus.h" +#include "ipu6-isys.h" +#include "ipu6-isys-subdev.h" + +unsigned int ipu6_isys_mbus_code_to_bpp(u32 code) +{ + switch (code) { + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_META_24: + return 24; + case MEDIA_BUS_FMT_RGB565_1X16: + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_YUYV8_1X16: + case MEDIA_BUS_FMT_Y16_1X16: + case MEDIA_BUS_FMT_META_16: + return 16; + case MEDIA_BUS_FMT_SBGGR12_1X12: + case MEDIA_BUS_FMT_SGBRG12_1X12: + case MEDIA_BUS_FMT_SGRBG12_1X12: + case MEDIA_BUS_FMT_SRGGB12_1X12: + case MEDIA_BUS_FMT_Y12_1X12: + case MEDIA_BUS_FMT_META_12: + return 12; + case MEDIA_BUS_FMT_SBGGR10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_Y10_1X10: + case MEDIA_BUS_FMT_META_10: + return 10; + case MEDIA_BUS_FMT_SBGGR8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: + case MEDIA_BUS_FMT_Y8_1X8: + case MEDIA_BUS_FMT_META_8: + return 8; + default: + WARN_ON(1); + return 8; + } +} + +unsigned int ipu6_isys_mbus_code_to_mipi(u32 code) +{ + switch (code) { + case MEDIA_BUS_FMT_RGB565_1X16: + return MIPI_CSI2_DT_RGB565; + case MEDIA_BUS_FMT_RGB888_1X24: + return MIPI_CSI2_DT_RGB888; + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_YUYV8_1X16: + return MIPI_CSI2_DT_YUV422_8B; + case MEDIA_BUS_FMT_SBGGR16_1X16: + case MEDIA_BUS_FMT_SGBRG16_1X16: + case MEDIA_BUS_FMT_SGRBG16_1X16: + case MEDIA_BUS_FMT_SRGGB16_1X16: + case MEDIA_BUS_FMT_Y16_1X16: + return MIPI_CSI2_DT_RAW16; + case MEDIA_BUS_FMT_SBGGR12_1X12: + case MEDIA_BUS_FMT_SGBRG12_1X12: + case MEDIA_BUS_FMT_SGRBG12_1X12: + case MEDIA_BUS_FMT_SRGGB12_1X12: + case MEDIA_BUS_FMT_Y12_1X12: + return MIPI_CSI2_DT_RAW12; + case MEDIA_BUS_FMT_SBGGR10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_Y10_1X10: + return MIPI_CSI2_DT_RAW10; + case MEDIA_BUS_FMT_SBGGR8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: + case MEDIA_BUS_FMT_Y8_1X8: + return MIPI_CSI2_DT_RAW8; + case MEDIA_BUS_FMT_META_8: + case MEDIA_BUS_FMT_META_10: + case MEDIA_BUS_FMT_META_12: + case MEDIA_BUS_FMT_META_16: + case MEDIA_BUS_FMT_META_24: + return MIPI_CSI2_DT_EMBEDDED_8B; + default: + /* return unavailable MIPI data type - 0x3f */ + WARN_ON(1); + return 0x3f; + } +} + +bool ipu6_isys_is_bayer_format(u32 code) +{ + switch (code) { + case MEDIA_BUS_FMT_SBGGR8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: + case MEDIA_BUS_FMT_SBGGR10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_SBGGR12_1X12: + case MEDIA_BUS_FMT_SGBRG12_1X12: + case MEDIA_BUS_FMT_SGRBG12_1X12: + case MEDIA_BUS_FMT_SRGGB12_1X12: + case MEDIA_BUS_FMT_SRGGB16_1X16: + case MEDIA_BUS_FMT_SGRBG16_1X16: + case MEDIA_BUS_FMT_SGBRG16_1X16: + case MEDIA_BUS_FMT_SBGGR16_1X16: + return true; + default: + return false; + } +} + +u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y) +{ + static const u32 code_map[] = { + MEDIA_BUS_FMT_SRGGB8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SBGGR8_1X8, + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SRGGB12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SBGGR12_1X12, + MEDIA_BUS_FMT_SRGGB16_1X16, + MEDIA_BUS_FMT_SGRBG16_1X16, + MEDIA_BUS_FMT_SGBRG16_1X16, + MEDIA_BUS_FMT_SBGGR16_1X16, + }; + u32 i; + + for (i = 0; i < ARRAY_SIZE(code_map); i++) + if (code_map[i] == code) + break; + + if (WARN_ON(i == ARRAY_SIZE(code_map))) + return code; + + return code_map[i ^ (((y & 1) << 1) | (x & 1))]; +} + +int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); + struct v4l2_mbus_framefmt *fmt; + struct v4l2_rect *crop; + u32 code = asd->supported_codes[0]; + u32 other_pad, other_stream; + unsigned int i; + int ret; + + /* No transcoding, source and sink formats must match. */ + if ((sd->entity.pads[format->pad].flags & MEDIA_PAD_FL_SOURCE) && + sd->entity.num_pads > 1) + return v4l2_subdev_get_fmt(sd, state, format); + + format->format.width = clamp(format->format.width, IPU6_ISYS_MIN_WIDTH, + IPU6_ISYS_MAX_WIDTH); + format->format.height = clamp(format->format.height, + IPU6_ISYS_MIN_HEIGHT, + IPU6_ISYS_MAX_HEIGHT); + + for (i = 0; asd->supported_codes[i]; i++) { + if (asd->supported_codes[i] == format->format.code) { + code = asd->supported_codes[i]; + break; + } + } + format->format.code = code; + format->format.field = V4L2_FIELD_NONE; + + /* Store the format and propagate it to the source pad. */ + fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + if (!(sd->entity.pads[format->pad].flags & MEDIA_PAD_FL_SINK)) + return 0; + + /* propagate format to following source pad */ + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, + format->pad, + format->stream, + &other_pad, + &other_stream); + if (ret) + return -EINVAL; + + crop = v4l2_subdev_state_get_crop(state, other_pad, other_stream); + /* reset crop */ + crop->left = 0; + crop->top = 0; + crop->width = fmt->width; + crop->height = fmt->height; + + return 0; +} + +int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); + const u32 *supported_codes = asd->supported_codes; + u32 index; + + for (index = 0; supported_codes[index]; index++) { + if (index == code->index) { + code->code = supported_codes[index]; + return 0; + } + } + + return -EINVAL; +} + +static int subdev_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + static const struct v4l2_mbus_framefmt format = { + .width = 4096, + .height = 3072, + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .field = V4L2_FIELD_NONE, + }; + int ret; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret) + return ret; + + return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); +} + +u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad) +{ + struct v4l2_subdev_state *state; + struct v4l2_subdev_route *routes; + unsigned int i; + u32 source_stream = 0; + + state = v4l2_subdev_lock_and_get_active_state(sd); + if (!state) + return 0; + + routes = state->routing.routes; + for (i = 0; i < state->routing.num_routes; i++) { + if (routes[i].source_pad == pad) { + source_stream = routes[i].source_stream; + break; + } + } + + v4l2_subdev_unlock_state(state); + + return source_stream; +} + +static int ipu6_isys_subdev_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route route = { + .sink_pad = 0, + .sink_stream = 0, + .source_pad = 1, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }; + struct v4l2_subdev_krouting routing = { + .num_routes = 1, + .routes = &route, + }; + + return subdev_set_routing(sd, state, &routing); +} + +int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + return subdev_set_routing(sd, state, routing); +} + +static const struct v4l2_subdev_internal_ops ipu6_isys_subdev_internal_ops = { + .init_state = ipu6_isys_subdev_init_state, +}; + +int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd, + const struct v4l2_subdev_ops *ops, + unsigned int nr_ctrls, + unsigned int num_sink_pads, + unsigned int num_source_pads) +{ + unsigned int num_pads = num_sink_pads + num_source_pads; + unsigned int i; + int ret; + + v4l2_subdev_init(&asd->sd, ops); + + asd->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS | + V4L2_SUBDEV_FL_STREAMS; + asd->sd.owner = THIS_MODULE; + asd->sd.dev = &asd->isys->adev->auxdev.dev; + asd->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + asd->sd.internal_ops = &ipu6_isys_subdev_internal_ops; + + asd->pad = devm_kcalloc(&asd->isys->adev->auxdev.dev, num_pads, + sizeof(*asd->pad), GFP_KERNEL); + if (!asd->pad) + return -ENOMEM; + + for (i = 0; i < num_sink_pads; i++) + asd->pad[i].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + + for (i = num_sink_pads; i < num_pads; i++) + asd->pad[i].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&asd->sd.entity, num_pads, asd->pad); + if (ret) + return ret; + + if (asd->ctrl_init) { + ret = v4l2_ctrl_handler_init(&asd->ctrl_handler, nr_ctrls); + if (ret) + goto out_media_entity_cleanup; + + asd->ctrl_init(&asd->sd); + if (asd->ctrl_handler.error) { + ret = asd->ctrl_handler.error; + goto out_v4l2_ctrl_handler_free; + } + + asd->sd.ctrl_handler = &asd->ctrl_handler; + } + + asd->source = -1; + + return 0; + +out_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(&asd->ctrl_handler); + +out_media_entity_cleanup: + media_entity_cleanup(&asd->sd.entity); + + return ret; +} + +void ipu6_isys_subdev_cleanup(struct ipu6_isys_subdev *asd) +{ + media_entity_cleanup(&asd->sd.entity); + v4l2_ctrl_handler_free(&asd->ctrl_handler); +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h new file mode 100644 index 000000000..35069099c --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013--2024 Intel Corporation */ + +#ifndef IPU6_ISYS_SUBDEV_H +#define IPU6_ISYS_SUBDEV_H + +#include + +#include +#include +#include + +struct ipu6_isys; + +struct ipu6_isys_subdev { + struct v4l2_subdev sd; + struct ipu6_isys *isys; + u32 const *supported_codes; + struct media_pad *pad; + struct v4l2_ctrl_handler ctrl_handler; + void (*ctrl_init)(struct v4l2_subdev *sd); + int source; /* SSI stream source; -1 if unset */ +}; + +#define to_ipu6_isys_subdev(__sd) \ + container_of(__sd, struct ipu6_isys_subdev, sd) + +unsigned int ipu6_isys_mbus_code_to_bpp(u32 code); +unsigned int ipu6_isys_mbus_code_to_mipi(u32 code); +bool ipu6_isys_is_bayer_format(u32 code); +u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y); + +int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt); +int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum + *code); +u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad); +int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing); +int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd, + const struct v4l2_subdev_ops *ops, + unsigned int nr_ctrls, + unsigned int num_sink_pads, + unsigned int num_source_pads); +void ipu6_isys_subdev_cleanup(struct ipu6_isys_subdev *asd); +#endif /* IPU6_ISYS_SUBDEV_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c new file mode 100644 index 000000000..3ac48d207 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c @@ -0,0 +1,1368 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ipu6.h" +#include "ipu6-bus.h" +#include "ipu6-cpd.h" +#include "ipu6-fw-isys.h" +#include "ipu6-isys.h" +#include "ipu6-isys-csi2.h" +#include "ipu6-isys-queue.h" +#include "ipu6-isys-video.h" +#include "ipu6-platform-regs.h" + +const struct ipu6_isys_pixelformat ipu6_isys_pfmts[] = { + { V4L2_PIX_FMT_SBGGR12, 16, 12, MEDIA_BUS_FMT_SBGGR12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16 }, + { V4L2_PIX_FMT_SGBRG12, 16, 12, MEDIA_BUS_FMT_SGBRG12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16 }, + { V4L2_PIX_FMT_SGRBG12, 16, 12, MEDIA_BUS_FMT_SGRBG12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16 }, + { V4L2_PIX_FMT_SRGGB12, 16, 12, MEDIA_BUS_FMT_SRGGB12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16 }, + { V4L2_PIX_FMT_SBGGR10, 16, 10, MEDIA_BUS_FMT_SBGGR10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16 }, + { V4L2_PIX_FMT_SGBRG10, 16, 10, MEDIA_BUS_FMT_SGBRG10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16 }, + { V4L2_PIX_FMT_SGRBG10, 16, 10, MEDIA_BUS_FMT_SGRBG10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16 }, + { V4L2_PIX_FMT_SRGGB10, 16, 10, MEDIA_BUS_FMT_SRGGB10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16 }, + { V4L2_PIX_FMT_SBGGR8, 8, 8, MEDIA_BUS_FMT_SBGGR8_1X8, + IPU6_FW_ISYS_FRAME_FORMAT_RAW8 }, + { V4L2_PIX_FMT_SGBRG8, 8, 8, MEDIA_BUS_FMT_SGBRG8_1X8, + IPU6_FW_ISYS_FRAME_FORMAT_RAW8 }, + { V4L2_PIX_FMT_SGRBG8, 8, 8, MEDIA_BUS_FMT_SGRBG8_1X8, + IPU6_FW_ISYS_FRAME_FORMAT_RAW8 }, + { V4L2_PIX_FMT_SRGGB8, 8, 8, MEDIA_BUS_FMT_SRGGB8_1X8, + IPU6_FW_ISYS_FRAME_FORMAT_RAW8 }, + { V4L2_PIX_FMT_SBGGR12P, 12, 12, MEDIA_BUS_FMT_SBGGR12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW12 }, + { V4L2_PIX_FMT_SGBRG12P, 12, 12, MEDIA_BUS_FMT_SGBRG12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW12 }, + { V4L2_PIX_FMT_SGRBG12P, 12, 12, MEDIA_BUS_FMT_SGRBG12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW12 }, + { V4L2_PIX_FMT_SRGGB12P, 12, 12, MEDIA_BUS_FMT_SRGGB12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW12 }, + { V4L2_PIX_FMT_SBGGR10P, 10, 10, MEDIA_BUS_FMT_SBGGR10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW10 }, + { V4L2_PIX_FMT_SGBRG10P, 10, 10, MEDIA_BUS_FMT_SGBRG10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW10 }, + { V4L2_PIX_FMT_SGRBG10P, 10, 10, MEDIA_BUS_FMT_SGRBG10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW10 }, + { V4L2_PIX_FMT_SRGGB10P, 10, 10, MEDIA_BUS_FMT_SRGGB10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW10 }, + + { V4L2_PIX_FMT_GREY, 8, 8, MEDIA_BUS_FMT_Y8_1X8, + IPU6_FW_ISYS_FRAME_FORMAT_RAW8 }, + { V4L2_PIX_FMT_Y10, 16, 10, MEDIA_BUS_FMT_Y10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16 }, + { V4L2_PIX_FMT_Y12, 16, 12, MEDIA_BUS_FMT_Y12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16 }, + { V4L2_PIX_FMT_Y16, 16, 16, MEDIA_BUS_FMT_Y16_1X16, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16 }, + { V4L2_PIX_FMT_Y10P, 10, 10, MEDIA_BUS_FMT_Y10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW10 }, + { V4L2_PIX_FMT_Y12P, 12, 12, MEDIA_BUS_FMT_Y12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW12 }, + + { V4L2_PIX_FMT_UYVY, 16, 16, MEDIA_BUS_FMT_UYVY8_1X16, + IPU6_FW_ISYS_FRAME_FORMAT_UYVY}, + { V4L2_PIX_FMT_YUYV, 16, 16, MEDIA_BUS_FMT_YUYV8_1X16, + IPU6_FW_ISYS_FRAME_FORMAT_YUYV}, + { V4L2_PIX_FMT_RGB565, 16, 16, MEDIA_BUS_FMT_RGB565_1X16, + IPU6_FW_ISYS_FRAME_FORMAT_RGB565 }, + { V4L2_PIX_FMT_BGR24, 24, 24, MEDIA_BUS_FMT_RGB888_1X24, + IPU6_FW_ISYS_FRAME_FORMAT_RGBA888 }, + { V4L2_META_FMT_GENERIC_8, 8, 8, MEDIA_BUS_FMT_META_8, + IPU6_FW_ISYS_FRAME_FORMAT_RAW8, true }, + { V4L2_META_FMT_GENERIC_CSI2_10, 10, 10, MEDIA_BUS_FMT_META_10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW10, true }, + { V4L2_META_FMT_GENERIC_CSI2_12, 12, 12, MEDIA_BUS_FMT_META_12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW12, true }, + { V4L2_META_FMT_GENERIC_CSI2_16, 16, 16, MEDIA_BUS_FMT_META_16, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16, true }, +}; + +static int video_open(struct file *file) +{ + struct ipu6_isys_video *av = video_drvdata(file); + struct ipu6_isys *isys = av->isys; + struct ipu6_bus_device *adev = isys->adev; + + mutex_lock(&isys->mutex); + if (isys->need_reset) { + mutex_unlock(&isys->mutex); + dev_warn(&adev->auxdev.dev, "isys power cycle required\n"); + return -EIO; + } + mutex_unlock(&isys->mutex); + + return v4l2_fh_open(file); +} + +const struct ipu6_isys_pixelformat * +ipu6_isys_get_isys_format(u32 pixelformat, u32 type) +{ + const struct ipu6_isys_pixelformat *default_pfmt = NULL; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) { + const struct ipu6_isys_pixelformat *pfmt = &ipu6_isys_pfmts[i]; + + if (type && ((!pfmt->is_meta && + type != V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (pfmt->is_meta && + type != V4L2_BUF_TYPE_META_CAPTURE))) + continue; + + if (!default_pfmt) + default_pfmt = pfmt; + + if (pfmt->pixelformat != pixelformat) + continue; + + return pfmt; + } + + return default_pfmt; +} + +static int ipu6_isys_vidioc_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct ipu6_isys_video *av = video_drvdata(file); + + strscpy(cap->driver, IPU6_ISYS_NAME, sizeof(cap->driver)); + strscpy(cap->card, av->isys->media_dev.model, sizeof(cap->card)); + + return 0; +} + +static int ipu6_isys_vidioc_enum_fmt(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + unsigned int i, num_found; + + for (i = 0, num_found = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) { + if ((ipu6_isys_pfmts[i].is_meta && + f->type != V4L2_BUF_TYPE_META_CAPTURE) || + (!ipu6_isys_pfmts[i].is_meta && + f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)) + continue; + + if (f->mbus_code && f->mbus_code != ipu6_isys_pfmts[i].code) + continue; + + if (num_found < f->index) { + num_found++; + continue; + } + + f->flags = 0; + f->pixelformat = ipu6_isys_pfmts[i].pixelformat; + + return 0; + } + + return -EINVAL; +} + +static int ipu6_isys_vidioc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + unsigned int i; + + if (fsize->index > 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) { + if (fsize->pixel_format != ipu6_isys_pfmts[i].pixelformat) + continue; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width = IPU6_ISYS_MIN_WIDTH; + fsize->stepwise.max_width = IPU6_ISYS_MAX_WIDTH; + fsize->stepwise.min_height = IPU6_ISYS_MIN_HEIGHT; + fsize->stepwise.max_height = IPU6_ISYS_MAX_HEIGHT; + fsize->stepwise.step_width = 2; + fsize->stepwise.step_height = 2; + + return 0; + } + + return -EINVAL; +} + +static int ipu6_isys_vidioc_g_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct ipu6_isys_video *av = video_drvdata(file); + + f->fmt.pix = av->pix_fmt; + + return 0; +} + +static int ipu6_isys_vidioc_g_fmt_meta_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct ipu6_isys_video *av = video_drvdata(file); + + f->fmt.meta = av->meta_fmt; + + return 0; +} + +static void ipu6_isys_try_fmt_cap(struct ipu6_isys_video *av, u32 type, + u32 *format, u32 *width, u32 *height, + u32 *bytesperline, u32 *sizeimage) +{ + const struct ipu6_isys_pixelformat *pfmt = + ipu6_isys_get_isys_format(*format, type); + + *format = pfmt->pixelformat; + *width = clamp(*width, IPU6_ISYS_MIN_WIDTH, IPU6_ISYS_MAX_WIDTH); + *height = clamp(*height, IPU6_ISYS_MIN_HEIGHT, IPU6_ISYS_MAX_HEIGHT); + + if (pfmt->bpp != pfmt->bpp_packed) + *bytesperline = *width * DIV_ROUND_UP(pfmt->bpp, BITS_PER_BYTE); + else + *bytesperline = DIV_ROUND_UP(*width * pfmt->bpp, BITS_PER_BYTE); + + *bytesperline = ALIGN(*bytesperline, 64); + + /* + * (height + 1) * bytesperline due to a hardware issue: the DMA unit + * is a power of two, and a line should be transferred as few units + * as possible. The result is that up to line length more data than + * the image size may be transferred to memory after the image. + * Another limitation is the GDA allocation unit size. For low + * resolution it gives a bigger number. Use larger one to avoid + * memory corruption. + */ + *sizeimage = *bytesperline * *height + + max(*bytesperline, + av->isys->pdata->ipdata->isys_dma_overshoot); +} + +static void __ipu6_isys_vidioc_try_fmt_vid_cap(struct ipu6_isys_video *av, + struct v4l2_format *f) +{ + ipu6_isys_try_fmt_cap(av, f->type, &f->fmt.pix.pixelformat, + &f->fmt.pix.width, &f->fmt.pix.height, + &f->fmt.pix.bytesperline, &f->fmt.pix.sizeimage); + + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW; + f->fmt.pix.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + f->fmt.pix.quantization = V4L2_QUANTIZATION_DEFAULT; + f->fmt.pix.xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int ipu6_isys_vidioc_try_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct ipu6_isys_video *av = video_drvdata(file); + + if (vb2_is_busy(&av->aq.vbq)) + return -EBUSY; + + __ipu6_isys_vidioc_try_fmt_vid_cap(av, f); + + return 0; +} + +static int __ipu6_isys_vidioc_try_fmt_meta_cap(struct ipu6_isys_video *av, + struct v4l2_format *f) +{ + ipu6_isys_try_fmt_cap(av, f->type, &f->fmt.meta.dataformat, + &f->fmt.meta.width, &f->fmt.meta.height, + &f->fmt.meta.bytesperline, + &f->fmt.meta.buffersize); + + return 0; +} + +static int ipu6_isys_vidioc_try_fmt_meta_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct ipu6_isys_video *av = video_drvdata(file); + + __ipu6_isys_vidioc_try_fmt_meta_cap(av, f); + + return 0; +} + +static int ipu6_isys_vidioc_s_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct ipu6_isys_video *av = video_drvdata(file); + + ipu6_isys_vidioc_try_fmt_vid_cap(file, fh, f); + av->pix_fmt = f->fmt.pix; + + return 0; +} + +static int ipu6_isys_vidioc_s_fmt_meta_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct ipu6_isys_video *av = video_drvdata(file); + + if (vb2_is_busy(&av->aq.vbq)) + return -EBUSY; + + ipu6_isys_vidioc_try_fmt_meta_cap(file, fh, f); + av->meta_fmt = f->fmt.meta; + + return 0; +} + +static int ipu6_isys_vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + struct ipu6_isys_video *av = video_drvdata(file); + int ret; + + av->aq.vbq.is_multiplanar = V4L2_TYPE_IS_MULTIPLANAR(p->type); + av->aq.vbq.is_output = V4L2_TYPE_IS_OUTPUT(p->type); + + ret = vb2_queue_change_type(&av->aq.vbq, p->type); + if (ret) + return ret; + + return vb2_ioctl_reqbufs(file, priv, p); +} + +static int ipu6_isys_vidioc_create_bufs(struct file *file, void *priv, + struct v4l2_create_buffers *p) +{ + struct ipu6_isys_video *av = video_drvdata(file); + int ret; + + av->aq.vbq.is_multiplanar = V4L2_TYPE_IS_MULTIPLANAR(p->format.type); + av->aq.vbq.is_output = V4L2_TYPE_IS_OUTPUT(p->format.type); + + ret = vb2_queue_change_type(&av->aq.vbq, p->format.type); + if (ret) + return ret; + + return vb2_ioctl_create_bufs(file, priv, p); +} + +static int link_validate(struct media_link *link) +{ + struct ipu6_isys_video *av = + container_of(link->sink, struct ipu6_isys_video, pad); + struct device *dev = &av->isys->adev->auxdev.dev; + struct v4l2_subdev_state *s_state; + struct v4l2_subdev *s_sd; + struct v4l2_mbus_framefmt *s_fmt; + struct media_pad *s_pad; + u32 s_stream, code; + int ret = -EPIPE; + + if (!link->source->entity) + return ret; + + s_sd = media_entity_to_v4l2_subdev(link->source->entity); + s_state = v4l2_subdev_get_unlocked_active_state(s_sd); + if (!s_state) + return ret; + + dev_dbg(dev, "validating link \"%s\":%u -> \"%s\"\n", + link->source->entity->name, link->source->index, + link->sink->entity->name); + + s_pad = media_pad_remote_pad_first(&av->pad); + s_stream = ipu6_isys_get_src_stream_by_src_pad(s_sd, s_pad->index); + + v4l2_subdev_lock_state(s_state); + + s_fmt = v4l2_subdev_state_get_format(s_state, s_pad->index, s_stream); + if (!s_fmt) { + dev_err(dev, "failed to get source pad format\n"); + goto unlock; + } + + code = ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0)->code; + + if (s_fmt->width != ipu6_isys_get_frame_width(av) || + s_fmt->height != ipu6_isys_get_frame_height(av) || + s_fmt->code != code) { + dev_dbg(dev, "format mismatch %dx%d,%x != %dx%d,%x\n", + s_fmt->width, s_fmt->height, s_fmt->code, + ipu6_isys_get_frame_width(av), + ipu6_isys_get_frame_height(av), code); + goto unlock; + } + + v4l2_subdev_unlock_state(s_state); + + return 0; +unlock: + v4l2_subdev_unlock_state(s_state); + + return ret; +} + +static void get_stream_opened(struct ipu6_isys_video *av) +{ + unsigned long flags; + + spin_lock_irqsave(&av->isys->streams_lock, flags); + av->isys->stream_opened++; + spin_unlock_irqrestore(&av->isys->streams_lock, flags); +} + +static void put_stream_opened(struct ipu6_isys_video *av) +{ + unsigned long flags; + + spin_lock_irqsave(&av->isys->streams_lock, flags); + av->isys->stream_opened--; + spin_unlock_irqrestore(&av->isys->streams_lock, flags); +} + +static int ipu6_isys_fw_pin_cfg(struct ipu6_isys_video *av, + struct ipu6_fw_isys_stream_cfg_data_abi *cfg) +{ + struct media_pad *src_pad = media_pad_remote_pad_first(&av->pad); + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(src_pad->entity); + struct v4l2_subdev_state *state = v4l2_subdev_get_locked_active_state(sd); + struct ipu6_fw_isys_input_pin_info_abi *input_pin; + struct ipu6_fw_isys_output_pin_info_abi *output_pin; + struct ipu6_isys_stream *stream = av->stream; + struct ipu6_isys_queue *aq = &av->aq; + struct v4l2_mbus_framefmt fmt; + const struct ipu6_isys_pixelformat *pfmt = + ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0); + struct v4l2_rect v4l2_crop; + struct ipu6_isys *isys = av->isys; + int input_pins = cfg->nof_input_pins++; + int output_pins; + u32 src_stream; + + src_stream = ipu6_isys_get_src_stream_by_src_pad(sd, src_pad->index); + fmt = *v4l2_subdev_state_get_format(state, src_pad->index, src_stream); + v4l2_crop = *v4l2_subdev_state_get_crop(state, src_pad->index, src_stream); + + input_pin = &cfg->input_pins[input_pins]; + input_pin->input_res.width = fmt.width; + input_pin->input_res.height = fmt.height; + input_pin->dt = av->dt; + input_pin->bits_per_pix = pfmt->bpp_packed; + input_pin->mapped_dt = 0x40; /* invalid mipi data type */ + input_pin->mipi_decompression = 0; + input_pin->capture_mode = IPU6_FW_ISYS_CAPTURE_MODE_REGULAR; + input_pin->mipi_store_mode = pfmt->bpp == pfmt->bpp_packed ? + IPU6_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER : + IPU6_FW_ISYS_MIPI_STORE_MODE_NORMAL; + input_pin->crop_first_and_last_lines = v4l2_crop.top & 1; + + output_pins = cfg->nof_output_pins++; + aq->fw_output = output_pins; + stream->output_pins_queue[output_pins] = aq; + + output_pin = &cfg->output_pins[output_pins]; + output_pin->input_pin_id = input_pins; + output_pin->output_res.width = ipu6_isys_get_frame_width(av); + output_pin->output_res.height = ipu6_isys_get_frame_height(av); + + output_pin->stride = ipu6_isys_get_bytes_per_line(av); + if (pfmt->bpp != pfmt->bpp_packed) + output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_RAW_SOC; + else + output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_MIPI; + output_pin->ft = pfmt->css_pixelformat; + output_pin->send_irq = 1; + memset(output_pin->ts_offsets, 0, sizeof(output_pin->ts_offsets)); + output_pin->s2m_pixel_soc_pixel_remapping = + S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; + output_pin->csi_be_soc_pixel_remapping = + CSI_BE_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; + + output_pin->snoopable = true; + output_pin->error_handling_enable = false; + output_pin->sensor_type = isys->sensor_type++; + if (isys->sensor_type > isys->pdata->ipdata->sensor_type_end) + isys->sensor_type = isys->pdata->ipdata->sensor_type_start; + + return 0; +} + +static int start_stream_firmware(struct ipu6_isys_video *av, + struct ipu6_isys_buffer_list *bl) +{ + struct ipu6_fw_isys_stream_cfg_data_abi *stream_cfg; + struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; + struct ipu6_isys_stream *stream = av->stream; + struct device *dev = &av->isys->adev->auxdev.dev; + struct isys_fw_msgs *msg = NULL; + struct ipu6_isys_queue *aq; + int ret, retout, tout; + u16 send_type; + + msg = ipu6_get_fw_msg_buf(stream); + if (!msg) + return -ENOMEM; + + stream_cfg = &msg->fw_msg.stream; + stream_cfg->src = stream->stream_source; + stream_cfg->vc = stream->vc; + stream_cfg->isl_use = 0; + stream_cfg->sensor_type = IPU6_FW_ISYS_SENSOR_MODE_NORMAL; + + list_for_each_entry(aq, &stream->queues, node) { + struct ipu6_isys_video *__av = ipu6_isys_queue_to_video(aq); + + ret = ipu6_isys_fw_pin_cfg(__av, stream_cfg); + if (ret < 0) { + ipu6_put_fw_msg_buf(av->isys, (uintptr_t)stream_cfg); + return ret; + } + } + + ipu6_fw_isys_dump_stream_cfg(dev, stream_cfg); + + stream->nr_output_pins = stream_cfg->nof_output_pins; + + reinit_completion(&stream->stream_open_completion); + + ret = ipu6_fw_isys_complex_cmd(av->isys, stream->stream_handle, + stream_cfg, msg->dma_addr, + sizeof(*stream_cfg), + IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN); + if (ret < 0) { + dev_err(dev, "can't open stream (%d)\n", ret); + ipu6_put_fw_msg_buf(av->isys, (uintptr_t)stream_cfg); + return ret; + } + + get_stream_opened(av); + + tout = wait_for_completion_timeout(&stream->stream_open_completion, + IPU6_FW_CALL_TIMEOUT_JIFFIES); + + ipu6_put_fw_msg_buf(av->isys, (uintptr_t)stream_cfg); + + if (!tout) { + dev_err(dev, "stream open time out\n"); + ret = -ETIMEDOUT; + goto out_put_stream_opened; + } + if (stream->error) { + dev_err(dev, "stream open error: %d\n", stream->error); + ret = -EIO; + goto out_put_stream_opened; + } + dev_dbg(dev, "start stream: open complete\n"); + + if (bl) { + msg = ipu6_get_fw_msg_buf(stream); + if (!msg) { + ret = -ENOMEM; + goto out_put_stream_opened; + } + buf = &msg->fw_msg.frame; + ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl); + ipu6_isys_buffer_list_queue(bl, + IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); + } + + reinit_completion(&stream->stream_start_completion); + + if (bl) { + send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE; + ipu6_fw_isys_dump_frame_buff_set(dev, buf, + stream_cfg->nof_output_pins); + ret = ipu6_fw_isys_complex_cmd(av->isys, stream->stream_handle, + buf, msg->dma_addr, + sizeof(*buf), send_type); + } else { + send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START; + ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, + send_type); + } + + if (ret < 0) { + dev_err(dev, "can't start streaming (%d)\n", ret); + goto out_stream_close; + } + + tout = wait_for_completion_timeout(&stream->stream_start_completion, + IPU6_FW_CALL_TIMEOUT_JIFFIES); + if (!tout) { + dev_err(dev, "stream start time out\n"); + ret = -ETIMEDOUT; + goto out_stream_close; + } + if (stream->error) { + dev_err(dev, "stream start error: %d\n", stream->error); + ret = -EIO; + goto out_stream_close; + } + dev_dbg(dev, "start stream: complete\n"); + + return 0; + +out_stream_close: + reinit_completion(&stream->stream_close_completion); + + retout = ipu6_fw_isys_simple_cmd(av->isys, + stream->stream_handle, + IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE); + if (retout < 0) { + dev_dbg(dev, "can't close stream (%d)\n", retout); + goto out_put_stream_opened; + } + + tout = wait_for_completion_timeout(&stream->stream_close_completion, + IPU6_FW_CALL_TIMEOUT_JIFFIES); + if (!tout) + dev_err(dev, "stream close time out\n"); + else if (stream->error) + dev_err(dev, "stream close error: %d\n", stream->error); + else + dev_dbg(dev, "stream close complete\n"); + +out_put_stream_opened: + put_stream_opened(av); + + return ret; +} + +static void stop_streaming_firmware(struct ipu6_isys_video *av) +{ + struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu6_isys_stream *stream = av->stream; + int ret, tout; + + reinit_completion(&stream->stream_stop_completion); + + ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, + IPU6_FW_ISYS_SEND_TYPE_STREAM_FLUSH); + + if (ret < 0) { + dev_err(dev, "can't stop stream (%d)\n", ret); + return; + } + + tout = wait_for_completion_timeout(&stream->stream_stop_completion, + IPU6_FW_CALL_TIMEOUT_JIFFIES); + if (!tout) + dev_warn(dev, "stream stop time out\n"); + else if (stream->error) + dev_warn(dev, "stream stop error: %d\n", stream->error); + else + dev_dbg(dev, "stop stream: complete\n"); +} + +static void close_streaming_firmware(struct ipu6_isys_video *av) +{ + struct ipu6_isys_stream *stream = av->stream; + struct device *dev = &av->isys->adev->auxdev.dev; + int ret, tout; + + reinit_completion(&stream->stream_close_completion); + + ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, + IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE); + if (ret < 0) { + dev_err(dev, "can't close stream (%d)\n", ret); + return; + } + + tout = wait_for_completion_timeout(&stream->stream_close_completion, + IPU6_FW_CALL_TIMEOUT_JIFFIES); + if (!tout) + dev_warn(dev, "stream close time out\n"); + else if (stream->error) + dev_warn(dev, "stream close error: %d\n", stream->error); + else + dev_dbg(dev, "close stream: complete\n"); + + put_stream_opened(av); +} + +int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av, + struct media_entity *source_entity, + int nr_queues) +{ + struct ipu6_isys_stream *stream = av->stream; + struct ipu6_isys_csi2 *csi2; + + if (WARN_ON(stream->nr_streaming)) + return -EINVAL; + + stream->nr_queues = nr_queues; + atomic_set(&stream->sequence, 0); + + stream->seq_index = 0; + memset(stream->seq, 0, sizeof(stream->seq)); + + if (WARN_ON(!list_empty(&stream->queues))) + return -EINVAL; + + stream->stream_source = stream->asd->source; + csi2 = ipu6_isys_subdev_to_csi2(stream->asd); + csi2->receiver_errors = 0; + + dev_dbg(&av->isys->adev->auxdev.dev, + "prepare stream: external entity %s\n", + source_entity->name); + + return 0; +} + +void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av, + struct media_entity *source) +{ + struct ipu6_isys *isys = av->isys; + struct ipu6_isys_csi2 *csi2 = NULL; + struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; + struct device *dev = &isys->adev->auxdev.dev; + struct v4l2_mbus_framefmt format; + struct v4l2_subdev *esd; + struct v4l2_control hb = { .id = V4L2_CID_HBLANK, .value = 0 }; + unsigned int bpp, lanes; + s64 link_freq = 0; + u64 pixel_rate = 0; + int ret; + + esd = media_entity_to_v4l2_subdev(source); + + av->watermark.width = ipu6_isys_get_frame_width(av); + av->watermark.height = ipu6_isys_get_frame_height(av); + av->watermark.sram_gran_shift = isys->pdata->ipdata->sram_gran_shift; + av->watermark.sram_gran_size = isys->pdata->ipdata->sram_gran_size; + + ret = v4l2_g_ctrl(esd->ctrl_handler, &hb); + if (!ret && hb.value >= 0) + av->watermark.hblank = hb.value; + else + av->watermark.hblank = 0; + + csi2 = ipu6_isys_subdev_to_csi2(av->stream->asd); + link_freq = ipu6_isys_csi2_get_link_freq(csi2); + if (link_freq > 0) { + struct v4l2_subdev_state *state = + v4l2_subdev_lock_and_get_active_state(&csi2->asd.sd); + + lanes = csi2->nlanes; + format = *v4l2_subdev_state_get_format(state, 0, + av->source_stream); + bpp = ipu6_isys_mbus_code_to_bpp(format.code); + pixel_rate = mul_u64_u32_div(link_freq, lanes * 2, bpp); + + v4l2_subdev_unlock_state(state); + } + + av->watermark.pixel_rate = pixel_rate; + + if (!pixel_rate) { + mutex_lock(&iwake_watermark->mutex); + iwake_watermark->force_iwake_disable = true; + mutex_unlock(&iwake_watermark->mutex); + dev_warn(dev, "unexpected pixel_rate from %s, disable iwake.\n", + source->name); + } +} + +static void calculate_stream_datarate(struct ipu6_isys_video *av) +{ + struct video_stream_watermark *watermark = &av->watermark; + const struct ipu6_isys_pixelformat *pfmt = + ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0); + u32 pages_per_line, pb_bytes_per_line, pixels_per_line, bytes_per_line; + u64 line_time_ns, stream_data_rate; + u16 shift, size; + + shift = watermark->sram_gran_shift; + size = watermark->sram_gran_size; + + pixels_per_line = watermark->width + watermark->hblank; + line_time_ns = div_u64(pixels_per_line * NSEC_PER_SEC, + watermark->pixel_rate); + bytes_per_line = watermark->width * pfmt->bpp / 8; + pages_per_line = DIV_ROUND_UP(bytes_per_line, size); + pb_bytes_per_line = pages_per_line << shift; + stream_data_rate = div64_u64(pb_bytes_per_line * 1000, line_time_ns); + + watermark->stream_data_rate = stream_data_rate; +} + +void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state) +{ + struct isys_iwake_watermark *iwake_watermark = + &av->isys->iwake_watermark; + + if (!av->watermark.pixel_rate) + return; + + if (state) { + calculate_stream_datarate(av); + mutex_lock(&iwake_watermark->mutex); + list_add(&av->watermark.stream_node, + &iwake_watermark->video_list); + mutex_unlock(&iwake_watermark->mutex); + } else { + av->watermark.stream_data_rate = 0; + mutex_lock(&iwake_watermark->mutex); + list_del(&av->watermark.stream_node); + mutex_unlock(&iwake_watermark->mutex); + } + + update_watermark_setting(av->isys); +} + +void ipu6_isys_put_stream(struct ipu6_isys_stream *stream) +{ + struct device *dev; + unsigned int i; + unsigned long flags; + + if (!stream) { + pr_err("ipu6-isys: no available stream\n"); + return; + } + + dev = &stream->isys->adev->auxdev.dev; + + spin_lock_irqsave(&stream->isys->streams_lock, flags); + for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { + if (&stream->isys->streams[i] == stream) { + if (stream->isys->streams_ref_count[i] > 0) + stream->isys->streams_ref_count[i]--; + else + dev_warn(dev, "invalid stream %d\n", i); + + break; + } + } + spin_unlock_irqrestore(&stream->isys->streams_lock, flags); +} + +static struct ipu6_isys_stream * +ipu6_isys_get_stream(struct ipu6_isys_video *av, struct ipu6_isys_subdev *asd) +{ + struct ipu6_isys_stream *stream = NULL; + struct ipu6_isys *isys = av->isys; + unsigned long flags; + unsigned int i; + u8 vc = av->vc; + + if (!isys) + return NULL; + + spin_lock_irqsave(&isys->streams_lock, flags); + for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { + if (isys->streams_ref_count[i] && isys->streams[i].vc == vc && + isys->streams[i].asd == asd) { + isys->streams_ref_count[i]++; + stream = &isys->streams[i]; + break; + } + } + + if (!stream) { + for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { + if (!isys->streams_ref_count[i]) { + isys->streams_ref_count[i]++; + stream = &isys->streams[i]; + stream->vc = vc; + stream->asd = asd; + break; + } + } + } + spin_unlock_irqrestore(&isys->streams_lock, flags); + + return stream; +} + +struct ipu6_isys_stream * +ipu6_isys_query_stream_by_handle(struct ipu6_isys *isys, u8 stream_handle) +{ + unsigned long flags; + struct ipu6_isys_stream *stream = NULL; + + if (!isys) + return NULL; + + if (stream_handle >= IPU6_ISYS_MAX_STREAMS) { + dev_err(&isys->adev->auxdev.dev, + "stream_handle %d is invalid\n", stream_handle); + return NULL; + } + + spin_lock_irqsave(&isys->streams_lock, flags); + if (isys->streams_ref_count[stream_handle] > 0) { + isys->streams_ref_count[stream_handle]++; + stream = &isys->streams[stream_handle]; + } + spin_unlock_irqrestore(&isys->streams_lock, flags); + + return stream; +} + +struct ipu6_isys_stream * +ipu6_isys_query_stream_by_source(struct ipu6_isys *isys, int source, u8 vc) +{ + struct ipu6_isys_stream *stream = NULL; + unsigned long flags; + unsigned int i; + + if (!isys) + return NULL; + + if (source < 0) { + dev_err(&isys->adev->auxdev.dev, + "query stream with invalid port number\n"); + return NULL; + } + + spin_lock_irqsave(&isys->streams_lock, flags); + for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { + if (!isys->streams_ref_count[i]) + continue; + + if (isys->streams[i].stream_source == source && + isys->streams[i].vc == vc) { + stream = &isys->streams[i]; + isys->streams_ref_count[i]++; + break; + } + } + spin_unlock_irqrestore(&isys->streams_lock, flags); + + return stream; +} + +static u64 get_stream_mask_by_pipeline(struct ipu6_isys_video *__av) +{ + struct media_pipeline *pipeline = + media_entity_pipeline(&__av->vdev.entity); + unsigned int i; + u64 stream_mask = 0; + + for (i = 0; i < NR_OF_CSI2_SRC_PADS; i++) { + struct ipu6_isys_video *av = &__av->csi2->av[i]; + + if (pipeline == media_entity_pipeline(&av->vdev.entity)) + stream_mask |= BIT_ULL(av->source_stream); + } + + return stream_mask; +} + +int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state, + struct ipu6_isys_buffer_list *bl) +{ + struct v4l2_subdev_krouting *routing; + struct ipu6_isys_stream *stream = av->stream; + struct v4l2_subdev_state *subdev_state; + struct device *dev = &av->isys->adev->auxdev.dev; + struct v4l2_subdev *sd; + struct media_pad *r_pad; + u32 sink_pad, sink_stream; + u64 r_stream; + u64 stream_mask = 0; + int ret = 0; + + dev_dbg(dev, "set stream: %d\n", state); + + sd = &stream->asd->sd; + r_pad = media_pad_remote_pad_first(&av->pad); + r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, r_pad->index); + + subdev_state = v4l2_subdev_lock_and_get_active_state(sd); + routing = &subdev_state->routing; + ret = v4l2_subdev_routing_find_opposite_end(routing, r_pad->index, + r_stream, &sink_pad, + &sink_stream); + v4l2_subdev_unlock_state(subdev_state); + if (ret) + return ret; + + stream_mask = get_stream_mask_by_pipeline(av); + if (!state) { + stop_streaming_firmware(av); + + /* stop sub-device which connects with video */ + dev_dbg(dev, "stream off entity %s pad:%d mask:0x%llx\n", + sd->name, r_pad->index, stream_mask); + ret = v4l2_subdev_disable_streams(sd, r_pad->index, + stream_mask); + if (ret) + dev_err(dev, "stream off %s failed with %d\n", sd->name, + ret); + + close_streaming_firmware(av); + } else { + ret = start_stream_firmware(av, bl); + if (ret) { + dev_err(dev, "start stream of firmware failed\n"); + return ret; + } + + /* start sub-device which connects with video */ + dev_dbg(dev, "stream on %s pad %d mask 0x%llx\n", sd->name, + r_pad->index, stream_mask); + ret = v4l2_subdev_enable_streams(sd, r_pad->index, stream_mask); + if (ret) { + dev_err(dev, "stream on %s failed with %d\n", sd->name, + ret); + goto out_media_entity_stop_streaming_firmware; + } + } + + av->streaming = state; + + return 0; + +out_media_entity_stop_streaming_firmware: + stop_streaming_firmware(av); + close_streaming_firmware(av); + + return ret; +} + +static const struct v4l2_ioctl_ops ipu6_v4l2_ioctl_ops = { + .vidioc_querycap = ipu6_isys_vidioc_querycap, + .vidioc_enum_fmt_vid_cap = ipu6_isys_vidioc_enum_fmt, + .vidioc_enum_fmt_meta_cap = ipu6_isys_vidioc_enum_fmt, + .vidioc_enum_framesizes = ipu6_isys_vidioc_enum_framesizes, + .vidioc_g_fmt_vid_cap = ipu6_isys_vidioc_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = ipu6_isys_vidioc_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = ipu6_isys_vidioc_try_fmt_vid_cap, + .vidioc_g_fmt_meta_cap = ipu6_isys_vidioc_g_fmt_meta_cap, + .vidioc_s_fmt_meta_cap = ipu6_isys_vidioc_s_fmt_meta_cap, + .vidioc_try_fmt_meta_cap = ipu6_isys_vidioc_try_fmt_meta_cap, + .vidioc_reqbufs = ipu6_isys_vidioc_reqbufs, + .vidioc_create_bufs = ipu6_isys_vidioc_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_expbuf = vb2_ioctl_expbuf, +}; + +static const struct media_entity_operations entity_ops = { + .link_validate = link_validate, +}; + +static const struct v4l2_file_operations isys_fops = { + .owner = THIS_MODULE, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, + .open = video_open, + .release = vb2_fop_release, +}; + +int ipu6_isys_fw_open(struct ipu6_isys *isys) +{ + struct ipu6_bus_device *adev = isys->adev; + const struct ipu6_isys_internal_pdata *ipdata = isys->pdata->ipdata; + int ret; + + ret = pm_runtime_resume_and_get(&adev->auxdev.dev); + if (ret < 0) + return ret; + + mutex_lock(&isys->mutex); + + if (isys->ref_count++) + goto unlock; + + ipu6_configure_spc(adev->isp, &ipdata->hw_variant, + IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX, isys->pdata->base, + adev->pkg_dir, adev->pkg_dir_dma_addr); + + /* + * Buffers could have been left to wrong queue at last closure. + * Move them now back to empty buffer queue. + */ + ipu6_cleanup_fw_msg_bufs(isys); + + if (isys->fwcom) { + /* + * Something went wrong in previous shutdown. As we are now + * restarting isys we can safely delete old context. + */ + dev_warn(&adev->auxdev.dev, "clearing old context\n"); + ipu6_fw_isys_cleanup(isys); + } + + ret = ipu6_fw_isys_init(isys, ipdata->num_parallel_streams); + if (ret < 0) + goto out; + +unlock: + mutex_unlock(&isys->mutex); + + return 0; + +out: + isys->ref_count--; + mutex_unlock(&isys->mutex); + pm_runtime_put(&adev->auxdev.dev); + + return ret; +} + +void ipu6_isys_fw_close(struct ipu6_isys *isys) +{ + mutex_lock(&isys->mutex); + + isys->ref_count--; + if (!isys->ref_count) { + ipu6_fw_isys_close(isys); + if (isys->fwcom) { + isys->need_reset = true; + dev_warn(&isys->adev->auxdev.dev, + "failed to close fw isys\n"); + } + } + + mutex_unlock(&isys->mutex); + + if (isys->need_reset) + pm_runtime_put_sync(&isys->adev->auxdev.dev); + else + pm_runtime_put(&isys->adev->auxdev.dev); +} + +int ipu6_isys_setup_video(struct ipu6_isys_video *av, + struct media_pad *remote_pad, + struct media_pad *source_pad, int *nr_queues) +{ + const struct ipu6_isys_pixelformat *pfmt = + ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0); + struct device *dev = &av->isys->adev->auxdev.dev; + struct v4l2_mbus_frame_desc_entry entry; + struct v4l2_subdev_route *route = NULL; + struct v4l2_subdev_route *r; + struct v4l2_subdev_state *state; + struct v4l2_subdev *remote_sd = + media_entity_to_v4l2_subdev(remote_pad->entity); + struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(remote_sd); + int ret = -EINVAL; + + *nr_queues = 0; + + /* Find the root */ + state = v4l2_subdev_lock_and_get_active_state(remote_sd); + for_each_active_route(&state->routing, r) { + (*nr_queues)++; + + if (r->source_pad == remote_pad->index) + route = r; + } + + if (!route) { + v4l2_subdev_unlock_state(state); + dev_dbg(dev, "Failed to find route\n"); + return -ENODEV; + } + av->source_stream = route->sink_stream; + v4l2_subdev_unlock_state(state); + + ret = ipu6_isys_csi2_get_remote_desc(av->source_stream, + to_ipu6_isys_csi2(asd), + source_pad->entity, &entry); + if (ret == -ENOIOCTLCMD) { + av->vc = 0; + av->dt = ipu6_isys_mbus_code_to_mipi(pfmt->code); + } else if (!ret) { + dev_dbg(dev, "Framedesc: stream %u, len %u, vc %u, dt %#x\n", + entry.stream, entry.length, entry.bus.csi2.vc, + entry.bus.csi2.dt); + + av->vc = entry.bus.csi2.vc; + av->dt = entry.bus.csi2.dt; + } else { + dev_err(dev, "failed to get remote frame desc\n"); + return ret; + } + + ret = video_device_pipeline_alloc_start(&av->vdev); + if (ret < 0) { + dev_dbg(dev, "media pipeline start failed\n"); + return ret; + } + + av->stream = ipu6_isys_get_stream(av, asd); + if (!av->stream) { + video_device_pipeline_stop(&av->vdev); + dev_err(dev, "no available stream for firmware\n"); + return -EINVAL; + } + + return 0; +} + +/* + * Do everything that's needed to initialise things related to video + * buffer queue, video node, and the related media entity. The caller + * is expected to assign isys field and set the name of the video + * device. + */ +int ipu6_isys_video_init(struct ipu6_isys_video *av) +{ + struct v4l2_format format = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .fmt.pix = { + .width = 1920, + .height = 1080, + }, + }; + struct v4l2_format format_meta = { + .type = V4L2_BUF_TYPE_META_CAPTURE, + .fmt.meta = { + .width = 1920, + .height = 4, + }, + }; + int ret; + + mutex_init(&av->mutex); + av->vdev.device_caps = V4L2_CAP_STREAMING | V4L2_CAP_IO_MC | + V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE; + av->vdev.vfl_dir = VFL_DIR_RX; + + ret = ipu6_isys_queue_init(&av->aq); + if (ret) + goto out_free_watermark; + + av->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; + ret = media_entity_pads_init(&av->vdev.entity, 1, &av->pad); + if (ret) + goto out_vb2_queue_release; + + av->vdev.entity.ops = &entity_ops; + av->vdev.release = video_device_release_empty; + av->vdev.fops = &isys_fops; + av->vdev.v4l2_dev = &av->isys->v4l2_dev; + av->vdev.dev_parent = &av->isys->adev->isp->pdev->dev; + if (!av->vdev.ioctl_ops) + av->vdev.ioctl_ops = &ipu6_v4l2_ioctl_ops; + av->vdev.queue = &av->aq.vbq; + av->vdev.lock = &av->mutex; + + __ipu6_isys_vidioc_try_fmt_vid_cap(av, &format); + av->pix_fmt = format.fmt.pix; + __ipu6_isys_vidioc_try_fmt_meta_cap(av, &format_meta); + av->meta_fmt = format_meta.fmt.meta; + + video_set_drvdata(&av->vdev, av); + + ret = video_register_device(&av->vdev, VFL_TYPE_VIDEO, -1); + if (ret) + goto out_media_entity_cleanup; + + return ret; + +out_media_entity_cleanup: + vb2_video_unregister_device(&av->vdev); + media_entity_cleanup(&av->vdev.entity); + +out_vb2_queue_release: + vb2_queue_release(&av->aq.vbq); + +out_free_watermark: + mutex_destroy(&av->mutex); + + return ret; +} + +void ipu6_isys_video_cleanup(struct ipu6_isys_video *av) +{ + vb2_video_unregister_device(&av->vdev); + media_entity_cleanup(&av->vdev.entity); + mutex_destroy(&av->mutex); +} + +u32 ipu6_isys_get_format(struct ipu6_isys_video *av) +{ + if (av->aq.vbq.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return av->pix_fmt.pixelformat; + + if (av->aq.vbq.type == V4L2_BUF_TYPE_META_CAPTURE) + return av->meta_fmt.dataformat; + + return 0; +} + +u32 ipu6_isys_get_data_size(struct ipu6_isys_video *av) +{ + if (av->aq.vbq.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return av->pix_fmt.sizeimage; + + if (av->aq.vbq.type == V4L2_BUF_TYPE_META_CAPTURE) + return av->meta_fmt.buffersize; + + return 0; +} + +u32 ipu6_isys_get_bytes_per_line(struct ipu6_isys_video *av) +{ + if (av->aq.vbq.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return av->pix_fmt.bytesperline; + + if (av->aq.vbq.type == V4L2_BUF_TYPE_META_CAPTURE) + return av->meta_fmt.bytesperline; + + return 0; +} + +u32 ipu6_isys_get_frame_width(struct ipu6_isys_video *av) +{ + if (av->aq.vbq.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return av->pix_fmt.width; + + if (av->aq.vbq.type == V4L2_BUF_TYPE_META_CAPTURE) + return av->meta_fmt.width; + + return 0; +} + +u32 ipu6_isys_get_frame_height(struct ipu6_isys_video *av) +{ + if (av->aq.vbq.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return av->pix_fmt.height; + + if (av->aq.vbq.type == V4L2_BUF_TYPE_META_CAPTURE) + return av->meta_fmt.height; + + return 0; +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.h b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h new file mode 100644 index 000000000..2ff53315d --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h @@ -0,0 +1,135 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013--2024 Intel Corporation */ + +#ifndef IPU6_ISYS_VIDEO_H +#define IPU6_ISYS_VIDEO_H + +#include +#include +#include +#include +#include + +#include +#include + +#include "ipu6-isys-queue.h" + +#define IPU6_ISYS_OUTPUT_PINS 11 +#define IPU6_ISYS_MAX_PARALLEL_SOF 2 + +struct file; +struct ipu6_isys; +struct ipu6_isys_csi2; +struct ipu6_isys_subdev; + +struct ipu6_isys_pixelformat { + u32 pixelformat; + u32 bpp; + u32 bpp_packed; + u32 code; + u32 css_pixelformat; + bool is_meta; +}; + +struct sequence_info { + unsigned int sequence; + u64 timestamp; +}; + +/* + * Align with firmware stream. Each stream represents a CSI virtual channel. + * May map to multiple video devices + */ +struct ipu6_isys_stream { + struct mutex mutex; + atomic_t sequence; + unsigned int seq_index; + struct sequence_info seq[IPU6_ISYS_MAX_PARALLEL_SOF]; + int stream_source; + int stream_handle; + unsigned int nr_output_pins; + struct ipu6_isys_subdev *asd; + + int nr_queues; /* Number of capture queues */ + int nr_streaming; + int streaming; /* Has streaming been really started? */ + struct list_head queues; + struct completion stream_open_completion; + struct completion stream_close_completion; + struct completion stream_start_completion; + struct completion stream_stop_completion; + struct ipu6_isys *isys; + + struct ipu6_isys_queue *output_pins_queue[IPU6_ISYS_OUTPUT_PINS]; + int error; + u8 vc; +}; + +struct video_stream_watermark { + u32 width; + u32 height; + u32 hblank; + u32 frame_rate; + u64 pixel_rate; + u64 stream_data_rate; + u16 sram_gran_shift; + u16 sram_gran_size; + struct list_head stream_node; +}; + +struct ipu6_isys_video { + struct ipu6_isys_queue aq; + /* Serialise access to other fields in the struct. */ + struct mutex mutex; + struct media_pad pad; + struct video_device vdev; + struct v4l2_pix_format pix_fmt; + struct v4l2_meta_format meta_fmt; + struct ipu6_isys *isys; + struct ipu6_isys_csi2 *csi2; + struct ipu6_isys_stream *stream; + unsigned int streaming; + struct video_stream_watermark watermark; + u32 source_stream; + u8 vc; + u8 dt; +}; + +#define ipu6_isys_queue_to_video(__aq) \ + container_of(__aq, struct ipu6_isys_video, aq) + +extern const struct ipu6_isys_pixelformat ipu6_isys_pfmts[]; +extern const struct ipu6_isys_pixelformat ipu6_isys_pfmts_packed[]; + +const struct ipu6_isys_pixelformat * +ipu6_isys_get_isys_format(u32 pixelformat, u32 code); +int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av, + struct media_entity *source_entity, + int nr_queues); +int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state, + struct ipu6_isys_buffer_list *bl); +int ipu6_isys_fw_open(struct ipu6_isys *isys); +void ipu6_isys_fw_close(struct ipu6_isys *isys); +int ipu6_isys_setup_video(struct ipu6_isys_video *av, + struct media_pad *remote_pad, + struct media_pad *source_pad, int *nr_queues); +int ipu6_isys_video_init(struct ipu6_isys_video *av); +void ipu6_isys_video_cleanup(struct ipu6_isys_video *av); +void ipu6_isys_put_stream(struct ipu6_isys_stream *stream); +struct ipu6_isys_stream * +ipu6_isys_query_stream_by_handle(struct ipu6_isys *isys, u8 stream_handle); +struct ipu6_isys_stream * +ipu6_isys_query_stream_by_source(struct ipu6_isys *isys, int source, u8 vc); + +void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av, + struct media_entity *source); +void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state); + +u32 ipu6_isys_get_format(struct ipu6_isys_video *av); +u32 ipu6_isys_get_data_size(struct ipu6_isys_video *av); +u32 ipu6_isys_get_bytes_per_line(struct ipu6_isys_video *av); +u32 ipu6_isys_get_frame_width(struct ipu6_isys_video *av); +u32 ipu6_isys_get_frame_height(struct ipu6_isys_video *av); + +#endif /* IPU6_ISYS_VIDEO_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c new file mode 100644 index 000000000..24db2763d --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -0,0 +1,1366 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "ipu6-bus.h" +#include "ipu6-cpd.h" +#include "ipu6-dma.h" +#include "ipu6-isys.h" +#include "ipu6-isys-csi2.h" +#include "ipu6-mmu.h" +#include "ipu6-platform-buttress-regs.h" +#include "ipu6-platform-isys-csi2-reg.h" +#include "ipu6-platform-regs.h" + +#define IPU6_BUTTRESS_FABIC_CONTROL 0x68 +#define GDA_ENABLE_IWAKE_INDEX 2 +#define GDA_IWAKE_THRESHOLD_INDEX 1 +#define GDA_IRQ_CRITICAL_THRESHOLD_INDEX 0 +#define GDA_MEMOPEN_THRESHOLD_INDEX 3 +#define DEFAULT_DID_RATIO 90 +#define DEFAULT_IWAKE_THRESHOLD 0x42 +#define DEFAULT_MEM_OPEN_TIME 10 +#define ONE_THOUSAND_MICROSECOND 1000 +/* One page is 2KB, 8 x 16 x 16 = 2048B = 2KB */ +#define ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE 0x800 + +/* LTR & DID value are 10 bit at most */ +#define LTR_DID_VAL_MAX 1023 +#define LTR_DEFAULT_VALUE 0x70503c19 +#define FILL_TIME_DEFAULT_VALUE 0xfff0783c +#define LTR_DID_PKGC_2R 20 +#define LTR_SCALE_DEFAULT 5 +#define LTR_SCALE_1024NS 2 +#define DID_SCALE_1US 2 +#define DID_SCALE_32US 3 +#define REG_PKGC_PMON_CFG 0xb00 + +#define VAL_PKGC_PMON_CFG_RESET 0x38 +#define VAL_PKGC_PMON_CFG_START 0x7 + +#define IS_PIXEL_BUFFER_PAGES 0x80 +/* + * when iwake mode is disabled, the critical threshold is statically set + * to 75% of the IS pixel buffer, criticalThreshold = (128 * 3) / 4 + */ +#define CRITICAL_THRESHOLD_IWAKE_DISABLE (IS_PIXEL_BUFFER_PAGES * 3 / 4) + +union fabric_ctrl { + struct { + u16 ltr_val : 10; + u16 ltr_scale : 3; + u16 reserved : 3; + u16 did_val : 10; + u16 did_scale : 3; + u16 reserved2 : 1; + u16 keep_power_in_D0 : 1; + u16 keep_power_override : 1; + } bits; + u32 value; +}; + +enum ltr_did_type { + LTR_IWAKE_ON, + LTR_IWAKE_OFF, + LTR_ISYS_ON, + LTR_ISYS_OFF, + LTR_ENHANNCE_IWAKE, + LTR_TYPE_MAX +}; + +#define ISYS_PM_QOS_VALUE 300 + +static int isys_isr_one(struct ipu6_bus_device *adev); + +static int +isys_complete_ext_device_registration(struct ipu6_isys *isys, + struct v4l2_subdev *sd, + struct ipu6_isys_csi2_config *csi2) +{ + struct device *dev = &isys->adev->auxdev.dev; + unsigned int i; + int ret; + + for (i = 0; i < sd->entity.num_pads; i++) { + if (sd->entity.pads[i].flags & MEDIA_PAD_FL_SOURCE) + break; + } + + if (i == sd->entity.num_pads) { + dev_warn(dev, "no src pad in external entity\n"); + ret = -ENOENT; + goto unregister_subdev; + } + + ret = media_create_pad_link(&sd->entity, i, + &isys->csi2[csi2->port].asd.sd.entity, + 0, MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_warn(dev, "can't create link\n"); + goto unregister_subdev; + } + + isys->csi2[csi2->port].nlanes = csi2->nlanes; + + return 0; + +unregister_subdev: + v4l2_device_unregister_subdev(sd); + + return ret; +} + +static void isys_stream_init(struct ipu6_isys *isys) +{ + u32 i; + + for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { + mutex_init(&isys->streams[i].mutex); + init_completion(&isys->streams[i].stream_open_completion); + init_completion(&isys->streams[i].stream_close_completion); + init_completion(&isys->streams[i].stream_start_completion); + init_completion(&isys->streams[i].stream_stop_completion); + INIT_LIST_HEAD(&isys->streams[i].queues); + isys->streams[i].isys = isys; + isys->streams[i].stream_handle = i; + isys->streams[i].vc = INVALID_VC_ID; + } +} + +static void isys_csi2_unregister_subdevices(struct ipu6_isys *isys) +{ + const struct ipu6_isys_internal_csi2_pdata *csi2 = + &isys->pdata->ipdata->csi2; + unsigned int i; + + for (i = 0; i < csi2->nports; i++) + ipu6_isys_csi2_cleanup(&isys->csi2[i]); +} + +static int isys_csi2_register_subdevices(struct ipu6_isys *isys) +{ + const struct ipu6_isys_internal_csi2_pdata *csi2_pdata = + &isys->pdata->ipdata->csi2; + unsigned int i; + int ret; + + for (i = 0; i < csi2_pdata->nports; i++) { + ret = ipu6_isys_csi2_init(&isys->csi2[i], isys, + isys->pdata->base + + CSI_REG_PORT_BASE(i), i); + if (ret) + goto fail; + + isys->isr_csi2_bits |= IPU6_ISYS_UNISPART_IRQ_CSI2(i); + } + + return 0; + +fail: + while (i--) + ipu6_isys_csi2_cleanup(&isys->csi2[i]); + + return ret; +} + +static int isys_csi2_create_media_links(struct ipu6_isys *isys) +{ + const struct ipu6_isys_internal_csi2_pdata *csi2_pdata = + &isys->pdata->ipdata->csi2; + struct device *dev = &isys->adev->auxdev.dev; + unsigned int i, j; + int ret; + + for (i = 0; i < csi2_pdata->nports; i++) { + struct media_entity *sd = &isys->csi2[i].asd.sd.entity; + + for (j = 0; j < NR_OF_CSI2_SRC_PADS; j++) { + struct ipu6_isys_video *av = &isys->csi2[i].av[j]; + + ret = media_create_pad_link(sd, CSI2_PAD_SRC + j, + &av->vdev.entity, 0, 0); + if (ret) { + dev_err(dev, "CSI2 can't create link\n"); + return ret; + } + + av->csi2 = &isys->csi2[i]; + } + } + + return 0; +} + +static void isys_unregister_video_devices(struct ipu6_isys *isys) +{ + const struct ipu6_isys_internal_csi2_pdata *csi2_pdata = + &isys->pdata->ipdata->csi2; + unsigned int i, j; + + for (i = 0; i < csi2_pdata->nports; i++) + for (j = 0; j < NR_OF_CSI2_SRC_PADS; j++) + ipu6_isys_video_cleanup(&isys->csi2[i].av[j]); +} + +static int isys_register_video_devices(struct ipu6_isys *isys) +{ + const struct ipu6_isys_internal_csi2_pdata *csi2_pdata = + &isys->pdata->ipdata->csi2; + unsigned int i, j; + int ret; + + for (i = 0; i < csi2_pdata->nports; i++) { + for (j = 0; j < NR_OF_CSI2_SRC_PADS; j++) { + struct ipu6_isys_video *av = &isys->csi2[i].av[j]; + + snprintf(av->vdev.name, sizeof(av->vdev.name), + IPU6_ISYS_ENTITY_PREFIX " ISYS Capture %u", + i * NR_OF_CSI2_SRC_PADS + j); + av->isys = isys; + av->aq.vbq.buf_struct_size = + sizeof(struct ipu6_isys_video_buffer); + + ret = ipu6_isys_video_init(av); + if (ret) + goto fail; + } + } + + return 0; + +fail: + while (i--) { + while (j--) + ipu6_isys_video_cleanup(&isys->csi2[i].av[j]); + j = NR_OF_CSI2_SRC_PADS; + } + + return ret; +} + +static void isys_setup_hw(struct ipu6_isys *isys) +{ + void __iomem *base = isys->pdata->base; + const u8 *thd = isys->pdata->ipdata->hw_variant.cdc_fifo_threshold; + u32 irqs = 0; + unsigned int i, nports; + + nports = isys->pdata->ipdata->csi2.nports; + + /* Enable irqs for all MIPI ports */ + for (i = 0; i < nports; i++) + irqs |= IPU6_ISYS_UNISPART_IRQ_CSI2(i); + + writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_edge); + writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_lnp); + writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_mask); + writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_enable); + writel(GENMASK(19, 0), + base + isys->pdata->ipdata->csi2.ctrl0_irq_clear); + + irqs = ISYS_UNISPART_IRQS; + writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_EDGE); + writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_LEVEL_NOT_PULSE); + writel(GENMASK(28, 0), base + IPU6_REG_ISYS_UNISPART_IRQ_CLEAR); + writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_MASK); + writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_ENABLE); + + writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_REG); + writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_MUX_REG); + + /* Write CDC FIFO threshold values for isys */ + for (i = 0; i < isys->pdata->ipdata->hw_variant.cdc_fifos; i++) + writel(thd[i], base + IPU6_REG_ISYS_CDC_THRESHOLD(i)); +} + +static void ipu6_isys_csi2_isr(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys_stream *stream; + unsigned int i; + u32 status; + int source; + + ipu6_isys_register_errors(csi2); + + status = readl(csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); + + writel(status, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); + + source = csi2->asd.source; + for (i = 0; i < NR_OF_CSI2_VC; i++) { + if (status & IPU_CSI_RX_IRQ_FS_VC(i)) { + stream = ipu6_isys_query_stream_by_source(csi2->isys, + source, i); + if (stream) { + ipu6_isys_csi2_sof_event_by_stream(stream); + ipu6_isys_put_stream(stream); + } + } + + if (status & IPU_CSI_RX_IRQ_FE_VC(i)) { + stream = ipu6_isys_query_stream_by_source(csi2->isys, + source, i); + if (stream) { + ipu6_isys_csi2_eof_event_by_stream(stream); + ipu6_isys_put_stream(stream); + } + } + } +} + +static irqreturn_t isys_isr(struct ipu6_bus_device *adev) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + void __iomem *base = isys->pdata->base; + u32 status_sw, status_csi; + u32 ctrl0_status, ctrl0_clear; + + spin_lock(&isys->power_lock); + if (!isys->power) { + spin_unlock(&isys->power_lock); + return IRQ_NONE; + } + + ctrl0_status = isys->pdata->ipdata->csi2.ctrl0_irq_status; + ctrl0_clear = isys->pdata->ipdata->csi2.ctrl0_irq_clear; + + status_csi = readl(isys->pdata->base + ctrl0_status); + status_sw = readl(isys->pdata->base + + IPU6_REG_ISYS_UNISPART_IRQ_STATUS); + + writel(ISYS_UNISPART_IRQS & ~IPU6_ISYS_UNISPART_IRQ_SW, + base + IPU6_REG_ISYS_UNISPART_IRQ_MASK); + + do { + writel(status_csi, isys->pdata->base + ctrl0_clear); + + writel(status_sw, isys->pdata->base + + IPU6_REG_ISYS_UNISPART_IRQ_CLEAR); + + if (isys->isr_csi2_bits & status_csi) { + unsigned int i; + + for (i = 0; i < isys->pdata->ipdata->csi2.nports; i++) { + /* irq from not enabled port */ + if (!isys->csi2[i].base) + continue; + if (status_csi & IPU6_ISYS_UNISPART_IRQ_CSI2(i)) + ipu6_isys_csi2_isr(&isys->csi2[i]); + } + } + + writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_REG); + + if (!isys_isr_one(adev)) + status_sw = IPU6_ISYS_UNISPART_IRQ_SW; + else + status_sw = 0; + + status_csi = readl(isys->pdata->base + ctrl0_status); + status_sw |= readl(isys->pdata->base + + IPU6_REG_ISYS_UNISPART_IRQ_STATUS); + } while ((status_csi & isys->isr_csi2_bits) || + (status_sw & IPU6_ISYS_UNISPART_IRQ_SW)); + + writel(ISYS_UNISPART_IRQS, base + IPU6_REG_ISYS_UNISPART_IRQ_MASK); + + spin_unlock(&isys->power_lock); + + return IRQ_HANDLED; +} + +static void get_lut_ltrdid(struct ipu6_isys *isys, struct ltr_did *pltr_did) +{ + struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; + struct ltr_did ltrdid_default; + + ltrdid_default.lut_ltr.value = LTR_DEFAULT_VALUE; + ltrdid_default.lut_fill_time.value = FILL_TIME_DEFAULT_VALUE; + + if (iwake_watermark->ltrdid.lut_ltr.value) + *pltr_did = iwake_watermark->ltrdid; + else + *pltr_did = ltrdid_default; +} + +static int set_iwake_register(struct ipu6_isys *isys, u32 index, u32 value) +{ + struct device *dev = &isys->adev->auxdev.dev; + u32 req_id = index; + u32 offset = 0; + int ret; + + ret = ipu6_fw_isys_send_proxy_token(isys, req_id, index, offset, value); + if (ret) + dev_err(dev, "write %d failed %d", index, ret); + + return ret; +} + +/* + * When input system is powered up and before enabling any new sensor capture, + * or after disabling any sensor capture the following values need to be set: + * LTR_value = LTR(usec) from calculation; + * LTR_scale = 2; + * DID_value = DID(usec) from calculation; + * DID_scale = 2; + * + * When input system is powered down, the LTR and DID values + * must be returned to the default values: + * LTR_value = 1023; + * LTR_scale = 5; + * DID_value = 1023; + * DID_scale = 2; + */ +static void set_iwake_ltrdid(struct ipu6_isys *isys, u16 ltr, u16 did, + enum ltr_did_type use) +{ + struct device *dev = &isys->adev->auxdev.dev; + u16 ltr_val, ltr_scale = LTR_SCALE_1024NS; + u16 did_val, did_scale = DID_SCALE_1US; + struct ipu6_device *isp = isys->adev->isp; + union fabric_ctrl fc; + + switch (use) { + case LTR_IWAKE_ON: + ltr_val = min_t(u16, ltr, (u16)LTR_DID_VAL_MAX); + did_val = min_t(u16, did, (u16)LTR_DID_VAL_MAX); + ltr_scale = (ltr == LTR_DID_VAL_MAX && + did == LTR_DID_VAL_MAX) ? + LTR_SCALE_DEFAULT : LTR_SCALE_1024NS; + break; + case LTR_ISYS_ON: + case LTR_IWAKE_OFF: + ltr_val = LTR_DID_PKGC_2R; + did_val = LTR_DID_PKGC_2R; + break; + case LTR_ISYS_OFF: + ltr_val = LTR_DID_VAL_MAX; + did_val = LTR_DID_VAL_MAX; + ltr_scale = LTR_SCALE_DEFAULT; + break; + case LTR_ENHANNCE_IWAKE: + if (ltr == LTR_DID_VAL_MAX && did == LTR_DID_VAL_MAX) { + ltr_val = LTR_DID_VAL_MAX; + did_val = LTR_DID_VAL_MAX; + ltr_scale = LTR_SCALE_DEFAULT; + } else if (did < ONE_THOUSAND_MICROSECOND) { + ltr_val = ltr; + did_val = did; + } else { + ltr_val = ltr; + /* div 90% value by 32 to account for scale change */ + did_val = did / 32; + did_scale = DID_SCALE_32US; + } + break; + default: + ltr_val = LTR_DID_VAL_MAX; + did_val = LTR_DID_VAL_MAX; + ltr_scale = LTR_SCALE_DEFAULT; + break; + } + + fc.value = readl(isp->base + IPU6_BUTTRESS_FABIC_CONTROL); + fc.bits.ltr_val = ltr_val; + fc.bits.ltr_scale = ltr_scale; + fc.bits.did_val = did_val; + fc.bits.did_scale = did_scale; + + dev_dbg(dev, "ltr: value %u scale %u, did: value %u scale %u\n", + ltr_val, ltr_scale, did_val, did_scale); + writel(fc.value, isp->base + IPU6_BUTTRESS_FABIC_CONTROL); +} + +/* + * Driver may clear register GDA_ENABLE_IWAKE before FW configures the + * stream for debug purpose. Otherwise driver should not access this register. + */ +static void enable_iwake(struct ipu6_isys *isys, bool enable) +{ + struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; + int ret; + + mutex_lock(&iwake_watermark->mutex); + + if (iwake_watermark->iwake_enabled == enable) { + mutex_unlock(&iwake_watermark->mutex); + return; + } + + ret = set_iwake_register(isys, GDA_ENABLE_IWAKE_INDEX, enable); + if (!ret) + iwake_watermark->iwake_enabled = enable; + + mutex_unlock(&iwake_watermark->mutex); +} + +void update_watermark_setting(struct ipu6_isys *isys) +{ + struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; + u32 iwake_threshold, iwake_critical_threshold, page_num; + struct device *dev = &isys->adev->auxdev.dev; + u32 calc_fill_time_us = 0, ltr = 0, did = 0; + struct video_stream_watermark *p_watermark; + enum ltr_did_type ltr_did_type; + struct list_head *stream_node; + u64 isys_pb_datarate_mbs = 0; + u32 mem_open_threshold = 0; + struct ltr_did ltrdid; + u64 threshold_bytes; + u32 max_sram_size; + u32 shift; + + shift = isys->pdata->ipdata->sram_gran_shift; + max_sram_size = isys->pdata->ipdata->max_sram_size; + + mutex_lock(&iwake_watermark->mutex); + if (iwake_watermark->force_iwake_disable) { + set_iwake_ltrdid(isys, 0, 0, LTR_IWAKE_OFF); + set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX, + CRITICAL_THRESHOLD_IWAKE_DISABLE); + goto unlock_exit; + } + + if (list_empty(&iwake_watermark->video_list)) { + isys_pb_datarate_mbs = 0; + } else { + list_for_each(stream_node, &iwake_watermark->video_list) { + p_watermark = list_entry(stream_node, + struct video_stream_watermark, + stream_node); + isys_pb_datarate_mbs += p_watermark->stream_data_rate; + } + } + mutex_unlock(&iwake_watermark->mutex); + + if (!isys_pb_datarate_mbs) { + enable_iwake(isys, false); + set_iwake_ltrdid(isys, 0, 0, LTR_IWAKE_OFF); + mutex_lock(&iwake_watermark->mutex); + set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX, + CRITICAL_THRESHOLD_IWAKE_DISABLE); + goto unlock_exit; + } + + enable_iwake(isys, true); + calc_fill_time_us = div64_u64(max_sram_size, isys_pb_datarate_mbs); + + if (isys->pdata->ipdata->enhanced_iwake) { + ltr = isys->pdata->ipdata->ltr; + did = calc_fill_time_us * DEFAULT_DID_RATIO / 100; + ltr_did_type = LTR_ENHANNCE_IWAKE; + } else { + get_lut_ltrdid(isys, <rdid); + + if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th0) + ltr = 0; + else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th1) + ltr = ltrdid.lut_ltr.bits.val0; + else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th2) + ltr = ltrdid.lut_ltr.bits.val1; + else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th3) + ltr = ltrdid.lut_ltr.bits.val2; + else + ltr = ltrdid.lut_ltr.bits.val3; + + did = calc_fill_time_us - ltr; + ltr_did_type = LTR_IWAKE_ON; + } + + set_iwake_ltrdid(isys, ltr, did, ltr_did_type); + + /* calculate iwake threshold with 2KB granularity pages */ + threshold_bytes = did * isys_pb_datarate_mbs; + iwake_threshold = max_t(u32, 1, threshold_bytes >> shift); + iwake_threshold = min_t(u32, iwake_threshold, max_sram_size); + + mutex_lock(&iwake_watermark->mutex); + if (isys->pdata->ipdata->enhanced_iwake) { + set_iwake_register(isys, GDA_IWAKE_THRESHOLD_INDEX, + DEFAULT_IWAKE_THRESHOLD); + /* calculate number of pages that will be filled in 10 usec */ + page_num = (DEFAULT_MEM_OPEN_TIME * isys_pb_datarate_mbs) / + ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE; + page_num += ((DEFAULT_MEM_OPEN_TIME * isys_pb_datarate_mbs) % + ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE) ? 1 : 0; + mem_open_threshold = isys->pdata->ipdata->memopen_threshold; + mem_open_threshold = max_t(u32, mem_open_threshold, page_num); + dev_dbg(dev, "mem_open_threshold: %u\n", mem_open_threshold); + set_iwake_register(isys, GDA_MEMOPEN_THRESHOLD_INDEX, + mem_open_threshold); + } else { + set_iwake_register(isys, GDA_IWAKE_THRESHOLD_INDEX, + iwake_threshold); + } + + iwake_critical_threshold = iwake_threshold + + (IS_PIXEL_BUFFER_PAGES - iwake_threshold) / 2; + + dev_dbg(dev, "threshold: %u critical: %u\n", iwake_threshold, + iwake_critical_threshold); + + set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX, + iwake_critical_threshold); + + writel(VAL_PKGC_PMON_CFG_RESET, + isys->adev->isp->base + REG_PKGC_PMON_CFG); + writel(VAL_PKGC_PMON_CFG_START, + isys->adev->isp->base + REG_PKGC_PMON_CFG); +unlock_exit: + mutex_unlock(&iwake_watermark->mutex); +} + +static void isys_iwake_watermark_init(struct ipu6_isys *isys) +{ + struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; + + INIT_LIST_HEAD(&iwake_watermark->video_list); + mutex_init(&iwake_watermark->mutex); + + iwake_watermark->ltrdid.lut_ltr.value = 0; + iwake_watermark->isys = isys; + iwake_watermark->iwake_enabled = false; + iwake_watermark->force_iwake_disable = false; +} + +static void isys_iwake_watermark_cleanup(struct ipu6_isys *isys) +{ + struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; + + mutex_lock(&iwake_watermark->mutex); + list_del(&iwake_watermark->video_list); + mutex_unlock(&iwake_watermark->mutex); + + mutex_destroy(&iwake_watermark->mutex); +} + +/* The .bound() notifier callback when a match is found */ +static int isys_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asc) +{ + struct ipu6_isys *isys = + container_of(notifier, struct ipu6_isys, notifier); + struct sensor_async_sd *s_asd = + container_of(asc, struct sensor_async_sd, asc); + int ret; + + if (s_asd->csi2.port >= isys->pdata->ipdata->csi2.nports) { + dev_err(&isys->adev->auxdev.dev, "invalid csi2 port %u\n", + s_asd->csi2.port); + return -EINVAL; + } + + ret = ipu_bridge_instantiate_vcm(sd->dev); + if (ret) { + dev_err(&isys->adev->auxdev.dev, "instantiate vcm failed\n"); + return ret; + } + + dev_dbg(&isys->adev->auxdev.dev, "bind %s nlanes is %d port is %d\n", + sd->name, s_asd->csi2.nlanes, s_asd->csi2.port); + ret = isys_complete_ext_device_registration(isys, sd, &s_asd->csi2); + if (ret) + return ret; + + return v4l2_device_register_subdev_nodes(&isys->v4l2_dev); +} + +static int isys_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct ipu6_isys *isys = + container_of(notifier, struct ipu6_isys, notifier); + + return v4l2_device_register_subdev_nodes(&isys->v4l2_dev); +} + +static const struct v4l2_async_notifier_operations isys_async_ops = { + .bound = isys_notifier_bound, + .complete = isys_notifier_complete, +}; + +#define ISYS_MAX_PORTS 8 +static int isys_notifier_init(struct ipu6_isys *isys) +{ + struct ipu6_device *isp = isys->adev->isp; + struct device *dev = &isp->pdev->dev; + unsigned int i; + int ret; + + v4l2_async_nf_init(&isys->notifier, &isys->v4l2_dev); + + for (i = 0; i < ISYS_MAX_PORTS; i++) { + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct sensor_async_sd *s_asd; + struct fwnode_handle *ep; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), i, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + continue; + + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret) { + dev_err(dev, "fwnode endpoint parse failed: %d\n", ret); + goto err_parse; + } + + s_asd = v4l2_async_nf_add_fwnode_remote(&isys->notifier, ep, + struct sensor_async_sd); + if (IS_ERR(s_asd)) { + ret = PTR_ERR(s_asd); + dev_err(dev, "add remove fwnode failed: %d\n", ret); + goto err_parse; + } + + s_asd->csi2.port = vep.base.port; + s_asd->csi2.nlanes = vep.bus.mipi_csi2.num_data_lanes; + + dev_dbg(dev, "remote endpoint port %d with %d lanes added\n", + s_asd->csi2.port, s_asd->csi2.nlanes); + + fwnode_handle_put(ep); + + continue; + +err_parse: + fwnode_handle_put(ep); + v4l2_async_nf_cleanup(&isys->notifier); + return ret; + } + + isys->notifier.ops = &isys_async_ops; + ret = v4l2_async_nf_register(&isys->notifier); + if (ret) { + dev_err(dev, "failed to register async notifier : %d\n", ret); + v4l2_async_nf_cleanup(&isys->notifier); + } + + return ret; +} + +static void isys_notifier_cleanup(struct ipu6_isys *isys) +{ + v4l2_async_nf_unregister(&isys->notifier); + v4l2_async_nf_cleanup(&isys->notifier); +} + +static int isys_register_devices(struct ipu6_isys *isys) +{ + struct device *dev = &isys->adev->auxdev.dev; + struct pci_dev *pdev = isys->adev->isp->pdev; + int ret; + + isys->media_dev.dev = dev; + media_device_pci_init(&isys->media_dev, + pdev, IPU6_MEDIA_DEV_MODEL_NAME); + + strscpy(isys->v4l2_dev.name, isys->media_dev.model, + sizeof(isys->v4l2_dev.name)); + + ret = media_device_register(&isys->media_dev); + if (ret < 0) + goto out_media_device_unregister; + + isys->v4l2_dev.mdev = &isys->media_dev; + isys->v4l2_dev.ctrl_handler = NULL; + + ret = v4l2_device_register(dev, &isys->v4l2_dev); + if (ret < 0) + goto out_media_device_unregister; + + ret = isys_register_video_devices(isys); + if (ret) + goto out_v4l2_device_unregister; + + ret = isys_csi2_register_subdevices(isys); + if (ret) + goto out_isys_unregister_video_device; + + ret = isys_csi2_create_media_links(isys); + if (ret) + goto out_isys_unregister_subdevices; + + ret = isys_notifier_init(isys); + if (ret) + goto out_isys_unregister_subdevices; + + return 0; + +out_isys_unregister_subdevices: + isys_csi2_unregister_subdevices(isys); + +out_isys_unregister_video_device: + isys_unregister_video_devices(isys); + +out_v4l2_device_unregister: + v4l2_device_unregister(&isys->v4l2_dev); + +out_media_device_unregister: + media_device_unregister(&isys->media_dev); + media_device_cleanup(&isys->media_dev); + + dev_err(dev, "failed to register isys devices\n"); + + return ret; +} + +static void isys_unregister_devices(struct ipu6_isys *isys) +{ + isys_unregister_video_devices(isys); + isys_csi2_unregister_subdevices(isys); + v4l2_device_unregister(&isys->v4l2_dev); + media_device_unregister(&isys->media_dev); + media_device_cleanup(&isys->media_dev); +} + +static int isys_runtime_pm_resume(struct device *dev) +{ + struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + struct ipu6_device *isp = adev->isp; + unsigned long flags; + int ret; + + ret = ipu6_mmu_hw_init(adev->mmu); + if (ret) + return ret; + + cpu_latency_qos_update_request(&isys->pm_qos, ISYS_PM_QOS_VALUE); + + ret = ipu6_buttress_start_tsc_sync(isp); + if (ret) + return ret; + + spin_lock_irqsave(&isys->power_lock, flags); + isys->power = 1; + spin_unlock_irqrestore(&isys->power_lock, flags); + + isys_setup_hw(isys); + + set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_ON); + + return 0; +} + +static int isys_runtime_pm_suspend(struct device *dev) +{ + struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); + struct ipu6_isys *isys = dev_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&isys->power_lock, flags); + isys->power = 0; + spin_unlock_irqrestore(&isys->power_lock, flags); + + mutex_lock(&isys->mutex); + isys->need_reset = false; + mutex_unlock(&isys->mutex); + + isys->phy_termcal_val = 0; + cpu_latency_qos_update_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE); + + set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_OFF); + + ipu6_mmu_hw_cleanup(adev->mmu); + + return 0; +} + +static int isys_suspend(struct device *dev) +{ + struct ipu6_isys *isys = dev_get_drvdata(dev); + + /* If stream is open, refuse to suspend */ + if (isys->stream_opened) + return -EBUSY; + + return 0; +} + +static int isys_resume(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops isys_pm_ops = { + .runtime_suspend = isys_runtime_pm_suspend, + .runtime_resume = isys_runtime_pm_resume, + .suspend = isys_suspend, + .resume = isys_resume, +}; + +static void free_fw_msg_bufs(struct ipu6_isys *isys) +{ + struct isys_fw_msgs *fwmsg, *safe; + + list_for_each_entry_safe(fwmsg, safe, &isys->framebuflist, head) + ipu6_dma_free(isys->adev, sizeof(struct isys_fw_msgs), fwmsg, + fwmsg->dma_addr, 0); + + list_for_each_entry_safe(fwmsg, safe, &isys->framebuflist_fw, head) + ipu6_dma_free(isys->adev, sizeof(struct isys_fw_msgs), fwmsg, + fwmsg->dma_addr, 0); +} + +static int alloc_fw_msg_bufs(struct ipu6_isys *isys, int amount) +{ + struct isys_fw_msgs *addr; + dma_addr_t dma_addr; + unsigned long flags; + unsigned int i; + + for (i = 0; i < amount; i++) { + addr = ipu6_dma_alloc(isys->adev, sizeof(*addr), + &dma_addr, GFP_KERNEL, 0); + if (!addr) + break; + addr->dma_addr = dma_addr; + + spin_lock_irqsave(&isys->listlock, flags); + list_add(&addr->head, &isys->framebuflist); + spin_unlock_irqrestore(&isys->listlock, flags); + } + + if (i == amount) + return 0; + + spin_lock_irqsave(&isys->listlock, flags); + while (!list_empty(&isys->framebuflist)) { + addr = list_first_entry(&isys->framebuflist, + struct isys_fw_msgs, head); + list_del(&addr->head); + spin_unlock_irqrestore(&isys->listlock, flags); + ipu6_dma_free(isys->adev, sizeof(struct isys_fw_msgs), addr, + addr->dma_addr, 0); + spin_lock_irqsave(&isys->listlock, flags); + } + spin_unlock_irqrestore(&isys->listlock, flags); + + return -ENOMEM; +} + +struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream) +{ + struct ipu6_isys *isys = stream->isys; + struct device *dev = &isys->adev->auxdev.dev; + struct isys_fw_msgs *msg; + unsigned long flags; + int ret; + + spin_lock_irqsave(&isys->listlock, flags); + if (list_empty(&isys->framebuflist)) { + spin_unlock_irqrestore(&isys->listlock, flags); + dev_dbg(dev, "Frame list empty\n"); + + ret = alloc_fw_msg_bufs(isys, 5); + if (ret < 0) + return NULL; + + spin_lock_irqsave(&isys->listlock, flags); + if (list_empty(&isys->framebuflist)) { + spin_unlock_irqrestore(&isys->listlock, flags); + dev_err(dev, "Frame list empty\n"); + return NULL; + } + } + msg = list_last_entry(&isys->framebuflist, struct isys_fw_msgs, head); + list_move(&msg->head, &isys->framebuflist_fw); + spin_unlock_irqrestore(&isys->listlock, flags); + memset(&msg->fw_msg, 0, sizeof(msg->fw_msg)); + + return msg; +} + +void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys) +{ + struct isys_fw_msgs *fwmsg, *fwmsg0; + unsigned long flags; + + spin_lock_irqsave(&isys->listlock, flags); + list_for_each_entry_safe(fwmsg, fwmsg0, &isys->framebuflist_fw, head) + list_move(&fwmsg->head, &isys->framebuflist); + spin_unlock_irqrestore(&isys->listlock, flags); +} + +void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, uintptr_t data) +{ + struct isys_fw_msgs *msg; + unsigned long flags; + void *ptr = (void *)data; + + if (!ptr) + return; + + spin_lock_irqsave(&isys->listlock, flags); + msg = container_of(ptr, struct isys_fw_msgs, fw_msg.dummy); + list_move(&msg->head, &isys->framebuflist); + spin_unlock_irqrestore(&isys->listlock, flags); +} + +static int isys_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *auxdev_id) +{ + const struct ipu6_isys_internal_csi2_pdata *csi2_pdata; + struct ipu6_bus_device *adev = auxdev_to_adev(auxdev); + struct ipu6_device *isp = adev->isp; + const struct firmware *fw; + struct ipu6_isys *isys; + unsigned int i; + int ret; + + if (!isp->bus_ready_to_probe) + return -EPROBE_DEFER; + + isys = devm_kzalloc(&auxdev->dev, sizeof(*isys), GFP_KERNEL); + if (!isys) + return -ENOMEM; + + adev->auxdrv_data = + (const struct ipu6_auxdrv_data *)auxdev_id->driver_data; + adev->auxdrv = to_auxiliary_drv(auxdev->dev.driver); + isys->adev = adev; + isys->pdata = adev->pdata; + csi2_pdata = &isys->pdata->ipdata->csi2; + + isys->csi2 = devm_kcalloc(&auxdev->dev, csi2_pdata->nports, + sizeof(*isys->csi2), GFP_KERNEL); + if (!isys->csi2) + return -ENOMEM; + + /* initial sensor type */ + isys->sensor_type = isys->pdata->ipdata->sensor_type_start; + + spin_lock_init(&isys->streams_lock); + spin_lock_init(&isys->power_lock); + isys->power = 0; + isys->phy_termcal_val = 0; + + mutex_init(&isys->mutex); + mutex_init(&isys->stream_mutex); + + spin_lock_init(&isys->listlock); + INIT_LIST_HEAD(&isys->framebuflist); + INIT_LIST_HEAD(&isys->framebuflist_fw); + + isys->icache_prefetch = 0; + + dev_set_drvdata(&auxdev->dev, isys); + + isys_stream_init(isys); + + if (!isp->secure_mode) { + fw = isp->cpd_fw; + ret = ipu6_buttress_map_fw_image(adev, fw, &adev->fw_sgt); + if (ret) + goto release_firmware; + + ret = ipu6_cpd_create_pkg_dir(adev, isp->cpd_fw->data); + if (ret) + goto remove_shared_buffer; + } + + cpu_latency_qos_add_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE); + + ret = alloc_fw_msg_bufs(isys, 20); + if (ret < 0) + goto out_remove_pkg_dir_shared_buffer; + + isys_iwake_watermark_init(isys); + + if (is_ipu6se(adev->isp->hw_ver)) + isys->phy_set_power = ipu6_isys_jsl_phy_set_power; + else if (is_ipu6ep_mtl(adev->isp->hw_ver)) + isys->phy_set_power = ipu6_isys_dwc_phy_set_power; + else + isys->phy_set_power = ipu6_isys_mcd_phy_set_power; + + ret = isys_register_devices(isys); + if (ret) + goto free_fw_msg_bufs; + + return 0; + +free_fw_msg_bufs: + free_fw_msg_bufs(isys); +out_remove_pkg_dir_shared_buffer: + cpu_latency_qos_remove_request(&isys->pm_qos); + if (!isp->secure_mode) + ipu6_cpd_free_pkg_dir(adev); +remove_shared_buffer: + if (!isp->secure_mode) + ipu6_buttress_unmap_fw_image(adev, &adev->fw_sgt); +release_firmware: + if (!isp->secure_mode) + release_firmware(adev->fw); + + for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) + mutex_destroy(&isys->streams[i].mutex); + + mutex_destroy(&isys->mutex); + mutex_destroy(&isys->stream_mutex); + + return ret; +} + +static void isys_remove(struct auxiliary_device *auxdev) +{ + struct ipu6_bus_device *adev = auxdev_to_adev(auxdev); + struct ipu6_isys *isys = dev_get_drvdata(&auxdev->dev); + struct ipu6_device *isp = adev->isp; + unsigned int i; + + free_fw_msg_bufs(isys); + + isys_unregister_devices(isys); + isys_notifier_cleanup(isys); + + cpu_latency_qos_remove_request(&isys->pm_qos); + + if (!isp->secure_mode) { + ipu6_cpd_free_pkg_dir(adev); + ipu6_buttress_unmap_fw_image(adev, &adev->fw_sgt); + release_firmware(adev->fw); + } + + for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) + mutex_destroy(&isys->streams[i].mutex); + + isys_iwake_watermark_cleanup(isys); + mutex_destroy(&isys->stream_mutex); + mutex_destroy(&isys->mutex); +} + +struct fwmsg { + int type; + char *msg; + bool valid_ts; +}; + +static const struct fwmsg fw_msg[] = { + {IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE, "STREAM_OPEN_DONE", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK, "STREAM_CLOSE_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK, "STREAM_START_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK, + "STREAM_START_AND_CAPTURE_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK, "STREAM_STOP_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK, "STREAM_FLUSH_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY, "PIN_DATA_READY", 1}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK, "STREAM_CAPTURE_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE, + "STREAM_START_AND_CAPTURE_DONE", 1}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE, "STREAM_CAPTURE_DONE", 1}, + {IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF, "FRAME_SOF", 1}, + {IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF, "FRAME_EOF", 1}, + {IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY, "STATS_READY", 1}, + {-1, "UNKNOWN MESSAGE", 0} +}; + +static u32 resp_type_to_index(int type) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(fw_msg); i++) + if (fw_msg[i].type == type) + return i; + + return ARRAY_SIZE(fw_msg) - 1; +} + +static int isys_isr_one(struct ipu6_bus_device *adev) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + struct ipu6_fw_isys_resp_info_abi *resp; + struct ipu6_isys_stream *stream; + struct ipu6_isys_csi2 *csi2 = NULL; + u32 index; + u64 ts; + + if (!isys->fwcom) + return 1; + + resp = ipu6_fw_isys_get_resp(isys->fwcom, IPU6_BASE_MSG_RECV_QUEUES); + if (!resp) + return 1; + + ts = (u64)resp->timestamp[1] << 32 | resp->timestamp[0]; + + index = resp_type_to_index(resp->type); + dev_dbg(&adev->auxdev.dev, + "FW resp %02d %s, stream %u, ts 0x%16.16llx, pin %d\n", + resp->type, fw_msg[index].msg, resp->stream_handle, + fw_msg[index].valid_ts ? ts : 0, resp->pin_id); + + if (resp->error_info.error == IPU6_FW_ISYS_ERROR_STREAM_IN_SUSPENSION) + /* Suspension is kind of special case: not enough buffers */ + dev_dbg(&adev->auxdev.dev, + "FW error resp SUSPENSION, details %d\n", + resp->error_info.error_details); + else if (resp->error_info.error) + dev_dbg(&adev->auxdev.dev, + "FW error resp error %d, details %d\n", + resp->error_info.error, resp->error_info.error_details); + + if (resp->stream_handle >= IPU6_ISYS_MAX_STREAMS) { + dev_err(&adev->auxdev.dev, "bad stream handle %u\n", + resp->stream_handle); + goto leave; + } + + stream = ipu6_isys_query_stream_by_handle(isys, resp->stream_handle); + if (!stream) { + dev_err(&adev->auxdev.dev, "stream of stream_handle %u is unused\n", + resp->stream_handle); + goto leave; + } + stream->error = resp->error_info.error; + + csi2 = ipu6_isys_subdev_to_csi2(stream->asd); + + switch (resp->type) { + case IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE: + complete(&stream->stream_open_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK: + complete(&stream->stream_close_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK: + complete(&stream->stream_start_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK: + complete(&stream->stream_start_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK: + complete(&stream->stream_stop_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK: + complete(&stream->stream_stop_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY: + /* + * firmware only release the capture msg until software + * get pin_data_ready event + */ + ipu6_put_fw_msg_buf(ipu6_bus_get_drvdata(adev), resp->buf_id); + if (resp->pin_id < IPU6_ISYS_OUTPUT_PINS && + stream->output_pins_queue[resp->pin_id]) + ipu6_isys_queue_buf_ready(stream, resp); + else + dev_warn(&adev->auxdev.dev, + "%d:No queue for pin id %d\n", + resp->stream_handle, resp->pin_id); + if (csi2) + ipu6_isys_csi2_error(csi2); + + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK: + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE: + case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE: + break; + case IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF: + + ipu6_isys_csi2_sof_event_by_stream(stream); + stream->seq[stream->seq_index].sequence = + atomic_read(&stream->sequence) - 1; + stream->seq[stream->seq_index].timestamp = ts; + dev_dbg(&adev->auxdev.dev, + "sof: handle %d: (index %u), timestamp 0x%16.16llx\n", + resp->stream_handle, + stream->seq[stream->seq_index].sequence, ts); + stream->seq_index = (stream->seq_index + 1) + % IPU6_ISYS_MAX_PARALLEL_SOF; + break; + case IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF: + ipu6_isys_csi2_eof_event_by_stream(stream); + dev_dbg(&adev->auxdev.dev, + "eof: handle %d: (index %u), timestamp 0x%16.16llx\n", + resp->stream_handle, + stream->seq[stream->seq_index].sequence, ts); + break; + case IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY: + break; + default: + dev_err(&adev->auxdev.dev, "%d:unknown response type %u\n", + resp->stream_handle, resp->type); + break; + } + + ipu6_isys_put_stream(stream); +leave: + ipu6_fw_isys_put_resp(isys->fwcom, IPU6_BASE_MSG_RECV_QUEUES); + return 0; +} + +static const struct ipu6_auxdrv_data ipu6_isys_auxdrv_data = { + .isr = isys_isr, + .isr_threaded = NULL, + .wake_isr_thread = false, +}; + +static const struct auxiliary_device_id ipu6_isys_id_table[] = { + { + .name = "intel_ipu6.isys", + .driver_data = (kernel_ulong_t)&ipu6_isys_auxdrv_data, + }, + { } +}; +MODULE_DEVICE_TABLE(auxiliary, ipu6_isys_id_table); + +static struct auxiliary_driver isys_driver = { + .name = IPU6_ISYS_NAME, + .probe = isys_probe, + .remove = isys_remove, + .id_table = ipu6_isys_id_table, + .driver = { + .pm = &isys_pm_ops, + }, +}; + +module_auxiliary_driver(isys_driver); + +MODULE_AUTHOR("Sakari Ailus "); +MODULE_AUTHOR("Tianshu Qiu "); +MODULE_AUTHOR("Bingbu Cao "); +MODULE_AUTHOR("Yunliang Ding "); +MODULE_AUTHOR("Hongju Wang"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Intel IPU6 input system driver"); +MODULE_IMPORT_NS("INTEL_IPU6"); +MODULE_IMPORT_NS("INTEL_IPU_BRIDGE"); diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.h b/drivers/media/pci/intel/ipu6/ipu6-isys.h new file mode 100644 index 000000000..7fb8cb820 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.h @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013--2024 Intel Corporation */ + +#ifndef IPU6_ISYS_H +#define IPU6_ISYS_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ipu6.h" +#include "ipu6-fw-isys.h" +#include "ipu6-isys-csi2.h" +#include "ipu6-isys-video.h" + +struct ipu6_bus_device; + +#define IPU6_ISYS_ENTITY_PREFIX "Intel IPU6" +/* FW support max 16 streams */ +#define IPU6_ISYS_MAX_STREAMS 16 +#define ISYS_UNISPART_IRQS (IPU6_ISYS_UNISPART_IRQ_SW | \ + IPU6_ISYS_UNISPART_IRQ_CSI0 | \ + IPU6_ISYS_UNISPART_IRQ_CSI1) + +/* + * Current message queue configuration. These must be big enough + * so that they never gets full. Queues are located in system memory + */ +#define IPU6_ISYS_SIZE_RECV_QUEUE 40 +#define IPU6_ISYS_SIZE_SEND_QUEUE 40 +#define IPU6_ISYS_SIZE_PROXY_RECV_QUEUE 5 +#define IPU6_ISYS_SIZE_PROXY_SEND_QUEUE 5 +#define IPU6_ISYS_NUM_RECV_QUEUE 1 + +#define IPU6_ISYS_MIN_WIDTH 2U +#define IPU6_ISYS_MIN_HEIGHT 1U +#define IPU6_ISYS_MAX_WIDTH 4672U +#define IPU6_ISYS_MAX_HEIGHT 3416U + +/* the threshold granularity is 2KB on IPU6 */ +#define IPU6_SRAM_GRANULARITY_SHIFT 11 +#define IPU6_SRAM_GRANULARITY_SIZE 2048 +/* the threshold granularity is 1KB on IPU6SE */ +#define IPU6SE_SRAM_GRANULARITY_SHIFT 10 +#define IPU6SE_SRAM_GRANULARITY_SIZE 1024 +/* IS pixel buffer is 256KB, MaxSRAMSize is 200KB on IPU6 */ +#define IPU6_MAX_SRAM_SIZE (200 << 10) +/* IS pixel buffer is 128KB, MaxSRAMSize is 96KB on IPU6SE */ +#define IPU6SE_MAX_SRAM_SIZE (96 << 10) + +#define IPU6EP_LTR_VALUE 200 +#define IPU6EP_MIN_MEMOPEN_TH 0x4 +#define IPU6EP_MTL_LTR_VALUE 1023 +#define IPU6EP_MTL_MIN_MEMOPEN_TH 0xc + +struct ltr_did { + union { + u32 value; + struct { + u8 val0; + u8 val1; + u8 val2; + u8 val3; + } bits; + } lut_ltr; + union { + u32 value; + struct { + u8 th0; + u8 th1; + u8 th2; + u8 th3; + } bits; + } lut_fill_time; +}; + +struct isys_iwake_watermark { + bool iwake_enabled; + bool force_iwake_disable; + u32 iwake_threshold; + u64 isys_pixelbuffer_datarate; + struct ltr_did ltrdid; + struct mutex mutex; /* protect whole struct */ + struct ipu6_isys *isys; + struct list_head video_list; +}; + +struct ipu6_isys_csi2_config { + u32 nlanes; + u32 port; +}; + +struct sensor_async_sd { + struct v4l2_async_connection asc; + struct ipu6_isys_csi2_config csi2; +}; + +/* + * struct ipu6_isys + * + * @media_dev: Media device + * @v4l2_dev: V4L2 device + * @adev: ISYS bus device + * @power: Is ISYS powered on or not? + * @isr_bits: Which bits does the ISR handle? + * @power_lock: Serialise access to power (power state in general) + * @csi2_rx_ctrl_cached: cached shared value between all CSI2 receivers + * @streams_lock: serialise access to streams + * @streams: streams per firmware stream ID + * @fwcom: fw communication layer private pointer + * or optional external library private pointer + * @phy_termcal_val: the termination calibration value, only used for DWC PHY + * @need_reset: Isys requires d0i0->i3 transition + * @ref_count: total number of callers fw open + * @mutex: serialise access isys video open/release related operations + * @stream_mutex: serialise stream start and stop, queueing requests + * @pdata: platform data pointer + * @csi2: CSI-2 receivers + */ +struct ipu6_isys { + struct media_device media_dev; + struct v4l2_device v4l2_dev; + struct ipu6_bus_device *adev; + + int power; + spinlock_t power_lock; + u32 isr_csi2_bits; + u32 csi2_rx_ctrl_cached; + spinlock_t streams_lock; + struct ipu6_isys_stream streams[IPU6_ISYS_MAX_STREAMS]; + int streams_ref_count[IPU6_ISYS_MAX_STREAMS]; + void *fwcom; + u32 phy_termcal_val; + bool need_reset; + bool icache_prefetch; + bool csi2_cse_ipc_not_supported; + unsigned int ref_count; + unsigned int stream_opened; + unsigned int sensor_type; + + struct mutex mutex; + struct mutex stream_mutex; + + struct ipu6_isys_pdata *pdata; + + int (*phy_set_power)(struct ipu6_isys *isys, + struct ipu6_isys_csi2_config *cfg, + const struct ipu6_isys_csi2_timing *timing, + bool on); + + struct ipu6_isys_csi2 *csi2; + + struct pm_qos_request pm_qos; + spinlock_t listlock; /* Protect framebuflist */ + struct list_head framebuflist; + struct list_head framebuflist_fw; + struct v4l2_async_notifier notifier; + struct isys_iwake_watermark iwake_watermark; +}; + +struct isys_fw_msgs { + union { + u64 dummy; + struct ipu6_fw_isys_frame_buff_set_abi frame; + struct ipu6_fw_isys_stream_cfg_data_abi stream; + } fw_msg; + struct list_head head; + dma_addr_t dma_addr; +}; + +struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream); +void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, uintptr_t data); +void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys); + +extern const struct v4l2_ioctl_ops ipu6_isys_ioctl_ops; + +void update_watermark_setting(struct ipu6_isys *isys); + +int ipu6_isys_mcd_phy_set_power(struct ipu6_isys *isys, + struct ipu6_isys_csi2_config *cfg, + const struct ipu6_isys_csi2_timing *timing, + bool on); + +int ipu6_isys_dwc_phy_set_power(struct ipu6_isys *isys, + struct ipu6_isys_csi2_config *cfg, + const struct ipu6_isys_csi2_timing *timing, + bool on); + +int ipu6_isys_jsl_phy_set_power(struct ipu6_isys *isys, + struct ipu6_isys_csi2_config *cfg, + const struct ipu6_isys_csi2_timing *timing, + bool on); +#endif /* IPU6_ISYS_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.c b/drivers/media/pci/intel/ipu6/ipu6-mmu.c new file mode 100644 index 000000000..0e6dca36b --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.c @@ -0,0 +1,806 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipu6.h" +#include "ipu6-dma.h" +#include "ipu6-mmu.h" +#include "ipu6-platform-regs.h" + +#define ISP_PAGE_SHIFT 12 +#define ISP_PAGE_SIZE BIT(ISP_PAGE_SHIFT) +#define ISP_PAGE_MASK (~(ISP_PAGE_SIZE - 1)) + +#define ISP_L1PT_SHIFT 22 +#define ISP_L1PT_MASK (~((1U << ISP_L1PT_SHIFT) - 1)) + +#define ISP_L2PT_SHIFT 12 +#define ISP_L2PT_MASK (~(ISP_L1PT_MASK | (~(ISP_PAGE_MASK)))) + +#define ISP_L1PT_PTES 1024 +#define ISP_L2PT_PTES 1024 + +#define ISP_PADDR_SHIFT 12 + +#define REG_TLB_INVALIDATE 0x0000 + +#define REG_L1_PHYS 0x0004 /* 27-bit pfn */ +#define REG_INFO 0x0008 + +#define TBL_PHYS_ADDR(a) ((phys_addr_t)(a) << ISP_PADDR_SHIFT) + +static void tlb_invalidate(struct ipu6_mmu *mmu) +{ + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&mmu->ready_lock, flags); + if (!mmu->ready) { + spin_unlock_irqrestore(&mmu->ready_lock, flags); + return; + } + + for (i = 0; i < mmu->nr_mmus; i++) { + /* + * To avoid the HW bug induced dead lock in some of the IPU6 + * MMUs on successive invalidate calls, we need to first do a + * read to the page table base before writing the invalidate + * register. MMUs which need to implement this WA, will have + * the insert_read_before_invalidate flags set as true. + * Disregard the return value of the read. + */ + if (mmu->mmu_hw[i].insert_read_before_invalidate) + readl(mmu->mmu_hw[i].base + REG_L1_PHYS); + + writel(0xffffffff, mmu->mmu_hw[i].base + + REG_TLB_INVALIDATE); + /* + * The TLB invalidation is a "single cycle" (IOMMU clock cycles) + * When the actual MMIO write reaches the IPU6 TLB Invalidate + * register, wmb() will force the TLB invalidate out if the CPU + * attempts to update the IOMMU page table (or sooner). + */ + wmb(); + } + spin_unlock_irqrestore(&mmu->ready_lock, flags); +} + +#ifdef DEBUG +static void page_table_dump(struct ipu6_mmu_info *mmu_info) +{ + u32 l1_idx; + + dev_dbg(mmu_info->dev, "begin IOMMU page table dump\n"); + + for (l1_idx = 0; l1_idx < ISP_L1PT_PTES; l1_idx++) { + u32 l2_idx; + u32 iova = (phys_addr_t)l1_idx << ISP_L1PT_SHIFT; + phys_addr_t l2_phys; + + if (mmu_info->l1_pt[l1_idx] == mmu_info->dummy_l2_pteval) + continue; + + l2_phys = TBL_PHYS_ADDR(mmu_info->l1_pt[l1_idx]); + dev_dbg(mmu_info->dev, + "l1 entry %u; iovas 0x%8.8x-0x%8.8x, at %pap\n", + l1_idx, iova, iova + ISP_PAGE_SIZE, &l2_phys); + + for (l2_idx = 0; l2_idx < ISP_L2PT_PTES; l2_idx++) { + u32 *l2_pt = mmu_info->l2_pts[l1_idx]; + u32 iova2 = iova + (l2_idx << ISP_L2PT_SHIFT); + + if (l2_pt[l2_idx] == mmu_info->dummy_page_pteval) + continue; + + dev_dbg(mmu_info->dev, + "\tl2 entry %u; iova 0x%8.8x, phys %pa\n", + l2_idx, iova2, + TBL_PHYS_ADDR(l2_pt[l2_idx])); + } + } + + dev_dbg(mmu_info->dev, "end IOMMU page table dump\n"); +} +#endif /* DEBUG */ + +static dma_addr_t map_single(struct ipu6_mmu_info *mmu_info, void *ptr) +{ + dma_addr_t dma; + + dma = dma_map_single(mmu_info->dev, ptr, PAGE_SIZE, DMA_BIDIRECTIONAL); + if (dma_mapping_error(mmu_info->dev, dma)) + return 0; + + return dma; +} + +static int get_dummy_page(struct ipu6_mmu_info *mmu_info) +{ + void *pt = (void *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32); + dma_addr_t dma; + + if (!pt) + return -ENOMEM; + + dev_dbg(mmu_info->dev, "dummy_page: get_zeroed_page() == %p\n", pt); + + dma = map_single(mmu_info, pt); + if (!dma) { + dev_err(mmu_info->dev, "Failed to map dummy page\n"); + goto err_free_page; + } + + mmu_info->dummy_page = pt; + mmu_info->dummy_page_pteval = dma >> ISP_PAGE_SHIFT; + + return 0; + +err_free_page: + free_page((unsigned long)pt); + return -ENOMEM; +} + +static void free_dummy_page(struct ipu6_mmu_info *mmu_info) +{ + dma_unmap_single(mmu_info->dev, + TBL_PHYS_ADDR(mmu_info->dummy_page_pteval), + PAGE_SIZE, DMA_BIDIRECTIONAL); + free_page((unsigned long)mmu_info->dummy_page); +} + +static int alloc_dummy_l2_pt(struct ipu6_mmu_info *mmu_info) +{ + u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32); + dma_addr_t dma; + unsigned int i; + + if (!pt) + return -ENOMEM; + + dev_dbg(mmu_info->dev, "dummy_l2: get_zeroed_page() = %p\n", pt); + + dma = map_single(mmu_info, pt); + if (!dma) { + dev_err(mmu_info->dev, "Failed to map l2pt page\n"); + goto err_free_page; + } + + for (i = 0; i < ISP_L2PT_PTES; i++) + pt[i] = mmu_info->dummy_page_pteval; + + mmu_info->dummy_l2_pt = pt; + mmu_info->dummy_l2_pteval = dma >> ISP_PAGE_SHIFT; + + return 0; + +err_free_page: + free_page((unsigned long)pt); + return -ENOMEM; +} + +static void free_dummy_l2_pt(struct ipu6_mmu_info *mmu_info) +{ + dma_unmap_single(mmu_info->dev, + TBL_PHYS_ADDR(mmu_info->dummy_l2_pteval), + PAGE_SIZE, DMA_BIDIRECTIONAL); + free_page((unsigned long)mmu_info->dummy_l2_pt); +} + +static u32 *alloc_l1_pt(struct ipu6_mmu_info *mmu_info) +{ + u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32); + dma_addr_t dma; + unsigned int i; + + if (!pt) + return NULL; + + dev_dbg(mmu_info->dev, "alloc_l1: get_zeroed_page() = %p\n", pt); + + for (i = 0; i < ISP_L1PT_PTES; i++) + pt[i] = mmu_info->dummy_l2_pteval; + + dma = map_single(mmu_info, pt); + if (!dma) { + dev_err(mmu_info->dev, "Failed to map l1pt page\n"); + goto err_free_page; + } + + mmu_info->l1_pt_dma = dma >> ISP_PADDR_SHIFT; + dev_dbg(mmu_info->dev, "l1 pt %p mapped at %pad\n", pt, &dma); + + return pt; + +err_free_page: + free_page((unsigned long)pt); + return NULL; +} + +static u32 *alloc_l2_pt(struct ipu6_mmu_info *mmu_info) +{ + u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32); + unsigned int i; + + if (!pt) + return NULL; + + dev_dbg(mmu_info->dev, "alloc_l2: get_zeroed_page() = %p\n", pt); + + for (i = 0; i < ISP_L2PT_PTES; i++) + pt[i] = mmu_info->dummy_page_pteval; + + return pt; +} + +static void l2_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova, + phys_addr_t dummy, size_t size) +{ + unsigned int l2_entries; + unsigned int l2_idx; + unsigned long flags; + u32 l1_idx; + u32 *l2_pt; + + spin_lock_irqsave(&mmu_info->lock, flags); + for (l1_idx = iova >> ISP_L1PT_SHIFT; + size > 0 && l1_idx < ISP_L1PT_PTES; l1_idx++) { + dev_dbg(mmu_info->dev, + "unmapping l2 pgtable (l1 index %u (iova 0x%8.8lx))\n", + l1_idx, iova); + + if (mmu_info->l1_pt[l1_idx] == mmu_info->dummy_l2_pteval) { + dev_err(mmu_info->dev, + "unmap not mapped iova 0x%8.8lx l1 index %u\n", + iova, l1_idx); + continue; + } + l2_pt = mmu_info->l2_pts[l1_idx]; + + l2_entries = 0; + for (l2_idx = (iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT; + size > 0 && l2_idx < ISP_L2PT_PTES; l2_idx++) { + phys_addr_t pteval = TBL_PHYS_ADDR(l2_pt[l2_idx]); + + dev_dbg(mmu_info->dev, + "unmap l2 index %u with pteval 0x%p\n", + l2_idx, &pteval); + l2_pt[l2_idx] = mmu_info->dummy_page_pteval; + + iova += ISP_PAGE_SIZE; + size -= ISP_PAGE_SIZE; + + l2_entries++; + } + + WARN_ON_ONCE(!l2_entries); + clflush_cache_range(&l2_pt[l2_idx - l2_entries], + sizeof(l2_pt[0]) * l2_entries); + } + + WARN_ON_ONCE(size); + spin_unlock_irqrestore(&mmu_info->lock, flags); +} + +static int l2_map(struct ipu6_mmu_info *mmu_info, unsigned long iova, + phys_addr_t paddr, size_t size) +{ + struct device *dev = mmu_info->dev; + unsigned int l2_entries; + u32 *l2_pt, *l2_virt; + unsigned int l2_idx; + unsigned long flags; + size_t mapped = 0; + dma_addr_t dma; + u32 l1_entry; + u32 l1_idx; + int err = 0; + + spin_lock_irqsave(&mmu_info->lock, flags); + + paddr = ALIGN(paddr, ISP_PAGE_SIZE); + for (l1_idx = iova >> ISP_L1PT_SHIFT; + size > 0 && l1_idx < ISP_L1PT_PTES; l1_idx++) { + dev_dbg(dev, + "mapping l2 page table for l1 index %u (iova %8.8x)\n", + l1_idx, (u32)iova); + + l1_entry = mmu_info->l1_pt[l1_idx]; + if (l1_entry == mmu_info->dummy_l2_pteval) { + l2_virt = mmu_info->l2_pts[l1_idx]; + if (likely(!l2_virt)) { + l2_virt = alloc_l2_pt(mmu_info); + if (!l2_virt) { + err = -ENOMEM; + goto error; + } + } + + dma = map_single(mmu_info, l2_virt); + if (!dma) { + dev_err(dev, "Failed to map l2pt page\n"); + free_page((unsigned long)l2_virt); + err = -EINVAL; + goto error; + } + + l1_entry = dma >> ISP_PADDR_SHIFT; + + dev_dbg(dev, "page for l1_idx %u %p allocated\n", + l1_idx, l2_virt); + mmu_info->l1_pt[l1_idx] = l1_entry; + mmu_info->l2_pts[l1_idx] = l2_virt; + + clflush_cache_range(&mmu_info->l1_pt[l1_idx], + sizeof(mmu_info->l1_pt[l1_idx])); + } + + l2_pt = mmu_info->l2_pts[l1_idx]; + l2_entries = 0; + + for (l2_idx = (iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT; + size > 0 && l2_idx < ISP_L2PT_PTES; l2_idx++) { + l2_pt[l2_idx] = paddr >> ISP_PADDR_SHIFT; + + dev_dbg(dev, "l2 index %u mapped as 0x%8.8x\n", l2_idx, + l2_pt[l2_idx]); + + iova += ISP_PAGE_SIZE; + paddr += ISP_PAGE_SIZE; + mapped += ISP_PAGE_SIZE; + size -= ISP_PAGE_SIZE; + + l2_entries++; + } + + WARN_ON_ONCE(!l2_entries); + clflush_cache_range(&l2_pt[l2_idx - l2_entries], + sizeof(l2_pt[0]) * l2_entries); + } + + spin_unlock_irqrestore(&mmu_info->lock, flags); + + return 0; + +error: + spin_unlock_irqrestore(&mmu_info->lock, flags); + /* unroll mapping in case something went wrong */ + if (mapped) + l2_unmap(mmu_info, iova - mapped, paddr - mapped, mapped); + + return err; +} + +static int __ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova, + phys_addr_t paddr, size_t size) +{ + u32 iova_start = round_down(iova, ISP_PAGE_SIZE); + u32 iova_end = ALIGN(iova + size, ISP_PAGE_SIZE); + + dev_dbg(mmu_info->dev, + "mapping iova 0x%8.8x--0x%8.8x, size %zu at paddr %pap\n", + iova_start, iova_end, size, &paddr); + + return l2_map(mmu_info, iova_start, paddr, size); +} + +static void __ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, + unsigned long iova, size_t size) +{ + l2_unmap(mmu_info, iova, 0, size); +} + +static int allocate_trash_buffer(struct ipu6_mmu *mmu) +{ + unsigned int n_pages = PFN_UP(IPU6_MMUV2_TRASH_RANGE); + struct iova *iova; + unsigned int i; + dma_addr_t dma; + unsigned long iova_addr; + int ret; + + /* Allocate 8MB in iova range */ + iova = alloc_iova(&mmu->dmap->iovad, n_pages, + PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0); + if (!iova) { + dev_err(mmu->dev, "cannot allocate iova range for trash\n"); + return -ENOMEM; + } + + dma = dma_map_page(mmu->dmap->mmu_info->dev, mmu->trash_page, 0, + PAGE_SIZE, DMA_BIDIRECTIONAL); + if (dma_mapping_error(mmu->dmap->mmu_info->dev, dma)) { + dev_err(mmu->dmap->mmu_info->dev, "Failed to map trash page\n"); + ret = -ENOMEM; + goto out_free_iova; + } + + mmu->pci_trash_page = dma; + + /* + * Map the 8MB iova address range to the same physical trash page + * mmu->trash_page which is already reserved at the probe + */ + iova_addr = iova->pfn_lo; + for (i = 0; i < n_pages; i++) { + ret = ipu6_mmu_map(mmu->dmap->mmu_info, PFN_PHYS(iova_addr), + mmu->pci_trash_page, PAGE_SIZE); + if (ret) { + dev_err(mmu->dev, + "mapping trash buffer range failed\n"); + goto out_unmap; + } + + iova_addr++; + } + + mmu->iova_trash_page = PFN_PHYS(iova->pfn_lo); + dev_dbg(mmu->dev, "iova trash buffer for MMUID: %d is %u\n", + mmu->mmid, (unsigned int)mmu->iova_trash_page); + return 0; + +out_unmap: + ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo), + PFN_PHYS(iova_size(iova))); + dma_unmap_page(mmu->dmap->mmu_info->dev, mmu->pci_trash_page, + PAGE_SIZE, DMA_BIDIRECTIONAL); +out_free_iova: + __free_iova(&mmu->dmap->iovad, iova); + return ret; +} + +int ipu6_mmu_hw_init(struct ipu6_mmu *mmu) +{ + struct ipu6_mmu_info *mmu_info; + unsigned long flags; + unsigned int i; + + mmu_info = mmu->dmap->mmu_info; + + /* Initialise the each MMU HW block */ + for (i = 0; i < mmu->nr_mmus; i++) { + struct ipu6_mmu_hw *mmu_hw = &mmu->mmu_hw[i]; + unsigned int j; + u16 block_addr; + + /* Write page table address per MMU */ + writel((phys_addr_t)mmu_info->l1_pt_dma, + mmu->mmu_hw[i].base + REG_L1_PHYS); + + /* Set info bits per MMU */ + writel(mmu->mmu_hw[i].info_bits, + mmu->mmu_hw[i].base + REG_INFO); + + /* Configure MMU TLB stream configuration for L1 */ + for (j = 0, block_addr = 0; j < mmu_hw->nr_l1streams; + block_addr += mmu->mmu_hw[i].l1_block_sz[j], j++) { + if (block_addr > IPU6_MAX_LI_BLOCK_ADDR) { + dev_err(mmu->dev, "invalid L1 configuration\n"); + return -EINVAL; + } + + /* Write block start address for each streams */ + writel(block_addr, mmu_hw->base + + mmu_hw->l1_stream_id_reg_offset + 4 * j); + } + + /* Configure MMU TLB stream configuration for L2 */ + for (j = 0, block_addr = 0; j < mmu_hw->nr_l2streams; + block_addr += mmu->mmu_hw[i].l2_block_sz[j], j++) { + if (block_addr > IPU6_MAX_L2_BLOCK_ADDR) { + dev_err(mmu->dev, "invalid L2 configuration\n"); + return -EINVAL; + } + + writel(block_addr, mmu_hw->base + + mmu_hw->l2_stream_id_reg_offset + 4 * j); + } + } + + if (!mmu->trash_page) { + int ret; + + mmu->trash_page = alloc_page(GFP_KERNEL); + if (!mmu->trash_page) { + dev_err(mmu->dev, "insufficient memory for trash buffer\n"); + return -ENOMEM; + } + + ret = allocate_trash_buffer(mmu); + if (ret) { + __free_page(mmu->trash_page); + mmu->trash_page = NULL; + dev_err(mmu->dev, "trash buffer allocation failed\n"); + return ret; + } + } + + spin_lock_irqsave(&mmu->ready_lock, flags); + mmu->ready = true; + spin_unlock_irqrestore(&mmu->ready_lock, flags); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_init, "INTEL_IPU6"); + +static struct ipu6_mmu_info *ipu6_mmu_alloc(struct ipu6_device *isp) +{ + struct ipu6_mmu_info *mmu_info; + int ret; + + mmu_info = kzalloc_obj(*mmu_info); + if (!mmu_info) + return NULL; + + mmu_info->aperture_start = 0; + mmu_info->aperture_end = + (dma_addr_t)DMA_BIT_MASK(isp->secure_mode ? + IPU6_MMU_ADDR_BITS : + IPU6_MMU_ADDR_BITS_NON_SECURE); + mmu_info->pgsize_bitmap = SZ_4K; + mmu_info->dev = &isp->pdev->dev; + + ret = get_dummy_page(mmu_info); + if (ret) + goto err_free_info; + + ret = alloc_dummy_l2_pt(mmu_info); + if (ret) + goto err_free_dummy_page; + + mmu_info->l2_pts = vzalloc(ISP_L2PT_PTES * sizeof(*mmu_info->l2_pts)); + if (!mmu_info->l2_pts) + goto err_free_dummy_l2_pt; + + /* + * We always map the L1 page table (a single page as well as + * the L2 page tables). + */ + mmu_info->l1_pt = alloc_l1_pt(mmu_info); + if (!mmu_info->l1_pt) + goto err_free_l2_pts; + + spin_lock_init(&mmu_info->lock); + + dev_dbg(mmu_info->dev, "domain initialised\n"); + + return mmu_info; + +err_free_l2_pts: + vfree(mmu_info->l2_pts); +err_free_dummy_l2_pt: + free_dummy_l2_pt(mmu_info); +err_free_dummy_page: + free_dummy_page(mmu_info); +err_free_info: + kfree(mmu_info); + + return NULL; +} + +void ipu6_mmu_hw_cleanup(struct ipu6_mmu *mmu) +{ + unsigned long flags; + + spin_lock_irqsave(&mmu->ready_lock, flags); + mmu->ready = false; + spin_unlock_irqrestore(&mmu->ready_lock, flags); +} +EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_cleanup, "INTEL_IPU6"); + +static struct ipu6_dma_mapping *alloc_dma_mapping(struct ipu6_device *isp) +{ + struct ipu6_dma_mapping *dmap; + + dmap = kzalloc_obj(*dmap); + if (!dmap) + return NULL; + + dmap->mmu_info = ipu6_mmu_alloc(isp); + if (!dmap->mmu_info) { + kfree(dmap); + return NULL; + } + + init_iova_domain(&dmap->iovad, SZ_4K, 1); + dmap->mmu_info->dmap = dmap; + + dev_dbg(&isp->pdev->dev, "alloc mapping\n"); + + iova_cache_get(); + + return dmap; +} + +phys_addr_t ipu6_mmu_iova_to_phys(struct ipu6_mmu_info *mmu_info, + dma_addr_t iova) +{ + phys_addr_t phy_addr; + unsigned long flags; + u32 *l2_pt; + + spin_lock_irqsave(&mmu_info->lock, flags); + l2_pt = mmu_info->l2_pts[iova >> ISP_L1PT_SHIFT]; + phy_addr = (phys_addr_t)l2_pt[(iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT]; + phy_addr <<= ISP_PAGE_SHIFT; + spin_unlock_irqrestore(&mmu_info->lock, flags); + + return phy_addr; +} + +void ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova, + size_t size) +{ + unsigned int min_pagesz; + + dev_dbg(mmu_info->dev, "unmapping iova 0x%lx size 0x%zx\n", iova, size); + + /* find out the minimum page size supported */ + min_pagesz = 1 << __ffs(mmu_info->pgsize_bitmap); + + /* + * The virtual address and the size of the mapping must be + * aligned (at least) to the size of the smallest page supported + * by the hardware + */ + if (!IS_ALIGNED(iova | size, min_pagesz)) { + dev_err(NULL, "unaligned: iova 0x%lx size 0x%zx min_pagesz 0x%x\n", + iova, size, min_pagesz); + return; + } + + __ipu6_mmu_unmap(mmu_info, iova, size); +} + +int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova, + phys_addr_t paddr, size_t size) +{ + unsigned int min_pagesz; + + if (mmu_info->pgsize_bitmap == 0UL) + return -ENODEV; + + /* find out the minimum page size supported */ + min_pagesz = 1 << __ffs(mmu_info->pgsize_bitmap); + + /* + * both the virtual address and the physical one, as well as + * the size of the mapping, must be aligned (at least) to the + * size of the smallest page supported by the hardware + */ + if (!IS_ALIGNED(iova | paddr | size, min_pagesz)) { + dev_err(mmu_info->dev, + "unaligned: iova %lx pa %pa size %zx min_pagesz %x\n", + iova, &paddr, size, min_pagesz); + return -EINVAL; + } + + dev_dbg(mmu_info->dev, "map: iova 0x%lx pa %pa size 0x%zx\n", + iova, &paddr, size); + + return __ipu6_mmu_map(mmu_info, iova, paddr, size); +} + +static void ipu6_mmu_destroy(struct ipu6_mmu *mmu) +{ + struct ipu6_dma_mapping *dmap = mmu->dmap; + struct ipu6_mmu_info *mmu_info = dmap->mmu_info; + struct iova *iova; + u32 l1_idx; + + if (mmu->iova_trash_page) { + iova = find_iova(&dmap->iovad, PHYS_PFN(mmu->iova_trash_page)); + if (iova) { + /* unmap and free the trash buffer iova */ + ipu6_mmu_unmap(mmu_info, PFN_PHYS(iova->pfn_lo), + PFN_PHYS(iova_size(iova))); + __free_iova(&dmap->iovad, iova); + } else { + dev_err(mmu->dev, "trash buffer iova not found.\n"); + } + + mmu->iova_trash_page = 0; + dma_unmap_page(mmu_info->dev, mmu->pci_trash_page, + PAGE_SIZE, DMA_BIDIRECTIONAL); + mmu->pci_trash_page = 0; + __free_page(mmu->trash_page); + } + + for (l1_idx = 0; l1_idx < ISP_L1PT_PTES; l1_idx++) { + if (mmu_info->l1_pt[l1_idx] != mmu_info->dummy_l2_pteval) { + dma_unmap_single(mmu_info->dev, + TBL_PHYS_ADDR(mmu_info->l1_pt[l1_idx]), + PAGE_SIZE, DMA_BIDIRECTIONAL); + free_page((unsigned long)mmu_info->l2_pts[l1_idx]); + } + } + + vfree(mmu_info->l2_pts); + free_dummy_page(mmu_info); + dma_unmap_single(mmu_info->dev, TBL_PHYS_ADDR(mmu_info->l1_pt_dma), + PAGE_SIZE, DMA_BIDIRECTIONAL); + free_page((unsigned long)mmu_info->dummy_l2_pt); + free_page((unsigned long)mmu_info->l1_pt); + kfree(mmu_info); +} + +struct ipu6_mmu *ipu6_mmu_init(struct device *dev, + void __iomem *base, int mmid, + const struct ipu6_hw_variants *hw) +{ + struct ipu6_device *isp = pci_get_drvdata(to_pci_dev(dev)); + struct ipu6_mmu_pdata *pdata; + struct ipu6_mmu *mmu; + unsigned int i; + + if (hw->nr_mmus > IPU6_MMU_MAX_DEVICES) + return ERR_PTR(-EINVAL); + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < hw->nr_mmus; i++) { + struct ipu6_mmu_hw *pdata_mmu = &pdata->mmu_hw[i]; + const struct ipu6_mmu_hw *src_mmu = &hw->mmu_hw[i]; + + if (src_mmu->nr_l1streams > IPU6_MMU_MAX_TLB_L1_STREAMS || + src_mmu->nr_l2streams > IPU6_MMU_MAX_TLB_L2_STREAMS) + return ERR_PTR(-EINVAL); + + *pdata_mmu = *src_mmu; + pdata_mmu->base = base + src_mmu->offset; + } + + mmu = devm_kzalloc(dev, sizeof(*mmu), GFP_KERNEL); + if (!mmu) + return ERR_PTR(-ENOMEM); + + mmu->mmid = mmid; + mmu->mmu_hw = pdata->mmu_hw; + mmu->nr_mmus = hw->nr_mmus; + mmu->tlb_invalidate = tlb_invalidate; + mmu->ready = false; + INIT_LIST_HEAD(&mmu->vma_list); + spin_lock_init(&mmu->ready_lock); + + mmu->dmap = alloc_dma_mapping(isp); + if (!mmu->dmap) { + dev_err(dev, "can't alloc dma mapping\n"); + return ERR_PTR(-ENOMEM); + } + + return mmu; +} + +void ipu6_mmu_cleanup(struct ipu6_mmu *mmu) +{ + struct ipu6_dma_mapping *dmap = mmu->dmap; + + ipu6_mmu_destroy(mmu); + mmu->dmap = NULL; + iova_cache_put(); + put_iova_domain(&dmap->iovad); + kfree(dmap); +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.h b/drivers/media/pci/intel/ipu6/ipu6-mmu.h new file mode 100644 index 000000000..8e40b4a66 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013--2024 Intel Corporation */ + +#ifndef IPU6_MMU_H +#define IPU6_MMU_H + +#define ISYS_MMID 1 +#define PSYS_MMID 0 + +#include +#include +#include + +struct device; +struct page; +struct ipu6_hw_variants; + +struct ipu6_mmu_info { + struct device *dev; + + u32 *l1_pt; + u32 l1_pt_dma; + u32 **l2_pts; + + u32 *dummy_l2_pt; + u32 dummy_l2_pteval; + void *dummy_page; + u32 dummy_page_pteval; + + dma_addr_t aperture_start; + dma_addr_t aperture_end; + unsigned long pgsize_bitmap; + + spinlock_t lock; /* Serialize access to users */ + struct ipu6_dma_mapping *dmap; +}; + +struct ipu6_mmu { + struct list_head node; + + struct ipu6_mmu_hw *mmu_hw; + unsigned int nr_mmus; + unsigned int mmid; + + phys_addr_t pgtbl; + struct device *dev; + + struct ipu6_dma_mapping *dmap; + struct list_head vma_list; + + struct page *trash_page; + dma_addr_t pci_trash_page; /* IOVA from PCI DMA services (parent) */ + dma_addr_t iova_trash_page; /* IOVA for IPU6 child nodes to use */ + + bool ready; + spinlock_t ready_lock; /* Serialize access to bool ready */ + + void (*tlb_invalidate)(struct ipu6_mmu *mmu); +}; + +struct ipu6_mmu *ipu6_mmu_init(struct device *dev, + void __iomem *base, int mmid, + const struct ipu6_hw_variants *hw); +void ipu6_mmu_cleanup(struct ipu6_mmu *mmu); +int ipu6_mmu_hw_init(struct ipu6_mmu *mmu); +void ipu6_mmu_hw_cleanup(struct ipu6_mmu *mmu); +int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova, + phys_addr_t paddr, size_t size); +void ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova, + size_t size); +phys_addr_t ipu6_mmu_iova_to_phys(struct ipu6_mmu_info *mmu_info, + dma_addr_t iova); +#endif diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h new file mode 100644 index 000000000..efd65e494 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2023--2024 Intel Corporation */ + +#ifndef IPU6_PLATFORM_BUTTRESS_REGS_H +#define IPU6_PLATFORM_BUTTRESS_REGS_H + +#include + +/* IS_WORKPOINT_REQ */ +#define IPU6_BUTTRESS_REG_IS_FREQ_CTL 0x34 +/* PS_WORKPOINT_REQ */ +#define IPU6_BUTTRESS_REG_PS_FREQ_CTL 0x38 + +/* should be tuned for real silicon */ +#define IPU6_IS_FREQ_CTL_DEFAULT_RATIO 0x08 +#define IPU6SE_IS_FREQ_CTL_DEFAULT_RATIO 0x0a +#define IPU6_PS_FREQ_CTL_DEFAULT_RATIO 0x0d + +#define IPU6_IS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO 0x10 +#define IPU6_PS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO 0x0708 + +#define IPU6_BUTTRESS_PWR_STATE_IS_PWR_SHIFT 3 +#define IPU6_BUTTRESS_PWR_STATE_IS_PWR_MASK GENMASK(4, 3) + +#define IPU6_BUTTRESS_PWR_STATE_PS_PWR_SHIFT 6 +#define IPU6_BUTTRESS_PWR_STATE_PS_PWR_MASK GENMASK(7, 6) + +#define IPU6_BUTTRESS_PWR_STATE_DN_DONE 0x0 +#define IPU6_BUTTRESS_PWR_STATE_UP_PROCESS 0x1 +#define IPU6_BUTTRESS_PWR_STATE_DN_PROCESS 0x2 +#define IPU6_BUTTRESS_PWR_STATE_UP_DONE 0x3 + +#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_0 0x270 +#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_1 0x274 +#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_2 0x278 +#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_3 0x27c +#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_4 0x280 +#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_5 0x284 +#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_6 0x288 +#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_7 0x28c + +#define BUTTRESS_REG_WDT 0x8 +#define BUTTRESS_REG_BTRS_CTRL 0xc +#define BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0 BIT(0) +#define BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1 BIT(1) +#define BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND GENMASK(9, 8) + +#define BUTTRESS_REG_FW_RESET_CTL 0x30 +#define BUTTRESS_FW_RESET_CTL_START BIT(0) +#define BUTTRESS_FW_RESET_CTL_DONE BIT(1) + +#define BUTTRESS_REG_IS_FREQ_CTL 0x34 +#define BUTTRESS_REG_PS_FREQ_CTL 0x38 + +#define BUTTRESS_FREQ_CTL_START BIT(31) +#define BUTTRESS_FREQ_CTL_ICCMAX_LEVEL GENMASK(19, 16) +#define BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK GENMASK(15, 8) +#define BUTTRESS_FREQ_CTL_RATIO_MASK GENMASK(7, 0) + +#define BUTTRESS_REG_PWR_STATE 0x5c + +#define BUTTRESS_PWR_STATE_RESET 0x0 +#define BUTTRESS_PWR_STATE_PWR_ON_DONE 0x1 +#define BUTTRESS_PWR_STATE_PWR_RDY 0x3 +#define BUTTRESS_PWR_STATE_PWR_IDLE 0x4 + +#define BUTTRESS_PWR_STATE_HH_STATUS_MASK GENMASK(12, 11) + +enum { + BUTTRESS_PWR_STATE_HH_STATE_IDLE, + BUTTRESS_PWR_STATE_HH_STATE_IN_PRGS, + BUTTRESS_PWR_STATE_HH_STATE_DONE, + BUTTRESS_PWR_STATE_HH_STATE_ERR, +}; + +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_MASK GENMASK(23, 19) + +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_IDLE 0x0 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PLL_CMP 0x1 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_CLKACK 0x2 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PG_ACK 0x3 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_ASSRT_CYCLES 0x4 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_STOP_CLK_CYCLES1 0x5 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_STOP_CLK_CYCLES2 0x6 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_DEASSRT_CYCLES 0x7 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_FUSE_WR_CMP 0x8 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_BRK_POINT 0x9 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_IS_RDY 0xa +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_HALT_HALTED 0xb +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_DURATION_CNT3 0xc +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_CLKACK_PD 0xd +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_PD_BRK_POINT 0xe +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PD_PG_ACK0 0xf + +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_MASK GENMASK(28, 24) + +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_IDLE 0x0 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_PLL_IP_RDY 0x1 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_RO_PRE_CNT_EXH 0x2 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_VGI_PWRGOOD 0x3 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_RO_POST_CNT_EXH 0x4 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WR_PLL_RATIO 0x5 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_PLL_CMP 0x6 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_CLKACK 0x7 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RST_ASSRT_CYCLES 0x8 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_STOP_CLK_CYCLES1 0x9 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_STOP_CLK_CYCLES2 0xa +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RST_DEASSRT_CYCLES 0xb +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_PU_BRK_PNT 0xc +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_FUSE_ACCPT 0xd +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_PS_PWR_UP 0xf +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_4_HALTED 0x10 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RESET_CNT3 0x11 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PD_CLKACK 0x12 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PD_OFF_IND 0x13 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_PH4 0x14 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_PLL_CMP 0x15 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_CLKACK 0x16 + +#define BUTTRESS_REG_SECURITY_CTL 0x300 +#define BUTTRESS_REG_SKU 0x314 +#define BUTTRESS_REG_SECURITY_TOUCH 0x318 +#define BUTTRESS_REG_CAMERA_MASK 0x84 + +#define BUTTRESS_SECURITY_CTL_FW_SECURE_MODE BIT(16) +#define BUTTRESS_SECURITY_CTL_FW_SETUP_MASK GENMASK(4, 0) + +#define BUTTRESS_SECURITY_CTL_FW_SETUP_DONE BIT(0) +#define BUTTRESS_SECURITY_CTL_AUTH_DONE BIT(1) +#define BUTTRESS_SECURITY_CTL_AUTH_FAILED BIT(3) + +#define BUTTRESS_REG_FW_SOURCE_BASE_LO 0x78 +#define BUTTRESS_REG_FW_SOURCE_BASE_HI 0x7C +#define BUTTRESS_REG_FW_SOURCE_SIZE 0x80 + +#define BUTTRESS_REG_ISR_STATUS 0x90 +#define BUTTRESS_REG_ISR_ENABLED_STATUS 0x94 +#define BUTTRESS_REG_ISR_ENABLE 0x98 +#define BUTTRESS_REG_ISR_CLEAR 0x9C + +#define BUTTRESS_ISR_IS_IRQ BIT(0) +#define BUTTRESS_ISR_PS_IRQ BIT(1) +#define BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE BIT(2) +#define BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH BIT(3) +#define BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING BIT(4) +#define BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING BIT(5) +#define BUTTRESS_ISR_CSE_CSR_SET BIT(6) +#define BUTTRESS_ISR_ISH_CSR_SET BIT(7) +#define BUTTRESS_ISR_SPURIOUS_CMP BIT(8) +#define BUTTRESS_ISR_WATCHDOG_EXPIRED BIT(9) +#define BUTTRESS_ISR_PUNIT_2_IUNIT_IRQ BIT(10) +#define BUTTRESS_ISR_SAI_VIOLATION BIT(11) +#define BUTTRESS_ISR_HW_ASSERTION BIT(12) +#define BUTTRESS_ISR_IS_CORRECTABLE_MEM_ERR BIT(13) +#define BUTTRESS_ISR_IS_FATAL_MEM_ERR BIT(14) +#define BUTTRESS_ISR_IS_NON_FATAL_MEM_ERR BIT(15) +#define BUTTRESS_ISR_PS_CORRECTABLE_MEM_ERR BIT(16) +#define BUTTRESS_ISR_PS_FATAL_MEM_ERR BIT(17) +#define BUTTRESS_ISR_PS_NON_FATAL_MEM_ERR BIT(18) +#define BUTTRESS_ISR_PS_FAST_THROTTLE BIT(19) +#define BUTTRESS_ISR_UFI_ERROR BIT(20) + +#define BUTTRESS_REG_IU2CSEDB0 0x100 + +#define BUTTRESS_IU2CSEDB0_BUSY BIT(31) +#define BUTTRESS_IU2CSEDB0_IPC_CLIENT_ID_VAL 2 + +#define BUTTRESS_REG_IU2CSEDATA0 0x104 + +#define BUTTRESS_IU2CSEDATA0_IPC_BOOT_LOAD 1 +#define BUTTRESS_IU2CSEDATA0_IPC_AUTH_RUN 2 +#define BUTTRESS_IU2CSEDATA0_IPC_AUTH_REPLACE 3 +#define BUTTRESS_IU2CSEDATA0_IPC_UPDATE_SECURE_TOUCH 16 + +#define BUTTRESS_CSE2IUDATA0_IPC_BOOT_LOAD_DONE BIT(0) +#define BUTTRESS_CSE2IUDATA0_IPC_AUTH_RUN_DONE BIT(1) +#define BUTTRESS_CSE2IUDATA0_IPC_AUTH_REPLACE_DONE BIT(2) +#define BUTTRESS_CSE2IUDATA0_IPC_UPDATE_SECURE_TOUCH_DONE BIT(4) + +#define BUTTRESS_REG_IU2CSECSR 0x108 + +#define BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE1 BIT(0) +#define BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE2 BIT(1) +#define BUTTRESS_IU2CSECSR_IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE BIT(2) +#define BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ BIT(3) +#define BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID BIT(4) +#define BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ BIT(5) + +#define BUTTRESS_REG_CSE2IUDB0 0x304 +#define BUTTRESS_REG_CSE2IUCSR 0x30C +#define BUTTRESS_REG_CSE2IUDATA0 0x308 + +/* 0x20 == NACK, 0xf == unknown command */ +#define BUTTRESS_CSE2IUDATA0_IPC_NACK 0xf20 +#define BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK GENMASK(15, 0) + +#define BUTTRESS_REG_ISH2IUCSR 0x50 +#define BUTTRESS_REG_ISH2IUDB0 0x54 +#define BUTTRESS_REG_ISH2IUDATA0 0x58 + +#define BUTTRESS_REG_IU2ISHDB0 0x10C +#define BUTTRESS_REG_IU2ISHDATA0 0x110 +#define BUTTRESS_REG_IU2ISHDATA1 0x114 +#define BUTTRESS_REG_IU2ISHCSR 0x118 + +#define BUTTRESS_REG_FABRIC_CMD 0x88 + +#define BUTTRESS_FABRIC_CMD_START_TSC_SYNC BIT(0) +#define BUTTRESS_FABRIC_CMD_IS_DRAIN BIT(4) + +#define BUTTRESS_REG_TSW_CTL 0x120 +#define BUTTRESS_TSW_CTL_SOFT_RESET BIT(8) + +#define BUTTRESS_REG_TSC_LO 0x164 +#define BUTTRESS_REG_TSC_HI 0x168 + +#define BUTTRESS_IRQS (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \ + BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE | \ + BUTTRESS_ISR_IS_IRQ | BUTTRESS_ISR_PS_IRQ) + +#define BUTTRESS_EVENT (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \ + BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE | \ + BUTTRESS_ISR_SAI_VIOLATION) +#endif /* IPU6_PLATFORM_BUTTRESS_REGS_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-isys-csi2-reg.h b/drivers/media/pci/intel/ipu6/ipu6-platform-isys-csi2-reg.h new file mode 100644 index 000000000..cc5837753 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-platform-isys-csi2-reg.h @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2023--2024 Intel Corporation */ + +#ifndef IPU6_PLATFORM_ISYS_CSI2_REG_H +#define IPU6_PLATFORM_ISYS_CSI2_REG_H + +#include + +#define CSI_REG_BASE 0x220000 +#define CSI_REG_PORT_BASE(id) (CSI_REG_BASE + (id) * 0x1000) + +/* CSI Port Genral Purpose Registers */ +#define CSI_REG_PORT_GPREG_SRST 0x0 +#define CSI_REG_PORT_GPREG_CSI2_SLV_REG_SRST 0x4 +#define CSI_REG_PORT_GPREG_CSI2_PORT_CONTROL 0x8 + +/* + * Port IRQs mapping events: + * IRQ0 - CSI_FE event + * IRQ1 - CSI_SYNC + * IRQ2 - S2M_SIDS0TO7 + * IRQ3 - S2M_SIDS8TO15 + */ +#define CSI_PORT_REG_BASE_IRQ_CSI 0x80 +#define CSI_PORT_REG_BASE_IRQ_CSI_SYNC 0xA0 +#define CSI_PORT_REG_BASE_IRQ_S2M_SIDS0TOS7 0xC0 +#define CSI_PORT_REG_BASE_IRQ_S2M_SIDS8TOS15 0xE0 + +#define CSI_PORT_REG_BASE_IRQ_EDGE_OFFSET 0x0 +#define CSI_PORT_REG_BASE_IRQ_MASK_OFFSET 0x4 +#define CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET 0x8 +#define CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET 0xc +#define CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET 0x10 +#define CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET 0x14 + +#define IPU6SE_CSI_RX_ERROR_IRQ_MASK GENMASK(18, 0) +#define IPU6_CSI_RX_ERROR_IRQ_MASK GENMASK(19, 0) + +#define CSI_RX_NUM_ERRORS_IN_IRQ 20 +#define CSI_RX_NUM_IRQ 32 + +#define IPU_CSI_RX_IRQ_FS_VC(chn) (1 << ((chn) * 2)) +#define IPU_CSI_RX_IRQ_FE_VC(chn) (2 << ((chn) * 2)) + +/* PPI2CSI */ +#define CSI_REG_PPI2CSI_ENABLE 0x200 +#define CSI_REG_PPI2CSI_CONFIG_PPI_INTF 0x204 +#define PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK GENMASK(4, 3) +#define CSI_REG_PPI2CSI_CONFIG_CSI_FEATURE 0x208 + +enum CSI_PPI2CSI_CTRL { + CSI_PPI2CSI_DISABLE = 0, + CSI_PPI2CSI_ENABLE = 1, +}; + +/* CSI_FE */ +#define CSI_REG_CSI_FE_ENABLE 0x280 +#define CSI_REG_CSI_FE_MODE 0x284 +#define CSI_REG_CSI_FE_MUX_CTRL 0x288 +#define CSI_REG_CSI_FE_SYNC_CNTR_SEL 0x290 + +enum CSI_FE_ENABLE_TYPE { + CSI_FE_DISABLE = 0, + CSI_FE_ENABLE = 1, +}; + +enum CSI_FE_MODE_TYPE { + CSI_FE_DPHY_MODE = 0, + CSI_FE_CPHY_MODE = 1, +}; + +enum CSI_FE_INPUT_SELECTOR { + CSI_SENSOR_INPUT = 0, + CSI_MIPIGEN_INPUT = 1, +}; + +enum CSI_FE_SYNC_CNTR_SEL_TYPE { + CSI_CNTR_SENSOR_LINE_ID = BIT(0), + CSI_CNTR_INT_LINE_PKT_ID = ~CSI_CNTR_SENSOR_LINE_ID, + CSI_CNTR_SENSOR_FRAME_ID = BIT(1), + CSI_CNTR_INT_FRAME_PKT_ID = ~CSI_CNTR_SENSOR_FRAME_ID, +}; + +/* CSI HUB General Purpose Registers */ +#define CSI_REG_HUB_GPREG_SRST (CSI_REG_BASE + 0x18000) +#define CSI_REG_HUB_GPREG_SLV_REG_SRST (CSI_REG_BASE + 0x18004) + +#define CSI_REG_HUB_DRV_ACCESS_PORT(id) (CSI_REG_BASE + 0x18018 + (id) * 4) +#define CSI_REG_HUB_FW_ACCESS_PORT_OFS 0x17000 +#define CSI_REG_HUB_FW_ACCESS_PORT_V6OFS 0x16000 +#define CSI_REG_HUB_FW_ACCESS_PORT(ofs, id) \ + (CSI_REG_BASE + (ofs) + (id) * 4) + +enum CSI_PORT_CLK_GATING_SWITCH { + CSI_PORT_CLK_GATING_OFF = 0, + CSI_PORT_CLK_GATING_ON = 1, +}; + +#define CSI_REG_BASE_HUB_IRQ 0x18200 + +#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE 0x238200 +#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK 0x238204 +#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS 0x238208 +#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR 0x23820c +#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE 0x238210 +#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE 0x238214 + +#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_EDGE 0x238220 +#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_MASK 0x238224 +#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_STATUS 0x238228 +#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_CLEAR 0x23822c +#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_ENABLE 0x238230 +#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_LEVEL_NOT_PULSE 0x238234 + +/* MTL IPU6V6 irq ctrl0 & ctrl1 */ +#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE 0x238700 +#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK 0x238704 +#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS 0x238708 +#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR 0x23870c +#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE 0x238710 +#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE 0x238714 + +#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_EDGE 0x238720 +#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_MASK 0x238724 +#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_STATUS 0x238728 +#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_CLEAR 0x23872c +#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_ENABLE 0x238730 +#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_LEVEL_NOT_PULSE 0x238734 + +/* + * 3:0 CSI_PORT.irq_out[3:0] CSI_PORT_CTRL0 IRQ outputs (4bits) + * [0] CSI_PORT.IRQ_CTRL0_csi + * [1] CSI_PORT.IRQ_CTRL1_csi_sync + * [2] CSI_PORT.IRQ_CTRL2_s2m_sids0to7 + * [3] CSI_PORT.IRQ_CTRL3_s2m_sids8to15 + */ +#define IPU6_ISYS_UNISPART_IRQ_CSI2(port) \ + (0x3 << ((port) * IPU6_CSI_IRQ_NUM_PER_PIPE)) + +/* + * ipu6se support 2 front ends, 2 port per front end, 4 ports 0..3 + * sip0 - 0, 1 + * sip1 - 2, 3 + * 0 and 2 support 4 data lanes, 1 and 3 support 2 data lanes + * all offset are base from isys base address + */ + +#define CSI2_HUB_GPREG_SIP_SRST(sip) (0x238038 + (sip) * 4) +#define CSI2_HUB_GPREG_SIP_FB_PORT_CFG(sip) (0x238050 + (sip) * 4) + +#define CSI2_HUB_GPREG_DPHY_TIMER_INCR 0x238040 +#define CSI2_HUB_GPREG_HPLL_FREQ 0x238044 +#define CSI2_HUB_GPREG_IS_CLK_RATIO 0x238048 +#define CSI2_HUB_GPREG_HPLL_FREQ_ISCLK_RATE_OVERRIDE 0x23804c +#define CSI2_HUB_GPREG_PORT_CLKGATING_DISABLE 0x238058 +#define CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL 0x23805c +#define CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL 0x238088 +#define CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL 0x2380a4 +#define CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL 0x2380d0 + +#define CSI2_SIP_TOP_CSI_RX_BASE(sip) (0x23805c + (sip) * 0x48) +#define CSI2_SIP_TOP_CSI_RX_PORT_BASE_0(port) (0x23805c + ((port) / 2) * 0x48) +#define CSI2_SIP_TOP_CSI_RX_PORT_BASE_1(port) (0x238088 + ((port) / 2) * 0x48) + +/* offset from port base */ +#define CSI2_SIP_TOP_CSI_RX_PORT_CONTROL 0x0 +#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_CLANE 0x4 +#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_CLANE 0x8 +#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_DLANE(lane) (0xc + (lane) * 8) +#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_DLANE(lane) (0x10 + (lane) * 8) + +#endif /* IPU6_ISYS_CSI2_REG_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-regs.h b/drivers/media/pci/intel/ipu6/ipu6-platform-regs.h new file mode 100644 index 000000000..b883385ad --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-platform-regs.h @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2018 - 2024 Intel Corporation */ + +#ifndef IPU6_PLATFORM_REGS_H +#define IPU6_PLATFORM_REGS_H + +#include + +/* + * IPU6 uses uniform address within IPU6, therefore all subsystem registers + * locates in one single space starts from 0 but in different sctions with + * different addresses, the subsystem offsets are defined to 0 as the + * register definition will have the address offset to 0. + */ +#define IPU6_UNIFIED_OFFSET 0 + +#define IPU6_ISYS_IOMMU0_OFFSET 0x2e0000 +#define IPU6_ISYS_IOMMU1_OFFSET 0x2e0500 +#define IPU6_ISYS_IOMMUI_OFFSET 0x2e0a00 + +#define IPU6_PSYS_IOMMU0_OFFSET 0x1b0000 +#define IPU6_PSYS_IOMMU1_OFFSET 0x1b0700 +#define IPU6_PSYS_IOMMU1R_OFFSET 0x1b0e00 +#define IPU6_PSYS_IOMMUI_OFFSET 0x1b1500 + +/* the offset from IOMMU base register */ +#define IPU6_MMU_L1_STREAM_ID_REG_OFFSET 0x0c +#define IPU6_MMU_L2_STREAM_ID_REG_OFFSET 0x4c +#define IPU6_PSYS_MMU1W_L2_STREAM_ID_REG_OFFSET 0x8c + +#define IPU6_MMU_INFO_OFFSET 0x8 + +#define IPU6_ISYS_SPC_OFFSET 0x210000 + +#define IPU6SE_PSYS_SPC_OFFSET 0x110000 +#define IPU6_PSYS_SPC_OFFSET 0x118000 + +#define IPU6_ISYS_DMEM_OFFSET 0x200000 +#define IPU6_PSYS_DMEM_OFFSET 0x100000 + +#define IPU6_REG_ISYS_UNISPART_IRQ_EDGE 0x27c000 +#define IPU6_REG_ISYS_UNISPART_IRQ_MASK 0x27c004 +#define IPU6_REG_ISYS_UNISPART_IRQ_STATUS 0x27c008 +#define IPU6_REG_ISYS_UNISPART_IRQ_CLEAR 0x27c00c +#define IPU6_REG_ISYS_UNISPART_IRQ_ENABLE 0x27c010 +#define IPU6_REG_ISYS_UNISPART_IRQ_LEVEL_NOT_PULSE 0x27c014 +#define IPU6_REG_ISYS_UNISPART_SW_IRQ_REG 0x27c414 +#define IPU6_REG_ISYS_UNISPART_SW_IRQ_MUX_REG 0x27c418 +#define IPU6_ISYS_UNISPART_IRQ_CSI0 BIT(2) +#define IPU6_ISYS_UNISPART_IRQ_CSI1 BIT(3) +#define IPU6_ISYS_UNISPART_IRQ_SW BIT(22) + +#define IPU6_REG_ISYS_ISL_TOP_IRQ_EDGE 0x2b0200 +#define IPU6_REG_ISYS_ISL_TOP_IRQ_MASK 0x2b0204 +#define IPU6_REG_ISYS_ISL_TOP_IRQ_STATUS 0x2b0208 +#define IPU6_REG_ISYS_ISL_TOP_IRQ_CLEAR 0x2b020c +#define IPU6_REG_ISYS_ISL_TOP_IRQ_ENABLE 0x2b0210 +#define IPU6_REG_ISYS_ISL_TOP_IRQ_LEVEL_NOT_PULSE 0x2b0214 + +#define IPU6_REG_ISYS_CMPR_TOP_IRQ_EDGE 0x2d2100 +#define IPU6_REG_ISYS_CMPR_TOP_IRQ_MASK 0x2d2104 +#define IPU6_REG_ISYS_CMPR_TOP_IRQ_STATUS 0x2d2108 +#define IPU6_REG_ISYS_CMPR_TOP_IRQ_CLEAR 0x2d210c +#define IPU6_REG_ISYS_CMPR_TOP_IRQ_ENABLE 0x2d2110 +#define IPU6_REG_ISYS_CMPR_TOP_IRQ_LEVEL_NOT_PULSE 0x2d2114 + +/* CDC Burst collector thresholds for isys - 3 FIFOs i = 0..2 */ +#define IPU6_REG_ISYS_CDC_THRESHOLD(i) (0x27c400 + ((i) * 4)) + +#define IPU6_CSI_IRQ_NUM_PER_PIPE 4 +#define IPU6SE_ISYS_CSI_PORT_NUM 4 +#define IPU6_ISYS_CSI_PORT_NUM 8 + +#define IPU6_ISYS_CSI_PORT_IRQ(irq_num) BIT(irq_num) + +/* PKG DIR OFFSET in IMR in secure mode */ +#define IPU6_PKG_DIR_IMR_OFFSET 0x40 + +#define IPU6_ISYS_REG_SPC_STATUS_CTRL 0x0 + +#define IPU6_ISYS_SPC_STATUS_START BIT(1) +#define IPU6_ISYS_SPC_STATUS_RUN BIT(3) +#define IPU6_ISYS_SPC_STATUS_READY BIT(5) +#define IPU6_ISYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE BIT(12) +#define IPU6_ISYS_SPC_STATUS_ICACHE_PREFETCH BIT(13) + +#define IPU6_PSYS_REG_SPC_STATUS_CTRL 0x0 +#define IPU6_PSYS_REG_SPC_START_PC 0x4 +#define IPU6_PSYS_REG_SPC_ICACHE_BASE 0x10 +#define IPU6_REG_PSYS_INFO_SEG_0_CONFIG_ICACHE_MASTER 0x14 + +#define IPU6_PSYS_SPC_STATUS_START BIT(1) +#define IPU6_PSYS_SPC_STATUS_RUN BIT(3) +#define IPU6_PSYS_SPC_STATUS_READY BIT(5) +#define IPU6_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE BIT(12) +#define IPU6_PSYS_SPC_STATUS_ICACHE_PREFETCH BIT(13) + +#define IPU6_PSYS_REG_SPP0_STATUS_CTRL 0x20000 + +#define IPU6_INFO_ENABLE_SNOOP BIT(0) +#define IPU6_INFO_DEC_FORCE_FLUSH BIT(1) +#define IPU6_INFO_DEC_PASS_THROUGH BIT(2) +#define IPU6_INFO_ZLW BIT(3) +#define IPU6_INFO_REQUEST_DESTINATION_IOSF BIT(9) +#define IPU6_INFO_IMR_BASE BIT(10) +#define IPU6_INFO_IMR_DESTINED BIT(11) + +#define IPU6_INFO_REQUEST_DESTINATION_PRIMARY IPU6_INFO_REQUEST_DESTINATION_IOSF + +/* + * s2m_pixel_soc_pixel_remapping is dedicated for the enabling of the + * pixel s2m remp ability.Remap here means that s2m rearange the order + * of the pixels in each 4 pixels group. + * For examle, mirroring remping means that if input's 4 first pixels + * are 1 2 3 4 then in output we should see 4 3 2 1 in this 4 first pixels. + * 0xE4 is from s2m MAS document. It means no remapping. + */ +#define S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING 0xe4 +/* + * csi_be_soc_pixel_remapping is for the enabling of the pixel remapping. + * This remapping is exactly like the stream2mmio remapping. + */ +#define CSI_BE_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING 0xe4 + +#define IPU6_REG_DMA_TOP_AB_GROUP1_BASE_ADDR 0x1ae000 +#define IPU6_REG_DMA_TOP_AB_GROUP2_BASE_ADDR 0x1af000 +#define IPU6_REG_DMA_TOP_AB_RING_MIN_OFFSET(n) (0x4 + (n) * 0xc) +#define IPU6_REG_DMA_TOP_AB_RING_MAX_OFFSET(n) (0x8 + (n) * 0xc) +#define IPU6_REG_DMA_TOP_AB_RING_ACCESS_OFFSET(n) (0xc + (n) * 0xc) + +enum ipu6_device_ab_group1_target_id { + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R0_SPC_DMEM, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R1_SPC_DMEM, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R2_SPC_DMEM, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R3_SPC_STATUS_REG, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R4_SPC_MASTER_BASE_ADDR, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R5_SPC_PC_STALL, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R6_SPC_EQ, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R7_SPC_RESERVED, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R8_SPC_RESERVED, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R9_SPP0, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R10_SPP1, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R11_CENTRAL_R1, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R12_IRQ, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R13_CENTRAL_R2, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R14_DMA, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R15_DMA, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R16_GP, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R17_ZLW_INSERTER, + IPU6_DEVICE_AB_GROUP1_TARGET_ID_R18_AB, +}; + +enum nci_ab_access_mode { + NCI_AB_ACCESS_MODE_RW, /* read & write */ + NCI_AB_ACCESS_MODE_RO, /* read only */ + NCI_AB_ACCESS_MODE_WO, /* write only */ + NCI_AB_ACCESS_MODE_NA, /* No access at all */ +}; + +/* IRQ-related registers in PSYS */ +#define IPU6_REG_PSYS_GPDEV_IRQ_EDGE 0x1aa200 +#define IPU6_REG_PSYS_GPDEV_IRQ_MASK 0x1aa204 +#define IPU6_REG_PSYS_GPDEV_IRQ_STATUS 0x1aa208 +#define IPU6_REG_PSYS_GPDEV_IRQ_CLEAR 0x1aa20c +#define IPU6_REG_PSYS_GPDEV_IRQ_ENABLE 0x1aa210 +#define IPU6_REG_PSYS_GPDEV_IRQ_LEVEL_NOT_PULSE 0x1aa214 +/* There are 8 FW interrupts, n = 0..7 */ +#define IPU6_PSYS_GPDEV_FWIRQ0 5 +#define IPU6_PSYS_GPDEV_FWIRQ1 6 +#define IPU6_PSYS_GPDEV_FWIRQ2 7 +#define IPU6_PSYS_GPDEV_FWIRQ3 8 +#define IPU6_PSYS_GPDEV_FWIRQ4 9 +#define IPU6_PSYS_GPDEV_FWIRQ5 10 +#define IPU6_PSYS_GPDEV_FWIRQ6 11 +#define IPU6_PSYS_GPDEV_FWIRQ7 12 +#define IPU6_PSYS_GPDEV_IRQ_FWIRQ(n) BIT(n) +#define IPU6_REG_PSYS_GPDEV_FWIRQ(n) (4 * (n) + 0x1aa100) + +#endif /* IPU6_PLATFORM_REGS_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c new file mode 100644 index 000000000..5449a2006 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -0,0 +1,842 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ipu6.h" +#include "ipu6-bus.h" +#include "ipu6-buttress.h" +#include "ipu6-cpd.h" +#include "ipu6-isys.h" +#include "ipu6-mmu.h" +#include "ipu6-platform-buttress-regs.h" +#include "ipu6-platform-isys-csi2-reg.h" +#include "ipu6-platform-regs.h" + +#define IPU6_PCI_BAR 0 + +struct ipu6_cell_program { + u32 magic_number; + + u32 blob_offset; + u32 blob_size; + + u32 start[3]; + + u32 icache_source; + u32 icache_target; + u32 icache_size; + + u32 pmem_source; + u32 pmem_target; + u32 pmem_size; + + u32 data_source; + u32 data_target; + u32 data_size; + + u32 bss_target; + u32 bss_size; + + u32 cell_id; + u32 regs_addr; + + u32 cell_pmem_data_bus_address; + u32 cell_dmem_data_bus_address; + u32 cell_pmem_control_bus_address; + u32 cell_dmem_control_bus_address; + + u32 next; + u32 dummy[2]; +}; + +static struct ipu6_isys_internal_pdata isys_ipdata = { + .hw_variant = { + .offset = IPU6_UNIFIED_OFFSET, + .nr_mmus = 3, + .mmu_hw = { + { + .offset = IPU6_ISYS_IOMMU0_OFFSET, + .info_bits = IPU6_INFO_REQUEST_DESTINATION_IOSF, + .nr_l1streams = 16, + .l1_block_sz = { + 3, 8, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 1, 1 + }, + .nr_l2streams = 16, + .l2_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .insert_read_before_invalidate = false, + .l1_stream_id_reg_offset = + IPU6_MMU_L1_STREAM_ID_REG_OFFSET, + .l2_stream_id_reg_offset = + IPU6_MMU_L2_STREAM_ID_REG_OFFSET, + }, + { + .offset = IPU6_ISYS_IOMMU1_OFFSET, + .info_bits = 0, + .nr_l1streams = 16, + .l1_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 1, 1, 4 + }, + .nr_l2streams = 16, + .l2_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .insert_read_before_invalidate = false, + .l1_stream_id_reg_offset = + IPU6_MMU_L1_STREAM_ID_REG_OFFSET, + .l2_stream_id_reg_offset = + IPU6_MMU_L2_STREAM_ID_REG_OFFSET, + }, + { + .offset = IPU6_ISYS_IOMMUI_OFFSET, + .info_bits = 0, + .nr_l1streams = 0, + .nr_l2streams = 0, + .insert_read_before_invalidate = false, + }, + }, + .cdc_fifos = 3, + .cdc_fifo_threshold = {6, 8, 2}, + .dmem_offset = IPU6_ISYS_DMEM_OFFSET, + .spc_offset = IPU6_ISYS_SPC_OFFSET, + }, + .isys_dma_overshoot = IPU6_ISYS_OVERALLOC_MIN, +}; + +static struct ipu6_psys_internal_pdata psys_ipdata = { + .hw_variant = { + .offset = IPU6_UNIFIED_OFFSET, + .nr_mmus = 4, + .mmu_hw = { + { + .offset = IPU6_PSYS_IOMMU0_OFFSET, + .info_bits = + IPU6_INFO_REQUEST_DESTINATION_IOSF, + .nr_l1streams = 16, + .l1_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .nr_l2streams = 16, + .l2_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .insert_read_before_invalidate = false, + .l1_stream_id_reg_offset = + IPU6_MMU_L1_STREAM_ID_REG_OFFSET, + .l2_stream_id_reg_offset = + IPU6_MMU_L2_STREAM_ID_REG_OFFSET, + }, + { + .offset = IPU6_PSYS_IOMMU1_OFFSET, + .info_bits = 0, + .nr_l1streams = 32, + .l1_block_sz = { + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 10, + 5, 4, 14, 6, 4, 14, 6, 4, 8, + 4, 2, 1, 1, 1, 1, 14 + }, + .nr_l2streams = 32, + .l2_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .insert_read_before_invalidate = false, + .l1_stream_id_reg_offset = + IPU6_MMU_L1_STREAM_ID_REG_OFFSET, + .l2_stream_id_reg_offset = + IPU6_PSYS_MMU1W_L2_STREAM_ID_REG_OFFSET, + }, + { + .offset = IPU6_PSYS_IOMMU1R_OFFSET, + .info_bits = 0, + .nr_l1streams = 16, + .l1_block_sz = { + 1, 4, 4, 4, 4, 16, 8, 4, 32, + 16, 16, 2, 2, 2, 1, 12 + }, + .nr_l2streams = 16, + .l2_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .insert_read_before_invalidate = false, + .l1_stream_id_reg_offset = + IPU6_MMU_L1_STREAM_ID_REG_OFFSET, + .l2_stream_id_reg_offset = + IPU6_MMU_L2_STREAM_ID_REG_OFFSET, + }, + { + .offset = IPU6_PSYS_IOMMUI_OFFSET, + .info_bits = 0, + .nr_l1streams = 0, + .nr_l2streams = 0, + .insert_read_before_invalidate = false, + }, + }, + .dmem_offset = IPU6_PSYS_DMEM_OFFSET, + }, +}; + +static const struct ipu6_buttress_ctrl isys_buttress_ctrl = { + .ratio = IPU6_IS_FREQ_CTL_DEFAULT_RATIO, + .qos_floor = IPU6_IS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO, + .freq_ctl = IPU6_BUTTRESS_REG_IS_FREQ_CTL, + .pwr_sts_shift = IPU6_BUTTRESS_PWR_STATE_IS_PWR_SHIFT, + .pwr_sts_mask = IPU6_BUTTRESS_PWR_STATE_IS_PWR_MASK, + .pwr_sts_on = IPU6_BUTTRESS_PWR_STATE_UP_DONE, + .pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE, +}; + +static const struct ipu6_buttress_ctrl psys_buttress_ctrl = { + .ratio = IPU6_PS_FREQ_CTL_DEFAULT_RATIO, + .qos_floor = IPU6_PS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO, + .freq_ctl = IPU6_BUTTRESS_REG_PS_FREQ_CTL, + .pwr_sts_shift = IPU6_BUTTRESS_PWR_STATE_PS_PWR_SHIFT, + .pwr_sts_mask = IPU6_BUTTRESS_PWR_STATE_PS_PWR_MASK, + .pwr_sts_on = IPU6_BUTTRESS_PWR_STATE_UP_DONE, + .pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE, +}; + +static void +ipu6_pkg_dir_configure_spc(struct ipu6_device *isp, + const struct ipu6_hw_variants *hw_variant, + int pkg_dir_idx, void __iomem *base, + u64 *pkg_dir, dma_addr_t pkg_dir_vied_address) +{ + struct ipu6_cell_program *prog; + void __iomem *spc_base; + u32 server_fw_addr; + dma_addr_t dma_addr; + u32 pg_offset; + + server_fw_addr = lower_32_bits(*(pkg_dir + (pkg_dir_idx + 1) * 2)); + if (pkg_dir_idx == IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX) + dma_addr = sg_dma_address(isp->isys->fw_sgt.sgl); + else + dma_addr = sg_dma_address(isp->psys->fw_sgt.sgl); + + pg_offset = server_fw_addr - dma_addr; + prog = (struct ipu6_cell_program *)((uintptr_t)isp->cpd_fw->data + + pg_offset); + spc_base = base + prog->regs_addr; + if (spc_base != (base + hw_variant->spc_offset)) + dev_warn(&isp->pdev->dev, + "SPC reg addr %p not matching value from CPD %p\n", + base + hw_variant->spc_offset, spc_base); + writel(server_fw_addr + prog->blob_offset + + prog->icache_source, spc_base + IPU6_PSYS_REG_SPC_ICACHE_BASE); + writel(IPU6_INFO_REQUEST_DESTINATION_IOSF, + spc_base + IPU6_REG_PSYS_INFO_SEG_0_CONFIG_ICACHE_MASTER); + writel(prog->start[1], spc_base + IPU6_PSYS_REG_SPC_START_PC); + writel(pkg_dir_vied_address, base + hw_variant->dmem_offset); +} + +void ipu6_configure_spc(struct ipu6_device *isp, + const struct ipu6_hw_variants *hw_variant, + int pkg_dir_idx, void __iomem *base, u64 *pkg_dir, + dma_addr_t pkg_dir_dma_addr) +{ + void __iomem *dmem_base = base + hw_variant->dmem_offset; + void __iomem *spc_regs_base = base + hw_variant->spc_offset; + u32 val; + + val = readl(spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL); + val |= IPU6_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE; + writel(val, spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL); + + if (isp->secure_mode) + writel(IPU6_PKG_DIR_IMR_OFFSET, dmem_base); + else + ipu6_pkg_dir_configure_spc(isp, hw_variant, pkg_dir_idx, base, + pkg_dir, pkg_dir_dma_addr); +} +EXPORT_SYMBOL_NS_GPL(ipu6_configure_spc, "INTEL_IPU6"); + +#define IPU6_ISYS_CSI2_NPORTS 4 +#define IPU6SE_ISYS_CSI2_NPORTS 4 +#define IPU6_TGL_ISYS_CSI2_NPORTS 8 +#define IPU6EP_MTL_ISYS_CSI2_NPORTS 6 + +static void ipu6_internal_pdata_init(struct ipu6_device *isp) +{ + u8 hw_ver = isp->hw_ver; + + isys_ipdata.num_parallel_streams = IPU6_ISYS_NUM_STREAMS; + isys_ipdata.sram_gran_shift = IPU6_SRAM_GRANULARITY_SHIFT; + isys_ipdata.sram_gran_size = IPU6_SRAM_GRANULARITY_SIZE; + isys_ipdata.max_sram_size = IPU6_MAX_SRAM_SIZE; + isys_ipdata.sensor_type_start = IPU6_FW_ISYS_SENSOR_TYPE_START; + isys_ipdata.sensor_type_end = IPU6_FW_ISYS_SENSOR_TYPE_END; + isys_ipdata.max_streams = IPU6_ISYS_NUM_STREAMS; + isys_ipdata.max_send_queues = IPU6_N_MAX_SEND_QUEUES; + isys_ipdata.max_sram_blocks = IPU6_NOF_SRAM_BLOCKS_MAX; + isys_ipdata.max_devq_size = IPU6_DEV_SEND_QUEUE_SIZE; + isys_ipdata.csi2.nports = IPU6_ISYS_CSI2_NPORTS; + isys_ipdata.csi2.irq_mask = IPU6_CSI_RX_ERROR_IRQ_MASK; + isys_ipdata.csi2.ctrl0_irq_edge = IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE; + isys_ipdata.csi2.ctrl0_irq_clear = + IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR; + isys_ipdata.csi2.ctrl0_irq_mask = IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK; + isys_ipdata.csi2.ctrl0_irq_enable = + IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE; + isys_ipdata.csi2.ctrl0_irq_status = + IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS; + isys_ipdata.csi2.ctrl0_irq_lnp = + IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE; + isys_ipdata.enhanced_iwake = is_ipu6ep_mtl(hw_ver) || is_ipu6ep(hw_ver); + psys_ipdata.hw_variant.spc_offset = IPU6_PSYS_SPC_OFFSET; + isys_ipdata.csi2.fw_access_port_ofs = CSI_REG_HUB_FW_ACCESS_PORT_OFS; + + if (is_ipu6ep(hw_ver)) { + isys_ipdata.ltr = IPU6EP_LTR_VALUE; + isys_ipdata.memopen_threshold = IPU6EP_MIN_MEMOPEN_TH; + } + + if (is_ipu6_tgl(hw_ver)) + isys_ipdata.csi2.nports = IPU6_TGL_ISYS_CSI2_NPORTS; + + if (is_ipu6ep_mtl(hw_ver)) { + isys_ipdata.csi2.nports = IPU6EP_MTL_ISYS_CSI2_NPORTS; + + isys_ipdata.csi2.ctrl0_irq_edge = + IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE; + isys_ipdata.csi2.ctrl0_irq_clear = + IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR; + isys_ipdata.csi2.ctrl0_irq_mask = + IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK; + isys_ipdata.csi2.ctrl0_irq_enable = + IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE; + isys_ipdata.csi2.ctrl0_irq_lnp = + IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE; + isys_ipdata.csi2.ctrl0_irq_status = + IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS; + isys_ipdata.csi2.fw_access_port_ofs = + CSI_REG_HUB_FW_ACCESS_PORT_V6OFS; + isys_ipdata.ltr = IPU6EP_MTL_LTR_VALUE; + isys_ipdata.memopen_threshold = IPU6EP_MTL_MIN_MEMOPEN_TH; + } + + if (is_ipu6se(hw_ver)) { + isys_ipdata.csi2.nports = IPU6SE_ISYS_CSI2_NPORTS; + isys_ipdata.csi2.irq_mask = IPU6SE_CSI_RX_ERROR_IRQ_MASK; + isys_ipdata.num_parallel_streams = IPU6SE_ISYS_NUM_STREAMS; + isys_ipdata.sram_gran_shift = IPU6SE_SRAM_GRANULARITY_SHIFT; + isys_ipdata.sram_gran_size = IPU6SE_SRAM_GRANULARITY_SIZE; + isys_ipdata.max_sram_size = IPU6SE_MAX_SRAM_SIZE; + isys_ipdata.sensor_type_start = + IPU6SE_FW_ISYS_SENSOR_TYPE_START; + isys_ipdata.sensor_type_end = IPU6SE_FW_ISYS_SENSOR_TYPE_END; + isys_ipdata.max_streams = IPU6SE_ISYS_NUM_STREAMS; + isys_ipdata.max_send_queues = IPU6SE_N_MAX_SEND_QUEUES; + isys_ipdata.max_sram_blocks = IPU6SE_NOF_SRAM_BLOCKS_MAX; + isys_ipdata.max_devq_size = IPU6SE_DEV_SEND_QUEUE_SIZE; + psys_ipdata.hw_variant.spc_offset = IPU6SE_PSYS_SPC_OFFSET; + } +} + +static struct ipu6_bus_device * +ipu6_isys_init(struct pci_dev *pdev, struct device *parent, + struct ipu6_buttress_ctrl *ctrl, void __iomem *base, + const struct ipu6_isys_internal_pdata *ipdata) +{ + struct device *dev = &pdev->dev; + struct ipu6_bus_device *isys_adev; + struct ipu6_isys_pdata *pdata; + int ret; + + ret = ipu_bridge_init(dev, ipu_bridge_parse_ssdb); + if (ret) { + dev_err_probe(dev, ret, "IPU6 bridge init failed\n"); + return ERR_PTR(ret); + } + + pdata = kzalloc_obj(*pdata); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->base = base; + pdata->ipdata = ipdata; + + isys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl, + IPU6_ISYS_NAME); + if (IS_ERR(isys_adev)) { + kfree(pdata); + return dev_err_cast_probe(dev, isys_adev, + "ipu6_bus_initialize_device isys failed\n"); + } + + isys_adev->mmu = ipu6_mmu_init(dev, base, ISYS_MMID, + &ipdata->hw_variant); + if (IS_ERR(isys_adev->mmu)) { + put_device(&isys_adev->auxdev.dev); + return dev_err_cast_probe(dev, isys_adev->mmu, + "ipu6_mmu_init(isys_adev->mmu) failed\n"); + } + + isys_adev->mmu->dev = &isys_adev->auxdev.dev; + + ret = ipu6_bus_add_device(isys_adev); + if (ret) + return ERR_PTR(ret); + + return isys_adev; +} + +static struct ipu6_bus_device * +ipu6_psys_init(struct pci_dev *pdev, struct device *parent, + struct ipu6_buttress_ctrl *ctrl, void __iomem *base, + const struct ipu6_psys_internal_pdata *ipdata) +{ + struct ipu6_bus_device *psys_adev; + struct ipu6_psys_pdata *pdata; + int ret; + + pdata = kzalloc_obj(*pdata); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->base = base; + pdata->ipdata = ipdata; + + psys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl, + IPU6_PSYS_NAME); + if (IS_ERR(psys_adev)) { + kfree(pdata); + return dev_err_cast_probe(&pdev->dev, psys_adev, + "ipu6_bus_initialize_device psys failed\n"); + } + + psys_adev->mmu = ipu6_mmu_init(&pdev->dev, base, PSYS_MMID, + &ipdata->hw_variant); + if (IS_ERR(psys_adev->mmu)) { + put_device(&psys_adev->auxdev.dev); + return dev_err_cast_probe(&pdev->dev, psys_adev->mmu, + "ipu6_mmu_init(psys_adev->mmu) failed\n"); + } + + psys_adev->mmu->dev = &psys_adev->auxdev.dev; + + ret = ipu6_bus_add_device(psys_adev); + if (ret) + return ERR_PTR(ret); + + return psys_adev; +} + +static int ipu6_pci_config_setup(struct pci_dev *dev, u8 hw_ver) +{ + int ret; + + /* No PCI msi capability for IPU6EP */ + if (is_ipu6ep(hw_ver) || is_ipu6ep_mtl(hw_ver)) { + /* likely do nothing as msi not enabled by default */ + pci_disable_msi(dev); + return 0; + } + + ret = pci_alloc_irq_vectors(dev, 1, 1, PCI_IRQ_MSI); + if (ret < 0) + return dev_err_probe(&dev->dev, ret, "Request msi failed"); + + return 0; +} + +static void ipu6_configure_vc_mechanism(struct ipu6_device *isp) +{ + u32 val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL); + + if (IPU6_BTRS_ARB_STALL_MODE_VC0 == IPU6_BTRS_ARB_MODE_TYPE_STALL) + val |= BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0; + else + val &= ~BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0; + + if (IPU6_BTRS_ARB_STALL_MODE_VC1 == IPU6_BTRS_ARB_MODE_TYPE_STALL) + val |= BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1; + else + val &= ~BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1; + + writel(val, isp->base + BUTTRESS_REG_BTRS_CTRL); +} + +static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct ipu6_buttress_ctrl *isys_ctrl = NULL, *psys_ctrl = NULL; + struct device *dev = &pdev->dev; + void __iomem *isys_base = NULL; + void __iomem *psys_base = NULL; + struct ipu6_device *isp; + phys_addr_t phys; + u32 val, version, sku_id; + int ret; + + isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); + if (!isp) + return -ENOMEM; + + isp->pdev = pdev; + INIT_LIST_HEAD(&isp->devices); + + ret = pcim_enable_device(pdev); + if (ret) + return dev_err_probe(dev, ret, "Enable PCI device failed\n"); + + phys = pci_resource_start(pdev, IPU6_PCI_BAR); + dev_dbg(dev, "IPU6 PCI bar[%u] = %pa\n", IPU6_PCI_BAR, &phys); + + isp->base = pcim_iomap_region(pdev, IPU6_PCI_BAR, IPU6_NAME); + if (IS_ERR(isp->base)) + return dev_err_probe(dev, PTR_ERR(isp->base), + "Failed to I/O mem remapping\n"); + + pci_set_drvdata(pdev, isp); + pci_set_master(pdev); + + isp->cpd_metadata_cmpnt_size = sizeof(struct ipu6_cpd_metadata_cmpnt); + switch (id->device) { + case PCI_DEVICE_ID_INTEL_IPU6: + isp->hw_ver = IPU6_VER_6; + isp->cpd_fw_name = IPU6_FIRMWARE_NAME; + break; + case PCI_DEVICE_ID_INTEL_IPU6SE: + isp->hw_ver = IPU6_VER_6SE; + isp->cpd_fw_name = IPU6SE_FIRMWARE_NAME; + isp->cpd_metadata_cmpnt_size = + sizeof(struct ipu6se_cpd_metadata_cmpnt); + break; + case PCI_DEVICE_ID_INTEL_IPU6EP_ADLP: + case PCI_DEVICE_ID_INTEL_IPU6EP_RPLP: + isp->hw_ver = IPU6_VER_6EP; + isp->cpd_fw_name = IPU6EP_FIRMWARE_NAME; + break; + case PCI_DEVICE_ID_INTEL_IPU6EP_ADLN: + isp->hw_ver = IPU6_VER_6EP; + isp->cpd_fw_name = IPU6EPADLN_FIRMWARE_NAME; + break; + case PCI_DEVICE_ID_INTEL_IPU6EP_MTL: + isp->hw_ver = IPU6_VER_6EP_MTL; + isp->cpd_fw_name = IPU6EPMTL_FIRMWARE_NAME; + break; + default: + return dev_err_probe(dev, -ENODEV, + "Unsupported IPU6 device %x\n", + id->device); + } + + ipu6_internal_pdata_init(isp); + + isys_base = isp->base + isys_ipdata.hw_variant.offset; + psys_base = isp->base + psys_ipdata.hw_variant.offset; + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(39)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set DMA mask\n"); + + dma_set_max_seg_size(dev, UINT_MAX); + + ret = ipu6_pci_config_setup(pdev, isp->hw_ver); + if (ret) + return ret; + + ret = ipu6_buttress_init(isp); + if (ret) + return ret; + + ret = request_firmware(&isp->cpd_fw, isp->cpd_fw_name, dev); + if (ret) { + dev_err_probe(&isp->pdev->dev, ret, + "Requesting signed firmware %s failed\n", + isp->cpd_fw_name); + goto buttress_exit; + } + + ret = ipu6_cpd_validate_cpd_file(isp, isp->cpd_fw->data, + isp->cpd_fw->size); + if (ret) { + dev_err_probe(&isp->pdev->dev, ret, + "Failed to validate cpd\n"); + goto out_ipu6_bus_del_devices; + } + + isys_ctrl = devm_kmemdup(dev, &isys_buttress_ctrl, + sizeof(isys_buttress_ctrl), GFP_KERNEL); + if (!isys_ctrl) { + ret = -ENOMEM; + goto out_ipu6_bus_del_devices; + } + + isp->isys = ipu6_isys_init(pdev, dev, isys_ctrl, isys_base, + &isys_ipdata); + if (IS_ERR(isp->isys)) { + ret = PTR_ERR(isp->isys); + goto out_ipu6_bus_del_devices; + } + + psys_ctrl = devm_kmemdup(dev, &psys_buttress_ctrl, + sizeof(psys_buttress_ctrl), GFP_KERNEL); + if (!psys_ctrl) { + ret = -ENOMEM; + goto out_ipu6_bus_del_devices; + } + + isp->psys = ipu6_psys_init(pdev, &isp->isys->auxdev.dev, psys_ctrl, + psys_base, &psys_ipdata); + if (IS_ERR(isp->psys)) { + ret = PTR_ERR(isp->psys); + goto out_ipu6_bus_del_devices; + } + + ret = pm_runtime_resume_and_get(&isp->psys->auxdev.dev); + if (ret < 0) + goto out_ipu6_bus_del_devices; + + ret = ipu6_mmu_hw_init(isp->psys->mmu); + if (ret) { + dev_err_probe(&isp->pdev->dev, ret, + "Failed to set MMU hardware\n"); + goto out_ipu6_rpm_put; + } + + ret = ipu6_buttress_map_fw_image(isp->psys, isp->cpd_fw, + &isp->psys->fw_sgt); + if (ret) { + dev_err_probe(&isp->pdev->dev, ret, "failed to map fw image\n"); + goto out_ipu6_rpm_put; + } + + ret = ipu6_cpd_create_pkg_dir(isp->psys, isp->cpd_fw->data); + if (ret) { + dev_err_probe(&isp->pdev->dev, ret, + "failed to create pkg dir\n"); + goto out_ipu6_rpm_put; + } + + ret = devm_request_threaded_irq(dev, pdev->irq, ipu6_buttress_isr, + ipu6_buttress_isr_threaded, + IRQF_SHARED, IPU6_NAME, isp); + if (ret) { + dev_err_probe(dev, ret, "Requesting irq failed\n"); + goto out_ipu6_rpm_put; + } + + ret = ipu6_buttress_authenticate(isp); + if (ret) { + dev_err_probe(&isp->pdev->dev, ret, + "FW authentication failed\n"); + goto out_free_irq; + } + + ipu6_mmu_hw_cleanup(isp->psys->mmu); + pm_runtime_put(&isp->psys->auxdev.dev); + + /* Configure the arbitration mechanisms for VC requests */ + ipu6_configure_vc_mechanism(isp); + + val = readl(isp->base + BUTTRESS_REG_SKU); + sku_id = FIELD_GET(GENMASK(6, 4), val); + version = FIELD_GET(GENMASK(3, 0), val); + dev_info(dev, "IPU%u-v%u[%x] hardware version %d\n", version, sku_id, + pdev->device, isp->hw_ver); + + pm_runtime_put_noidle(dev); + pm_runtime_allow(dev); + + isp->bus_ready_to_probe = true; + + return 0; + +out_free_irq: + devm_free_irq(dev, pdev->irq, isp); +out_ipu6_rpm_put: + pm_runtime_put_sync(&isp->psys->auxdev.dev); +out_ipu6_bus_del_devices: + if (!IS_ERR_OR_NULL(isp->psys)) { + ipu6_cpd_free_pkg_dir(isp->psys); + ipu6_buttress_unmap_fw_image(isp->psys, &isp->psys->fw_sgt); + } + if (!IS_ERR_OR_NULL(isp->psys) && !IS_ERR_OR_NULL(isp->psys->mmu)) + ipu6_mmu_cleanup(isp->psys->mmu); + if (!IS_ERR_OR_NULL(isp->isys) && !IS_ERR_OR_NULL(isp->isys->mmu)) + ipu6_mmu_cleanup(isp->isys->mmu); + ipu6_bus_del_devices(pdev); + release_firmware(isp->cpd_fw); +buttress_exit: + ipu6_buttress_exit(isp); + + return ret; +} + +static void ipu6_pci_remove(struct pci_dev *pdev) +{ + struct ipu6_device *isp = pci_get_drvdata(pdev); + struct ipu6_mmu *isys_mmu = isp->isys->mmu; + struct ipu6_mmu *psys_mmu = isp->psys->mmu; + + devm_free_irq(&pdev->dev, pdev->irq, isp); + ipu6_cpd_free_pkg_dir(isp->psys); + + ipu6_buttress_unmap_fw_image(isp->psys, &isp->psys->fw_sgt); + ipu6_buttress_exit(isp); + + ipu6_bus_del_devices(pdev); + + pm_runtime_forbid(&pdev->dev); + pm_runtime_get_noresume(&pdev->dev); + + release_firmware(isp->cpd_fw); + + ipu6_mmu_cleanup(psys_mmu); + ipu6_mmu_cleanup(isys_mmu); +} + +static void ipu6_pci_reset_prepare(struct pci_dev *pdev) +{ + struct ipu6_device *isp = pci_get_drvdata(pdev); + + pm_runtime_forbid(&isp->pdev->dev); +} + +static void ipu6_pci_reset_done(struct pci_dev *pdev) +{ + struct ipu6_device *isp = pci_get_drvdata(pdev); + + ipu6_buttress_restore(isp); + if (isp->secure_mode) + ipu6_buttress_reset_authentication(isp); + + isp->need_ipc_reset = true; + pm_runtime_allow(&isp->pdev->dev); +} + +/* + * PCI base driver code requires driver to provide these to enable + * PCI device level PM state transitions (D0<->D3) + */ +static int ipu6_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + + synchronize_irq(pdev->irq); + return 0; +} + +static int ipu6_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct ipu6_device *isp = pci_get_drvdata(pdev); + struct ipu6_buttress *b = &isp->buttress; + int ret; + + /* Configure the arbitration mechanisms for VC requests */ + ipu6_configure_vc_mechanism(isp); + + isp->secure_mode = ipu6_buttress_get_secure_mode(isp); + dev_info(dev, "IPU6 in %s mode\n", + isp->secure_mode ? "secure" : "non-secure"); + + ipu6_buttress_restore(isp); + + ret = ipu6_buttress_ipc_reset(isp, &b->cse); + if (ret) + dev_err(&isp->pdev->dev, "IPC reset protocol failed!\n"); + + ret = pm_runtime_resume_and_get(&isp->psys->auxdev.dev); + if (ret < 0) { + dev_err(&isp->psys->auxdev.dev, "Failed to get runtime PM\n"); + return 0; + } + + ret = ipu6_buttress_authenticate(isp); + if (ret) + dev_err(&isp->pdev->dev, "FW authentication failed(%d)\n", ret); + + pm_runtime_put(&isp->psys->auxdev.dev); + + return 0; +} + +static int ipu6_runtime_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct ipu6_device *isp = pci_get_drvdata(pdev); + int ret; + + ipu6_configure_vc_mechanism(isp); + ipu6_buttress_restore(isp); + + if (isp->need_ipc_reset) { + struct ipu6_buttress *b = &isp->buttress; + + isp->need_ipc_reset = false; + ret = ipu6_buttress_ipc_reset(isp, &b->cse); + if (ret) + dev_err(&isp->pdev->dev, "IPC reset protocol failed\n"); + } + + return 0; +} + +static const struct dev_pm_ops ipu6_pm_ops = { + SYSTEM_SLEEP_PM_OPS(&ipu6_suspend, &ipu6_resume) + RUNTIME_PM_OPS(&ipu6_suspend, &ipu6_runtime_resume, NULL) +}; + +MODULE_DEVICE_TABLE(pci, ipu6_pci_tbl); + +static const struct pci_error_handlers pci_err_handlers = { + .reset_prepare = ipu6_pci_reset_prepare, + .reset_done = ipu6_pci_reset_done, +}; + +static struct pci_driver ipu6_pci_driver = { + .name = IPU6_NAME, + .id_table = ipu6_pci_tbl, + .probe = ipu6_pci_probe, + .remove = ipu6_pci_remove, + .driver = { + .pm = pm_ptr(&ipu6_pm_ops), + }, + .err_handler = &pci_err_handlers, +}; + +module_pci_driver(ipu6_pci_driver); + +MODULE_IMPORT_NS("INTEL_IPU_BRIDGE"); +MODULE_AUTHOR("Sakari Ailus "); +MODULE_AUTHOR("Tianshu Qiu "); +MODULE_AUTHOR("Bingbu Cao "); +MODULE_AUTHOR("Qingwu Zhang "); +MODULE_AUTHOR("Yunliang Ding "); +MODULE_AUTHOR("Hongju Wang"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Intel IPU6 PCI driver"); diff --git a/drivers/media/pci/intel/ipu6/ipu6.h b/drivers/media/pci/intel/ipu6/ipu6.h new file mode 100644 index 000000000..92e3c3414 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6.h @@ -0,0 +1,342 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013 - 2024 Intel Corporation */ + +#ifndef IPU6_H +#define IPU6_H + +#include +#include +#include + +#include "ipu6-buttress.h" + +struct firmware; +struct pci_dev; +struct ipu6_bus_device; + +#define IPU6_NAME "intel-ipu6" +#define IPU6_MEDIA_DEV_MODEL_NAME "ipu6" + +#define IPU6SE_FIRMWARE_NAME "intel/ipu/ipu6se_fw.bin" +#define IPU6EP_FIRMWARE_NAME "intel/ipu/ipu6ep_fw.bin" +#define IPU6_FIRMWARE_NAME "intel/ipu/ipu6_fw.bin" +#define IPU6EPMTL_FIRMWARE_NAME "intel/ipu/ipu6epmtl_fw.bin" +#define IPU6EPADLN_FIRMWARE_NAME "intel/ipu/ipu6epadln_fw.bin" + +enum ipu6_version { + IPU6_VER_INVALID = 0, + IPU6_VER_6 = 1, + IPU6_VER_6SE = 3, + IPU6_VER_6EP = 5, + IPU6_VER_6EP_MTL = 6, +}; + +/* + * IPU6 - TGL + * IPU6SE - JSL + * IPU6EP - ADL/RPL + * IPU6EP_MTL - MTL + */ +static inline bool is_ipu6se(u8 hw_ver) +{ + return hw_ver == IPU6_VER_6SE; +} + +static inline bool is_ipu6ep(u8 hw_ver) +{ + return hw_ver == IPU6_VER_6EP; +} + +static inline bool is_ipu6ep_mtl(u8 hw_ver) +{ + return hw_ver == IPU6_VER_6EP_MTL; +} + +static inline bool is_ipu6_tgl(u8 hw_ver) +{ + return hw_ver == IPU6_VER_6; +} + +/* + * ISYS DMA can overshoot. For higher resolutions over allocation is one line + * but it must be at minimum 1024 bytes. Value could be different in + * different versions / generations thus provide it via platform data. + */ +#define IPU6_ISYS_OVERALLOC_MIN 1024 + +/* Physical pages in GDA is 128, page size is 2K for IPU6, 1K for others */ +#define IPU6_DEVICE_GDA_NR_PAGES 128 + +/* Virtualization factor to calculate the available virtual pages */ +#define IPU6_DEVICE_GDA_VIRT_FACTOR 32 + +struct ipu6_device { + struct pci_dev *pdev; + struct list_head devices; + struct ipu6_bus_device *isys; + struct ipu6_bus_device *psys; + struct ipu6_buttress buttress; + + const struct firmware *cpd_fw; + const char *cpd_fw_name; + u32 cpd_metadata_cmpnt_size; + + void __iomem *base; + bool need_ipc_reset; + bool secure_mode; + u8 hw_ver; + bool bus_ready_to_probe; +}; + +#define IPU6_ISYS_NAME "isys" +#define IPU6_PSYS_NAME "psys" + +#define IPU6_MMU_MAX_DEVICES 4 +#define IPU6_MMU_ADDR_BITS 32 +/* The firmware is accessible within the first 2 GiB only in non-secure mode. */ +#define IPU6_MMU_ADDR_BITS_NON_SECURE 31 + +#define IPU6_MMU_MAX_TLB_L1_STREAMS 32 +#define IPU6_MMU_MAX_TLB_L2_STREAMS 32 +#define IPU6_MAX_LI_BLOCK_ADDR 128 +#define IPU6_MAX_L2_BLOCK_ADDR 64 + +#define IPU6SE_ISYS_NUM_STREAMS IPU6SE_NONSECURE_STREAM_ID_MAX +#define IPU6_ISYS_NUM_STREAMS IPU6_NONSECURE_STREAM_ID_MAX + +/* + * To maximize the IOSF utlization, IPU6 need to send requests in bursts. + * At the DMA interface with the buttress, there are CDC FIFOs with burst + * collection capability. CDC FIFO burst collectors have a configurable + * threshold and is configured based on the outcome of performance measurements. + * + * isys has 3 ports with IOSF interface for VC0, VC1 and VC2 + * psys has 4 ports with IOSF interface for VC0, VC1w, VC1r and VC2 + * + * Threshold values are pre-defined and are arrived at after performance + * evaluations on a type of IPU6 + */ +#define IPU6_MAX_VC_IOSF_PORTS 4 + +/* + * IPU6 must configure correct arbitration mechanism related to the IOSF VC + * requests. There are two options per VC0 and VC1 - > 0 means rearbitrate on + * stall and 1 means stall until the request is completed. + */ +#define IPU6_BTRS_ARB_MODE_TYPE_REARB 0 +#define IPU6_BTRS_ARB_MODE_TYPE_STALL 1 + +/* Currently chosen arbitration mechanism for VC0 */ +#define IPU6_BTRS_ARB_STALL_MODE_VC0 \ + IPU6_BTRS_ARB_MODE_TYPE_REARB + +/* Currently chosen arbitration mechanism for VC1 */ +#define IPU6_BTRS_ARB_STALL_MODE_VC1 \ + IPU6_BTRS_ARB_MODE_TYPE_REARB + +/* + * MMU Invalidation HW bug workaround by ZLW mechanism + * + * Old IPU6 MMUV2 has a bug in the invalidation mechanism which might result in + * wrong translation or replication of the translation. This will cause data + * corruption. So we cannot directly use the MMU V2 invalidation registers + * to invalidate the MMU. Instead, whenever an invalidate is called, we need to + * clear the TLB by evicting all the valid translations by filling it with trash + * buffer (which is guaranteed not to be used by any other processes). ZLW is + * used to fill the L1 and L2 caches with the trash buffer translations. ZLW + * or Zero length write, is pre-fetch mechanism to pre-fetch the pages in + * advance to the L1 and L2 caches without triggering any memory operations. + * + * In MMU V2, L1 -> 16 streams and 64 blocks, maximum 16 blocks per stream + * One L1 block has 16 entries, hence points to 16 * 4K pages + * L2 -> 16 streams and 32 blocks. 2 blocks per streams + * One L2 block maps to 1024 L1 entries, hence points to 4MB address range + * 2 blocks per L2 stream means, 1 stream points to 8MB range + * + * As we need to clear the caches and 8MB being the biggest cache size, we need + * to have trash buffer which points to 8MB address range. As these trash + * buffers are not used for any memory transactions, we need only the least + * amount of physical memory. So we reserve 8MB IOVA address range but only + * one page is reserved from physical memory. Each of this 8MB IOVA address + * range is then mapped to the same physical memory page. + */ +/* One L2 entry maps 1024 L1 entries and one L1 entry per page */ +#define IPU6_MMUV2_L2_RANGE (1024 * PAGE_SIZE) +/* Max L2 blocks per stream */ +#define IPU6_MMUV2_MAX_L2_BLOCKS 2 +/* Max L1 blocks per stream */ +#define IPU6_MMUV2_MAX_L1_BLOCKS 16 +#define IPU6_MMUV2_TRASH_RANGE (IPU6_MMUV2_L2_RANGE * IPU6_MMUV2_MAX_L2_BLOCKS) +/* Entries per L1 block */ +#define MMUV2_ENTRIES_PER_L1_BLOCK 16 +#define MMUV2_TRASH_L1_BLOCK_OFFSET (MMUV2_ENTRIES_PER_L1_BLOCK * PAGE_SIZE) +#define MMUV2_TRASH_L2_BLOCK_OFFSET IPU6_MMUV2_L2_RANGE + +/* + * In some of the IPU6 MMUs, there is provision to configure L1 and L2 page + * table caches. Both these L1 and L2 caches are divided into multiple sections + * called streams. There is maximum 16 streams for both caches. Each of these + * sections are subdivided into multiple blocks. When nr_l1streams = 0 and + * nr_l2streams = 0, means the MMU is of type MMU_V1 and do not support + * L1/L2 page table caches. + * + * L1 stream per block sizes are configurable and varies per usecase. + * L2 has constant block sizes - 2 blocks per stream. + * + * MMU1 support pre-fetching of the pages to have less cache lookup misses. To + * enable the pre-fetching, MMU1 AT (Address Translator) device registers + * need to be configured. + * + * There are four types of memory accesses which requires ZLW configuration. + * ZLW(Zero Length Write) is a mechanism to enable VT-d pre-fetching on IOMMU. + * + * 1. Sequential Access or 1D mode + * Set ZLW_EN -> 1 + * set ZLW_PAGE_CROSS_1D -> 1 + * Set ZLW_N to "N" pages so that ZLW will be inserte N pages ahead where + * N is pre-defined and hardcoded in the platform data + * Set ZLW_2D -> 0 + * + * 2. ZLW 2D mode + * Set ZLW_EN -> 1 + * set ZLW_PAGE_CROSS_1D -> 1, + * Set ZLW_N -> 0 + * Set ZLW_2D -> 1 + * + * 3. ZLW Enable (no 1D or 2D mode) + * Set ZLW_EN -> 1 + * set ZLW_PAGE_CROSS_1D -> 0, + * Set ZLW_N -> 0 + * Set ZLW_2D -> 0 + * + * 4. ZLW disable + * Set ZLW_EN -> 0 + * set ZLW_PAGE_CROSS_1D -> 0, + * Set ZLW_N -> 0 + * Set ZLW_2D -> 0 + * + * To configure the ZLW for the above memory access, four registers are + * available. Hence to track these four settings, we have the following entries + * in the struct ipu6_mmu_hw. Each of these entries are per stream and + * available only for the L1 streams. + * + * a. l1_zlw_en -> To track zlw enabled per stream (ZLW_EN) + * b. l1_zlw_1d_mode -> Track 1D mode per stream. ZLW inserted at page boundary + * c. l1_ins_zlw_ahead_pages -> to track how advance the ZLW need to be inserted + * Insert ZLW request N pages ahead address. + * d. l1_zlw_2d_mode -> To track 2D mode per stream (ZLW_2D) + * + * + * Currently L1/L2 streams, blocks, AT ZLW configurations etc. are pre-defined + * as per the usecase specific calculations. Any change to this pre-defined + * table has to happen in sync with IPU6 FW. + */ +struct ipu6_mmu_hw { + union { + unsigned long offset; + void __iomem *base; + }; + u32 info_bits; + u8 nr_l1streams; + /* + * L1 has variable blocks per stream - total of 64 blocks and maximum of + * 16 blocks per stream. Configurable by using the block start address + * per stream. Block start address is calculated from the block size + */ + u8 l1_block_sz[IPU6_MMU_MAX_TLB_L1_STREAMS]; + /* Is ZLW is enabled in each stream */ + bool l1_zlw_en[IPU6_MMU_MAX_TLB_L1_STREAMS]; + bool l1_zlw_1d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS]; + u8 l1_ins_zlw_ahead_pages[IPU6_MMU_MAX_TLB_L1_STREAMS]; + bool l1_zlw_2d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS]; + + u32 l1_stream_id_reg_offset; + u32 l2_stream_id_reg_offset; + + u8 nr_l2streams; + /* + * L2 has fixed 2 blocks per stream. Block address is calculated + * from the block size + */ + u8 l2_block_sz[IPU6_MMU_MAX_TLB_L2_STREAMS]; + /* flag to track if WA is needed for successive invalidate HW bug */ + bool insert_read_before_invalidate; +}; + +struct ipu6_mmu_pdata { + u32 nr_mmus; + struct ipu6_mmu_hw mmu_hw[IPU6_MMU_MAX_DEVICES]; + int mmid; +}; + +struct ipu6_isys_csi2_pdata { + void __iomem *base; +}; + +struct ipu6_isys_internal_csi2_pdata { + u32 nports; + u32 irq_mask; + u32 ctrl0_irq_edge; + u32 ctrl0_irq_clear; + u32 ctrl0_irq_mask; + u32 ctrl0_irq_enable; + u32 ctrl0_irq_lnp; + u32 ctrl0_irq_status; + u32 fw_access_port_ofs; +}; + +struct ipu6_isys_internal_tpg_pdata { + u32 ntpgs; + u32 *offsets; + u32 *sels; +}; + +struct ipu6_hw_variants { + unsigned long offset; + u32 nr_mmus; + struct ipu6_mmu_hw mmu_hw[IPU6_MMU_MAX_DEVICES]; + u8 cdc_fifos; + u8 cdc_fifo_threshold[IPU6_MAX_VC_IOSF_PORTS]; + u32 dmem_offset; + u32 spc_offset; +}; + +struct ipu6_isys_internal_pdata { + struct ipu6_isys_internal_csi2_pdata csi2; + struct ipu6_hw_variants hw_variant; + u32 num_parallel_streams; + u32 isys_dma_overshoot; + u32 sram_gran_shift; + u32 sram_gran_size; + u32 max_sram_size; + u32 max_streams; + u32 max_send_queues; + u32 max_sram_blocks; + u32 max_devq_size; + u32 sensor_type_start; + u32 sensor_type_end; + u32 ltr; + u32 memopen_threshold; + bool enhanced_iwake; +}; + +struct ipu6_isys_pdata { + void __iomem *base; + const struct ipu6_isys_internal_pdata *ipdata; +}; + +struct ipu6_psys_internal_pdata { + struct ipu6_hw_variants hw_variant; +}; + +struct ipu6_psys_pdata { + void __iomem *base; + const struct ipu6_psys_internal_pdata *ipdata; +}; + +int ipu6_fw_authenticate(void *data, u64 val); +void ipu6_configure_spc(struct ipu6_device *isp, + const struct ipu6_hw_variants *hw_variant, + int pkg_dir_idx, void __iomem *base, u64 *pkg_dir, + dma_addr_t pkg_dir_dma_addr); +#endif /* IPU6_H */ diff --git a/drivers/media/pci/intel/ivsc/Kconfig b/drivers/media/pci/intel/ivsc/Kconfig new file mode 100644 index 000000000..a7d9607ec --- /dev/null +++ b/drivers/media/pci/intel/ivsc/Kconfig @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (C) 2023, Intel Corporation. All rights reserved. + +config INTEL_VSC + tristate "Intel Visual Sensing Controller" + depends on INTEL_MEI && ACPI && VIDEO_DEV + depends on IPU_BRIDGE || !IPU_BRIDGE + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This adds support for Intel Visual Sensing Controller (IVSC). + + The IVSC support is split into two devices, ACE (Algorithm + Context Engine) and CSI (Camera Serial Interface), each of which + have their own drivers. The ACE is used to select the ownership + of the sensor between the IVSC and the host CPU while the CSI is + used to both select the routing destination for the data the + sensor transmits over the CSI-2 bus between the IVSC and the + host CPU and to configure the CSI-2 bus itself. + + The modules will be called ivsc-ace and ivsc-csi. diff --git a/drivers/media/pci/intel/ivsc/Makefile b/drivers/media/pci/intel/ivsc/Makefile new file mode 100644 index 000000000..00fad29a6 --- /dev/null +++ b/drivers/media/pci/intel/ivsc/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2023, Intel Corporation. All rights reserved. + +obj-$(CONFIG_INTEL_VSC) += ivsc-csi.o +ivsc-csi-y += mei_csi.o + +obj-$(CONFIG_INTEL_VSC) += ivsc-ace.o +ivsc-ace-y += mei_ace.o diff --git a/drivers/media/pci/intel/ivsc/mei_ace.c b/drivers/media/pci/intel/ivsc/mei_ace.c new file mode 100644 index 000000000..b306a320b --- /dev/null +++ b/drivers/media/pci/intel/ivsc/mei_ace.c @@ -0,0 +1,582 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2023 Intel Corporation. All rights reserved. + * Intel Visual Sensing Controller ACE Linux driver + */ + +/* + * To set ownership of camera sensor, there is specific command, which + * is sent via MEI protocol. That's a two-step scheme where the firmware + * first acks receipt of the command and later responses the command was + * executed. The command sending function uses "completion" as the + * synchronization mechanism. The notification for command is received + * via a mei callback which wakes up the caller. There can be only one + * outstanding command at a time. + * + * The power line of camera sensor is directly connected to IVSC instead + * of host, when camera sensor ownership is switched to host, sensor is + * already powered up by firmware. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* indicating driver message */ +#define ACE_DRV_MSG 1 +/* indicating set command */ +#define ACE_CMD_SET 4 +/* command timeout determined experimentally */ +#define ACE_CMD_TIMEOUT (5 * HZ) +/* indicating the first command block */ +#define ACE_CMD_INIT_BLOCK 1 +/* indicating the last command block */ +#define ACE_CMD_FINAL_BLOCK 1 +/* size of camera status notification content */ +#define ACE_CAMERA_STATUS_SIZE 5 + +/* UUID used to get firmware id */ +#define ACE_GET_FW_ID_UUID UUID_LE(0x6167DCFB, 0x72F1, 0x4584, 0xBF, \ + 0xE3, 0x84, 0x17, 0x71, 0xAA, 0x79, 0x0B) + +/* UUID used to get csi device */ +#define MEI_CSI_UUID UUID_LE(0x92335FCF, 0x3203, 0x4472, \ + 0xAF, 0x93, 0x7b, 0x44, 0x53, 0xAC, 0x29, 0xDA) + +/* identify firmware event type */ +enum ace_event_type { + /* firmware ready */ + ACE_FW_READY = 0x8, + + /* command response */ + ACE_CMD_RESPONSE = 0x10, +}; + +/* identify camera sensor ownership */ +enum ace_camera_owner { + ACE_CAMERA_IVSC, + ACE_CAMERA_HOST, +}; + +/* identify the command id supported by firmware IPC */ +enum ace_cmd_id { + /* used to switch camera sensor to host */ + ACE_SWITCH_CAMERA_TO_HOST = 0x13, + + /* used to switch camera sensor to IVSC */ + ACE_SWITCH_CAMERA_TO_IVSC = 0x14, + + /* used to get firmware id */ + ACE_GET_FW_ID = 0x1A, +}; + +/* ACE command header structure */ +struct ace_cmd_hdr { + u32 firmware_id : 16; + u32 instance_id : 8; + u32 type : 5; + u32 rsp : 1; + u32 msg_tgt : 1; + u32 _hw_rsvd_1 : 1; + u32 param_size : 20; + u32 cmd_id : 8; + u32 final_block : 1; + u32 init_block : 1; + u32 _hw_rsvd_2 : 2; +} __packed; + +/* ACE command parameter structure */ +union ace_cmd_param { + uuid_le uuid; + u32 param; +}; + +/* ACE command structure */ +struct ace_cmd { + struct ace_cmd_hdr hdr; + union ace_cmd_param param; +} __packed; + +/* ACE notification header */ +union ace_notif_hdr { + struct _confirm { + u32 status : 24; + u32 type : 5; + u32 rsp : 1; + u32 msg_tgt : 1; + u32 _hw_rsvd_1 : 1; + u32 param_size : 20; + u32 cmd_id : 8; + u32 final_block : 1; + u32 init_block : 1; + u32 _hw_rsvd_2 : 2; + } __packed ack; + + struct _event { + u32 rsvd1 : 16; + u32 event_type : 8; + u32 type : 5; + u32 ack : 1; + u32 msg_tgt : 1; + u32 _hw_rsvd_1 : 1; + u32 rsvd2 : 30; + u32 _hw_rsvd_2 : 2; + } __packed event; + + struct _response { + u32 event_id : 16; + u32 notif_type : 8; + u32 type : 5; + u32 rsp : 1; + u32 msg_tgt : 1; + u32 _hw_rsvd_1 : 1; + u32 event_data_size : 16; + u32 request_target : 1; + u32 request_type : 5; + u32 cmd_id : 8; + u32 _hw_rsvd_2 : 2; + } __packed response; +}; + +/* ACE notification content */ +union ace_notif_cont { + u16 firmware_id; + u8 state_notif; + u8 camera_status[ACE_CAMERA_STATUS_SIZE]; +}; + +/* ACE notification structure */ +struct ace_notif { + union ace_notif_hdr hdr; + union ace_notif_cont cont; +} __packed; + +struct mei_ace { + struct mei_cl_device *cldev; + + /* command ack */ + struct ace_notif cmd_ack; + /* command response */ + struct ace_notif cmd_response; + /* used to wait for command ack and response */ + struct completion cmd_completion; + /* lock used to prevent multiple call to send command */ + struct mutex lock; + + /* used to construct command */ + u16 firmware_id; + + struct device *csi_dev; + + /* runtime PM link from ace to csi */ + struct device_link *csi_link; + + struct work_struct work; +}; + +static inline void init_cmd_hdr(struct ace_cmd_hdr *hdr) +{ + memset(hdr, 0, sizeof(struct ace_cmd_hdr)); + + hdr->type = ACE_CMD_SET; + hdr->msg_tgt = ACE_DRV_MSG; + hdr->init_block = ACE_CMD_INIT_BLOCK; + hdr->final_block = ACE_CMD_FINAL_BLOCK; +} + +static int construct_command(struct mei_ace *ace, struct ace_cmd *cmd, + enum ace_cmd_id cmd_id) +{ + union ace_cmd_param *param = &cmd->param; + struct ace_cmd_hdr *hdr = &cmd->hdr; + + init_cmd_hdr(hdr); + + hdr->cmd_id = cmd_id; + switch (cmd_id) { + case ACE_GET_FW_ID: + param->uuid = ACE_GET_FW_ID_UUID; + hdr->param_size = sizeof(param->uuid); + break; + case ACE_SWITCH_CAMERA_TO_IVSC: + param->param = 0; + hdr->firmware_id = ace->firmware_id; + hdr->param_size = sizeof(param->param); + break; + case ACE_SWITCH_CAMERA_TO_HOST: + hdr->firmware_id = ace->firmware_id; + break; + default: + return -EINVAL; + } + + return hdr->param_size + sizeof(cmd->hdr); +} + +/* send command to firmware */ +static int mei_ace_send(struct mei_ace *ace, struct ace_cmd *cmd, + size_t len, bool only_ack) +{ + union ace_notif_hdr *resp_hdr = &ace->cmd_response.hdr; + union ace_notif_hdr *ack_hdr = &ace->cmd_ack.hdr; + struct ace_cmd_hdr *cmd_hdr = &cmd->hdr; + int ret; + + mutex_lock(&ace->lock); + + reinit_completion(&ace->cmd_completion); + + ret = mei_cldev_send(ace->cldev, (u8 *)cmd, len); + if (ret < 0) + goto out; + + ret = wait_for_completion_killable_timeout(&ace->cmd_completion, + ACE_CMD_TIMEOUT); + if (ret < 0) { + goto out; + } else if (!ret) { + ret = -ETIMEDOUT; + goto out; + } + + if (ack_hdr->ack.cmd_id != cmd_hdr->cmd_id) { + ret = -EINVAL; + goto out; + } + + /* command ack status */ + ret = ack_hdr->ack.status; + if (ret) { + ret = -EIO; + goto out; + } + + if (only_ack) + goto out; + + ret = wait_for_completion_killable_timeout(&ace->cmd_completion, + ACE_CMD_TIMEOUT); + if (ret < 0) { + goto out; + } else if (!ret) { + ret = -ETIMEDOUT; + goto out; + } else { + ret = 0; + } + + if (resp_hdr->response.cmd_id != cmd_hdr->cmd_id) + ret = -EINVAL; + +out: + mutex_unlock(&ace->lock); + + return ret; +} + +static int ace_set_camera_owner(struct mei_ace *ace, + enum ace_camera_owner owner) +{ + enum ace_cmd_id cmd_id; + struct ace_cmd cmd; + int cmd_size; + int ret; + + if (owner == ACE_CAMERA_IVSC) + cmd_id = ACE_SWITCH_CAMERA_TO_IVSC; + else + cmd_id = ACE_SWITCH_CAMERA_TO_HOST; + + cmd_size = construct_command(ace, &cmd, cmd_id); + if (cmd_size >= 0) + ret = mei_ace_send(ace, &cmd, cmd_size, false); + else + ret = cmd_size; + + return ret; +} + +/* the first command downloaded to firmware */ +static inline int ace_get_firmware_id(struct mei_ace *ace) +{ + struct ace_cmd cmd; + int cmd_size; + int ret; + + cmd_size = construct_command(ace, &cmd, ACE_GET_FW_ID); + if (cmd_size >= 0) + ret = mei_ace_send(ace, &cmd, cmd_size, true); + else + ret = cmd_size; + + return ret; +} + +static void handle_command_response(struct mei_ace *ace, + struct ace_notif *resp, int len) +{ + union ace_notif_hdr *hdr = &resp->hdr; + + switch (hdr->response.cmd_id) { + case ACE_SWITCH_CAMERA_TO_IVSC: + case ACE_SWITCH_CAMERA_TO_HOST: + memcpy(&ace->cmd_response, resp, len); + complete(&ace->cmd_completion); + break; + case ACE_GET_FW_ID: + break; + default: + break; + } +} + +static void handle_command_ack(struct mei_ace *ace, + struct ace_notif *ack, int len) +{ + union ace_notif_hdr *hdr = &ack->hdr; + + switch (hdr->ack.cmd_id) { + case ACE_GET_FW_ID: + ace->firmware_id = ack->cont.firmware_id; + fallthrough; + case ACE_SWITCH_CAMERA_TO_IVSC: + case ACE_SWITCH_CAMERA_TO_HOST: + memcpy(&ace->cmd_ack, ack, len); + complete(&ace->cmd_completion); + break; + default: + break; + } +} + +/* callback for receive */ +static void mei_ace_rx(struct mei_cl_device *cldev) +{ + struct mei_ace *ace = mei_cldev_get_drvdata(cldev); + struct ace_notif event; + union ace_notif_hdr *hdr = &event.hdr; + int ret; + + ret = mei_cldev_recv(cldev, (u8 *)&event, sizeof(event)); + if (ret < 0) { + dev_err(&cldev->dev, "recv error: %d\n", ret); + return; + } + + if (hdr->event.ack) { + handle_command_ack(ace, &event, ret); + return; + } + + switch (hdr->event.event_type) { + case ACE_CMD_RESPONSE: + handle_command_response(ace, &event, ret); + break; + case ACE_FW_READY: + /* + * firmware ready notification sent to driver + * after HECI client connected with firmware. + */ + dev_dbg(&cldev->dev, "firmware ready\n"); + break; + default: + break; + } +} + +static int mei_ace_setup_dev_link(struct mei_ace *ace) +{ + struct device *dev = &ace->cldev->dev; + uuid_le uuid = MEI_CSI_UUID; + struct device *csi_dev; + char name[64]; + int ret; + + snprintf(name, sizeof(name), "%s-%pUl", dev_name(dev->parent), &uuid); + + csi_dev = device_find_child_by_name(dev->parent, name); + if (!csi_dev) { + ret = -EPROBE_DEFER; + goto err; + } else if (!dev_fwnode(csi_dev)) { + ret = -EPROBE_DEFER; + goto err_put; + } + + /* setup link between mei_ace and mei_csi */ + ace->csi_link = device_link_add(csi_dev, dev, DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE | DL_FLAG_STATELESS); + put_device(csi_dev); + if (!ace->csi_link) { + ret = -EINVAL; + dev_err(dev, "failed to link to %s\n", dev_name(csi_dev)); + goto err; + } + + ace->csi_dev = csi_dev; + + return 0; + +err_put: + put_device(csi_dev); + +err: + return ret; +} + +/* switch camera to host before probe sensor device */ +static void mei_ace_post_probe_work(struct work_struct *work) +{ + struct acpi_device *adev; + struct mei_ace *ace; + struct device *dev; + int ret; + + ace = container_of(work, struct mei_ace, work); + dev = &ace->cldev->dev; + + ret = ace_set_camera_owner(ace, ACE_CAMERA_HOST); + if (ret) { + dev_err(dev, "switch camera to host failed: %d\n", ret); + return; + } + + adev = ACPI_COMPANION(dev->parent); + if (!adev) + return; + + acpi_dev_clear_dependencies(adev); +} + +static int mei_ace_probe(struct mei_cl_device *cldev, + const struct mei_cl_device_id *id) +{ + struct device *dev = &cldev->dev; + struct mei_ace *ace; + int ret; + + ace = devm_kzalloc(dev, sizeof(struct mei_ace), GFP_KERNEL); + if (!ace) + return -ENOMEM; + + ace->cldev = cldev; + mutex_init(&ace->lock); + init_completion(&ace->cmd_completion); + INIT_WORK(&ace->work, mei_ace_post_probe_work); + + mei_cldev_set_drvdata(cldev, ace); + + ret = mei_cldev_enable(cldev); + if (ret < 0) { + dev_err(dev, "mei_cldev_enable failed: %d\n", ret); + goto destroy_mutex; + } + + ret = mei_cldev_register_rx_cb(cldev, mei_ace_rx); + if (ret) { + dev_err(dev, "event cb registration failed: %d\n", ret); + goto err_disable; + } + + ret = ace_get_firmware_id(ace); + if (ret) { + dev_err(dev, "get firmware id failed: %d\n", ret); + goto err_disable; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + ret = mei_ace_setup_dev_link(ace); + if (ret) + goto disable_pm; + + schedule_work(&ace->work); + + return 0; + +disable_pm: + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + +err_disable: + mei_cldev_disable(cldev); + +destroy_mutex: + mutex_destroy(&ace->lock); + + return ret; +} + +static void mei_ace_remove(struct mei_cl_device *cldev) +{ + struct mei_ace *ace = mei_cldev_get_drvdata(cldev); + + cancel_work_sync(&ace->work); + + device_link_del(ace->csi_link); + + pm_runtime_disable(&cldev->dev); + pm_runtime_set_suspended(&cldev->dev); + + ace_set_camera_owner(ace, ACE_CAMERA_IVSC); + + mei_cldev_disable(cldev); + + mutex_destroy(&ace->lock); +} + +static int __maybe_unused mei_ace_runtime_suspend(struct device *dev) +{ + struct mei_ace *ace = dev_get_drvdata(dev); + + return ace_set_camera_owner(ace, ACE_CAMERA_IVSC); +} + +static int __maybe_unused mei_ace_runtime_resume(struct device *dev) +{ + struct mei_ace *ace = dev_get_drvdata(dev); + + return ace_set_camera_owner(ace, ACE_CAMERA_HOST); +} + +static const struct dev_pm_ops mei_ace_pm_ops = { + SET_RUNTIME_PM_OPS(mei_ace_runtime_suspend, + mei_ace_runtime_resume, NULL) +}; + +#define MEI_ACE_UUID UUID_LE(0x5DB76CF6, 0x0A68, 0x4ED6, \ + 0x9B, 0x78, 0x03, 0x61, 0x63, 0x5E, 0x24, 0x47) + +static const struct mei_cl_device_id mei_ace_tbl[] = { + { .uuid = MEI_ACE_UUID, .version = MEI_CL_VERSION_ANY }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(mei, mei_ace_tbl); + +static struct mei_cl_driver mei_ace_driver = { + .id_table = mei_ace_tbl, + .name = KBUILD_MODNAME, + + .probe = mei_ace_probe, + .remove = mei_ace_remove, + + .driver = { + .pm = &mei_ace_pm_ops, + }, +}; + +module_mei_cl_driver(mei_ace_driver); + +MODULE_AUTHOR("Wentong Wu"); +MODULE_AUTHOR("Zhifeng Wang "); +MODULE_DESCRIPTION("Device driver for IVSC ACE"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/intel/ivsc/mei_csi.c b/drivers/media/pci/intel/ivsc/mei_csi.c new file mode 100644 index 000000000..c2917e156 --- /dev/null +++ b/drivers/media/pci/intel/ivsc/mei_csi.c @@ -0,0 +1,791 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2023 Intel Corporation. All rights reserved. + * Intel Visual Sensing Controller CSI Linux driver + */ + +/* + * To set ownership of CSI-2 link and to configure CSI-2 link, there + * are specific commands, which are sent via MEI protocol. The send + * command function uses "completion" as a synchronization mechanism. + * The response for command is received via a mei callback which wakes + * up the caller. There can be only one outstanding command at a time. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define MEI_CSI_ENTITY_NAME "Intel IVSC CSI" + +/* the 5s used here is based on experiment */ +#define CSI_CMD_TIMEOUT (5 * HZ) +/* to setup CSI-2 link an extra delay needed and determined experimentally */ +#define CSI_FW_READY_DELAY_MS 100 +/* link frequency unit is 100kHz */ +#define CSI_LINK_FREQ(x) ((u32)(div_u64(x, 100 * HZ_PER_KHZ))) + +/* + * identify the command id supported by firmware + * IPC, as well as the privacy notification id + * used when processing privacy event. + */ +enum csi_cmd_id { + /* used to set csi ownership */ + CSI_SET_OWNER = 0, + + /* used to configure CSI-2 link */ + CSI_SET_CONF = 2, + + /* privacy notification id used when privacy state changes */ + CSI_PRIVACY_NOTIF = 6, +}; + +/* CSI-2 link ownership definition */ +enum csi_link_owner { + CSI_LINK_IVSC, + CSI_LINK_HOST, +}; + +/* privacy status definition */ +enum ivsc_privacy_status { + CSI_PRIVACY_OFF, + CSI_PRIVACY_ON, + CSI_PRIVACY_MAX, +}; + +enum csi_pads { + CSI_PAD_SINK, + CSI_PAD_SOURCE, + CSI_NUM_PADS +}; + +/* configuration of the CSI-2 link between host and IVSC */ +struct csi_link_cfg { + /* number of data lanes used on the CSI-2 link */ + u32 nr_of_lanes; + + /* frequency of the CSI-2 link */ + u32 link_freq; + + /* for future use */ + u32 rsvd[2]; +} __packed; + +/* CSI command structure */ +struct csi_cmd { + u32 cmd_id; + union _cmd_param { + u32 param; + struct csi_link_cfg conf; + } param; +} __packed; + +/* CSI notification structure */ +struct csi_notif { + u32 cmd_id; + int status; + union _resp_cont { + u32 cont; + struct csi_link_cfg conf; + } cont; +} __packed; + +struct mei_csi { + struct mei_cl_device *cldev; + + /* command response */ + struct csi_notif cmd_response; + /* used to wait for command response from firmware */ + struct completion cmd_completion; + /* protect command download */ + struct mutex lock; + + struct v4l2_subdev subdev; + struct media_pad *remote; + struct v4l2_async_notifier notifier; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *freq_ctrl; + struct v4l2_ctrl *privacy_ctrl; + /* lock for v4l2 controls */ + struct mutex ctrl_lock; + /* start streaming or not */ + int streaming; + + struct media_pad pads[CSI_NUM_PADS]; + + /* number of data lanes used on the CSI-2 link */ + u32 nr_of_lanes; + /* frequency of the CSI-2 link */ + u64 link_freq; +}; + +static const struct v4l2_mbus_framefmt mei_csi_format_mbus_default = { + .width = 1, + .height = 1, + .code = MEDIA_BUS_FMT_Y8_1X8, + .field = V4L2_FIELD_NONE, +}; + +static inline struct mei_csi *notifier_to_csi(struct v4l2_async_notifier *n) +{ + return container_of(n, struct mei_csi, notifier); +} + +static inline struct mei_csi *sd_to_csi(struct v4l2_subdev *sd) +{ + return container_of(sd, struct mei_csi, subdev); +} + +/* send a command to firmware and mutex must be held by caller */ +static int mei_csi_send(struct mei_csi *csi, u8 *buf, size_t len) +{ + struct csi_cmd *cmd = (struct csi_cmd *)buf; + int ret; + + reinit_completion(&csi->cmd_completion); + + ret = mei_cldev_send(csi->cldev, buf, len); + if (ret < 0) + goto out; + + ret = wait_for_completion_killable_timeout(&csi->cmd_completion, + CSI_CMD_TIMEOUT); + if (ret < 0) { + goto out; + } else if (!ret) { + ret = -ETIMEDOUT; + goto out; + } + + /* command response status */ + ret = csi->cmd_response.status; + if (ret == -1) { + /* notify privacy on instead of reporting error */ + ret = 0; + v4l2_ctrl_s_ctrl(csi->privacy_ctrl, 1); + } else if (ret) { + ret = -EINVAL; + goto out; + } + + if (csi->cmd_response.cmd_id != cmd->cmd_id) + ret = -EINVAL; + +out: + return ret; +} + +/* set CSI-2 link ownership */ +static int csi_set_link_owner(struct mei_csi *csi, enum csi_link_owner owner) +{ + struct csi_cmd cmd = { 0 }; + size_t cmd_size; + int ret; + + cmd.cmd_id = CSI_SET_OWNER; + cmd.param.param = owner; + cmd_size = sizeof(cmd.cmd_id) + sizeof(cmd.param.param); + + mutex_lock(&csi->lock); + + ret = mei_csi_send(csi, (u8 *)&cmd, cmd_size); + + mutex_unlock(&csi->lock); + + return ret; +} + +/* configure CSI-2 link between host and IVSC */ +static int csi_set_link_cfg(struct mei_csi *csi) +{ + struct csi_cmd cmd = { 0 }; + size_t cmd_size; + int ret; + + cmd.cmd_id = CSI_SET_CONF; + cmd.param.conf.nr_of_lanes = csi->nr_of_lanes; + cmd.param.conf.link_freq = CSI_LINK_FREQ(csi->link_freq); + cmd_size = sizeof(cmd.cmd_id) + sizeof(cmd.param.conf); + + mutex_lock(&csi->lock); + + ret = mei_csi_send(csi, (u8 *)&cmd, cmd_size); + /* + * wait configuration ready if download success. placing + * delay under mutex is to make sure current command flow + * completed before starting a possible new one. + */ + if (!ret) + msleep(CSI_FW_READY_DELAY_MS); + + mutex_unlock(&csi->lock); + + return ret; +} + +/* callback for receive */ +static void mei_csi_rx(struct mei_cl_device *cldev) +{ + struct mei_csi *csi = mei_cldev_get_drvdata(cldev); + struct csi_notif notif = { 0 }; + int ret; + + ret = mei_cldev_recv(cldev, (u8 *)¬if, sizeof(notif)); + if (ret < 0) { + dev_err(&cldev->dev, "recv error: %d\n", ret); + return; + } + + switch (notif.cmd_id) { + case CSI_PRIVACY_NOTIF: + if (notif.cont.cont < CSI_PRIVACY_MAX) + v4l2_ctrl_s_ctrl(csi->privacy_ctrl, + notif.cont.cont == CSI_PRIVACY_ON); + break; + case CSI_SET_OWNER: + case CSI_SET_CONF: + memcpy(&csi->cmd_response, ¬if, ret); + + complete(&csi->cmd_completion); + break; + default: + break; + } +} + +static int mei_csi_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct mei_csi *csi = sd_to_csi(sd); + struct v4l2_subdev *remote_sd = + media_entity_to_v4l2_subdev(csi->remote->entity); + s64 freq; + int ret; + + if (enable && csi->streaming == 0) { + freq = v4l2_get_link_freq(csi->remote, 0, 0); + if (freq < 0) { + dev_err(&csi->cldev->dev, + "error %lld, invalid link_freq\n", freq); + ret = freq; + goto err; + } + csi->link_freq = freq; + + /* switch CSI-2 link to host */ + ret = csi_set_link_owner(csi, CSI_LINK_HOST); + if (ret < 0) + goto err; + + /* configure CSI-2 link */ + ret = csi_set_link_cfg(csi); + if (ret < 0) + goto err_switch; + + ret = v4l2_subdev_call(remote_sd, video, s_stream, 1); + if (ret) + goto err_switch; + } else if (!enable && csi->streaming == 1) { + v4l2_subdev_call(remote_sd, video, s_stream, 0); + + /* switch CSI-2 link to IVSC */ + ret = csi_set_link_owner(csi, CSI_LINK_IVSC); + if (ret < 0) + dev_warn(&csi->cldev->dev, + "failed to switch CSI2 link: %d\n", ret); + } + + csi->streaming = enable; + + return 0; + +err_switch: + csi_set_link_owner(csi, CSI_LINK_IVSC); + +err: + return ret; +} + +static int mei_csi_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_mbus_framefmt *mbusformat; + unsigned int i; + + for (i = 0; i < sd->entity.num_pads; i++) { + mbusformat = v4l2_subdev_state_get_format(sd_state, i); + *mbusformat = mei_csi_format_mbus_default; + } + + return 0; +} + +static int mei_csi_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *source_fmt; + struct v4l2_mbus_framefmt *sink_fmt; + + sink_fmt = v4l2_subdev_state_get_format(sd_state, CSI_PAD_SINK); + source_fmt = v4l2_subdev_state_get_format(sd_state, CSI_PAD_SOURCE); + + if (format->pad) { + *source_fmt = *sink_fmt; + + return 0; + } + + v4l_bound_align_image(&format->format.width, 1, 65536, 0, + &format->format.height, 1, 65536, 0, 0); + + switch (format->format.code) { + case MEDIA_BUS_FMT_RGB444_1X12: + case MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE: + case MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE: + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE: + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE: + case MEDIA_BUS_FMT_RGB565_1X16: + case MEDIA_BUS_FMT_BGR565_2X8_BE: + case MEDIA_BUS_FMT_BGR565_2X8_LE: + case MEDIA_BUS_FMT_RGB565_2X8_BE: + case MEDIA_BUS_FMT_RGB565_2X8_LE: + case MEDIA_BUS_FMT_RGB666_1X18: + case MEDIA_BUS_FMT_RBG888_1X24: + case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: + case MEDIA_BUS_FMT_BGR888_1X24: + case MEDIA_BUS_FMT_GBR888_1X24: + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_RGB888_2X12_BE: + case MEDIA_BUS_FMT_RGB888_2X12_LE: + case MEDIA_BUS_FMT_ARGB8888_1X32: + case MEDIA_BUS_FMT_RGB888_1X32_PADHI: + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_RGB121212_1X36: + case MEDIA_BUS_FMT_RGB161616_1X48: + case MEDIA_BUS_FMT_Y8_1X8: + case MEDIA_BUS_FMT_UV8_1X8: + case MEDIA_BUS_FMT_UYVY8_1_5X8: + case MEDIA_BUS_FMT_VYUY8_1_5X8: + case MEDIA_BUS_FMT_YUYV8_1_5X8: + case MEDIA_BUS_FMT_YVYU8_1_5X8: + case MEDIA_BUS_FMT_UYVY8_2X8: + case MEDIA_BUS_FMT_VYUY8_2X8: + case MEDIA_BUS_FMT_YUYV8_2X8: + case MEDIA_BUS_FMT_YVYU8_2X8: + case MEDIA_BUS_FMT_Y10_1X10: + case MEDIA_BUS_FMT_UYVY10_2X10: + case MEDIA_BUS_FMT_VYUY10_2X10: + case MEDIA_BUS_FMT_YUYV10_2X10: + case MEDIA_BUS_FMT_YVYU10_2X10: + case MEDIA_BUS_FMT_Y12_1X12: + case MEDIA_BUS_FMT_UYVY12_2X12: + case MEDIA_BUS_FMT_VYUY12_2X12: + case MEDIA_BUS_FMT_YUYV12_2X12: + case MEDIA_BUS_FMT_YVYU12_2X12: + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_VYUY8_1X16: + case MEDIA_BUS_FMT_YUYV8_1X16: + case MEDIA_BUS_FMT_YVYU8_1X16: + case MEDIA_BUS_FMT_YDYUYDYV8_1X16: + case MEDIA_BUS_FMT_UYVY10_1X20: + case MEDIA_BUS_FMT_VYUY10_1X20: + case MEDIA_BUS_FMT_YUYV10_1X20: + case MEDIA_BUS_FMT_YVYU10_1X20: + case MEDIA_BUS_FMT_VUY8_1X24: + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + case MEDIA_BUS_FMT_UYVY12_1X24: + case MEDIA_BUS_FMT_VYUY12_1X24: + case MEDIA_BUS_FMT_YUYV12_1X24: + case MEDIA_BUS_FMT_YVYU12_1X24: + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + case MEDIA_BUS_FMT_AYUV8_1X32: + case MEDIA_BUS_FMT_UYYVYY12_0_5X36: + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_YUV16_1X48: + case MEDIA_BUS_FMT_UYYVYY16_0_5X48: + case MEDIA_BUS_FMT_JPEG_1X8: + case MEDIA_BUS_FMT_AHSV8888_1X32: + case MEDIA_BUS_FMT_SBGGR8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: + case MEDIA_BUS_FMT_SBGGR10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_SBGGR12_1X12: + case MEDIA_BUS_FMT_SGBRG12_1X12: + case MEDIA_BUS_FMT_SGRBG12_1X12: + case MEDIA_BUS_FMT_SRGGB12_1X12: + case MEDIA_BUS_FMT_SBGGR14_1X14: + case MEDIA_BUS_FMT_SGBRG14_1X14: + case MEDIA_BUS_FMT_SGRBG14_1X14: + case MEDIA_BUS_FMT_SRGGB14_1X14: + case MEDIA_BUS_FMT_SBGGR16_1X16: + case MEDIA_BUS_FMT_SGBRG16_1X16: + case MEDIA_BUS_FMT_SGRBG16_1X16: + case MEDIA_BUS_FMT_SRGGB16_1X16: + break; + default: + format->format.code = MEDIA_BUS_FMT_Y8_1X8; + break; + } + + if (format->format.field == V4L2_FIELD_ANY) + format->format.field = V4L2_FIELD_NONE; + + *sink_fmt = format->format; + *source_fmt = *sink_fmt; + + return 0; +} + +static int mei_csi_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_config *mbus_config) +{ + struct mei_csi *csi = sd_to_csi(sd); + unsigned int i; + s64 freq; + + mbus_config->type = V4L2_MBUS_CSI2_DPHY; + for (i = 0; i < V4L2_MBUS_CSI2_MAX_DATA_LANES; i++) + mbus_config->bus.mipi_csi2.data_lanes[i] = i + 1; + mbus_config->bus.mipi_csi2.num_data_lanes = csi->nr_of_lanes; + + freq = v4l2_get_link_freq(csi->remote, 0, 0); + if (freq < 0) { + dev_err(&csi->cldev->dev, + "error %lld, invalid link_freq\n", freq); + return -EINVAL; + } + + csi->link_freq = freq; + mbus_config->link_freq = freq; + + return 0; +} + +static const struct v4l2_subdev_video_ops mei_csi_video_ops = { + .s_stream = mei_csi_set_stream, +}; + +static const struct v4l2_subdev_pad_ops mei_csi_pad_ops = { + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = mei_csi_set_fmt, + .get_mbus_config = mei_csi_get_mbus_config, +}; + +static const struct v4l2_subdev_ops mei_csi_subdev_ops = { + .video = &mei_csi_video_ops, + .pad = &mei_csi_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops mei_csi_internal_ops = { + .init_state = mei_csi_init_state, +}; + +static const struct media_entity_operations mei_csi_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int mei_csi_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *asd) +{ + struct mei_csi *csi = notifier_to_csi(notifier); + int pad; + + pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode, + MEDIA_PAD_FL_SOURCE); + if (pad < 0) + return pad; + + csi->remote = &subdev->entity.pads[pad]; + + return media_create_pad_link(&subdev->entity, pad, + &csi->subdev.entity, CSI_PAD_SINK, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +static void mei_csi_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *asd) +{ + struct mei_csi *csi = notifier_to_csi(notifier); + + csi->remote = NULL; +} + +static const struct v4l2_async_notifier_operations mei_csi_notify_ops = { + .bound = mei_csi_notify_bound, + .unbind = mei_csi_notify_unbind, +}; + +static int mei_csi_init_controls(struct mei_csi *csi) +{ + int ret; + + mutex_init(&csi->ctrl_lock); + + ret = v4l2_ctrl_handler_init(&csi->ctrl_handler, 1); + if (ret) + return ret; + + csi->ctrl_handler.lock = &csi->ctrl_lock; + + csi->privacy_ctrl = v4l2_ctrl_new_std(&csi->ctrl_handler, NULL, + V4L2_CID_PRIVACY, 0, 1, 1, 0); + if (csi->privacy_ctrl) + csi->privacy_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + if (csi->ctrl_handler.error) + return csi->ctrl_handler.error; + + csi->subdev.ctrl_handler = &csi->ctrl_handler; + + return 0; +} + +static int mei_csi_parse_firmware(struct mei_csi *csi) +{ + struct v4l2_fwnode_endpoint v4l2_ep = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct device *dev = &csi->cldev->dev; + struct v4l2_async_connection *asd; + struct fwnode_handle *sink_ep, *source_ep; + int ret; + + sink_ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0); + if (!sink_ep) { + dev_err(dev, "can't obtain sink endpoint\n"); + return -EINVAL; + } + + v4l2_async_subdev_nf_init(&csi->notifier, &csi->subdev); + csi->notifier.ops = &mei_csi_notify_ops; + + ret = v4l2_fwnode_endpoint_parse(sink_ep, &v4l2_ep); + if (ret) { + dev_err(dev, "could not parse v4l2 sink endpoint\n"); + goto out_nf_cleanup; + } + + csi->nr_of_lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes; + + source_ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 1, 0, 0); + if (!source_ep) { + ret = -ENOTCONN; + dev_err(dev, "can't obtain source endpoint\n"); + goto out_nf_cleanup; + } + + ret = v4l2_fwnode_endpoint_parse(source_ep, &v4l2_ep); + fwnode_handle_put(source_ep); + if (ret) { + dev_err(dev, "could not parse v4l2 source endpoint\n"); + goto out_nf_cleanup; + } + + if (csi->nr_of_lanes != v4l2_ep.bus.mipi_csi2.num_data_lanes) { + ret = -EINVAL; + dev_err(dev, + "the number of lanes does not match (%u vs. %u)\n", + csi->nr_of_lanes, v4l2_ep.bus.mipi_csi2.num_data_lanes); + goto out_nf_cleanup; + } + + asd = v4l2_async_nf_add_fwnode_remote(&csi->notifier, sink_ep, + struct v4l2_async_connection); + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto out_nf_cleanup; + } + + ret = v4l2_async_nf_register(&csi->notifier); + if (ret) + goto out_nf_cleanup; + + fwnode_handle_put(sink_ep); + + return 0; + +out_nf_cleanup: + v4l2_async_nf_cleanup(&csi->notifier); + fwnode_handle_put(sink_ep); + + return ret; +} + +static int mei_csi_probe(struct mei_cl_device *cldev, + const struct mei_cl_device_id *id) +{ + struct device *dev = &cldev->dev; + struct pci_dev *ipu; + struct mei_csi *csi; + unsigned int i; + int ret; + + for (i = 0, ipu = NULL; !ipu && ipu6_pci_tbl[i].vendor; i++) + ipu = pci_get_device(ipu6_pci_tbl[i].vendor, + ipu6_pci_tbl[i].device, NULL); + + if (!ipu) + return -ENODEV; + + ret = ipu_bridge_init(&ipu->dev, ipu_bridge_parse_ssdb); + put_device(&ipu->dev); + if (ret < 0) + return ret; + if (!dev_fwnode(dev)) { + dev_err(dev, "mei-csi probed without device fwnode!\n"); + return -ENXIO; + } + + csi = devm_kzalloc(dev, sizeof(struct mei_csi), GFP_KERNEL); + if (!csi) + return -ENOMEM; + + csi->cldev = cldev; + mutex_init(&csi->lock); + init_completion(&csi->cmd_completion); + + mei_cldev_set_drvdata(cldev, csi); + + ret = mei_cldev_enable(cldev); + if (ret < 0) { + dev_err(dev, "mei_cldev_enable failed: %d\n", ret); + goto destroy_mutex; + } + + ret = mei_cldev_register_rx_cb(cldev, mei_csi_rx); + if (ret) { + dev_err(dev, "event cb registration failed: %d\n", ret); + goto err_disable; + } + + ret = mei_csi_parse_firmware(csi); + if (ret) + goto err_disable; + + csi->subdev.dev = &cldev->dev; + csi->subdev.state_lock = &csi->lock; + v4l2_subdev_init(&csi->subdev, &mei_csi_subdev_ops); + csi->subdev.internal_ops = &mei_csi_internal_ops; + v4l2_set_subdevdata(&csi->subdev, csi); + csi->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + csi->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + csi->subdev.entity.ops = &mei_csi_entity_ops; + + snprintf(csi->subdev.name, sizeof(csi->subdev.name), + MEI_CSI_ENTITY_NAME); + + ret = mei_csi_init_controls(csi); + if (ret) + goto err_ctrl_handler; + + csi->pads[CSI_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + csi->pads[CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&csi->subdev.entity, CSI_NUM_PADS, + csi->pads); + if (ret) + goto err_ctrl_handler; + + ret = v4l2_subdev_init_finalize(&csi->subdev); + if (ret < 0) + goto err_entity; + + ret = v4l2_async_register_subdev(&csi->subdev); + if (ret < 0) + goto err_subdev; + + pm_runtime_enable(&cldev->dev); + + return 0; + +err_subdev: + v4l2_subdev_cleanup(&csi->subdev); + +err_entity: + media_entity_cleanup(&csi->subdev.entity); + +err_ctrl_handler: + v4l2_ctrl_handler_free(&csi->ctrl_handler); + mutex_destroy(&csi->ctrl_lock); + v4l2_async_nf_unregister(&csi->notifier); + v4l2_async_nf_cleanup(&csi->notifier); + +err_disable: + mei_cldev_disable(cldev); + +destroy_mutex: + mutex_destroy(&csi->lock); + + return ret; +} + +static void mei_csi_remove(struct mei_cl_device *cldev) +{ + struct mei_csi *csi = mei_cldev_get_drvdata(cldev); + + v4l2_async_nf_unregister(&csi->notifier); + v4l2_async_nf_cleanup(&csi->notifier); + v4l2_ctrl_handler_free(&csi->ctrl_handler); + mutex_destroy(&csi->ctrl_lock); + v4l2_async_unregister_subdev(&csi->subdev); + v4l2_subdev_cleanup(&csi->subdev); + media_entity_cleanup(&csi->subdev.entity); + + pm_runtime_disable(&cldev->dev); + + mei_cldev_disable(cldev); + + mutex_destroy(&csi->lock); +} + +#define MEI_CSI_UUID UUID_LE(0x92335FCF, 0x3203, 0x4472, \ + 0xAF, 0x93, 0x7b, 0x44, 0x53, 0xAC, 0x29, 0xDA) + +static const struct mei_cl_device_id mei_csi_tbl[] = { + { .uuid = MEI_CSI_UUID, .version = MEI_CL_VERSION_ANY }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(mei, mei_csi_tbl); + +static struct mei_cl_driver mei_csi_driver = { + .id_table = mei_csi_tbl, + .name = KBUILD_MODNAME, + + .probe = mei_csi_probe, + .remove = mei_csi_remove, +}; + +module_mei_cl_driver(mei_csi_driver); + +MODULE_IMPORT_NS("INTEL_IPU_BRIDGE"); +MODULE_AUTHOR("Wentong Wu"); +MODULE_AUTHOR("Zhifeng Wang "); +MODULE_DESCRIPTION("Device driver for IVSC CSI"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/ivtv/Kconfig b/drivers/media/pci/ivtv/Kconfig new file mode 100644 index 000000000..2498f9079 --- /dev/null +++ b/drivers/media/pci/ivtv/Kconfig @@ -0,0 +1,78 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_IVTV + tristate "Conexant cx23416/cx23415 MPEG encoder/decoder support" + depends on VIDEO_DEV && PCI && I2C + select I2C_ALGOBIT + depends on RC_CORE + select VIDEO_TUNER + select VIDEO_TVEEPROM + select VIDEO_CX2341X + select VIDEO_CX25840 + select VIDEO_MSP3400 + select VIDEO_SAA711X + select VIDEO_SAA717X + select VIDEO_SAA7127 + select VIDEO_CS53L32A + select VIDEO_M52790 + select VIDEO_WM8775 + select VIDEO_WM8739 + select VIDEO_VP27SMPX + select VIDEO_UPD64031A + select VIDEO_UPD64083 + help + This is a video4linux driver for Conexant cx23416 or cx23415 based + PCI personal video recorder devices. + + This is used in devices such as the Hauppauge PVR-150/250/350/500 + cards. + + To compile this driver as a module, choose M here: the + module will be called ivtv. + +config VIDEO_IVTV_ALSA + tristate "Conexant cx23415/cx23416 ALSA interface for PCM audio capture" + depends on VIDEO_IVTV && SND + select SND_PCM + help + This driver provides an ALSA interface as another method for user + applications to obtain PCM audio data from Conexant cx23415/cx23416 + based PCI TV cards supported by the ivtv driver. + + The ALSA interface has much wider use in user applications performing + PCM audio capture, than the V4L2 "/dev/video24" PCM audio interface + provided by the main ivtv driver. + + To compile this driver as a module, choose M here: the + module will be called ivtv-alsa. + +config VIDEO_FB_IVTV + tristate "Conexant cx23415 framebuffer support" + depends on VIDEO_IVTV && FB + select FB_IOMEM_HELPERS + help + This is a framebuffer driver for the Conexant cx23415 MPEG + encoder/decoder. + + This is used in the Hauppauge PVR-350 card. + + To compile this driver as a module, choose M here: the + module will be called ivtvfb. + +config VIDEO_FB_IVTV_FORCE_PAT + bool "force cx23415 framebuffer init with x86 PAT enabled" + depends on VIDEO_FB_IVTV && X86_PAT + help + With PAT enabled, the cx23415 framebuffer driver does not + utilize write-combined caching on the framebuffer memory. + For this reason, the driver will by default disable itself + when initializied on a kernel with PAT enabled (i.e. not + using the nopat kernel parameter). + + The driver is not easily upgradable to the PAT-aware + ioremap_wc() API since the firmware hides the address + ranges that should be marked write-combined from the driver. + + With this setting enabled, the framebuffer will initialize on + PAT-enabled systems but the framebuffer memory will be uncached. + + If unsure, say N. diff --git a/drivers/media/pci/ivtv/Makefile b/drivers/media/pci/ivtv/Makefile new file mode 100644 index 000000000..5de95dbe3 --- /dev/null +++ b/drivers/media/pci/ivtv/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 +ivtv-objs := ivtv-routing.o ivtv-cards.o ivtv-controls.o \ + ivtv-driver.o ivtv-fileops.o ivtv-firmware.o \ + ivtv-gpio.o ivtv-i2c.o ivtv-ioctl.o ivtv-irq.o \ + ivtv-mailbox.o ivtv-queue.o ivtv-streams.o ivtv-udma.o \ + ivtv-vbi.o ivtv-yuv.o +ivtv-alsa-objs := ivtv-alsa-main.o ivtv-alsa-pcm.o + +obj-$(CONFIG_VIDEO_IVTV) += ivtv.o +obj-$(CONFIG_VIDEO_IVTV_ALSA) += ivtv-alsa.o +obj-$(CONFIG_VIDEO_FB_IVTV) += ivtvfb.o + +ccflags-y += -I$(srctree)/drivers/media/tuners +ccflags-y += -I$(srctree)/drivers/media/dvb-frontends + diff --git a/drivers/media/pci/ivtv/ivtv-alsa-main.c b/drivers/media/pci/ivtv/ivtv-alsa-main.c new file mode 100644 index 000000000..59573debd --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-alsa-main.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA interface to ivtv PCM capture streams + * + * Copyright (C) 2009,2012 Andy Walls + * Copyright (C) 2009 Devin Heitmueller + * + * Portions of this work were sponsored by ONELAN Limited for the cx18 driver + */ + +#include "ivtv-driver.h" +#include "ivtv-version.h" +#include "ivtv-alsa.h" +#include "ivtv-alsa-pcm.h" + +#include +#include + +int ivtv_alsa_debug; +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; + +#define IVTV_DEBUG_ALSA_INFO(__fmt, __arg...) \ + do { \ + if (ivtv_alsa_debug & 2) \ + printk(KERN_INFO pr_fmt("%s: alsa:" __fmt), \ + __func__, ##__arg); \ + } while (0) + +module_param_named(debug, ivtv_alsa_debug, int, 0644); +MODULE_PARM_DESC(debug, + "Debug level (bitmask). Default: 0\n" + "\t\t\t 1/0x0001: warning\n" + "\t\t\t 2/0x0002: info\n"); + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, + "Index value for IVTV ALSA capture interface(s).\n"); + +MODULE_AUTHOR("Andy Walls"); +MODULE_DESCRIPTION("CX23415/CX23416 ALSA Interface"); +MODULE_LICENSE("GPL"); + +MODULE_VERSION(IVTV_VERSION); + +static inline +struct snd_ivtv_card *to_snd_ivtv_card(struct v4l2_device *v4l2_dev) +{ + return to_ivtv(v4l2_dev)->alsa; +} + +static void snd_ivtv_card_free(struct snd_ivtv_card *itvsc) +{ + if (itvsc == NULL) + return; + + if (itvsc->v4l2_dev != NULL) + to_ivtv(itvsc->v4l2_dev)->alsa = NULL; + + /* FIXME - take any other stopping actions needed */ + + kfree(itvsc); +} + +static void snd_ivtv_card_private_free(struct snd_card *sc) +{ + if (sc == NULL) + return; + snd_ivtv_card_free(sc->private_data); + sc->private_data = NULL; + sc->private_free = NULL; +} + +static int snd_ivtv_card_create(struct v4l2_device *v4l2_dev, + struct snd_card *sc, + struct snd_ivtv_card **itvsc) +{ + *itvsc = kzalloc_obj(struct snd_ivtv_card); + if (*itvsc == NULL) + return -ENOMEM; + + (*itvsc)->v4l2_dev = v4l2_dev; + (*itvsc)->sc = sc; + + sc->private_data = *itvsc; + sc->private_free = snd_ivtv_card_private_free; + + return 0; +} + +static int snd_ivtv_card_set_names(struct snd_ivtv_card *itvsc) +{ + struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); + struct snd_card *sc = itvsc->sc; + + /* sc->driver is used by alsa-lib's configurator: simple, unique */ + strscpy(sc->driver, "CX2341[56]", sizeof(sc->driver)); + + /* sc->shortname is a symlink in /proc/asound: IVTV-M -> cardN */ + snprintf(sc->shortname, sizeof(sc->shortname), "IVTV-%d", + itv->instance); + + /* sc->longname is read from /proc/asound/cards */ + snprintf(sc->longname, sizeof(sc->longname), + "CX2341[56] #%d %s TV/FM Radio/Line-In Capture", + itv->instance, itv->card_name); + + return 0; +} + +static int snd_ivtv_init(struct v4l2_device *v4l2_dev) +{ + struct ivtv *itv = to_ivtv(v4l2_dev); + struct snd_card *sc = NULL; + struct snd_ivtv_card *itvsc; + int ret, idx; + + /* Numbrs steps from "Writing an ALSA Driver" by Takashi Iwai */ + + /* (1) Check and increment the device index */ + /* This is a no-op for us. We'll use the itv->instance */ + + /* (2) Create a card instance */ + /* use first available id if not specified otherwise*/ + idx = index[itv->instance] == -1 ? SNDRV_DEFAULT_IDX1 : index[itv->instance]; + ret = snd_card_new(&itv->pdev->dev, + idx, + SNDRV_DEFAULT_STR1, /* xid from end of shortname*/ + THIS_MODULE, 0, &sc); + if (ret) { + IVTV_ALSA_ERR("%s: snd_card_new() failed with err %d\n", + __func__, ret); + goto err_exit; + } + + /* (3) Create a main component */ + ret = snd_ivtv_card_create(v4l2_dev, sc, &itvsc); + if (ret) { + IVTV_ALSA_ERR("%s: snd_ivtv_card_create() failed with err %d\n", + __func__, ret); + goto err_exit_free; + } + + /* (4) Set the driver ID and name strings */ + snd_ivtv_card_set_names(itvsc); + + /* (5) Create other components: PCM, & proc files */ + ret = snd_ivtv_pcm_create(itvsc); + if (ret) { + IVTV_ALSA_ERR("%s: snd_ivtv_pcm_create() failed with err %d\n", + __func__, ret); + goto err_exit_free; + } + /* FIXME - proc files */ + + /* (7) Set the driver data and return 0 */ + /* We do this out of normal order for PCI drivers to avoid races */ + itv->alsa = itvsc; + + /* (6) Register the card instance */ + ret = snd_card_register(sc); + if (ret) { + itv->alsa = NULL; + IVTV_ALSA_ERR("%s: snd_card_register() failed with err %d\n", + __func__, ret); + goto err_exit_free; + } + + IVTV_ALSA_INFO("%s: Instance %d registered as ALSA card %d\n", + __func__, itv->instance, sc->number); + + return 0; + +err_exit_free: + if (sc != NULL) + snd_card_free(sc); + kfree(itvsc); +err_exit: + return ret; +} + +static int ivtv_alsa_load(struct ivtv *itv) +{ + struct v4l2_device *v4l2_dev = &itv->v4l2_dev; + struct ivtv_stream *s; + + if (v4l2_dev == NULL) { + pr_err("ivtv-alsa: %s: struct v4l2_device * is NULL\n", + __func__); + return 0; + } + + itv = to_ivtv(v4l2_dev); + if (itv == NULL) { + pr_err("ivtv-alsa itv is NULL\n"); + return 0; + } + + s = &itv->streams[IVTV_ENC_STREAM_TYPE_PCM]; + if (s->vdev.v4l2_dev == NULL) { + IVTV_DEBUG_ALSA_INFO("PCM stream for card is disabled - skipping\n"); + return 0; + } + + if (itv->alsa != NULL) { + IVTV_ALSA_ERR("%s: struct snd_ivtv_card * already exists\n", + __func__); + return 0; + } + + if (snd_ivtv_init(v4l2_dev)) { + IVTV_ALSA_ERR("%s: failed to create struct snd_ivtv_card\n", + __func__); + } else { + IVTV_DEBUG_ALSA_INFO("created ivtv ALSA interface instance\n"); + } + return 0; +} + +static int __init ivtv_alsa_init(void) +{ + pr_info("ivtv-alsa: module loading...\n"); + ivtv_ext_init = &ivtv_alsa_load; + return 0; +} + +static void __exit snd_ivtv_exit(struct snd_ivtv_card *itvsc) +{ + struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); + + /* FIXME - pointer checks & shutdown itvsc */ + + snd_card_free(itvsc->sc); + itv->alsa = NULL; +} + +static int __exit ivtv_alsa_exit_callback(struct device *dev, void *data) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); + struct snd_ivtv_card *itvsc; + + if (v4l2_dev == NULL) { + pr_err("ivtv-alsa: %s: struct v4l2_device * is NULL\n", + __func__); + return 0; + } + + itvsc = to_snd_ivtv_card(v4l2_dev); + if (itvsc == NULL) { + IVTV_ALSA_WARN("%s: struct snd_ivtv_card * is NULL\n", + __func__); + return 0; + } + + snd_ivtv_exit(itvsc); + return 0; +} + +static void __exit ivtv_alsa_exit(void) +{ + struct device_driver *drv; + int ret; + + pr_info("ivtv-alsa: module unloading...\n"); + + drv = driver_find("ivtv", &pci_bus_type); + ret = driver_for_each_device(drv, NULL, NULL, ivtv_alsa_exit_callback); + (void)ret; /* suppress compiler warning */ + + ivtv_ext_init = NULL; + pr_info("ivtv-alsa: module unload complete\n"); +} + +module_init(ivtv_alsa_init); +module_exit(ivtv_alsa_exit); diff --git a/drivers/media/pci/ivtv/ivtv-alsa-pcm.c b/drivers/media/pci/ivtv/ivtv-alsa-pcm.c new file mode 100644 index 000000000..269a799ec --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-alsa-pcm.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA PCM device for the + * ALSA interface to ivtv PCM capture streams + * + * Copyright (C) 2009,2012 Andy Walls + * Copyright (C) 2009 Devin Heitmueller + * + * Portions of this work were sponsored by ONELAN Limited for the cx18 driver + */ + +#include "ivtv-driver.h" +#include "ivtv-queue.h" +#include "ivtv-streams.h" +#include "ivtv-fileops.h" +#include "ivtv-alsa.h" +#include "ivtv-alsa-pcm.h" + +#include +#include + + +static unsigned int pcm_debug; +module_param(pcm_debug, int, 0644); +MODULE_PARM_DESC(pcm_debug, "enable debug messages for pcm"); + +#define dprintk(fmt, arg...) \ + do { \ + if (pcm_debug) \ + pr_info("ivtv-alsa-pcm %s: " fmt, __func__, ##arg); \ + } while (0) + +static const struct snd_pcm_hardware snd_ivtv_hw_capture = { + .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID, + + .formats = SNDRV_PCM_FMTBIT_S16_LE, + + .rates = SNDRV_PCM_RATE_48000, + + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 62720 * 8, /* just about the value in usbaudio.c */ + .period_bytes_min = 64, /* 12544/2, */ + .period_bytes_max = 12544, + .periods_min = 2, + .periods_max = 98, /* 12544, */ +}; + +static void ivtv_alsa_announce_pcm_data(struct snd_ivtv_card *itvsc, + u8 *pcm_data, + size_t num_bytes) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int oldptr; + unsigned int stride; + int period_elapsed = 0; + int length; + + dprintk("ivtv alsa announce ptr=%p data=%p num_bytes=%zu\n", itvsc, + pcm_data, num_bytes); + + substream = itvsc->capture_pcm_substream; + if (substream == NULL) { + dprintk("substream was NULL\n"); + return; + } + + runtime = substream->runtime; + if (runtime == NULL) { + dprintk("runtime was NULL\n"); + return; + } + + stride = runtime->frame_bits >> 3; + if (stride == 0) { + dprintk("stride is zero\n"); + return; + } + + length = num_bytes / stride; + if (length == 0) { + dprintk("%s: length was zero\n", __func__); + return; + } + + if (runtime->dma_area == NULL) { + dprintk("dma area was NULL - ignoring\n"); + return; + } + + oldptr = itvsc->hwptr_done_capture; + if (oldptr + length >= runtime->buffer_size) { + unsigned int cnt = + runtime->buffer_size - oldptr; + memcpy(runtime->dma_area + oldptr * stride, pcm_data, + cnt * stride); + memcpy(runtime->dma_area, pcm_data + cnt * stride, + length * stride - cnt * stride); + } else { + memcpy(runtime->dma_area + oldptr * stride, pcm_data, + length * stride); + } + snd_pcm_stream_lock(substream); + + itvsc->hwptr_done_capture += length; + if (itvsc->hwptr_done_capture >= + runtime->buffer_size) + itvsc->hwptr_done_capture -= + runtime->buffer_size; + + itvsc->capture_transfer_done += length; + if (itvsc->capture_transfer_done >= + runtime->period_size) { + itvsc->capture_transfer_done -= + runtime->period_size; + period_elapsed = 1; + } + + snd_pcm_stream_unlock(substream); + + if (period_elapsed) + snd_pcm_period_elapsed(substream); +} + +static int snd_ivtv_pcm_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct v4l2_device *v4l2_dev = itvsc->v4l2_dev; + struct ivtv *itv = to_ivtv(v4l2_dev); + struct ivtv_stream *s; + struct ivtv_open_id item; + int ret; + + /* Instruct the CX2341[56] to start sending packets */ + snd_ivtv_lock(itvsc); + + if (ivtv_init_on_first_open(itv)) { + snd_ivtv_unlock(itvsc); + return -ENXIO; + } + + s = &itv->streams[IVTV_ENC_STREAM_TYPE_PCM]; + + item.itv = itv; + item.type = s->type; + + /* See if the stream is available */ + if (ivtv_claim_stream(&item, item.type)) { + /* No, it's already in use */ + snd_ivtv_unlock(itvsc); + return -EBUSY; + } + + if (test_bit(IVTV_F_S_STREAMOFF, &s->s_flags) || + test_and_set_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + /* We're already streaming. No additional action required */ + snd_ivtv_unlock(itvsc); + return 0; + } + + + runtime->hw = snd_ivtv_hw_capture; + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + itvsc->capture_pcm_substream = substream; + runtime->private_data = itv; + + itv->pcm_announce_callback = ivtv_alsa_announce_pcm_data; + + /* Not currently streaming, so start it up */ + set_bit(IVTV_F_S_STREAMING, &s->s_flags); + ret = ivtv_start_v4l2_encode_stream(s); + snd_ivtv_unlock(itvsc); + + return ret; +} + +static int snd_ivtv_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); + struct v4l2_device *v4l2_dev = itvsc->v4l2_dev; + struct ivtv *itv = to_ivtv(v4l2_dev); + struct ivtv_stream *s; + + /* Instruct the ivtv to stop sending packets */ + snd_ivtv_lock(itvsc); + s = &itv->streams[IVTV_ENC_STREAM_TYPE_PCM]; + ivtv_stop_v4l2_encode_stream(s, 0); + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + + ivtv_release_stream(s); + + itv->pcm_announce_callback = NULL; + snd_ivtv_unlock(itvsc); + + return 0; +} + +static int snd_ivtv_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); + + itvsc->hwptr_done_capture = 0; + itvsc->capture_transfer_done = 0; + + return 0; +} + +static int snd_ivtv_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + return 0; +} + +static +snd_pcm_uframes_t snd_ivtv_pcm_pointer(struct snd_pcm_substream *substream) +{ + unsigned long flags; + snd_pcm_uframes_t hwptr_done; + struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); + + spin_lock_irqsave(&itvsc->slock, flags); + hwptr_done = itvsc->hwptr_done_capture; + spin_unlock_irqrestore(&itvsc->slock, flags); + + return hwptr_done; +} + +static const struct snd_pcm_ops snd_ivtv_pcm_capture_ops = { + .open = snd_ivtv_pcm_capture_open, + .close = snd_ivtv_pcm_capture_close, + .prepare = snd_ivtv_pcm_prepare, + .trigger = snd_ivtv_pcm_trigger, + .pointer = snd_ivtv_pcm_pointer, +}; + +int snd_ivtv_pcm_create(struct snd_ivtv_card *itvsc) +{ + struct snd_pcm *sp; + struct snd_card *sc = itvsc->sc; + struct v4l2_device *v4l2_dev = itvsc->v4l2_dev; + struct ivtv *itv = to_ivtv(v4l2_dev); + int ret; + + ret = snd_pcm_new(sc, "CX2341[56] PCM", + 0, /* PCM device 0, the only one for this card */ + 0, /* 0 playback substreams */ + 1, /* 1 capture substream */ + &sp); + if (ret) { + IVTV_ALSA_ERR("%s: snd_ivtv_pcm_create() failed with err %d\n", + __func__, ret); + goto err_exit; + } + + spin_lock_init(&itvsc->slock); + + snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_CAPTURE, + &snd_ivtv_pcm_capture_ops); + snd_pcm_set_managed_buffer_all(sp, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); + sp->info_flags = 0; + sp->private_data = itvsc; + strscpy(sp->name, itv->card_name, sizeof(sp->name)); + + return 0; + +err_exit: + return ret; +} diff --git a/drivers/media/pci/ivtv/ivtv-alsa-pcm.h b/drivers/media/pci/ivtv/ivtv-alsa-pcm.h new file mode 100644 index 000000000..341ed4d27 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-alsa-pcm.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA PCM device for the + * ALSA interface to ivtv PCM capture streams + * + * Copyright (C) 2009,2012 Andy Walls + */ + +int snd_ivtv_pcm_create(struct snd_ivtv_card *itvsc); diff --git a/drivers/media/pci/ivtv/ivtv-alsa.h b/drivers/media/pci/ivtv/ivtv-alsa.h new file mode 100644 index 000000000..d9f685b1c --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-alsa.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA interface to ivtv PCM capture streams + * + * Copyright (C) 2009,2012 Andy Walls + * Copyright (C) 2009 Devin Heitmueller + */ + +struct snd_card; + +struct snd_ivtv_card { + struct v4l2_device *v4l2_dev; + struct snd_card *sc; + unsigned int capture_transfer_done; + unsigned int hwptr_done_capture; + struct snd_pcm_substream *capture_pcm_substream; + spinlock_t slock; +}; + +extern int ivtv_alsa_debug; + +/* + * File operations that manipulate the encoder or video or audio subdevices + * need to be serialized. Use the same lock we use for v4l2 file ops. + */ +static inline void snd_ivtv_lock(struct snd_ivtv_card *itvsc) +{ + struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); + mutex_lock(&itv->serialize_lock); +} + +static inline void snd_ivtv_unlock(struct snd_ivtv_card *itvsc) +{ + struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); + mutex_unlock(&itv->serialize_lock); +} + +#define IVTV_ALSA_DBGFLG_WARN (1 << 0) +#define IVTV_ALSA_DBGFLG_INFO (1 << 1) + +#define IVTV_ALSA_DEBUG(x, type, fmt, args...) \ + do { \ + if ((x) & ivtv_alsa_debug) \ + pr_info("%s-alsa: " type ": " fmt, \ + v4l2_dev->name , ## args); \ + } while (0) + +#define IVTV_ALSA_DEBUG_WARN(fmt, args...) \ + IVTV_ALSA_DEBUG(IVTV_ALSA_DBGFLG_WARN, "warning", fmt , ## args) + +#define IVTV_ALSA_DEBUG_INFO(fmt, args...) \ + IVTV_ALSA_DEBUG(IVTV_ALSA_DBGFLG_INFO, "info", fmt , ## args) + +#define IVTV_ALSA_ERR(fmt, args...) \ + pr_err("%s-alsa: " fmt, v4l2_dev->name , ## args) + +#define IVTV_ALSA_WARN(fmt, args...) \ + pr_warn("%s-alsa: " fmt, v4l2_dev->name , ## args) + +#define IVTV_ALSA_INFO(fmt, args...) \ + pr_info("%s-alsa: " fmt, v4l2_dev->name , ## args) diff --git a/drivers/media/pci/ivtv/ivtv-cards.c b/drivers/media/pci/ivtv/ivtv-cards.c new file mode 100644 index 000000000..f2ccf8e98 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-cards.c @@ -0,0 +1,1358 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Functions to query card hardware + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#include "ivtv-driver.h" +#include "ivtv-cards.h" +#include "ivtv-i2c.h" + +#include +#include +#include +#include +#include +#include + +#define MSP_TUNER MSP_INPUT(MSP_IN_SCART1, MSP_IN_TUNER1, \ + MSP_DSP_IN_TUNER, MSP_DSP_IN_TUNER) +#define MSP_SCART1 MSP_INPUT(MSP_IN_SCART1, MSP_IN_TUNER1, \ + MSP_DSP_IN_SCART, MSP_DSP_IN_SCART) +#define MSP_SCART2 MSP_INPUT(MSP_IN_SCART2, MSP_IN_TUNER1, \ + MSP_DSP_IN_SCART, MSP_DSP_IN_SCART) +#define MSP_SCART3 MSP_INPUT(MSP_IN_SCART3, MSP_IN_TUNER1, \ + MSP_DSP_IN_SCART, MSP_DSP_IN_SCART) +#define MSP_MONO MSP_INPUT(MSP_IN_MONO, MSP_IN_TUNER1, \ + MSP_DSP_IN_SCART, MSP_DSP_IN_SCART) + +#define V4L2_STD_PAL_SECAM (V4L2_STD_PAL|V4L2_STD_SECAM) + +/* usual i2c tuner addresses to probe */ +static struct ivtv_card_tuner_i2c ivtv_i2c_std = { + .radio = { I2C_CLIENT_END }, + .demod = { 0x43, I2C_CLIENT_END }, + .tv = { 0x61, 0x60, I2C_CLIENT_END }, +}; + +/* as above, but with possible radio tuner */ +static struct ivtv_card_tuner_i2c ivtv_i2c_radio = { + .radio = { 0x60, I2C_CLIENT_END }, + .demod = { 0x43, I2C_CLIENT_END }, + .tv = { 0x61, I2C_CLIENT_END }, +}; + +/* using the tda8290+75a combo */ +static struct ivtv_card_tuner_i2c ivtv_i2c_tda8290 = { + .radio = { I2C_CLIENT_END }, + .demod = { I2C_CLIENT_END }, + .tv = { 0x4b, I2C_CLIENT_END }, +}; + +/********************** card configuration *******************************/ + +/* Please add new PCI IDs to: https://pci-ids.ucw.cz/ + This keeps the PCI ID database up to date. Note that the entries + must be added under vendor 0x4444 (Conexant) as subsystem IDs. + New vendor IDs should still be added to the vendor ID list. */ + +/* Hauppauge PVR-250 cards */ + +/* Note: for Hauppauge cards the tveeprom information is used instead of PCI IDs */ +static const struct ivtv_card ivtv_card_pvr250 = { + .type = IVTV_CARD_PVR_250, + .name = "Hauppauge WinTV PVR-250", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7115, + .hw_audio = IVTV_HW_MSP34XX, + .hw_audio_ctrl = IVTV_HW_MSP34XX, + .hw_all = IVTV_HW_MSP34XX | IVTV_HW_SAA7115 | + IVTV_HW_TVEEPROM | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE0 }, + { IVTV_CARD_INPUT_SVIDEO2, 2, IVTV_SAA71XX_SVIDEO1 }, + { IVTV_CARD_INPUT_COMPOSITE2, 2, IVTV_SAA71XX_COMPOSITE1 }, + { IVTV_CARD_INPUT_COMPOSITE3, 1, IVTV_SAA71XX_COMPOSITE5 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, MSP_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, MSP_SCART1 }, + { IVTV_CARD_INPUT_LINE_IN2, MSP_SCART3 }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, MSP_SCART2 }, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Hauppauge PVR-350 cards */ + +/* Outputs for Hauppauge PVR350 cards */ +static struct ivtv_card_output ivtv_pvr350_outputs[] = { + { + .name = "S-Video + Composite", + .video_output = 0, + }, { + .name = "Composite", + .video_output = 1, + }, { + .name = "S-Video", + .video_output = 2, + }, { + .name = "RGB", + .video_output = 3, + }, { + .name = "YUV C", + .video_output = 4, + }, { + .name = "YUV V", + .video_output = 5, + } +}; + +static const struct ivtv_card ivtv_card_pvr350 = { + .type = IVTV_CARD_PVR_350, + .name = "Hauppauge WinTV PVR-350", + .v4l2_capabilities = IVTV_CAP_ENCODER | IVTV_CAP_DECODER, + .video_outputs = ivtv_pvr350_outputs, + .nof_outputs = ARRAY_SIZE(ivtv_pvr350_outputs), + .hw_video = IVTV_HW_SAA7115, + .hw_audio = IVTV_HW_MSP34XX, + .hw_audio_ctrl = IVTV_HW_MSP34XX, + .hw_all = IVTV_HW_MSP34XX | IVTV_HW_SAA7115 | + IVTV_HW_SAA7127 | IVTV_HW_TVEEPROM | IVTV_HW_TUNER | + IVTV_HW_I2C_IR_RX_HAUP_EXT | IVTV_HW_I2C_IR_RX_HAUP_INT, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE0 }, + { IVTV_CARD_INPUT_SVIDEO2, 2, IVTV_SAA71XX_SVIDEO1 }, + { IVTV_CARD_INPUT_COMPOSITE2, 2, IVTV_SAA71XX_COMPOSITE1 }, + { IVTV_CARD_INPUT_COMPOSITE3, 1, IVTV_SAA71XX_COMPOSITE5 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, MSP_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, MSP_SCART1 }, + { IVTV_CARD_INPUT_LINE_IN2, MSP_SCART3 }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, MSP_SCART2 }, + .i2c = &ivtv_i2c_std, +}; + +/* PVR-350 V1 boards have a different audio tuner input and use a + saa7114 instead of a saa7115. + Note that the info below comes from a pre-production model so it may + not be correct. Especially the audio behaves strangely (mono only it seems) */ +static const struct ivtv_card ivtv_card_pvr350_v1 = { + .type = IVTV_CARD_PVR_350_V1, + .name = "Hauppauge WinTV PVR-350 (V1)", + .v4l2_capabilities = IVTV_CAP_ENCODER | IVTV_CAP_DECODER, + .video_outputs = ivtv_pvr350_outputs, + .nof_outputs = ARRAY_SIZE(ivtv_pvr350_outputs), + .hw_video = IVTV_HW_SAA7114, + .hw_audio = IVTV_HW_MSP34XX, + .hw_audio_ctrl = IVTV_HW_MSP34XX, + .hw_all = IVTV_HW_MSP34XX | IVTV_HW_SAA7114 | + IVTV_HW_SAA7127 | IVTV_HW_TVEEPROM | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE0 }, + { IVTV_CARD_INPUT_SVIDEO2, 2, IVTV_SAA71XX_SVIDEO1 }, + { IVTV_CARD_INPUT_COMPOSITE2, 2, IVTV_SAA71XX_COMPOSITE1 }, + { IVTV_CARD_INPUT_COMPOSITE3, 1, IVTV_SAA71XX_COMPOSITE5 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, MSP_MONO }, + { IVTV_CARD_INPUT_LINE_IN1, MSP_SCART1 }, + { IVTV_CARD_INPUT_LINE_IN2, MSP_SCART3 }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, MSP_SCART2 }, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Hauppauge PVR-150/PVR-500 cards */ + +static const struct ivtv_card ivtv_card_pvr150 = { + .type = IVTV_CARD_PVR_150, + .name = "Hauppauge WinTV PVR-150", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_muxer = IVTV_HW_WM8775, + .hw_all = IVTV_HW_WM8775 | IVTV_HW_CX25840 | + IVTV_HW_TVEEPROM | IVTV_HW_TUNER | + IVTV_HW_I2C_IR_RX_HAUP_EXT | IVTV_HW_I2C_IR_RX_HAUP_INT | + IVTV_HW_Z8F0811_IR_HAUP, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, CX25840_COMPOSITE7 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, CX25840_SVIDEO1 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, CX25840_COMPOSITE3 }, + { IVTV_CARD_INPUT_SVIDEO2, 2, CX25840_SVIDEO2 }, + { IVTV_CARD_INPUT_COMPOSITE2, 2, CX25840_COMPOSITE4 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, + CX25840_AUDIO8, WM8775_AIN2 }, + { IVTV_CARD_INPUT_LINE_IN1, + CX25840_AUDIO_SERIAL, WM8775_AIN2 }, + { IVTV_CARD_INPUT_LINE_IN2, + CX25840_AUDIO_SERIAL, WM8775_AIN3 }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, + CX25840_AUDIO_SERIAL, WM8775_AIN4 }, + /* apparently needed for the IR blaster */ + .gpio_init = { .direction = 0x1f01, .initial_value = 0x26f3 }, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* AVerMedia M179 cards */ + +static const struct ivtv_card_pci_info ivtv_pci_m179[] = { + { PCI_DEVICE_ID_IVTV15, IVTV_PCI_ID_AVERMEDIA, 0xa3cf }, + { PCI_DEVICE_ID_IVTV15, IVTV_PCI_ID_AVERMEDIA, 0xa3ce }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_m179 = { + .type = IVTV_CARD_M179, + .name = "AVerMedia M179", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7114, + .hw_audio = IVTV_HW_GPIO, + .hw_audio_ctrl = IVTV_HW_GPIO, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA7114 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE3 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_GPIO_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_GPIO_LINE_IN }, + }, + .gpio_init = { .direction = 0xe380, .initial_value = 0x8290 }, + .gpio_audio_input = { .mask = 0x8040, .tuner = 0x8000, .linein = 0x0000 }, + .gpio_audio_mute = { .mask = 0x2000, .mute = 0x2000 }, + .gpio_audio_mode = { .mask = 0x4300, .mono = 0x4000, .stereo = 0x0200, + .lang1 = 0x0200, .lang2 = 0x0100, .both = 0x0000 }, + .gpio_audio_freq = { .mask = 0x0018, .f32000 = 0x0000, + .f44100 = 0x0008, .f48000 = 0x0010 }, + .gpio_audio_detect = { .mask = 0x4000, .stereo = 0x0000 }, + .tuners = { + /* As far as we know all M179 cards use this tuner */ + { .std = V4L2_STD_ALL, .tuner = TUNER_PHILIPS_NTSC }, + }, + .pci_list = ivtv_pci_m179, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Yuan MPG600/Kuroutoshikou ITVC16-STVLP cards */ + +static const struct ivtv_card_pci_info ivtv_pci_mpg600[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_YUAN1, 0xfff3 }, + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_YUAN1, 0xffff }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_mpg600 = { + .type = IVTV_CARD_MPG600, + .name = "Yuan MPG600, Kuroutoshikou ITVC16-STVLP", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7115, + .hw_audio = IVTV_HW_GPIO, + .hw_audio_ctrl = IVTV_HW_GPIO, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA7115 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE3 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_GPIO_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_GPIO_LINE_IN }, + }, + .gpio_init = { .direction = 0x3080, .initial_value = 0x0004 }, + .gpio_audio_input = { .mask = 0x3000, .tuner = 0x0000, .linein = 0x2000 }, + .gpio_audio_mute = { .mask = 0x0001, .mute = 0x0001 }, + .gpio_audio_mode = { .mask = 0x000e, .mono = 0x0006, .stereo = 0x0004, + .lang1 = 0x0004, .lang2 = 0x0000, .both = 0x0008 }, + .gpio_audio_detect = { .mask = 0x0900, .stereo = 0x0100 }, + .tuners = { + /* The PAL tuner is confirmed */ + { .std = V4L2_STD_PAL_SECAM, .tuner = TUNER_PHILIPS_FQ1216ME }, + { .std = V4L2_STD_ALL, .tuner = TUNER_PHILIPS_FQ1286 }, + }, + .pci_list = ivtv_pci_mpg600, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Yuan MPG160/Kuroutoshikou ITVC15-STVLP cards */ + +static const struct ivtv_card_pci_info ivtv_pci_mpg160[] = { + { PCI_DEVICE_ID_IVTV15, IVTV_PCI_ID_YUAN1, 0 }, + { PCI_DEVICE_ID_IVTV15, IVTV_PCI_ID_IODATA, 0x40a0 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_mpg160 = { + .type = IVTV_CARD_MPG160, + .name = "YUAN MPG160, Kuroutoshikou ITVC15-STVLP, I/O Data GV-M2TV/PCI", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7114, + .hw_audio = IVTV_HW_GPIO, + .hw_audio_ctrl = IVTV_HW_GPIO, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA7114 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE3 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_GPIO_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_GPIO_LINE_IN }, + }, + .gpio_init = { .direction = 0x7080, .initial_value = 0x400c }, + .gpio_audio_input = { .mask = 0x3000, .tuner = 0x0000, .linein = 0x2000 }, + .gpio_audio_mute = { .mask = 0x0001, .mute = 0x0001 }, + .gpio_audio_mode = { .mask = 0x000e, .mono = 0x0006, .stereo = 0x0004, + .lang1 = 0x0004, .lang2 = 0x0000, .both = 0x0008 }, + .gpio_audio_detect = { .mask = 0x0900, .stereo = 0x0100 }, + .tuners = { + { .std = V4L2_STD_PAL_SECAM, .tuner = TUNER_PHILIPS_FQ1216ME }, + { .std = V4L2_STD_ALL, .tuner = TUNER_PHILIPS_FQ1286 }, + }, + .pci_list = ivtv_pci_mpg160, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Yuan PG600/Diamond PVR-550 cards */ + +static const struct ivtv_card_pci_info ivtv_pci_pg600[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_DIAMONDMM, 0x0070 }, + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_YUAN3, 0x0600 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_pg600 = { + .type = IVTV_CARD_PG600, + .name = "Yuan PG600, Diamond PVR-550", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_all = IVTV_HW_CX25840 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, CX25840_COMPOSITE2 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, + CX25840_SVIDEO_LUMA3 | CX25840_SVIDEO_CHROMA4 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, CX25840_COMPOSITE1 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5 }, + { IVTV_CARD_INPUT_LINE_IN1, CX25840_AUDIO_SERIAL }, + }, + .tuners = { + { .std = V4L2_STD_PAL_SECAM, .tuner = TUNER_PHILIPS_FQ1216ME }, + { .std = V4L2_STD_ALL, .tuner = TUNER_PHILIPS_FQ1286 }, + }, + .pci_list = ivtv_pci_pg600, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Adaptec VideOh! AVC-2410 card */ + +static const struct ivtv_card_pci_info ivtv_pci_avc2410[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_ADAPTEC, 0x0093 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_avc2410 = { + .type = IVTV_CARD_AVC2410, + .name = "Adaptec VideOh! AVC-2410", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7115, + .hw_audio = IVTV_HW_MSP34XX, + .hw_audio_ctrl = IVTV_HW_MSP34XX, + .hw_muxer = IVTV_HW_CS53L32A, + .hw_all = IVTV_HW_MSP34XX | IVTV_HW_CS53L32A | + IVTV_HW_SAA7115 | IVTV_HW_TUNER | + IVTV_HW_I2C_IR_RX_ADAPTEC, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE3 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, + MSP_TUNER, CS53L32A_IN0 }, + { IVTV_CARD_INPUT_LINE_IN1, + MSP_SCART1, CS53L32A_IN2 }, + }, + /* This card has no eeprom and in fact the Windows driver relies + on the country/region setting of the user to decide which tuner + is available. */ + .tuners = { + { .std = V4L2_STD_PAL_SECAM, .tuner = TUNER_PHILIPS_FM1216ME_MK3 }, + { .std = V4L2_STD_ALL - V4L2_STD_NTSC_M_JP, + .tuner = TUNER_PHILIPS_FM1236_MK3 }, + { .std = V4L2_STD_NTSC_M_JP, .tuner = TUNER_PHILIPS_FQ1286 }, + }, + .pci_list = ivtv_pci_avc2410, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Adaptec VideOh! AVC-2010 card */ + +static const struct ivtv_card_pci_info ivtv_pci_avc2010[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_ADAPTEC, 0x0092 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_avc2010 = { + .type = IVTV_CARD_AVC2010, + .name = "Adaptec VideOh! AVC-2010", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7115, + .hw_audio = IVTV_HW_CS53L32A, + .hw_audio_ctrl = IVTV_HW_CS53L32A, + .hw_all = IVTV_HW_CS53L32A | IVTV_HW_SAA7115, + .video_inputs = { + { IVTV_CARD_INPUT_SVIDEO1, 0, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 0, IVTV_SAA71XX_COMPOSITE3 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_LINE_IN1, CS53L32A_IN2 }, + }, + /* Does not have a tuner */ + .pci_list = ivtv_pci_avc2010, +}; + +/* ------------------------------------------------------------------------- */ + +/* Nagase Transgear 5000TV card */ + +static const struct ivtv_card_pci_info ivtv_pci_tg5000tv[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_AVERMEDIA, 0xbfff }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_tg5000tv = { + .type = IVTV_CARD_TG5000TV, + .name = "Nagase Transgear 5000TV", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7114 | IVTV_HW_UPD64031A | IVTV_HW_UPD6408X | + IVTV_HW_GPIO, + .hw_audio = IVTV_HW_GPIO, + .hw_audio_ctrl = IVTV_HW_GPIO, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA7114 | IVTV_HW_TUNER | + IVTV_HW_UPD64031A | IVTV_HW_UPD6408X, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO2 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_SVIDEO2 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_GPIO_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_GPIO_LINE_IN }, + }, + .gr_config = UPD64031A_VERTICAL_EXTERNAL, + .gpio_init = { .direction = 0xe080, .initial_value = 0x8000 }, + .gpio_audio_input = { .mask = 0x8080, .tuner = 0x8000, .linein = 0x0080 }, + .gpio_audio_mute = { .mask = 0x6000, .mute = 0x6000 }, + .gpio_audio_mode = { .mask = 0x4300, .mono = 0x4000, .stereo = 0x0200, + .lang1 = 0x0300, .lang2 = 0x0000, .both = 0x0200 }, + .gpio_video_input = { .mask = 0x0030, .tuner = 0x0000, + .composite = 0x0010, .svideo = 0x0020 }, + .tuners = { + { .std = V4L2_STD_MN, .tuner = TUNER_PHILIPS_FQ1286 }, + }, + .pci_list = ivtv_pci_tg5000tv, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* AOpen VA2000MAX-SNT6 card */ + +static const struct ivtv_card_pci_info ivtv_pci_va2000[] = { + { PCI_DEVICE_ID_IVTV16, 0, 0xff5f }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_va2000 = { + .type = IVTV_CARD_VA2000MAX_SNT6, + .name = "AOpen VA2000MAX-SNT6", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7115 | IVTV_HW_UPD6408X, + .hw_audio = IVTV_HW_MSP34XX, + .hw_audio_ctrl = IVTV_HW_MSP34XX, + .hw_all = IVTV_HW_MSP34XX | IVTV_HW_SAA7115 | + IVTV_HW_UPD6408X | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_SVIDEO0 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, MSP_TUNER }, + }, + .tuners = { + { .std = V4L2_STD_MN, .tuner = TUNER_PHILIPS_FQ1286 }, + }, + .pci_list = ivtv_pci_va2000, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Yuan MPG600GR/Kuroutoshikou CX23416GYC-STVLP cards */ + +static const struct ivtv_card_pci_info ivtv_pci_cx23416gyc[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_YUAN1, 0x0600 }, + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_YUAN4, 0x0600 }, + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_MELCO, 0x0523 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_cx23416gyc = { + .type = IVTV_CARD_CX23416GYC, + .name = "Yuan MPG600GR, Kuroutoshikou CX23416GYC-STVLP", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA717X | IVTV_HW_GPIO | + IVTV_HW_UPD64031A | IVTV_HW_UPD6408X, + .hw_audio = IVTV_HW_SAA717X, + .hw_audio_ctrl = IVTV_HW_SAA717X, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA717X | IVTV_HW_TUNER | + IVTV_HW_UPD64031A | IVTV_HW_UPD6408X, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_SVIDEO3 | + IVTV_SAA717X_TUNER_FLAG }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_SVIDEO3 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_SAA717X_IN2 }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_SAA717X_IN0 }, + }, + .gr_config = UPD64031A_VERTICAL_EXTERNAL, + .gpio_init = { .direction = 0xf880, .initial_value = 0x8800 }, + .gpio_video_input = { .mask = 0x0020, .tuner = 0x0000, + .composite = 0x0020, .svideo = 0x0020 }, + .gpio_audio_freq = { .mask = 0xc000, .f32000 = 0x0000, + .f44100 = 0x4000, .f48000 = 0x8000 }, + .tuners = { + { .std = V4L2_STD_PAL_SECAM, .tuner = TUNER_PHILIPS_FM1216ME_MK3 }, + { .std = V4L2_STD_ALL, .tuner = TUNER_PHILIPS_FM1236_MK3 }, + }, + .pci_list = ivtv_pci_cx23416gyc, + .i2c = &ivtv_i2c_std, +}; + +static const struct ivtv_card ivtv_card_cx23416gyc_nogr = { + .type = IVTV_CARD_CX23416GYC_NOGR, + .name = "Yuan MPG600GR, Kuroutoshikou CX23416GYC-STVLP (no GR)", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA717X | IVTV_HW_GPIO | IVTV_HW_UPD6408X, + .hw_audio = IVTV_HW_SAA717X, + .hw_audio_ctrl = IVTV_HW_SAA717X, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA717X | IVTV_HW_TUNER | + IVTV_HW_UPD6408X, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 | + IVTV_SAA717X_TUNER_FLAG }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE0 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_SAA717X_IN2 }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_SAA717X_IN0 }, + }, + .gpio_init = { .direction = 0xf880, .initial_value = 0x8800 }, + .gpio_video_input = { .mask = 0x0020, .tuner = 0x0000, + .composite = 0x0020, .svideo = 0x0020 }, + .gpio_audio_freq = { .mask = 0xc000, .f32000 = 0x0000, + .f44100 = 0x4000, .f48000 = 0x8000 }, + .tuners = { + { .std = V4L2_STD_PAL_SECAM, .tuner = TUNER_PHILIPS_FM1216ME_MK3 }, + { .std = V4L2_STD_ALL, .tuner = TUNER_PHILIPS_FM1236_MK3 }, + }, + .i2c = &ivtv_i2c_std, +}; + +static const struct ivtv_card ivtv_card_cx23416gyc_nogrycs = { + .type = IVTV_CARD_CX23416GYC_NOGRYCS, + .name = "Yuan MPG600GR, Kuroutoshikou CX23416GYC-STVLP (no GR/YCS)", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA717X | IVTV_HW_GPIO, + .hw_audio = IVTV_HW_SAA717X, + .hw_audio_ctrl = IVTV_HW_SAA717X, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA717X | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 | + IVTV_SAA717X_TUNER_FLAG }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE0 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_SAA717X_IN2 }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_SAA717X_IN0 }, + }, + .gpio_init = { .direction = 0xf880, .initial_value = 0x8800 }, + .gpio_video_input = { .mask = 0x0020, .tuner = 0x0000, + .composite = 0x0020, .svideo = 0x0020 }, + .gpio_audio_freq = { .mask = 0xc000, .f32000 = 0x0000, + .f44100 = 0x4000, .f48000 = 0x8000 }, + .tuners = { + { .std = V4L2_STD_PAL_SECAM, .tuner = TUNER_PHILIPS_FM1216ME_MK3 }, + { .std = V4L2_STD_ALL, .tuner = TUNER_PHILIPS_FM1236_MK3 }, + }, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* I/O Data GV-MVP/RX & GV-MVP/RX2W (dual tuner) cards */ + +static const struct ivtv_card_pci_info ivtv_pci_gv_mvprx[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_IODATA, 0xd01e }, + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_IODATA, 0xd038 }, /* 2W unit #1 */ + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_IODATA, 0xd039 }, /* 2W unit #2 */ + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_gv_mvprx = { + .type = IVTV_CARD_GV_MVPRX, + .name = "I/O Data GV-MVP/RX, GV-MVP/RX2W (dual tuner)", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7115 | IVTV_HW_UPD64031A | IVTV_HW_UPD6408X, + .hw_audio = IVTV_HW_GPIO, + .hw_audio_ctrl = IVTV_HW_WM8739, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA7115 | IVTV_HW_VP27SMPX | + IVTV_HW_TUNER | IVTV_HW_WM8739 | + IVTV_HW_UPD64031A | IVTV_HW_UPD6408X, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO1 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_SVIDEO2 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_GPIO_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_GPIO_LINE_IN }, + }, + .gpio_init = { .direction = 0xc301, .initial_value = 0x0200 }, + .gpio_audio_input = { .mask = 0xffff, .tuner = 0x0200, .linein = 0x0300 }, + .tuners = { + /* This card has the Panasonic VP27 tuner */ + { .std = V4L2_STD_MN, .tuner = TUNER_PANASONIC_VP27 }, + }, + .pci_list = ivtv_pci_gv_mvprx, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* I/O Data GV-MVP/RX2E card */ + +static const struct ivtv_card_pci_info ivtv_pci_gv_mvprx2e[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_IODATA, 0xd025 }, + {0, 0, 0} +}; + +static const struct ivtv_card ivtv_card_gv_mvprx2e = { + .type = IVTV_CARD_GV_MVPRX2E, + .name = "I/O Data GV-MVP/RX2E", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7115, + .hw_audio = IVTV_HW_GPIO, + .hw_audio_ctrl = IVTV_HW_WM8739, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA7115 | IVTV_HW_TUNER | + IVTV_HW_VP27SMPX | IVTV_HW_WM8739, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE3 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_GPIO_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_GPIO_LINE_IN }, + }, + .gpio_init = { .direction = 0xc301, .initial_value = 0x0200 }, + .gpio_audio_input = { .mask = 0xffff, .tuner = 0x0200, .linein = 0x0300 }, + .tuners = { + /* This card has the Panasonic VP27 tuner */ + { .std = V4L2_STD_MN, .tuner = TUNER_PANASONIC_VP27 }, + }, + .pci_list = ivtv_pci_gv_mvprx2e, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* GotVIEW PCI DVD card */ + +static const struct ivtv_card_pci_info ivtv_pci_gotview_pci_dvd[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_YUAN1, 0x0600 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_gotview_pci_dvd = { + .type = IVTV_CARD_GOTVIEW_PCI_DVD, + .name = "GotView PCI DVD", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA717X, + .hw_audio = IVTV_HW_SAA717X, + .hw_audio_ctrl = IVTV_HW_SAA717X, + .hw_all = IVTV_HW_SAA717X | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE1 }, /* pin 116 */ + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, /* pin 114/109 */ + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE3 }, /* pin 118 */ + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_SAA717X_IN0 }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_SAA717X_IN2 }, + }, + .gpio_init = { .direction = 0xf000, .initial_value = 0xA000 }, + .tuners = { + /* This card has a Philips FQ1216ME MK3 tuner */ + { .std = V4L2_STD_PAL_SECAM, .tuner = TUNER_PHILIPS_FM1216ME_MK3 }, + }, + .pci_list = ivtv_pci_gotview_pci_dvd, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* GotVIEW PCI DVD2 Deluxe card */ + +static const struct ivtv_card_pci_info ivtv_pci_gotview_pci_dvd2[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_GOTVIEW1, 0x0600 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_gotview_pci_dvd2 = { + .type = IVTV_CARD_GOTVIEW_PCI_DVD2, + .name = "GotView PCI DVD2 Deluxe", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_muxer = IVTV_HW_GPIO, + .hw_all = IVTV_HW_CX25840 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, CX25840_COMPOSITE2 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, + CX25840_SVIDEO_LUMA3 | CX25840_SVIDEO_CHROMA4 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, CX25840_COMPOSITE1 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5, 0 }, + { IVTV_CARD_INPUT_LINE_IN1, CX25840_AUDIO_SERIAL, 1 }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO_SERIAL, 2 }, + .gpio_init = { .direction = 0x0800, .initial_value = 0 }, + .gpio_audio_input = { .mask = 0x0800, .tuner = 0, .linein = 0, .radio = 0x0800 }, + .tuners = { + /* This card has a Philips FQ1216ME MK5 tuner */ + { .std = V4L2_STD_PAL_SECAM, .tuner = TUNER_PHILIPS_FM1216ME_MK3 }, + }, + .pci_list = ivtv_pci_gotview_pci_dvd2, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Yuan MPC622 miniPCI card */ + +static const struct ivtv_card_pci_info ivtv_pci_yuan_mpc622[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_YUAN2, 0xd998 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_yuan_mpc622 = { + .type = IVTV_CARD_YUAN_MPC622, + .name = "Yuan MPC622", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_all = IVTV_HW_CX25840 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, CX25840_COMPOSITE2 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, + CX25840_SVIDEO_LUMA3 | CX25840_SVIDEO_CHROMA4 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, CX25840_COMPOSITE1 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5 }, + { IVTV_CARD_INPUT_LINE_IN1, CX25840_AUDIO_SERIAL }, + }, + .gpio_init = { .direction = 0x00ff, .initial_value = 0x0002 }, + .tuners = { + /* This card has the TDA8290/TDA8275 tuner chips */ + { .std = V4L2_STD_ALL, .tuner = TUNER_PHILIPS_TDA8290 }, + }, + .pci_list = ivtv_pci_yuan_mpc622, + .i2c = &ivtv_i2c_tda8290, +}; + +/* ------------------------------------------------------------------------- */ + +/* DIGITAL COWBOY DCT-MTVP1 card */ + +static const struct ivtv_card_pci_info ivtv_pci_dctmvtvp1[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_AVERMEDIA, 0xbfff }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_dctmvtvp1 = { + .type = IVTV_CARD_DCTMTVP1, + .name = "Digital Cowboy DCT-MTVP1", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7115 | IVTV_HW_UPD64031A | IVTV_HW_UPD6408X | + IVTV_HW_GPIO, + .hw_audio = IVTV_HW_GPIO, + .hw_audio_ctrl = IVTV_HW_GPIO, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA7115 | IVTV_HW_TUNER | + IVTV_HW_UPD64031A | IVTV_HW_UPD6408X, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO2 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_SVIDEO2 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_GPIO_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_GPIO_LINE_IN }, + }, + .gpio_init = { .direction = 0xe080, .initial_value = 0x8000 }, + .gpio_audio_input = { .mask = 0x8080, .tuner = 0x8000, .linein = 0x0080 }, + .gpio_audio_mute = { .mask = 0x6000, .mute = 0x6000 }, + .gpio_audio_mode = { .mask = 0x4300, .mono = 0x4000, .stereo = 0x0200, + .lang1 = 0x0300, .lang2 = 0x0000, .both = 0x0200 }, + .gpio_video_input = { .mask = 0x0030, .tuner = 0x0000, + .composite = 0x0010, .svideo = 0x0020}, + .tuners = { + { .std = V4L2_STD_MN, .tuner = TUNER_PHILIPS_FQ1286 }, + }, + .pci_list = ivtv_pci_dctmvtvp1, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Yuan PG600-2/GotView PCI DVD Lite cards */ + +static const struct ivtv_card_pci_info ivtv_pci_pg600v2[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_YUAN3, 0x0600 }, + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_GOTVIEW2, 0x0600 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_pg600v2 = { + .type = IVTV_CARD_PG600V2, + .name = "Yuan PG600-2, GotView PCI DVD Lite", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_all = IVTV_HW_CX25840 | IVTV_HW_TUNER, + /* XC2028 support apparently works for the Yuan, it's still + uncertain whether it also works with the GotView. */ + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, CX25840_COMPOSITE2 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, + CX25840_SVIDEO_LUMA3 | CX25840_SVIDEO_CHROMA4 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, CX25840_COMPOSITE1 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5 }, + { IVTV_CARD_INPUT_LINE_IN1, CX25840_AUDIO_SERIAL }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5 }, + .xceive_pin = 12, + .tuners = { + { .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 }, + }, + .pci_list = ivtv_pci_pg600v2, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Club3D ZAP-TV1x01 cards */ + +static const struct ivtv_card_pci_info ivtv_pci_club3d[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_YUAN3, 0x0600 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_club3d = { + .type = IVTV_CARD_CLUB3D, + .name = "Club3D ZAP-TV1x01", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_all = IVTV_HW_CX25840 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, CX25840_COMPOSITE2 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, + CX25840_SVIDEO_LUMA3 | CX25840_SVIDEO_CHROMA4 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, CX25840_COMPOSITE3 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5 }, + { IVTV_CARD_INPUT_LINE_IN1, CX25840_AUDIO_SERIAL }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5 }, + .xceive_pin = 12, + .tuners = { + { .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 }, + }, + .pci_list = ivtv_pci_club3d, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* AVerTV MCE 116 Plus (M116) card */ + +static const struct ivtv_card_pci_info ivtv_pci_avertv_mce116[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_AVERMEDIA, 0xc439 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_avertv_mce116 = { + .type = IVTV_CARD_AVERTV_MCE116, + .name = "AVerTV MCE 116 Plus", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_all = IVTV_HW_CX25840 | IVTV_HW_TUNER | IVTV_HW_WM8739 | + IVTV_HW_I2C_IR_RX_AVER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, CX25840_COMPOSITE2 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, CX25840_SVIDEO3 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, CX25840_COMPOSITE1 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5 }, + { IVTV_CARD_INPUT_LINE_IN1, CX25840_AUDIO_SERIAL, 1 }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5 }, + /* enable line-in */ + .gpio_init = { .direction = 0xe000, .initial_value = 0x4000 }, + .xceive_pin = 10, + .tuners = { + { .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 }, + }, + .pci_list = ivtv_pci_avertv_mce116, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* AVerMedia PVR-150 Plus / AVerTV M113 cards with a Daewoo/Partsnic Tuner */ + +static const struct ivtv_card_pci_info ivtv_pci_aver_pvr150[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_AVERMEDIA, 0xc034 }, /* NTSC */ + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_AVERMEDIA, 0xc035 }, /* NTSC FM */ + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_aver_pvr150 = { + .type = IVTV_CARD_AVER_PVR150PLUS, + .name = "AVerMedia PVR-150 Plus / AVerTV M113 Partsnic (Daewoo) Tuner", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_muxer = IVTV_HW_GPIO, + .hw_all = IVTV_HW_CX25840 | IVTV_HW_TUNER | + IVTV_HW_WM8739 | IVTV_HW_GPIO, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, CX25840_COMPOSITE2 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, CX25840_SVIDEO3 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, CX25840_COMPOSITE1 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5, 0 }, + { IVTV_CARD_INPUT_LINE_IN1, CX25840_AUDIO_SERIAL, 1 }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO_SERIAL, 2 }, + /* The 74HC4052 Dual 4:1 multiplexer is controlled by 2 GPIO lines */ + .gpio_init = { .direction = 0xc000, .initial_value = 0 }, + .gpio_audio_input = { .mask = 0xc000, + .tuner = 0x0000, + .linein = 0x4000, + .radio = 0x8000 }, + .tuners = { + /* Subsystem ID's 0xc03[45] have a Partsnic PTI-5NF05 tuner */ + { .std = V4L2_STD_MN, .tuner = TUNER_PARTSNIC_PTI_5NF05 }, + }, + .pci_list = ivtv_pci_aver_pvr150, + /* Subsystem ID 0xc035 has a TEA5767(?) FM tuner, 0xc034 does not */ + .i2c = &ivtv_i2c_radio, +}; + +/* ------------------------------------------------------------------------- */ + +/* AVerMedia UltraTV 1500 MCE (newer non-cx88 version, M113 variant) card */ + +static const struct ivtv_card_pci_info ivtv_pci_aver_ultra1500mce[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_AVERMEDIA, 0xc019 }, /* NTSC */ + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_AVERMEDIA, 0xc01b }, /* PAL/SECAM */ + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_aver_ultra1500mce = { + .type = IVTV_CARD_AVER_ULTRA1500MCE, + .name = "AVerMedia UltraTV 1500 MCE / AVerTV M113 Philips Tuner", + .comment = "For non-NTSC tuners, use the pal= or secam= module options", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_muxer = IVTV_HW_GPIO, + .hw_all = IVTV_HW_CX25840 | IVTV_HW_TUNER | + IVTV_HW_WM8739 | IVTV_HW_GPIO, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, CX25840_COMPOSITE2 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, CX25840_SVIDEO3 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, CX25840_COMPOSITE1 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5, 0 }, + { IVTV_CARD_INPUT_LINE_IN1, CX25840_AUDIO_SERIAL, 1 }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO_SERIAL, 2 }, + /* The 74HC4052 Dual 4:1 multiplexer is controlled by 2 GPIO lines */ + .gpio_init = { .direction = 0xc000, .initial_value = 0 }, + .gpio_audio_input = { .mask = 0xc000, + .tuner = 0x0000, + .linein = 0x4000, + .radio = 0x8000 }, + .tuners = { + /* The UltraTV 1500 MCE has a Philips FM1236 MK5 TV/FM tuner */ + { .std = V4L2_STD_MN, .tuner = TUNER_PHILIPS_FM1236_MK3 }, + { .std = V4L2_STD_PAL_SECAM, .tuner = TUNER_PHILIPS_FM1216MK5 }, + }, + .pci_list = ivtv_pci_aver_ultra1500mce, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* AVerMedia EZMaker PCI Deluxe card */ + +static const struct ivtv_card_pci_info ivtv_pci_aver_ezmaker[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_AVERMEDIA, 0xc03f }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_aver_ezmaker = { + .type = IVTV_CARD_AVER_EZMAKER, + .name = "AVerMedia EZMaker PCI Deluxe", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_all = IVTV_HW_CX25840 | IVTV_HW_WM8739, + .video_inputs = { + { IVTV_CARD_INPUT_SVIDEO1, 0, CX25840_SVIDEO3 }, + { IVTV_CARD_INPUT_COMPOSITE1, 0, CX25840_COMPOSITE1 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_LINE_IN1, CX25840_AUDIO_SERIAL, 0 }, + }, + .gpio_init = { .direction = 0x4000, .initial_value = 0x4000 }, + /* Does not have a tuner */ + .pci_list = ivtv_pci_aver_ezmaker, +}; + +/* ------------------------------------------------------------------------- */ + +/* ASUS Falcon2 */ + +static const struct ivtv_card_pci_info ivtv_pci_asus_falcon2[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_ASUSTEK, 0x4b66 }, + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_ASUSTEK, 0x462e }, + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_ASUSTEK, 0x4b2e }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_asus_falcon2 = { + .type = IVTV_CARD_ASUS_FALCON2, + .name = "ASUS Falcon2", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_muxer = IVTV_HW_M52790, + .hw_all = IVTV_HW_CX25840 | IVTV_HW_M52790 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, CX25840_COMPOSITE2 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, CX25840_SVIDEO3 }, + { IVTV_CARD_INPUT_COMPOSITE1, 2, CX25840_COMPOSITE2 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5, M52790_IN_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, CX25840_AUDIO_SERIAL, + M52790_IN_V2 | M52790_SW1_YCMIX | M52790_SW2_YCMIX }, + { IVTV_CARD_INPUT_LINE_IN1, CX25840_AUDIO_SERIAL, M52790_IN_V2 }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO_SERIAL, M52790_IN_TUNER }, + .tuners = { + { .std = V4L2_STD_MN, .tuner = TUNER_PHILIPS_FM1236_MK3 }, + }, + .pci_list = ivtv_pci_asus_falcon2, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* AVerMedia M104 miniPCI card */ + +static const struct ivtv_card_pci_info ivtv_pci_aver_m104[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_AVERMEDIA, 0xc136 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_aver_m104 = { + .type = IVTV_CARD_AVER_M104, + .name = "AVerMedia M104", + .comment = "Not yet supported!\n", + .v4l2_capabilities = 0, /*IVTV_CAP_ENCODER,*/ + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_all = IVTV_HW_CX25840 | IVTV_HW_TUNER | IVTV_HW_WM8739, + .video_inputs = { + { IVTV_CARD_INPUT_SVIDEO1, 0, CX25840_SVIDEO3 }, + { IVTV_CARD_INPUT_COMPOSITE1, 0, CX25840_COMPOSITE1 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_LINE_IN1, CX25840_AUDIO_SERIAL, 1 }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO_SERIAL, 2 }, + /* enable line-in + reset tuner */ + .gpio_init = { .direction = 0xe000, .initial_value = 0x4000 }, + .xceive_pin = 10, + .tuners = { + { .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 }, + }, + .pci_list = ivtv_pci_aver_m104, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ + +/* Buffalo PC-MV5L/PCI cards */ + +static const struct ivtv_card_pci_info ivtv_pci_buffalo[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_MELCO, 0x052b }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_buffalo = { + .type = IVTV_CARD_BUFFALO_MV5L, + .name = "Buffalo PC-MV5L/PCI", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_all = IVTV_HW_CX25840 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, CX25840_COMPOSITE2 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, + CX25840_SVIDEO_LUMA3 | CX25840_SVIDEO_CHROMA4 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, CX25840_COMPOSITE1 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5 }, + { IVTV_CARD_INPUT_LINE_IN1, CX25840_AUDIO_SERIAL }, + }, + .xceive_pin = 12, + .tuners = { + { .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 }, + }, + .pci_list = ivtv_pci_buffalo, + .i2c = &ivtv_i2c_std, +}; + +/* ------------------------------------------------------------------------- */ +/* Sony Kikyou */ + +static const struct ivtv_card_pci_info ivtv_pci_kikyou[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_SONY, 0x813d }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_kikyou = { + .type = IVTV_CARD_KIKYOU, + .name = "Sony VAIO Giga Pocket (ENX Kikyou)", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7115, + .hw_audio = IVTV_HW_GPIO, + .hw_audio_ctrl = IVTV_HW_GPIO, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA7115 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE1 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE1 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO1 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_GPIO_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_GPIO_LINE_IN }, + { IVTV_CARD_INPUT_LINE_IN2, IVTV_GPIO_LINE_IN }, + }, + .gpio_init = { .direction = 0x03e1, .initial_value = 0x0320 }, + .gpio_audio_input = { .mask = 0x0060, + .tuner = 0x0020, + .linein = 0x0000, + .radio = 0x0060 }, + .gpio_audio_mute = { .mask = 0x0000, + .mute = 0x0000 }, /* 0x200? Disable for now. */ + .gpio_audio_mode = { .mask = 0x0080, + .mono = 0x0000, + .stereo = 0x0000, /* SAP */ + .lang1 = 0x0080, + .lang2 = 0x0000, + .both = 0x0080 }, + .tuners = { + { .std = V4L2_STD_ALL, .tuner = TUNER_SONY_BTF_PXN01Z }, + }, + .pci_list = ivtv_pci_kikyou, + .i2c = &ivtv_i2c_std, +}; + + +static const struct ivtv_card *ivtv_card_list[] = { + &ivtv_card_pvr250, + &ivtv_card_pvr350, + &ivtv_card_pvr150, + &ivtv_card_m179, + &ivtv_card_mpg600, + &ivtv_card_mpg160, + &ivtv_card_pg600, + &ivtv_card_avc2410, + &ivtv_card_avc2010, + &ivtv_card_tg5000tv, + &ivtv_card_va2000, + &ivtv_card_cx23416gyc, + &ivtv_card_gv_mvprx, + &ivtv_card_gv_mvprx2e, + &ivtv_card_gotview_pci_dvd, + &ivtv_card_gotview_pci_dvd2, + &ivtv_card_yuan_mpc622, + &ivtv_card_dctmvtvp1, + &ivtv_card_pg600v2, + &ivtv_card_club3d, + &ivtv_card_avertv_mce116, + &ivtv_card_asus_falcon2, + &ivtv_card_aver_pvr150, + &ivtv_card_aver_ezmaker, + &ivtv_card_aver_m104, + &ivtv_card_buffalo, + &ivtv_card_aver_ultra1500mce, + &ivtv_card_kikyou, + + /* Variations of standard cards but with the same PCI IDs. + These cards must come last in this list. */ + &ivtv_card_pvr350_v1, + &ivtv_card_cx23416gyc_nogr, + &ivtv_card_cx23416gyc_nogrycs, +}; + +const struct ivtv_card *ivtv_get_card(u16 index) +{ + if (index >= ARRAY_SIZE(ivtv_card_list)) + return NULL; + return ivtv_card_list[index]; +} + +int ivtv_get_input(struct ivtv *itv, u16 index, struct v4l2_input *input) +{ + const struct ivtv_card_video_input *card_input = itv->card->video_inputs + index; + static const char * const input_strs[] = { + "Tuner 1", + "S-Video 1", + "S-Video 2", + "Composite 1", + "Composite 2", + "Composite 3" + }; + + if (index >= itv->nof_inputs) + return -EINVAL; + input->index = index; + strscpy(input->name, input_strs[card_input->video_type - 1], + sizeof(input->name)); + input->type = (card_input->video_type == IVTV_CARD_INPUT_VID_TUNER ? + V4L2_INPUT_TYPE_TUNER : V4L2_INPUT_TYPE_CAMERA); + input->audioset = (1 << itv->nof_audio_inputs) - 1; + input->std = (input->type == V4L2_INPUT_TYPE_TUNER) ? + itv->tuner_std : V4L2_STD_ALL; + return 0; +} + +int ivtv_get_output(struct ivtv *itv, u16 index, struct v4l2_output *output) +{ + const struct ivtv_card_output *card_output = itv->card->video_outputs + index; + + if (index >= itv->card->nof_outputs) + return -EINVAL; + output->index = index; + strscpy(output->name, card_output->name, sizeof(output->name)); + output->type = V4L2_OUTPUT_TYPE_ANALOG; + output->audioset = 1; + output->std = V4L2_STD_ALL; + return 0; +} + +int ivtv_get_audio_input(struct ivtv *itv, u16 index, struct v4l2_audio *audio) +{ + const struct ivtv_card_audio_input *aud_input = itv->card->audio_inputs + index; + static const char * const input_strs[] = { + "Tuner 1", + "Line In 1", + "Line In 2" + }; + + memset(audio, 0, sizeof(*audio)); + if (index >= itv->nof_audio_inputs) + return -EINVAL; + strscpy(audio->name, input_strs[aud_input->audio_type - 1], + sizeof(audio->name)); + audio->index = index; + audio->capability = V4L2_AUDCAP_STEREO; + return 0; +} + +int ivtv_get_audio_output(struct ivtv *itv, u16 index, struct v4l2_audioout *aud_output) +{ + memset(aud_output, 0, sizeof(*aud_output)); + if (itv->card->video_outputs == NULL || index != 0) + return -EINVAL; + strscpy(aud_output->name, "A/V Audio Out", sizeof(aud_output->name)); + return 0; +} diff --git a/drivers/media/pci/ivtv/ivtv-cards.h b/drivers/media/pci/ivtv/ivtv-cards.h new file mode 100644 index 000000000..af64e55d3 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-cards.h @@ -0,0 +1,314 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Functions to query card hardware + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#ifndef IVTV_CARDS_H +#define IVTV_CARDS_H + +/* Supported cards */ +#define IVTV_CARD_PVR_250 0 /* WinTV PVR 250 */ +#define IVTV_CARD_PVR_350 1 /* encoder, decoder, tv-out */ +#define IVTV_CARD_PVR_150 2 /* WinTV PVR 150 and PVR 500 (really just two + PVR150s on one PCI board) */ +#define IVTV_CARD_M179 3 /* AVerMedia M179 (encoder only) */ +#define IVTV_CARD_MPG600 4 /* Kuroutoshikou ITVC16-STVLP/YUAN MPG600, encoder only */ +#define IVTV_CARD_MPG160 5 /* Kuroutoshikou ITVC15-STVLP/YUAN MPG160 + cx23415 based, but does not have tv-out */ +#define IVTV_CARD_PG600 6 /* YUAN PG600/DIAMONDMM PVR-550 based on the CX Falcon 2 */ +#define IVTV_CARD_AVC2410 7 /* Adaptec AVC-2410 */ +#define IVTV_CARD_AVC2010 8 /* Adaptec AVD-2010 (No Tuner) */ +#define IVTV_CARD_TG5000TV 9 /* NAGASE TRANSGEAR 5000TV, encoder only */ +#define IVTV_CARD_VA2000MAX_SNT6 10 /* VA2000MAX-STN6 */ +#define IVTV_CARD_CX23416GYC 11 /* Kuroutoshikou CX23416GYC-STVLP (Yuan MPG600GR OEM) */ +#define IVTV_CARD_GV_MVPRX 12 /* I/O Data GV-MVP/RX, RX2, RX2W */ +#define IVTV_CARD_GV_MVPRX2E 13 /* I/O Data GV-MVP/RX2E */ +#define IVTV_CARD_GOTVIEW_PCI_DVD 14 /* GotView PCI DVD */ +#define IVTV_CARD_GOTVIEW_PCI_DVD2 15 /* GotView PCI DVD2 */ +#define IVTV_CARD_YUAN_MPC622 16 /* Yuan MPC622 miniPCI */ +#define IVTV_CARD_DCTMTVP1 17 /* DIGITAL COWBOY DCT-MTVP1 */ +#define IVTV_CARD_PG600V2 18 /* Yuan PG600V2/GotView PCI DVD Lite */ +#define IVTV_CARD_CLUB3D 19 /* Club3D ZAP-TV1x01 */ +#define IVTV_CARD_AVERTV_MCE116 20 /* AVerTV MCE 116 Plus */ +#define IVTV_CARD_ASUS_FALCON2 21 /* ASUS Falcon2 */ +#define IVTV_CARD_AVER_PVR150PLUS 22 /* AVerMedia PVR-150 Plus */ +#define IVTV_CARD_AVER_EZMAKER 23 /* AVerMedia EZMaker PCI Deluxe */ +#define IVTV_CARD_AVER_M104 24 /* AverMedia M104 miniPCI card */ +#define IVTV_CARD_BUFFALO_MV5L 25 /* Buffalo PC-MV5L/PCI card */ +#define IVTV_CARD_AVER_ULTRA1500MCE 26 /* AVerMedia UltraTV 1500 MCE */ +#define IVTV_CARD_KIKYOU 27 /* Sony VAIO Giga Pocket (ENX Kikyou) */ +#define IVTV_CARD_LAST 27 + +/* Variants of existing cards but with the same PCI IDs. The driver + detects these based on other device information. + These cards must always come last. + New cards must be inserted above, and the indices of the cards below + must be adjusted accordingly. */ + +/* PVR-350 V1 (uses saa7114) */ +#define IVTV_CARD_PVR_350_V1 (IVTV_CARD_LAST+1) +/* 2 variants of Kuroutoshikou CX23416GYC-STVLP (Yuan MPG600GR OEM) */ +#define IVTV_CARD_CX23416GYC_NOGR (IVTV_CARD_LAST+2) +#define IVTV_CARD_CX23416GYC_NOGRYCS (IVTV_CARD_LAST+3) + +/* system vendor and device IDs */ +#define PCI_VENDOR_ID_ICOMP 0x4444 +#define PCI_DEVICE_ID_IVTV15 0x0803 +#define PCI_DEVICE_ID_IVTV16 0x0016 + +/* subsystem vendor ID */ +#define IVTV_PCI_ID_HAUPPAUGE 0x0070 +#define IVTV_PCI_ID_HAUPPAUGE_ALT1 0x0270 +#define IVTV_PCI_ID_HAUPPAUGE_ALT2 0x4070 +#define IVTV_PCI_ID_ADAPTEC 0x9005 +#define IVTV_PCI_ID_ASUSTEK 0x1043 +#define IVTV_PCI_ID_AVERMEDIA 0x1461 +#define IVTV_PCI_ID_YUAN1 0x12ab +#define IVTV_PCI_ID_YUAN2 0xff01 +#define IVTV_PCI_ID_YUAN3 0xffab +#define IVTV_PCI_ID_YUAN4 0xfbab +#define IVTV_PCI_ID_DIAMONDMM 0xff92 +#define IVTV_PCI_ID_IODATA 0x10fc +#define IVTV_PCI_ID_MELCO 0x1154 +#define IVTV_PCI_ID_GOTVIEW1 0xffac +#define IVTV_PCI_ID_GOTVIEW2 0xffad +#define IVTV_PCI_ID_SONY 0x104d + +/* hardware flags, no gaps allowed */ +enum ivtv_hw_bits { + IVTV_HW_BIT_CX25840, + IVTV_HW_BIT_SAA7115, + IVTV_HW_BIT_SAA7127, + IVTV_HW_BIT_MSP34XX, + IVTV_HW_BIT_TUNER, + IVTV_HW_BIT_WM8775, + IVTV_HW_BIT_CS53L32A, + IVTV_HW_BIT_TVEEPROM, + IVTV_HW_BIT_SAA7114, + IVTV_HW_BIT_UPD64031A, + IVTV_HW_BIT_UPD6408X, + IVTV_HW_BIT_SAA717X, + IVTV_HW_BIT_WM8739, + IVTV_HW_BIT_VP27SMPX, + IVTV_HW_BIT_M52790, + IVTV_HW_BIT_GPIO, + IVTV_HW_BIT_I2C_IR_RX_AVER, + IVTV_HW_BIT_I2C_IR_RX_HAUP_EXT, /* External before internal */ + IVTV_HW_BIT_I2C_IR_RX_HAUP_INT, + IVTV_HW_BIT_Z8F0811_IR_HAUP, + IVTV_HW_BIT_I2C_IR_RX_ADAPTEC, + + IVTV_HW_MAX_BITS /* Should be the last one */ +}; + +#define IVTV_HW_CX25840 BIT(IVTV_HW_BIT_CX25840) +#define IVTV_HW_SAA7115 BIT(IVTV_HW_BIT_SAA7115) +#define IVTV_HW_SAA7127 BIT(IVTV_HW_BIT_SAA7127) +#define IVTV_HW_MSP34XX BIT(IVTV_HW_BIT_MSP34XX) +#define IVTV_HW_TUNER BIT(IVTV_HW_BIT_TUNER) +#define IVTV_HW_WM8775 BIT(IVTV_HW_BIT_WM8775) +#define IVTV_HW_CS53L32A BIT(IVTV_HW_BIT_CS53L32A) +#define IVTV_HW_TVEEPROM BIT(IVTV_HW_BIT_TVEEPROM) +#define IVTV_HW_SAA7114 BIT(IVTV_HW_BIT_SAA7114) +#define IVTV_HW_UPD64031A BIT(IVTV_HW_BIT_UPD64031A) +#define IVTV_HW_UPD6408X BIT(IVTV_HW_BIT_UPD6408X) +#define IVTV_HW_SAA717X BIT(IVTV_HW_BIT_SAA717X) +#define IVTV_HW_WM8739 BIT(IVTV_HW_BIT_WM8739) +#define IVTV_HW_VP27SMPX BIT(IVTV_HW_BIT_VP27SMPX) +#define IVTV_HW_M52790 BIT(IVTV_HW_BIT_M52790) +#define IVTV_HW_GPIO BIT(IVTV_HW_BIT_GPIO) +#define IVTV_HW_I2C_IR_RX_AVER BIT(IVTV_HW_BIT_I2C_IR_RX_AVER) +#define IVTV_HW_I2C_IR_RX_HAUP_EXT BIT(IVTV_HW_BIT_I2C_IR_RX_HAUP_EXT) +#define IVTV_HW_I2C_IR_RX_HAUP_INT BIT(IVTV_HW_BIT_I2C_IR_RX_HAUP_INT) +#define IVTV_HW_Z8F0811_IR_HAUP BIT(IVTV_HW_BIT_Z8F0811_IR_HAUP) +#define IVTV_HW_I2C_IR_RX_ADAPTEC BIT(IVTV_HW_BIT_I2C_IR_RX_ADAPTEC) + +#define IVTV_HW_SAA711X (IVTV_HW_SAA7115 | IVTV_HW_SAA7114) + +#define IVTV_HW_IR_ANY (IVTV_HW_I2C_IR_RX_AVER | \ + IVTV_HW_I2C_IR_RX_HAUP_EXT | \ + IVTV_HW_I2C_IR_RX_HAUP_INT | \ + IVTV_HW_Z8F0811_IR_HAUP | \ + IVTV_HW_I2C_IR_RX_ADAPTEC) + +/* video inputs */ +#define IVTV_CARD_INPUT_VID_TUNER 1 +#define IVTV_CARD_INPUT_SVIDEO1 2 +#define IVTV_CARD_INPUT_SVIDEO2 3 +#define IVTV_CARD_INPUT_COMPOSITE1 4 +#define IVTV_CARD_INPUT_COMPOSITE2 5 +#define IVTV_CARD_INPUT_COMPOSITE3 6 + +/* audio inputs */ +#define IVTV_CARD_INPUT_AUD_TUNER 1 +#define IVTV_CARD_INPUT_LINE_IN1 2 +#define IVTV_CARD_INPUT_LINE_IN2 3 + +#define IVTV_CARD_MAX_VIDEO_INPUTS 6 +#define IVTV_CARD_MAX_AUDIO_INPUTS 3 +#define IVTV_CARD_MAX_TUNERS 3 + +/* SAA71XX HW inputs */ +#define IVTV_SAA71XX_COMPOSITE0 0 +#define IVTV_SAA71XX_COMPOSITE1 1 +#define IVTV_SAA71XX_COMPOSITE2 2 +#define IVTV_SAA71XX_COMPOSITE3 3 +#define IVTV_SAA71XX_COMPOSITE4 4 +#define IVTV_SAA71XX_COMPOSITE5 5 +#define IVTV_SAA71XX_SVIDEO0 6 +#define IVTV_SAA71XX_SVIDEO1 7 +#define IVTV_SAA71XX_SVIDEO2 8 +#define IVTV_SAA71XX_SVIDEO3 9 + +/* SAA717X needs to mark the tuner input by ORing with this flag */ +#define IVTV_SAA717X_TUNER_FLAG 0x80 + +/* Dummy HW input */ +#define IVTV_DUMMY_AUDIO 0 + +/* GPIO HW inputs */ +#define IVTV_GPIO_TUNER 0 +#define IVTV_GPIO_LINE_IN 1 + +/* SAA717X HW inputs */ +#define IVTV_SAA717X_IN0 0 +#define IVTV_SAA717X_IN1 1 +#define IVTV_SAA717X_IN2 2 + +/* V4L2 capability aliases */ +#define IVTV_CAP_ENCODER (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER | \ + V4L2_CAP_AUDIO | V4L2_CAP_READWRITE | V4L2_CAP_VBI_CAPTURE | \ + V4L2_CAP_SLICED_VBI_CAPTURE) +#define IVTV_CAP_DECODER (V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_SLICED_VBI_OUTPUT) + +struct ivtv_card_video_input { + u8 video_type; /* video input type */ + u8 audio_index; /* index in ivtv_card_audio_input array */ + u16 video_input; /* hardware video input */ +}; + +struct ivtv_card_audio_input { + u8 audio_type; /* audio input type */ + u32 audio_input; /* hardware audio input */ + u16 muxer_input; /* hardware muxer input for boards with a + multiplexer chip */ +}; + +struct ivtv_card_output { + u8 name[32]; + u16 video_output; /* hardware video output */ +}; + +struct ivtv_card_pci_info { + u16 device; + u16 subsystem_vendor; + u16 subsystem_device; +}; + +/* GPIO definitions */ + +/* The mask is the set of bits used by the operation */ + +struct ivtv_gpio_init { /* set initial GPIO DIR and OUT values */ + u16 direction; /* DIR setting. Leave to 0 if no init is needed */ + u16 initial_value; +}; + +struct ivtv_gpio_video_input { /* select tuner/line in input */ + u16 mask; /* leave to 0 if not supported */ + u16 tuner; + u16 composite; + u16 svideo; +}; + +struct ivtv_gpio_audio_input { /* select tuner/line in input */ + u16 mask; /* leave to 0 if not supported */ + u16 tuner; + u16 linein; + u16 radio; +}; + +struct ivtv_gpio_audio_mute { + u16 mask; /* leave to 0 if not supported */ + u16 mute; /* set this value to mute, 0 to unmute */ +}; + +struct ivtv_gpio_audio_mode { + u16 mask; /* leave to 0 if not supported */ + u16 mono; /* set audio to mono */ + u16 stereo; /* set audio to stereo */ + u16 lang1; /* set audio to the first language */ + u16 lang2; /* set audio to the second language */ + u16 both; /* both languages are output */ +}; + +struct ivtv_gpio_audio_freq { + u16 mask; /* leave to 0 if not supported */ + u16 f32000; + u16 f44100; + u16 f48000; +}; + +struct ivtv_gpio_audio_detect { + u16 mask; /* leave to 0 if not supported */ + u16 stereo; /* if the input matches this value then + stereo is detected */ +}; + +struct ivtv_card_tuner { + v4l2_std_id std; /* standard for which the tuner is suitable */ + int tuner; /* tuner ID (from tuner.h) */ +}; + +struct ivtv_card_tuner_i2c { + unsigned short radio[2];/* radio tuner i2c address to probe */ + unsigned short demod[2];/* demodulator i2c address to probe */ + unsigned short tv[4]; /* tv tuner i2c addresses to probe */ +}; + +/* for card information/parameters */ +struct ivtv_card { + int type; + char *name; + char *comment; + u32 v4l2_capabilities; + u32 hw_video; /* hardware used to process video */ + u32 hw_audio; /* hardware used to process audio */ + u32 hw_audio_ctrl; /* hardware used for the V4L2 controls (only 1 dev allowed) */ + u32 hw_muxer; /* hardware used to multiplex audio input */ + u32 hw_all; /* all hardware used by the board */ + struct ivtv_card_video_input video_inputs[IVTV_CARD_MAX_VIDEO_INPUTS]; + struct ivtv_card_audio_input audio_inputs[IVTV_CARD_MAX_AUDIO_INPUTS]; + struct ivtv_card_audio_input radio_input; + int nof_outputs; + const struct ivtv_card_output *video_outputs; + u8 gr_config; /* config byte for the ghost reduction device */ + u8 xceive_pin; /* XCeive tuner GPIO reset pin */ + + /* GPIO card-specific settings */ + struct ivtv_gpio_init gpio_init; + struct ivtv_gpio_video_input gpio_video_input; + struct ivtv_gpio_audio_input gpio_audio_input; + struct ivtv_gpio_audio_mute gpio_audio_mute; + struct ivtv_gpio_audio_mode gpio_audio_mode; + struct ivtv_gpio_audio_freq gpio_audio_freq; + struct ivtv_gpio_audio_detect gpio_audio_detect; + + struct ivtv_card_tuner tuners[IVTV_CARD_MAX_TUNERS]; + struct ivtv_card_tuner_i2c *i2c; + + /* list of device and subsystem vendor/devices that + correspond to this card type. */ + const struct ivtv_card_pci_info *pci_list; +}; + +int ivtv_get_input(struct ivtv *itv, u16 index, struct v4l2_input *input); +int ivtv_get_output(struct ivtv *itv, u16 index, struct v4l2_output *output); +int ivtv_get_audio_input(struct ivtv *itv, u16 index, struct v4l2_audio *input); +int ivtv_get_audio_output(struct ivtv *itv, u16 index, struct v4l2_audioout *output); +const struct ivtv_card *ivtv_get_card(u16 index); + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-controls.c b/drivers/media/pci/ivtv/ivtv-controls.c new file mode 100644 index 000000000..f087a12c4 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-controls.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + ioctl control functions + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#include "ivtv-driver.h" +#include "ivtv-ioctl.h" +#include "ivtv-controls.h" +#include "ivtv-mailbox.h" + +static int ivtv_s_stream_vbi_fmt(struct cx2341x_handler *cxhdl, u32 fmt) +{ + struct ivtv *itv = container_of(cxhdl, struct ivtv, cxhdl); + + /* First try to allocate sliced VBI buffers if needed. */ + if (fmt && itv->vbi.sliced_mpeg_data[0] == NULL) { + int i; + + for (i = 0; i < IVTV_VBI_FRAMES; i++) { + /* Yuck, hardcoded. Needs to be a define */ + itv->vbi.sliced_mpeg_data[i] = kmalloc(2049, GFP_KERNEL); + if (itv->vbi.sliced_mpeg_data[i] == NULL) { + while (--i >= 0) { + kfree(itv->vbi.sliced_mpeg_data[i]); + itv->vbi.sliced_mpeg_data[i] = NULL; + } + return -ENOMEM; + } + } + } + + itv->vbi.insert_mpeg = fmt; + + if (itv->vbi.insert_mpeg == 0) { + return 0; + } + /* Need sliced data for mpeg insertion */ + if (ivtv_get_service_set(itv->vbi.sliced_in) == 0) { + if (itv->is_60hz) + itv->vbi.sliced_in->service_set = V4L2_SLICED_CAPTION_525; + else + itv->vbi.sliced_in->service_set = V4L2_SLICED_WSS_625; + ivtv_expand_service_set(itv->vbi.sliced_in, itv->is_50hz); + } + return 0; +} + +static int ivtv_s_video_encoding(struct cx2341x_handler *cxhdl, u32 val) +{ + struct ivtv *itv = container_of(cxhdl, struct ivtv, cxhdl); + int is_mpeg1 = val == V4L2_MPEG_VIDEO_ENCODING_MPEG_1; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + /* fix videodecoder resolution */ + format.format.width = cxhdl->width / (is_mpeg1 ? 2 : 1); + format.format.height = cxhdl->height; + format.format.code = MEDIA_BUS_FMT_FIXED; + v4l2_subdev_call(itv->sd_video, pad, set_fmt, NULL, &format); + return 0; +} + +static int ivtv_s_audio_sampling_freq(struct cx2341x_handler *cxhdl, u32 idx) +{ + static const u32 freqs[3] = { 44100, 48000, 32000 }; + struct ivtv *itv = container_of(cxhdl, struct ivtv, cxhdl); + + /* The audio clock of the digitizer must match the codec sample + rate otherwise you get some very strange effects. */ + if (idx < ARRAY_SIZE(freqs)) + ivtv_call_all(itv, audio, s_clock_freq, freqs[idx]); + return 0; +} + +static int ivtv_s_audio_mode(struct cx2341x_handler *cxhdl, u32 val) +{ + struct ivtv *itv = container_of(cxhdl, struct ivtv, cxhdl); + + itv->dualwatch_stereo_mode = val; + return 0; +} + +const struct cx2341x_handler_ops ivtv_cxhdl_ops = { + .s_audio_mode = ivtv_s_audio_mode, + .s_audio_sampling_freq = ivtv_s_audio_sampling_freq, + .s_video_encoding = ivtv_s_video_encoding, + .s_stream_vbi_fmt = ivtv_s_stream_vbi_fmt, +}; + +int ivtv_g_pts_frame(struct ivtv *itv, s64 *pts, s64 *frame) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + + if (test_bit(IVTV_F_I_VALID_DEC_TIMINGS, &itv->i_flags)) { + *pts = (s64)((u64)itv->last_dec_timing[2] << 32) | + (u64)itv->last_dec_timing[1]; + *frame = itv->last_dec_timing[0]; + return 0; + } + *pts = 0; + *frame = 0; + if (atomic_read(&itv->decoding)) { + if (ivtv_api(itv, CX2341X_DEC_GET_TIMING_INFO, 5, data)) { + IVTV_DEBUG_WARN("GET_TIMING: couldn't read clock\n"); + return -EIO; + } + memcpy(itv->last_dec_timing, data, sizeof(itv->last_dec_timing)); + set_bit(IVTV_F_I_VALID_DEC_TIMINGS, &itv->i_flags); + *pts = (s64)((u64) data[2] << 32) | (u64) data[1]; + *frame = data[0]; + /*timing->scr = (u64) (((u64) data[4] << 32) | (u64) (data[3]));*/ + } + return 0; +} + +static int ivtv_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ivtv *itv = container_of(ctrl->handler, struct ivtv, cxhdl.hdl); + + switch (ctrl->id) { + /* V4L2_CID_MPEG_VIDEO_DEC_PTS and V4L2_CID_MPEG_VIDEO_DEC_FRAME + control cluster */ + case V4L2_CID_MPEG_VIDEO_DEC_PTS: + return ivtv_g_pts_frame(itv, itv->ctrl_pts->p_new.p_s64, + itv->ctrl_frame->p_new.p_s64); + } + return 0; +} + +static int ivtv_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ivtv *itv = container_of(ctrl->handler, struct ivtv, cxhdl.hdl); + + switch (ctrl->id) { + /* V4L2_CID_MPEG_AUDIO_DEC_PLAYBACK and MULTILINGUAL_PLAYBACK + control cluster */ + case V4L2_CID_MPEG_AUDIO_DEC_PLAYBACK: + itv->audio_stereo_mode = itv->ctrl_audio_playback->val - 1; + itv->audio_bilingual_mode = itv->ctrl_audio_multilingual_playback->val - 1; + ivtv_vapi(itv, CX2341X_DEC_SET_AUDIO_MODE, 2, itv->audio_bilingual_mode, itv->audio_stereo_mode); + break; + } + return 0; +} + +const struct v4l2_ctrl_ops ivtv_hdl_out_ops = { + .s_ctrl = ivtv_s_ctrl, + .g_volatile_ctrl = ivtv_g_volatile_ctrl, +}; diff --git a/drivers/media/pci/ivtv/ivtv-controls.h b/drivers/media/pci/ivtv/ivtv-controls.h new file mode 100644 index 000000000..d152691ea --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-controls.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + ioctl control functions + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#ifndef IVTV_CONTROLS_H +#define IVTV_CONTROLS_H + +extern const struct cx2341x_handler_ops ivtv_cxhdl_ops; +extern const struct v4l2_ctrl_ops ivtv_hdl_out_ops; +int ivtv_g_pts_frame(struct ivtv *itv, s64 *pts, s64 *frame); + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-driver.c b/drivers/media/pci/ivtv/ivtv-driver.c new file mode 100644 index 000000000..b9ea56ec9 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-driver.c @@ -0,0 +1,1453 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + ivtv driver initialization and card probing + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + */ + +/* Main Driver file for the ivtv project: + * Driver for the Conexant CX23415/CX23416 chip. + * Author: Kevin Thayer (nufan_wfk at yahoo.com) + * License: GPL + * + * ----- + * MPG600/MPG160 support by T.Adachi + * and Takeru KOMORIYA + * + * AVerMedia M179 GPIO info by Chris Pinkham + * using information provided by Jiun-Kuei Jung @ AVerMedia. + * + * Kurouto Sikou CX23416GYC-STVLP tested by K.Ohta + * using information from T.Adachi,Takeru KOMORIYA and others :-) + * + * Nagase TRANSGEAR 5000TV, Aopen VA2000MAX-STN6 and I/O data GV-MVP/RX + * version by T.Adachi. Special thanks Mr.Suzuki + */ + +#include "ivtv-driver.h" +#include "ivtv-version.h" +#include "ivtv-fileops.h" +#include "ivtv-i2c.h" +#include "ivtv-firmware.h" +#include "ivtv-queue.h" +#include "ivtv-udma.h" +#include "ivtv-irq.h" +#include "ivtv-mailbox.h" +#include "ivtv-streams.h" +#include "ivtv-ioctl.h" +#include "ivtv-cards.h" +#include "ivtv-vbi.h" +#include "ivtv-routing.h" +#include "ivtv-controls.h" +#include "ivtv-gpio.h" +#include +#include +#include +#include "xc2028.h" +#include + +/* If you have already X v4l cards, then set this to X. This way + the device numbers stay matched. Example: you have a WinTV card + without radio and a PVR-350 with. Normally this would give a + video1 device together with a radio0 device for the PVR. By + setting this to 1 you ensure that radio0 is now also radio1. */ +int ivtv_first_minor; + +/* Callback for registering extensions */ +int (*ivtv_ext_init)(struct ivtv *); +EXPORT_SYMBOL(ivtv_ext_init); + +/* add your revision and whatnot here */ +static const struct pci_device_id ivtv_pci_tbl[] = { + { PCI_VDEVICE(ICOMP, PCI_DEVICE_ID_IVTV15) }, + { PCI_VDEVICE(ICOMP, PCI_DEVICE_ID_IVTV16) }, + { } +}; + +MODULE_DEVICE_TABLE(pci,ivtv_pci_tbl); + +/* ivtv instance counter */ +static atomic_t ivtv_instance = ATOMIC_INIT(0); + +/* Parameter declarations */ +static int cardtype[IVTV_MAX_CARDS]; +static int tuner[IVTV_MAX_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; +static int radio[IVTV_MAX_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; +static int i2c_clock_period[IVTV_MAX_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; + +static unsigned int cardtype_c = 1; +static unsigned int tuner_c = 1; +static int radio_c = 1; +static unsigned int i2c_clock_period_c = 1; +static char pal[] = "---"; +static char secam[] = "--"; +static char ntsc[] = "-"; + +/* Buffers */ + +/* DMA Buffers, Default size in MB allocated */ +#define IVTV_DEFAULT_ENC_MPG_BUFFERS 4 +#define IVTV_DEFAULT_ENC_YUV_BUFFERS 2 +#define IVTV_DEFAULT_ENC_VBI_BUFFERS 1 +/* Exception: size in kB for this stream (MB is overkill) */ +#define IVTV_DEFAULT_ENC_PCM_BUFFERS 320 +#define IVTV_DEFAULT_DEC_MPG_BUFFERS 1 +#define IVTV_DEFAULT_DEC_YUV_BUFFERS 1 +/* Exception: size in kB for this stream (MB is way overkill) */ +#define IVTV_DEFAULT_DEC_VBI_BUFFERS 64 + +static int enc_mpg_buffers = IVTV_DEFAULT_ENC_MPG_BUFFERS; +static int enc_yuv_buffers = IVTV_DEFAULT_ENC_YUV_BUFFERS; +static int enc_vbi_buffers = IVTV_DEFAULT_ENC_VBI_BUFFERS; +static int enc_pcm_buffers = IVTV_DEFAULT_ENC_PCM_BUFFERS; +static int dec_mpg_buffers = IVTV_DEFAULT_DEC_MPG_BUFFERS; +static int dec_yuv_buffers = IVTV_DEFAULT_DEC_YUV_BUFFERS; +static int dec_vbi_buffers = IVTV_DEFAULT_DEC_VBI_BUFFERS; + +static int ivtv_yuv_mode; +static int ivtv_yuv_threshold = -1; +static int ivtv_pci_latency = 1; + +int ivtv_debug; +#ifdef CONFIG_VIDEO_ADV_DEBUG +int ivtv_fw_debug; +#endif + +static int tunertype = -1; +static int newi2c = -1; + +module_param_array(tuner, int, &tuner_c, 0644); +module_param_array(radio, int, &radio_c, 0644); +module_param_array(cardtype, int, &cardtype_c, 0644); +module_param_string(pal, pal, sizeof(pal), 0644); +module_param_string(secam, secam, sizeof(secam), 0644); +module_param_string(ntsc, ntsc, sizeof(ntsc), 0644); +module_param_named(debug,ivtv_debug, int, 0644); +#ifdef CONFIG_VIDEO_ADV_DEBUG +module_param_named(fw_debug, ivtv_fw_debug, int, 0644); +#endif +module_param(ivtv_pci_latency, int, 0644); +module_param(ivtv_yuv_mode, int, 0644); +module_param(ivtv_yuv_threshold, int, 0644); +module_param(ivtv_first_minor, int, 0644); + +module_param(enc_mpg_buffers, int, 0644); +module_param(enc_yuv_buffers, int, 0644); +module_param(enc_vbi_buffers, int, 0644); +module_param(enc_pcm_buffers, int, 0644); +module_param(dec_mpg_buffers, int, 0644); +module_param(dec_yuv_buffers, int, 0644); +module_param(dec_vbi_buffers, int, 0644); + +module_param(tunertype, int, 0644); +module_param(newi2c, int, 0644); +module_param_array(i2c_clock_period, int, &i2c_clock_period_c, 0644); + +MODULE_PARM_DESC(tuner, "Tuner type selection,\n" + "\t\t\tsee tuner.h for values"); +MODULE_PARM_DESC(radio, + "Enable or disable the radio. Use only if autodetection\n" + "\t\t\tfails. 0 = disable, 1 = enable"); +MODULE_PARM_DESC(cardtype, + "Only use this option if your card is not detected properly.\n" + "\t\tSpecify card type:\n" + "\t\t\t 1 = WinTV PVR 250\n" + "\t\t\t 2 = WinTV PVR 350\n" + "\t\t\t 3 = WinTV PVR-150 or PVR-500\n" + "\t\t\t 4 = AVerMedia M179\n" + "\t\t\t 5 = YUAN MPG600/Kuroutoshikou iTVC16-STVLP\n" + "\t\t\t 6 = YUAN MPG160/Kuroutoshikou iTVC15-STVLP\n" + "\t\t\t 7 = YUAN PG600/DIAMONDMM PVR-550 (CX Falcon 2)\n" + "\t\t\t 8 = Adaptec AVC-2410\n" + "\t\t\t 9 = Adaptec AVC-2010\n" + "\t\t\t10 = NAGASE TRANSGEAR 5000TV\n" + "\t\t\t11 = AOpen VA2000MAX-STN6\n" + "\t\t\t12 = YUAN MPG600GR/Kuroutoshikou CX23416GYC-STVLP\n" + "\t\t\t13 = I/O Data GV-MVP/RX\n" + "\t\t\t14 = I/O Data GV-MVP/RX2E\n" + "\t\t\t15 = GOTVIEW PCI DVD\n" + "\t\t\t16 = GOTVIEW PCI DVD2 Deluxe\n" + "\t\t\t17 = Yuan MPC622\n" + "\t\t\t18 = Digital Cowboy DCT-MTVP1\n" + "\t\t\t19 = Yuan PG600V2/GotView PCI DVD Lite\n" + "\t\t\t20 = Club3D ZAP-TV1x01\n" + "\t\t\t21 = AverTV MCE 116 Plus\n" + "\t\t\t22 = ASUS Falcon2\n" + "\t\t\t23 = AverMedia PVR-150 Plus\n" + "\t\t\t24 = AverMedia EZMaker PCI Deluxe\n" + "\t\t\t25 = AverMedia M104 (not yet working)\n" + "\t\t\t26 = Buffalo PC-MV5L/PCI\n" + "\t\t\t27 = AVerMedia UltraTV 1500 MCE\n" + "\t\t\t28 = Sony VAIO Giga Pocket (ENX Kikyou)\n" + "\t\t\t 0 = Autodetect (default)\n" + "\t\t\t-1 = Ignore this card\n\t\t"); +MODULE_PARM_DESC(pal, "Set PAL standard: BGH, DK, I, M, N, Nc, 60"); +MODULE_PARM_DESC(secam, "Set SECAM standard: BGH, DK, L, LC"); +MODULE_PARM_DESC(ntsc, "Set NTSC standard: M, J (Japan), K (South Korea)"); +MODULE_PARM_DESC(tunertype, + "Specify tuner type:\n" + "\t\t\t 0 = tuner for PAL-B/G/H/D/K/I, SECAM-B/G/H/D/K/L/Lc\n" + "\t\t\t 1 = tuner for NTSC-M/J/K, PAL-M/N/Nc\n" + "\t\t\t-1 = Autodetect (default)\n"); +MODULE_PARM_DESC(debug, + "Debug level (bitmask). Default: 0\n" + "\t\t\t 1/0x0001: warning\n" + "\t\t\t 2/0x0002: info\n" + "\t\t\t 4/0x0004: mailbox\n" + "\t\t\t 8/0x0008: ioctl\n" + "\t\t\t 16/0x0010: file\n" + "\t\t\t 32/0x0020: dma\n" + "\t\t\t 64/0x0040: irq\n" + "\t\t\t 128/0x0080: decoder\n" + "\t\t\t 256/0x0100: yuv\n" + "\t\t\t 512/0x0200: i2c\n" + "\t\t\t1024/0x0400: high volume\n"); +#ifdef CONFIG_VIDEO_ADV_DEBUG +MODULE_PARM_DESC(fw_debug, + "Enable code for debugging firmware problems. Default: 0\n"); +#endif +MODULE_PARM_DESC(ivtv_pci_latency, + "Change the PCI latency to 64 if lower: 0 = No, 1 = Yes,\n" + "\t\t\tDefault: Yes"); +MODULE_PARM_DESC(ivtv_yuv_mode, + "Specify the yuv playback mode:\n" + "\t\t\t0 = interlaced\n\t\t\t1 = progressive\n\t\t\t2 = auto\n" + "\t\t\tDefault: 0 (interlaced)"); +MODULE_PARM_DESC(ivtv_yuv_threshold, + "If ivtv_yuv_mode is 2 (auto) then playback content as\n\t\tprogressive if src height <= ivtv_yuvthreshold\n" + "\t\t\tDefault: 480"); +MODULE_PARM_DESC(enc_mpg_buffers, + "Encoder MPG Buffers (in MB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_ENC_MPG_BUFFERS)); +MODULE_PARM_DESC(enc_yuv_buffers, + "Encoder YUV Buffers (in MB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_ENC_YUV_BUFFERS)); +MODULE_PARM_DESC(enc_vbi_buffers, + "Encoder VBI Buffers (in MB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_ENC_VBI_BUFFERS)); +MODULE_PARM_DESC(enc_pcm_buffers, + "Encoder PCM buffers (in kB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_ENC_PCM_BUFFERS)); +MODULE_PARM_DESC(dec_mpg_buffers, + "Decoder MPG buffers (in MB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_DEC_MPG_BUFFERS)); +MODULE_PARM_DESC(dec_yuv_buffers, + "Decoder YUV buffers (in MB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_DEC_YUV_BUFFERS)); +MODULE_PARM_DESC(dec_vbi_buffers, + "Decoder VBI buffers (in kB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_DEC_VBI_BUFFERS)); +MODULE_PARM_DESC(newi2c, + "Use new I2C implementation\n" + "\t\t\t-1 is autodetect, 0 is off, 1 is on\n" + "\t\t\tDefault is autodetect"); +MODULE_PARM_DESC(i2c_clock_period, + "Period of SCL for the I2C bus controlled by the CX23415/6\n" + "\t\t\tMin: 10 usec (100 kHz), Max: 4500 usec (222 Hz)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_I2C_CLOCK_PERIOD)); + +MODULE_PARM_DESC(ivtv_first_minor, "Set device node number assigned to first card"); + +MODULE_AUTHOR("Kevin Thayer, Chris Kennedy, Hans Verkuil"); +MODULE_DESCRIPTION("CX23415/CX23416 driver"); +MODULE_LICENSE("GPL"); + +MODULE_VERSION(IVTV_VERSION); + +#if defined(CONFIG_MODULES) && defined(MODULE) +static void request_module_async(struct work_struct *work) +{ + struct ivtv *dev = container_of(work, struct ivtv, request_module_wk); + + /* Make sure ivtv-alsa module is loaded */ + request_module("ivtv-alsa"); + + /* Initialize ivtv-alsa for this instance of the cx18 device */ + if (ivtv_ext_init != NULL) + ivtv_ext_init(dev); +} + +static void request_modules(struct ivtv *dev) +{ + INIT_WORK(&dev->request_module_wk, request_module_async); + schedule_work(&dev->request_module_wk); +} + +static void flush_request_modules(struct ivtv *dev) +{ + flush_work(&dev->request_module_wk); +} +#else +#define request_modules(dev) +#define flush_request_modules(dev) +#endif /* CONFIG_MODULES */ + +void ivtv_clear_irq_mask(struct ivtv *itv, u32 mask) +{ + itv->irqmask &= ~mask; + write_reg_sync(itv->irqmask, IVTV_REG_IRQMASK); +} + +void ivtv_set_irq_mask(struct ivtv *itv, u32 mask) +{ + itv->irqmask |= mask; + write_reg_sync(itv->irqmask, IVTV_REG_IRQMASK); +} + +int ivtv_set_output_mode(struct ivtv *itv, int mode) +{ + int old_mode; + + spin_lock(&itv->lock); + old_mode = itv->output_mode; + if (old_mode == 0) + itv->output_mode = old_mode = mode; + spin_unlock(&itv->lock); + return old_mode; +} + +struct ivtv_stream *ivtv_get_output_stream(struct ivtv *itv) +{ + switch (itv->output_mode) { + case OUT_MPG: + return &itv->streams[IVTV_DEC_STREAM_TYPE_MPG]; + case OUT_YUV: + return &itv->streams[IVTV_DEC_STREAM_TYPE_YUV]; + default: + return NULL; + } +} + +int ivtv_waitq(wait_queue_head_t *waitq) +{ + DEFINE_WAIT(wait); + + prepare_to_wait(waitq, &wait, TASK_INTERRUPTIBLE); + schedule(); + finish_wait(waitq, &wait); + return signal_pending(current) ? -EINTR : 0; +} + +/* Generic utility functions */ +int ivtv_msleep_timeout(unsigned int msecs, int intr) +{ + int timeout = msecs_to_jiffies(msecs); + + do { + set_current_state(intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE); + timeout = schedule_timeout(timeout); + if (intr) { + int ret = signal_pending(current); + + if (ret) + return ret; + } + } while (timeout); + return 0; +} + +/* Hauppauge card? get values from tveeprom */ +void ivtv_read_eeprom(struct ivtv *itv, struct tveeprom *tv) +{ + u8 eedata[256]; + + itv->i2c_client.addr = 0xA0 >> 1; + tveeprom_read(&itv->i2c_client, eedata, sizeof(eedata)); + tveeprom_hauppauge_analog(tv, eedata); +} + +static void ivtv_process_eeprom(struct ivtv *itv) +{ + struct tveeprom tv; + int pci_slot = PCI_SLOT(itv->pdev->devfn); + + ivtv_read_eeprom(itv, &tv); + + /* Many thanks to Steven Toth from Hauppauge for providing the + model numbers */ + switch (tv.model) { + /* In a few cases the PCI subsystem IDs do not correctly + identify the card. A better method is to check the + model number from the eeprom instead. */ + case 30012 ... 30039: /* Low profile PVR250 */ + case 32000 ... 32999: + case 48000 ... 48099: /* 48??? range are PVR250s with a cx23415 */ + case 48400 ... 48599: + itv->card = ivtv_get_card(IVTV_CARD_PVR_250); + break; + case 48100 ... 48399: + case 48600 ... 48999: + itv->card = ivtv_get_card(IVTV_CARD_PVR_350); + break; + case 23000 ... 23999: /* PVR500 */ + case 25000 ... 25999: /* Low profile PVR150 */ + case 26000 ... 26999: /* Regular PVR150 */ + itv->card = ivtv_get_card(IVTV_CARD_PVR_150); + break; + case 0: + IVTV_ERR("Invalid EEPROM\n"); + return; + default: + IVTV_ERR("Unknown model %d, defaulting to PVR-150\n", tv.model); + itv->card = ivtv_get_card(IVTV_CARD_PVR_150); + break; + } + + switch (tv.model) { + /* Old style PVR350 (with an saa7114) uses this input for + the tuner. */ + case 48254: + itv->card = ivtv_get_card(IVTV_CARD_PVR_350_V1); + break; + default: + break; + } + + itv->v4l2_cap = itv->card->v4l2_capabilities; + itv->card_name = itv->card->name; + itv->card_i2c = itv->card->i2c; + + /* If this is a PVR500 then it should be possible to detect whether it is the + first or second unit by looking at the subsystem device ID: is bit 4 is + set, then it is the second unit (according to info from Hauppauge). + + However, while this works for most cards, I have seen a few PVR500 cards + where both units have the same subsystem ID. + + So instead I look at the reported 'PCI slot' (which is the slot on the PVR500 + PCI bridge) and if it is 8, then it is assumed to be the first unit, otherwise + it is the second unit. It is possible that it is a different slot when ivtv is + used in Xen, in that case I ignore this card here. The worst that can happen + is that the card presents itself with a non-working radio device. + + This detection is needed since the eeprom reports incorrectly that a radio is + present on the second unit. */ + if (tv.model / 1000 == 23) { + static const struct ivtv_card_tuner_i2c ivtv_i2c_radio = { + .radio = { 0x60, I2C_CLIENT_END }, + .demod = { 0x43, I2C_CLIENT_END }, + .tv = { 0x61, I2C_CLIENT_END }, + }; + + itv->card_name = "WinTV PVR 500"; + itv->card_i2c = &ivtv_i2c_radio; + if (pci_slot == 8 || pci_slot == 9) { + int is_first = (pci_slot & 1) == 0; + + itv->card_name = is_first ? "WinTV PVR 500 (unit #1)" : + "WinTV PVR 500 (unit #2)"; + if (!is_first) { + IVTV_INFO("Correcting tveeprom data: no radio present on second unit\n"); + tv.has_radio = 0; + } + } + } + IVTV_INFO("Autodetected %s\n", itv->card_name); + + switch (tv.tuner_hauppauge_model) { + case 85: + case 99: + case 112: + itv->pvr150_workaround = 1; + break; + default: + break; + } + if (tv.tuner_type == TUNER_ABSENT) + IVTV_ERR("tveeprom cannot autodetect tuner!\n"); + + if (itv->options.tuner == -1) + itv->options.tuner = tv.tuner_type; + if (itv->options.radio == -1) + itv->options.radio = (tv.has_radio != 0); + /* only enable newi2c if an IR blaster is present */ + if (itv->options.newi2c == -1 && tv.has_ir) { + itv->options.newi2c = (tv.has_ir & 4) ? 1 : 0; + if (itv->options.newi2c) { + IVTV_INFO("Reopen i2c bus for IR-blaster support\n"); + exit_ivtv_i2c(itv); + init_ivtv_i2c(itv); + } + } + + if (itv->std != 0) + /* user specified tuner standard */ + return; + + /* autodetect tuner standard */ + if (tv.tuner_formats & V4L2_STD_PAL) { + IVTV_DEBUG_INFO("PAL tuner detected\n"); + itv->std |= V4L2_STD_PAL_BG | V4L2_STD_PAL_H; + } else if (tv.tuner_formats & V4L2_STD_NTSC) { + IVTV_DEBUG_INFO("NTSC tuner detected\n"); + itv->std |= V4L2_STD_NTSC_M; + } else if (tv.tuner_formats & V4L2_STD_SECAM) { + IVTV_DEBUG_INFO("SECAM tuner detected\n"); + itv->std |= V4L2_STD_SECAM_L; + } else { + IVTV_INFO("No tuner detected, default to NTSC-M\n"); + itv->std |= V4L2_STD_NTSC_M; + } +} + +static v4l2_std_id ivtv_parse_std(struct ivtv *itv) +{ + switch (pal[0]) { + case '6': + tunertype = 0; + return V4L2_STD_PAL_60; + case 'b': + case 'B': + case 'g': + case 'G': + case 'h': + case 'H': + tunertype = 0; + return V4L2_STD_PAL_BG | V4L2_STD_PAL_H; + case 'n': + case 'N': + tunertype = 1; + if (pal[1] == 'c' || pal[1] == 'C') + return V4L2_STD_PAL_Nc; + return V4L2_STD_PAL_N; + case 'i': + case 'I': + tunertype = 0; + return V4L2_STD_PAL_I; + case 'd': + case 'D': + case 'k': + case 'K': + tunertype = 0; + return V4L2_STD_PAL_DK; + case 'M': + case 'm': + tunertype = 1; + return V4L2_STD_PAL_M; + case '-': + break; + default: + IVTV_WARN("pal= argument not recognised\n"); + return 0; + } + + switch (secam[0]) { + case 'b': + case 'B': + case 'g': + case 'G': + case 'h': + case 'H': + tunertype = 0; + return V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H; + case 'd': + case 'D': + case 'k': + case 'K': + tunertype = 0; + return V4L2_STD_SECAM_DK; + case 'l': + case 'L': + tunertype = 0; + if (secam[1] == 'C' || secam[1] == 'c') + return V4L2_STD_SECAM_LC; + return V4L2_STD_SECAM_L; + case '-': + break; + default: + IVTV_WARN("secam= argument not recognised\n"); + return 0; + } + + switch (ntsc[0]) { + case 'm': + case 'M': + tunertype = 1; + return V4L2_STD_NTSC_M; + case 'j': + case 'J': + tunertype = 1; + return V4L2_STD_NTSC_M_JP; + case 'k': + case 'K': + tunertype = 1; + return V4L2_STD_NTSC_M_KR; + case '-': + break; + default: + IVTV_WARN("ntsc= argument not recognised\n"); + return 0; + } + + /* no match found */ + return 0; +} + +static void ivtv_process_options(struct ivtv *itv) +{ + const char *chipname; + int i, j; + + itv->options.kilobytes[IVTV_ENC_STREAM_TYPE_MPG] = enc_mpg_buffers * 1024; + itv->options.kilobytes[IVTV_ENC_STREAM_TYPE_YUV] = enc_yuv_buffers * 1024; + itv->options.kilobytes[IVTV_ENC_STREAM_TYPE_VBI] = enc_vbi_buffers * 1024; + itv->options.kilobytes[IVTV_ENC_STREAM_TYPE_PCM] = enc_pcm_buffers; + itv->options.kilobytes[IVTV_DEC_STREAM_TYPE_MPG] = dec_mpg_buffers * 1024; + itv->options.kilobytes[IVTV_DEC_STREAM_TYPE_YUV] = dec_yuv_buffers * 1024; + itv->options.kilobytes[IVTV_DEC_STREAM_TYPE_VBI] = dec_vbi_buffers; + itv->options.cardtype = cardtype[itv->instance]; + itv->options.tuner = tuner[itv->instance]; + itv->options.radio = radio[itv->instance]; + + itv->options.i2c_clock_period = i2c_clock_period[itv->instance]; + if (itv->options.i2c_clock_period == -1) + itv->options.i2c_clock_period = IVTV_DEFAULT_I2C_CLOCK_PERIOD; + else if (itv->options.i2c_clock_period < 10) + itv->options.i2c_clock_period = 10; + else if (itv->options.i2c_clock_period > 4500) + itv->options.i2c_clock_period = 4500; + + itv->options.newi2c = newi2c; + if (tunertype < -1 || tunertype > 1) { + IVTV_WARN("Invalid tunertype argument, will autodetect instead\n"); + tunertype = -1; + } + itv->std = ivtv_parse_std(itv); + if (itv->std == 0 && tunertype >= 0) + itv->std = tunertype ? V4L2_STD_MN : (V4L2_STD_ALL & ~V4L2_STD_MN); + itv->has_cx23415 = (itv->pdev->device == PCI_DEVICE_ID_IVTV15); + chipname = itv->has_cx23415 ? "cx23415" : "cx23416"; + if (itv->options.cardtype == -1) { + IVTV_INFO("Ignore card (detected %s based chip)\n", chipname); + return; + } + if ((itv->card = ivtv_get_card(itv->options.cardtype - 1))) { + IVTV_INFO("User specified %s card (detected %s based chip)\n", + itv->card->name, chipname); + } else if (itv->options.cardtype != 0) { + IVTV_ERR("Unknown user specified type, trying to autodetect card\n"); + } + if (itv->card == NULL) { + if (itv->pdev->subsystem_vendor == IVTV_PCI_ID_HAUPPAUGE || + itv->pdev->subsystem_vendor == IVTV_PCI_ID_HAUPPAUGE_ALT1 || + itv->pdev->subsystem_vendor == IVTV_PCI_ID_HAUPPAUGE_ALT2) { + itv->card = ivtv_get_card(itv->has_cx23415 ? IVTV_CARD_PVR_350 : IVTV_CARD_PVR_150); + IVTV_INFO("Autodetected Hauppauge card (%s based)\n", + chipname); + } + } + if (itv->card == NULL) { + for (i = 0; (itv->card = ivtv_get_card(i)); i++) { + if (itv->card->pci_list == NULL) + continue; + for (j = 0; itv->card->pci_list[j].device; j++) { + if (itv->pdev->device != + itv->card->pci_list[j].device) + continue; + if (itv->pdev->subsystem_vendor != + itv->card->pci_list[j].subsystem_vendor) + continue; + if (itv->pdev->subsystem_device != + itv->card->pci_list[j].subsystem_device) + continue; + IVTV_INFO("Autodetected %s card (%s based)\n", + itv->card->name, chipname); + goto done; + } + } + } +done: + + if (itv->card == NULL) { + itv->card = ivtv_get_card(IVTV_CARD_PVR_150); + IVTV_ERR("Unknown card: vendor/device: [%04x:%04x]\n", + itv->pdev->vendor, itv->pdev->device); + IVTV_ERR(" subsystem vendor/device: [%04x:%04x]\n", + itv->pdev->subsystem_vendor, itv->pdev->subsystem_device); + IVTV_ERR(" %s based\n", chipname); + IVTV_ERR("Defaulting to %s card\n", itv->card->name); + IVTV_ERR("Please mail the vendor/device and subsystem vendor/device IDs and what kind of\n"); + IVTV_ERR("card you have to the linux-media mailinglist (www.linuxtv.org)\n"); + IVTV_ERR("Prefix your subject line with [UNKNOWN IVTV CARD].\n"); + } + itv->v4l2_cap = itv->card->v4l2_capabilities; + itv->card_name = itv->card->name; + itv->card_i2c = itv->card->i2c; +} + +/* Precondition: the ivtv structure has been memset to 0. Only + the dev and num fields have been filled in. + No assumptions on the card type may be made here (see ivtv_init_struct2 + for that). + */ +static int ivtv_init_struct1(struct ivtv *itv) +{ + itv->base_addr = pci_resource_start(itv->pdev, 0); + itv->enc_mbox.max_mbox = 2; /* the encoder has 3 mailboxes (0-2) */ + itv->dec_mbox.max_mbox = 1; /* the decoder has 2 mailboxes (0-1) */ + + mutex_init(&itv->serialize_lock); + mutex_init(&itv->i2c_bus_lock); + mutex_init(&itv->udma.lock); + + spin_lock_init(&itv->lock); + spin_lock_init(&itv->dma_reg_lock); + + kthread_init_worker(&itv->irq_worker); + itv->irq_worker_task = kthread_run(kthread_worker_fn, &itv->irq_worker, + "%s", itv->v4l2_dev.name); + if (IS_ERR(itv->irq_worker_task)) { + IVTV_ERR("Could not create ivtv task\n"); + return -1; + } + /* must use the FIFO scheduler as it is realtime sensitive */ + sched_set_fifo(itv->irq_worker_task); + + kthread_init_work(&itv->irq_work, ivtv_irq_work_handler); + + /* Initial settings */ + itv->cxhdl.port = CX2341X_PORT_MEMORY; + itv->cxhdl.capabilities = CX2341X_CAP_HAS_SLICED_VBI; + init_waitqueue_head(&itv->eos_waitq); + init_waitqueue_head(&itv->event_waitq); + init_waitqueue_head(&itv->vsync_waitq); + init_waitqueue_head(&itv->dma_waitq); + timer_setup(&itv->dma_timer, ivtv_unfinished_dma, 0); + + itv->cur_dma_stream = -1; + itv->cur_pio_stream = -1; + + /* Ctrls */ + itv->speed = 1000; + + /* VBI */ + itv->vbi.in.type = V4L2_BUF_TYPE_VBI_CAPTURE; + itv->vbi.sliced_in = &itv->vbi.in.fmt.sliced; + + /* Init the sg table for osd/yuv output */ + sg_init_table(itv->udma.SGlist, IVTV_DMA_SG_OSD_ENT); + + /* OSD */ + itv->osd_global_alpha_state = 1; + itv->osd_global_alpha = 255; + + /* YUV */ + atomic_set(&itv->yuv_info.next_dma_frame, -1); + itv->yuv_info.lace_mode = ivtv_yuv_mode; + itv->yuv_info.lace_threshold = ivtv_yuv_threshold; + itv->yuv_info.max_frames_buffered = 3; + itv->yuv_info.track_osd = 1; + return 0; +} + +/* Second initialization part. Here the card type has been + autodetected. */ +static void ivtv_init_struct2(struct ivtv *itv) +{ + int i; + + for (i = 0; i < IVTV_CARD_MAX_VIDEO_INPUTS; i++) + if (itv->card->video_inputs[i].video_type == 0) + break; + itv->nof_inputs = i; + for (i = 0; i < IVTV_CARD_MAX_AUDIO_INPUTS; i++) + if (itv->card->audio_inputs[i].audio_type == 0) + break; + itv->nof_audio_inputs = i; + + if (itv->card->hw_all & IVTV_HW_CX25840) { + itv->vbi.sliced_size = 288; /* multiple of 16, real size = 284 */ + } else { + itv->vbi.sliced_size = 64; /* multiple of 16, real size = 52 */ + } + + /* Find tuner input */ + for (i = 0; i < itv->nof_inputs; i++) { + if (itv->card->video_inputs[i].video_type == + IVTV_CARD_INPUT_VID_TUNER) + break; + } + if (i >= itv->nof_inputs) + i = 0; + itv->active_input = i; + itv->audio_input = itv->card->video_inputs[i].audio_index; +} + +static int ivtv_setup_pci(struct ivtv *itv, struct pci_dev *pdev, + const struct pci_device_id *pci_id) +{ + u16 cmd; + unsigned char pci_latency; + + IVTV_DEBUG_INFO("Enabling pci device\n"); + + if (pcim_enable_device(pdev)) { + IVTV_ERR("Can't enable device!\n"); + return -EIO; + } + if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(32))) { + IVTV_ERR("No suitable DMA available.\n"); + return -EIO; + } + if (!devm_request_mem_region(&pdev->dev, itv->base_addr, + IVTV_ENCODER_SIZE, "ivtv encoder")) { + IVTV_ERR("Cannot request encoder memory region.\n"); + return -EIO; + } + + if (!devm_request_mem_region(&pdev->dev, + itv->base_addr + IVTV_REG_OFFSET, + IVTV_REG_SIZE, "ivtv registers")) { + IVTV_ERR("Cannot request register memory region.\n"); + return -EIO; + } + + if (itv->has_cx23415 && + !devm_request_mem_region(&pdev->dev, + itv->base_addr + IVTV_DECODER_OFFSET, + IVTV_DECODER_SIZE, "ivtv decoder")) { + IVTV_ERR("Cannot request decoder memory region.\n"); + return -EIO; + } + + /* Check for bus mastering */ + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (!(cmd & PCI_COMMAND_MASTER)) { + IVTV_DEBUG_INFO("Attempting to enable Bus Mastering\n"); + pci_set_master(pdev); + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (!(cmd & PCI_COMMAND_MASTER)) { + IVTV_ERR("Bus Mastering is not enabled\n"); + return -ENXIO; + } + } + IVTV_DEBUG_INFO("Bus Mastering Enabled.\n"); + + pci_read_config_byte(pdev, PCI_LATENCY_TIMER, &pci_latency); + + if (pci_latency < 64 && ivtv_pci_latency) { + IVTV_INFO("Unreasonably low latency timer, setting to 64 (was %d)\n", + pci_latency); + pci_write_config_byte(pdev, PCI_LATENCY_TIMER, 64); + pci_read_config_byte(pdev, PCI_LATENCY_TIMER, &pci_latency); + } + /* This config space value relates to DMA latencies. The + default value 0x8080 is too low however and will lead + to DMA errors. 0xffff is the max value which solves + these problems. */ + pci_write_config_dword(pdev, 0x40, 0xffff); + + IVTV_DEBUG_INFO("%d (rev %d) at %02x:%02x.%x, irq: %d, latency: %d, memory: 0x%llx\n", + pdev->device, pdev->revision, pdev->bus->number, + PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn), + pdev->irq, pci_latency, (u64)itv->base_addr); + + return 0; +} + +static void ivtv_load_and_init_modules(struct ivtv *itv) +{ + u32 hw = itv->card->hw_all; + unsigned i; + + /* check which i2c devices are actually found */ + for (i = 0; i < 32; i++) { + u32 device = BIT(i); + + if (!(device & hw)) + continue; + if (device == IVTV_HW_GPIO || device == IVTV_HW_TVEEPROM) { + /* GPIO and TVEEPROM do not use i2c probing */ + itv->hw_flags |= device; + continue; + } + if (ivtv_i2c_register(itv, i) == 0) + itv->hw_flags |= device; + } + + /* probe for legacy IR controllers that aren't in card definitions */ + if ((itv->hw_flags & IVTV_HW_IR_ANY) == 0) + ivtv_i2c_new_ir_legacy(itv); + + if (itv->card->hw_all & IVTV_HW_CX25840) + itv->sd_video = ivtv_find_hw(itv, IVTV_HW_CX25840); + else if (itv->card->hw_all & IVTV_HW_SAA717X) + itv->sd_video = ivtv_find_hw(itv, IVTV_HW_SAA717X); + else if (itv->card->hw_all & IVTV_HW_SAA7114) + itv->sd_video = ivtv_find_hw(itv, IVTV_HW_SAA7114); + else + itv->sd_video = ivtv_find_hw(itv, IVTV_HW_SAA7115); + itv->sd_audio = ivtv_find_hw(itv, itv->card->hw_audio_ctrl); + itv->sd_muxer = ivtv_find_hw(itv, itv->card->hw_muxer); + + hw = itv->hw_flags; + + if (itv->card->type == IVTV_CARD_CX23416GYC) { + /* Several variations of this card exist, detect which card + type should be used. */ + if ((hw & (IVTV_HW_UPD64031A | IVTV_HW_UPD6408X)) == 0) + itv->card = ivtv_get_card(IVTV_CARD_CX23416GYC_NOGRYCS); + else if ((hw & IVTV_HW_UPD64031A) == 0) + itv->card = ivtv_get_card(IVTV_CARD_CX23416GYC_NOGR); + } + else if (itv->card->type == IVTV_CARD_GV_MVPRX || + itv->card->type == IVTV_CARD_GV_MVPRX2E) { + /* The crystal frequency of GVMVPRX is 24.576MHz */ + v4l2_subdev_call(itv->sd_video, video, s_crystal_freq, + SAA7115_FREQ_24_576_MHZ, SAA7115_FREQ_FL_UCGC); + } + + if (hw & IVTV_HW_CX25840) { + itv->vbi.raw_decoder_line_size = 1444; + itv->vbi.raw_decoder_sav_odd_field = 0x20; + itv->vbi.raw_decoder_sav_even_field = 0x60; + itv->vbi.sliced_decoder_line_size = 272; + itv->vbi.sliced_decoder_sav_odd_field = 0xB0; + itv->vbi.sliced_decoder_sav_even_field = 0xF0; + } + + if (hw & IVTV_HW_SAA711X) { + /* determine the exact saa711x model */ + itv->hw_flags &= ~IVTV_HW_SAA711X; + + if (strstr(itv->sd_video->name, "saa7114")) { + itv->hw_flags |= IVTV_HW_SAA7114; + /* VBI is not yet supported by the saa7114 driver. */ + itv->v4l2_cap &= ~(V4L2_CAP_SLICED_VBI_CAPTURE|V4L2_CAP_VBI_CAPTURE); + } else { + itv->hw_flags |= IVTV_HW_SAA7115; + } + itv->vbi.raw_decoder_line_size = 1443; + itv->vbi.raw_decoder_sav_odd_field = 0x25; + itv->vbi.raw_decoder_sav_even_field = 0x62; + itv->vbi.sliced_decoder_line_size = 51; + itv->vbi.sliced_decoder_sav_odd_field = 0xAB; + itv->vbi.sliced_decoder_sav_even_field = 0xEC; + } + + if (hw & IVTV_HW_SAA717X) { + itv->vbi.raw_decoder_line_size = 1443; + itv->vbi.raw_decoder_sav_odd_field = 0x25; + itv->vbi.raw_decoder_sav_even_field = 0x62; + itv->vbi.sliced_decoder_line_size = 51; + itv->vbi.sliced_decoder_sav_odd_field = 0xAB; + itv->vbi.sliced_decoder_sav_even_field = 0xEC; + } +} + +static int ivtv_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id) +{ + int retval = 0; + int vbi_buf_size; + struct ivtv *itv; + + itv = kzalloc_obj(struct ivtv); + if (itv == NULL) + return -ENOMEM; + itv->pdev = pdev; + itv->instance = v4l2_device_set_name(&itv->v4l2_dev, "ivtv", + &ivtv_instance); + + retval = v4l2_device_register(&pdev->dev, &itv->v4l2_dev); + if (retval) { + kfree(itv); + return retval; + } + IVTV_INFO("Initializing card %d\n", itv->instance); + + ivtv_process_options(itv); + if (itv->options.cardtype == -1) { + retval = -ENODEV; + goto err; + } + if (ivtv_init_struct1(itv)) { + retval = -ENOMEM; + goto err; + } + retval = cx2341x_handler_init(&itv->cxhdl, 50); + if (retval) + goto err; + itv->v4l2_dev.ctrl_handler = &itv->cxhdl.hdl; + itv->cxhdl.ops = &ivtv_cxhdl_ops; + itv->cxhdl.priv = itv; + itv->cxhdl.func = ivtv_api_func; + + IVTV_DEBUG_INFO("base addr: 0x%llx\n", (u64)itv->base_addr); + + /* PCI Device Setup */ + retval = ivtv_setup_pci(itv, pdev, pci_id); + if (retval == -EIO || retval == -ENXIO) + goto free_worker; + + /* map io memory */ + IVTV_DEBUG_INFO("attempting ioremap at 0x%llx len 0x%08x\n", + (u64)itv->base_addr + IVTV_ENCODER_OFFSET, IVTV_ENCODER_SIZE); + itv->enc_mem = devm_ioremap(&pdev->dev, + itv->base_addr + IVTV_ENCODER_OFFSET, + IVTV_ENCODER_SIZE); + if (!itv->enc_mem) { + IVTV_ERR("ioremap failed. Can't get a window into CX23415/6 encoder memory\n"); + IVTV_ERR("Each capture card with a CX23415/6 needs 8 MB of vmalloc address space for this window\n"); + IVTV_ERR("Check the output of 'grep Vmalloc /proc/meminfo'\n"); + IVTV_ERR("Use the vmalloc= kernel command line option to set VmallocTotal to a larger value\n"); + retval = -ENOMEM; + goto free_worker; + } + + if (itv->has_cx23415) { + IVTV_DEBUG_INFO("attempting ioremap at 0x%llx len 0x%08x\n", + (u64)itv->base_addr + IVTV_DECODER_OFFSET, IVTV_DECODER_SIZE); + itv->dec_mem = devm_ioremap(&pdev->dev, + itv->base_addr + IVTV_DECODER_OFFSET, + IVTV_DECODER_SIZE); + if (!itv->dec_mem) { + IVTV_ERR("ioremap failed. Can't get a window into CX23415 decoder memory\n"); + IVTV_ERR("Each capture card with a CX23415 needs 8 MB of vmalloc address space for this window\n"); + IVTV_ERR("Check the output of 'grep Vmalloc /proc/meminfo'\n"); + IVTV_ERR("Use the vmalloc= kernel command line option to set VmallocTotal to a larger value\n"); + retval = -ENOMEM; + goto free_worker; + } + } + else { + itv->dec_mem = itv->enc_mem; + } + + /* map registers memory */ + IVTV_DEBUG_INFO("attempting ioremap at 0x%llx len 0x%08x\n", + (u64)itv->base_addr + IVTV_REG_OFFSET, IVTV_REG_SIZE); + itv->reg_mem = devm_ioremap(&pdev->dev, + itv->base_addr + IVTV_REG_OFFSET, + IVTV_REG_SIZE); + if (!itv->reg_mem) { + IVTV_ERR("ioremap failed. Can't get a window into CX23415/6 register space\n"); + IVTV_ERR("Each capture card with a CX23415/6 needs 64 kB of vmalloc address space for this window\n"); + IVTV_ERR("Check the output of 'grep Vmalloc /proc/meminfo'\n"); + IVTV_ERR("Use the vmalloc= kernel command line option to set VmallocTotal to a larger value\n"); + retval = -ENOMEM; + goto free_worker; + } + + retval = ivtv_gpio_init(itv); + if (retval) + goto free_worker; + + /* active i2c */ + IVTV_DEBUG_INFO("activating i2c...\n"); + if (init_ivtv_i2c(itv)) { + IVTV_ERR("Could not initialize i2c\n"); + goto free_worker; + } + + if (itv->card->hw_all & IVTV_HW_TVEEPROM) { + /* Based on the model number the cardtype may be changed. + The PCI IDs are not always reliable. */ + ivtv_process_eeprom(itv); + } + if (itv->card->comment) + IVTV_INFO("%s", itv->card->comment); + if (itv->card->v4l2_capabilities == 0) { + /* card was detected but is not supported */ + retval = -ENODEV; + goto free_i2c; + } + + if (itv->std == 0) { + itv->std = V4L2_STD_NTSC_M; + } + + if (itv->options.tuner == -1) { + int i; + + for (i = 0; i < IVTV_CARD_MAX_TUNERS; i++) { + if ((itv->std & itv->card->tuners[i].std) == 0) + continue; + itv->options.tuner = itv->card->tuners[i].tuner; + break; + } + } + /* if no tuner was found, then pick the first tuner in the card list */ + if (itv->options.tuner == -1 && itv->card->tuners[0].std) { + itv->std = itv->card->tuners[0].std; + if (itv->std & V4L2_STD_PAL) + itv->std = V4L2_STD_PAL_BG | V4L2_STD_PAL_H; + else if (itv->std & V4L2_STD_NTSC) + itv->std = V4L2_STD_NTSC_M; + else if (itv->std & V4L2_STD_SECAM) + itv->std = V4L2_STD_SECAM_L; + itv->options.tuner = itv->card->tuners[0].tuner; + } + if (itv->options.radio == -1) + itv->options.radio = (itv->card->radio_input.audio_type != 0); + + /* The card is now fully identified, continue with card-specific + initialization. */ + ivtv_init_struct2(itv); + + ivtv_load_and_init_modules(itv); + + if (itv->std & V4L2_STD_525_60) { + itv->is_60hz = 1; + itv->is_out_60hz = 1; + } else { + itv->is_50hz = 1; + itv->is_out_50hz = 1; + } + + itv->yuv_info.osd_full_w = 720; + itv->yuv_info.osd_full_h = itv->is_out_50hz ? 576 : 480; + itv->yuv_info.v4l2_src_w = itv->yuv_info.osd_full_w; + itv->yuv_info.v4l2_src_h = itv->yuv_info.osd_full_h; + + cx2341x_handler_set_50hz(&itv->cxhdl, itv->is_50hz); + + itv->stream_buf_size[IVTV_ENC_STREAM_TYPE_MPG] = 0x08000; + itv->stream_buf_size[IVTV_ENC_STREAM_TYPE_PCM] = 0x01200; + itv->stream_buf_size[IVTV_DEC_STREAM_TYPE_MPG] = 0x10000; + itv->stream_buf_size[IVTV_DEC_STREAM_TYPE_YUV] = 0x10000; + itv->stream_buf_size[IVTV_ENC_STREAM_TYPE_YUV] = 0x08000; + + /* Setup VBI Raw Size. Should be big enough to hold PAL. + It is possible to switch between PAL and NTSC, so we need to + take the largest size here. */ + /* 1456 is multiple of 16, real size = 1444 */ + itv->vbi.raw_size = 1456; + /* We use a buffer size of 1/2 of the total size needed for a + frame. This is actually very useful, since we now receive + a field at a time and that makes 'compressing' the raw data + down to size by stripping off the SAV codes a lot easier. + Note: having two different buffer sizes prevents standard + switching on the fly. We need to find a better solution... */ + vbi_buf_size = itv->vbi.raw_size * (itv->is_60hz ? 24 : 36) / 2; + itv->stream_buf_size[IVTV_ENC_STREAM_TYPE_VBI] = vbi_buf_size; + itv->stream_buf_size[IVTV_DEC_STREAM_TYPE_VBI] = sizeof(struct v4l2_sliced_vbi_data) * 36; + + if (itv->options.radio > 0) + itv->v4l2_cap |= V4L2_CAP_RADIO; + + if (itv->options.tuner > -1) { + struct tuner_setup setup; + + setup.addr = ADDR_UNSET; + setup.type = itv->options.tuner; + setup.mode_mask = T_ANALOG_TV; /* matches TV tuners */ + if (itv->options.radio > 0) + setup.mode_mask |= T_RADIO; + setup.tuner_callback = (setup.type == TUNER_XC2028) ? + ivtv_reset_tuner_gpio : NULL; + ivtv_call_all(itv, tuner, s_type_addr, &setup); + if (setup.type == TUNER_XC2028) { + static struct xc2028_ctrl ctrl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + }; + struct v4l2_priv_tun_config cfg = { + .tuner = itv->options.tuner, + .priv = &ctrl, + }; + ivtv_call_all(itv, tuner, s_config, &cfg); + } + } + + /* The tuner is fixed to the standard. The other inputs (e.g. S-Video) + are not. */ + itv->tuner_std = itv->std; + + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) { + struct v4l2_ctrl_handler *hdl = itv->v4l2_dev.ctrl_handler; + + itv->ctrl_pts = v4l2_ctrl_new_std(hdl, &ivtv_hdl_out_ops, + V4L2_CID_MPEG_VIDEO_DEC_PTS, 0, 0, 0, 0); + itv->ctrl_frame = v4l2_ctrl_new_std(hdl, &ivtv_hdl_out_ops, + V4L2_CID_MPEG_VIDEO_DEC_FRAME, 0, 0, 0, 0); + /* Note: V4L2_MPEG_AUDIO_DEC_PLAYBACK_AUTO is not supported, + mask that menu item. */ + itv->ctrl_audio_playback = + v4l2_ctrl_new_std_menu(hdl, &ivtv_hdl_out_ops, + V4L2_CID_MPEG_AUDIO_DEC_PLAYBACK, + V4L2_MPEG_AUDIO_DEC_PLAYBACK_SWAPPED_STEREO, + 1 << V4L2_MPEG_AUDIO_DEC_PLAYBACK_AUTO, + V4L2_MPEG_AUDIO_DEC_PLAYBACK_STEREO); + itv->ctrl_audio_multilingual_playback = + v4l2_ctrl_new_std_menu(hdl, &ivtv_hdl_out_ops, + V4L2_CID_MPEG_AUDIO_DEC_MULTILINGUAL_PLAYBACK, + V4L2_MPEG_AUDIO_DEC_PLAYBACK_SWAPPED_STEREO, + 1 << V4L2_MPEG_AUDIO_DEC_PLAYBACK_AUTO, + V4L2_MPEG_AUDIO_DEC_PLAYBACK_LEFT); + if (hdl->error) { + retval = hdl->error; + goto free_i2c; + } + v4l2_ctrl_cluster(2, &itv->ctrl_pts); + v4l2_ctrl_cluster(2, &itv->ctrl_audio_playback); + ivtv_call_all(itv, video, s_std_output, itv->std); + /* Turn off the output signal. The mpeg decoder is not yet + active so without this you would get a green image until the + mpeg decoder becomes active. */ + ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_stream, 0); + } + + /* clear interrupt mask, effectively disabling interrupts */ + ivtv_set_irq_mask(itv, 0xffffffff); + + /* Register IRQ */ + retval = request_irq(itv->pdev->irq, ivtv_irq_handler, + IRQF_SHARED, itv->v4l2_dev.name, (void *)itv); + if (retval) { + IVTV_ERR("Failed to register irq %d\n", retval); + goto free_i2c; + } + + retval = ivtv_streams_setup(itv); + if (retval) { + IVTV_ERR("Error %d setting up streams\n", retval); + goto free_irq; + } + retval = ivtv_streams_register(itv); + if (retval) { + IVTV_ERR("Error %d registering devices\n", retval); + goto free_streams; + } + IVTV_INFO("Initialized card: %s\n", itv->card_name); + + /* Load ivtv submodules (ivtv-alsa) */ + request_modules(itv); + return 0; + +free_streams: + ivtv_streams_cleanup(itv); +free_irq: + free_irq(itv->pdev->irq, (void *)itv); +free_i2c: + v4l2_ctrl_handler_free(&itv->cxhdl.hdl); + exit_ivtv_i2c(itv); +free_worker: + kthread_stop(itv->irq_worker_task); +err: + if (retval == 0) + retval = -ENODEV; + IVTV_ERR("Error %d on initialization\n", retval); + + v4l2_device_unregister(&itv->v4l2_dev); + kfree(itv); + return retval; +} + +int ivtv_init_on_first_open(struct ivtv *itv) +{ + /* Needed to call ioctls later */ + struct ivtv_stream *s = &itv->streams[IVTV_ENC_STREAM_TYPE_MPG]; + struct v4l2_frequency vf; + int fw_retry_count = 3; + int video_input; + + if (test_bit(IVTV_F_I_FAILED, &itv->i_flags)) + return -ENXIO; + + if (test_and_set_bit(IVTV_F_I_INITED, &itv->i_flags)) + return 0; + + while (--fw_retry_count > 0) { + /* load firmware */ + if (ivtv_firmware_init(itv) == 0) + break; + if (fw_retry_count > 1) + IVTV_WARN("Retry loading firmware\n"); + } + + if (fw_retry_count == 0) { + set_bit(IVTV_F_I_FAILED, &itv->i_flags); + return -ENXIO; + } + + /* Try and get firmware versions */ + IVTV_DEBUG_INFO("Getting firmware version..\n"); + ivtv_firmware_versions(itv); + + if (itv->card->hw_all & IVTV_HW_CX25840) + v4l2_subdev_call(itv->sd_video, core, load_fw); + + vf.tuner = 0; + vf.type = V4L2_TUNER_ANALOG_TV; + vf.frequency = 6400; /* the tuner 'baseline' frequency */ + + /* Set initial frequency. For PAL/SECAM broadcasts no + 'default' channel exists AFAIK. */ + if (itv->std == V4L2_STD_NTSC_M_JP) { + vf.frequency = 1460; /* ch. 1 91250*16/1000 */ + } + else if (itv->std & V4L2_STD_NTSC_M) { + vf.frequency = 1076; /* ch. 4 67250*16/1000 */ + } + + video_input = itv->active_input; + itv->active_input++; /* Force update of input */ + ivtv_do_s_input(itv, video_input); + + /* Let the VIDIOC_S_STD ioctl do all the work, keeps the code + in one place. */ + itv->std++; /* Force full standard initialization */ + itv->std_out = itv->std; + ivtv_do_s_frequency(s, &vf); + + if (itv->card->v4l2_capabilities & V4L2_CAP_VIDEO_OUTPUT) { + /* Turn on the TV-out: ivtv_init_mpeg_decoder() initializes + the mpeg decoder so now the saa7127 receives a proper + signal. */ + ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_stream, 1); + ivtv_init_mpeg_decoder(itv); + } + + /* On a cx23416 this seems to be able to enable DMA to the chip? */ + if (!itv->has_cx23415) + write_reg_sync(0x03, IVTV_REG_DMACONTROL); + + ivtv_s_std_enc(itv, itv->tuner_std); + + /* Default interrupts enabled. For the PVR350 this includes the + decoder VSYNC interrupt, which is always on. It is not only used + during decoding but also by the OSD. + Some old PVR250 cards had a cx23415, so testing for that is too + general. Instead test if the card has video output capability. */ + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) { + ivtv_clear_irq_mask(itv, IVTV_IRQ_MASK_INIT | IVTV_IRQ_DEC_VSYNC); + ivtv_set_osd_alpha(itv); + ivtv_s_std_dec(itv, itv->tuner_std); + } else { + ivtv_clear_irq_mask(itv, IVTV_IRQ_MASK_INIT); + } + + /* Setup initial controls */ + cx2341x_handler_setup(&itv->cxhdl); + return 0; +} + +static void ivtv_remove(struct pci_dev *pdev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev); + struct ivtv *itv = to_ivtv(v4l2_dev); + int i; + + IVTV_DEBUG_INFO("Removing card\n"); + + flush_request_modules(itv); + + if (test_bit(IVTV_F_I_INITED, &itv->i_flags)) { + /* Stop all captures */ + IVTV_DEBUG_INFO("Stopping all streams\n"); + if (atomic_read(&itv->capturing) > 0) + ivtv_stop_all_captures(itv); + + /* Stop all decoding */ + IVTV_DEBUG_INFO("Stopping decoding\n"); + + /* Turn off the TV-out */ + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) + ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_stream, 0); + if (atomic_read(&itv->decoding) > 0) { + int type; + + if (test_bit(IVTV_F_I_DEC_YUV, &itv->i_flags)) + type = IVTV_DEC_STREAM_TYPE_YUV; + else + type = IVTV_DEC_STREAM_TYPE_MPG; + ivtv_stop_v4l2_decode_stream(&itv->streams[type], + V4L2_DEC_CMD_STOP_TO_BLACK | V4L2_DEC_CMD_STOP_IMMEDIATELY, 0); + } + ivtv_halt_firmware(itv); + } + + /* Interrupts */ + ivtv_set_irq_mask(itv, 0xffffffff); + timer_shutdown_sync(&itv->dma_timer); + + /* Kill irq worker */ + kthread_flush_worker(&itv->irq_worker); + kthread_stop(itv->irq_worker_task); + + ivtv_streams_cleanup(itv); + ivtv_udma_free(itv); + + v4l2_ctrl_handler_free(&itv->cxhdl.hdl); + + exit_ivtv_i2c(itv); + + free_irq(itv->pdev->irq, (void *)itv); + + for (i = 0; i < IVTV_VBI_FRAMES; i++) + kfree(itv->vbi.sliced_mpeg_data[i]); + + pr_info("Removed %s\n", itv->card_name); + + v4l2_device_unregister(&itv->v4l2_dev); + kfree(itv); +} + +/* define a pci_driver for card detection */ +static struct pci_driver ivtv_pci_driver = { + .name = "ivtv", + .id_table = ivtv_pci_tbl, + .probe = ivtv_probe, + .remove = ivtv_remove, +}; + +static int __init module_start(void) +{ + pr_info("Start initialization, version %s\n", IVTV_VERSION); + + /* Validate parameters */ + if (ivtv_first_minor < 0 || ivtv_first_minor >= IVTV_MAX_CARDS) { + pr_err("Exiting, ivtv_first_minor must be between 0 and %d\n", + IVTV_MAX_CARDS - 1); + return -1; + } + + if (ivtv_debug < 0 || ivtv_debug > 2047) { + ivtv_debug = 0; + pr_info("Debug value must be >= 0 and <= 2047\n"); + } + + if (pci_register_driver(&ivtv_pci_driver)) { + pr_err("Error detecting PCI card\n"); + return -ENODEV; + } + pr_info("End initialization\n"); + return 0; +} + +static void __exit module_cleanup(void) +{ + pci_unregister_driver(&ivtv_pci_driver); +} + +/* Note: These symbols are exported because they are used by the ivtvfb + framebuffer module and an infrared module for the IR-blaster. */ +EXPORT_SYMBOL(ivtv_set_irq_mask); +EXPORT_SYMBOL(ivtv_api); +EXPORT_SYMBOL(ivtv_vapi); +EXPORT_SYMBOL(ivtv_vapi_result); +EXPORT_SYMBOL(ivtv_clear_irq_mask); +EXPORT_SYMBOL(ivtv_debug); +#ifdef CONFIG_VIDEO_ADV_DEBUG +EXPORT_SYMBOL(ivtv_fw_debug); +#endif +EXPORT_SYMBOL(ivtv_reset_ir_gpio); +EXPORT_SYMBOL(ivtv_udma_setup); +EXPORT_SYMBOL(ivtv_udma_unmap); +EXPORT_SYMBOL(ivtv_udma_alloc); +EXPORT_SYMBOL(ivtv_udma_prepare); +EXPORT_SYMBOL(ivtv_init_on_first_open); +EXPORT_SYMBOL(ivtv_firmware_check); + +module_init(module_start); +module_exit(module_cleanup); diff --git a/drivers/media/pci/ivtv/ivtv-driver.h b/drivers/media/pci/ivtv/ivtv-driver.h new file mode 100644 index 000000000..f1f189113 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-driver.h @@ -0,0 +1,829 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + ivtv driver internal defines and structures + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + */ + +#ifndef IVTV_DRIVER_H +#define IVTV_DRIVER_H + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +/* Internal header for ivtv project: + * Driver for the cx23415/6 chip. + * Author: Kevin Thayer (nufan_wfk at yahoo.com) + * License: GPL + * + * ----- + * MPG600/MPG160 support by T.Adachi + * and Takeru KOMORIYA + * + * AVerMedia M179 GPIO info by Chris Pinkham + * using information provided by Jiun-Kuei Jung @ AVerMedia. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Memory layout */ +#define IVTV_ENCODER_OFFSET 0x00000000 +#define IVTV_ENCODER_SIZE 0x00800000 /* Total size is 0x01000000, but only first half is used */ +#define IVTV_DECODER_OFFSET 0x01000000 +#define IVTV_DECODER_SIZE 0x00800000 /* Total size is 0x01000000, but only first half is used */ +#define IVTV_REG_OFFSET 0x02000000 +#define IVTV_REG_SIZE 0x00010000 + +/* Maximum ivtv driver instances. Some people have a huge number of + capture cards, so set this to a high value. */ +#define IVTV_MAX_CARDS 32 + +#define IVTV_ENC_STREAM_TYPE_MPG 0 +#define IVTV_ENC_STREAM_TYPE_YUV 1 +#define IVTV_ENC_STREAM_TYPE_VBI 2 +#define IVTV_ENC_STREAM_TYPE_PCM 3 +#define IVTV_ENC_STREAM_TYPE_RAD 4 +#define IVTV_DEC_STREAM_TYPE_MPG 5 +#define IVTV_DEC_STREAM_TYPE_VBI 6 +#define IVTV_DEC_STREAM_TYPE_VOUT 7 +#define IVTV_DEC_STREAM_TYPE_YUV 8 +#define IVTV_MAX_STREAMS 9 + +#define IVTV_DMA_SG_OSD_ENT (2883584/PAGE_SIZE) /* sg entities */ + +/* DMA Registers */ +#define IVTV_REG_DMAXFER (0x0000) +#define IVTV_REG_DMASTATUS (0x0004) +#define IVTV_REG_DECDMAADDR (0x0008) +#define IVTV_REG_ENCDMAADDR (0x000c) +#define IVTV_REG_DMACONTROL (0x0010) +#define IVTV_REG_IRQSTATUS (0x0040) +#define IVTV_REG_IRQMASK (0x0048) + +/* Setup Registers */ +#define IVTV_REG_ENC_SDRAM_REFRESH (0x07F8) +#define IVTV_REG_ENC_SDRAM_PRECHARGE (0x07FC) +#define IVTV_REG_DEC_SDRAM_REFRESH (0x08F8) +#define IVTV_REG_DEC_SDRAM_PRECHARGE (0x08FC) +#define IVTV_REG_VDM (0x2800) +#define IVTV_REG_AO (0x2D00) +#define IVTV_REG_BYTEFLUSH (0x2D24) +#define IVTV_REG_SPU (0x9050) +#define IVTV_REG_HW_BLOCKS (0x9054) +#define IVTV_REG_VPU (0x9058) +#define IVTV_REG_APU (0xA064) + +/* Other registers */ +#define IVTV_REG_DEC_LINE_FIELD (0x28C0) + +/* debugging */ +extern int ivtv_debug; +#ifdef CONFIG_VIDEO_ADV_DEBUG +extern int ivtv_fw_debug; +#endif + +#define IVTV_DBGFLG_WARN (1 << 0) +#define IVTV_DBGFLG_INFO (1 << 1) +#define IVTV_DBGFLG_MB (1 << 2) +#define IVTV_DBGFLG_IOCTL (1 << 3) +#define IVTV_DBGFLG_FILE (1 << 4) +#define IVTV_DBGFLG_DMA (1 << 5) +#define IVTV_DBGFLG_IRQ (1 << 6) +#define IVTV_DBGFLG_DEC (1 << 7) +#define IVTV_DBGFLG_YUV (1 << 8) +#define IVTV_DBGFLG_I2C (1 << 9) +/* Flag to turn on high volume debugging */ +#define IVTV_DBGFLG_HIGHVOL (1 << 10) + +#define IVTV_DEBUG(x, type, fmt, args...) \ + do { \ + if ((x) & ivtv_debug) \ + v4l2_info(&itv->v4l2_dev, " " type ": " fmt , ##args); \ + } while (0) +#define IVTV_DEBUG_WARN(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_WARN, "warn", fmt , ## args) +#define IVTV_DEBUG_INFO(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_INFO, "info", fmt , ## args) +#define IVTV_DEBUG_MB(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_MB, "mb", fmt , ## args) +#define IVTV_DEBUG_DMA(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_DMA, "dma", fmt , ## args) +#define IVTV_DEBUG_IOCTL(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_IOCTL, "ioctl", fmt , ## args) +#define IVTV_DEBUG_FILE(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_FILE, "file", fmt , ## args) +#define IVTV_DEBUG_I2C(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_I2C, "i2c", fmt , ## args) +#define IVTV_DEBUG_IRQ(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_IRQ, "irq", fmt , ## args) +#define IVTV_DEBUG_DEC(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_DEC, "dec", fmt , ## args) +#define IVTV_DEBUG_YUV(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_YUV, "yuv", fmt , ## args) + +#define IVTV_DEBUG_HIGH_VOL(x, type, fmt, args...) \ + do { \ + if (((x) & ivtv_debug) && (ivtv_debug & IVTV_DBGFLG_HIGHVOL)) \ + v4l2_info(&itv->v4l2_dev, " " type ": " fmt , ##args); \ + } while (0) +#define IVTV_DEBUG_HI_WARN(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_WARN, "warn", fmt , ## args) +#define IVTV_DEBUG_HI_INFO(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_INFO, "info", fmt , ## args) +#define IVTV_DEBUG_HI_MB(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_MB, "mb", fmt , ## args) +#define IVTV_DEBUG_HI_DMA(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_DMA, "dma", fmt , ## args) +#define IVTV_DEBUG_HI_IOCTL(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_IOCTL, "ioctl", fmt , ## args) +#define IVTV_DEBUG_HI_FILE(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_FILE, "file", fmt , ## args) +#define IVTV_DEBUG_HI_I2C(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_I2C, "i2c", fmt , ## args) +#define IVTV_DEBUG_HI_IRQ(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_IRQ, "irq", fmt , ## args) +#define IVTV_DEBUG_HI_DEC(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_DEC, "dec", fmt , ## args) +#define IVTV_DEBUG_HI_YUV(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_YUV, "yuv", fmt , ## args) + +/* Standard kernel messages */ +#define IVTV_ERR(fmt, args...) v4l2_err(&itv->v4l2_dev, fmt , ## args) +#define IVTV_WARN(fmt, args...) v4l2_warn(&itv->v4l2_dev, fmt , ## args) +#define IVTV_INFO(fmt, args...) v4l2_info(&itv->v4l2_dev, fmt , ## args) + +/* output modes (cx23415 only) */ +#define OUT_NONE 0 +#define OUT_MPG 1 +#define OUT_YUV 2 +#define OUT_UDMA_YUV 3 +#define OUT_PASSTHROUGH 4 + +#define IVTV_MAX_PGM_INDEX (400) + +/* Default I2C SCL period in microseconds */ +#define IVTV_DEFAULT_I2C_CLOCK_PERIOD 20 + +struct ivtv_options { + int kilobytes[IVTV_MAX_STREAMS]; /* size in kilobytes of each stream */ + int cardtype; /* force card type on load */ + int tuner; /* set tuner on load */ + int radio; /* enable/disable radio */ + int newi2c; /* new I2C algorithm */ + int i2c_clock_period; /* period of SCL for I2C bus */ +}; + +/* ivtv-specific mailbox template */ +struct ivtv_mailbox { + u32 flags; + u32 cmd; + u32 retval; + u32 timeout; + u32 data[CX2341X_MBOX_MAX_DATA]; +}; + +struct ivtv_api_cache { + unsigned long last_jiffies; /* when last command was issued */ + u32 data[CX2341X_MBOX_MAX_DATA]; /* last sent api data */ +}; + +struct ivtv_mailbox_data { + volatile struct ivtv_mailbox __iomem *mbox; + /* Bits 0-2 are for the encoder mailboxes, 0-1 are for the decoder mailboxes. + If the bit is set, then the corresponding mailbox is in use by the driver. */ + unsigned long busy; + u8 max_mbox; +}; + +/* per-buffer bit flags */ +#define IVTV_F_B_NEED_BUF_SWAP (1 << 0) /* this buffer should be byte swapped */ + +/* per-stream, s_flags */ +#define IVTV_F_S_DMA_PENDING 0 /* this stream has pending DMA */ +#define IVTV_F_S_DMA_HAS_VBI 1 /* the current DMA request also requests VBI data */ +#define IVTV_F_S_NEEDS_DATA 2 /* this decoding stream needs more data */ + +#define IVTV_F_S_CLAIMED 3 /* this stream is claimed */ +#define IVTV_F_S_STREAMING 4 /* the fw is decoding/encoding this stream */ +#define IVTV_F_S_INTERNAL_USE 5 /* this stream is used internally (sliced VBI processing) */ +#define IVTV_F_S_PASSTHROUGH 6 /* this stream is in passthrough mode */ +#define IVTV_F_S_STREAMOFF 7 /* signal end of stream EOS */ +#define IVTV_F_S_APPL_IO 8 /* this stream is used read/written by an application */ + +#define IVTV_F_S_PIO_PENDING 9 /* this stream has pending PIO */ +#define IVTV_F_S_PIO_HAS_VBI 1 /* the current PIO request also requests VBI data */ + +/* per-ivtv, i_flags */ +#define IVTV_F_I_DMA 0 /* DMA in progress */ +#define IVTV_F_I_UDMA 1 /* UDMA in progress */ +#define IVTV_F_I_UDMA_PENDING 2 /* UDMA pending */ +#define IVTV_F_I_SPEED_CHANGE 3 /* a speed change is in progress */ +#define IVTV_F_I_EOS 4 /* end of encoder stream reached */ +#define IVTV_F_I_RADIO_USER 5 /* the radio tuner is selected */ +#define IVTV_F_I_DIG_RST 6 /* reset digitizer */ +#define IVTV_F_I_DEC_YUV 7 /* YUV instead of MPG is being decoded */ +#define IVTV_F_I_UPDATE_CC 9 /* CC should be updated */ +#define IVTV_F_I_UPDATE_WSS 10 /* WSS should be updated */ +#define IVTV_F_I_UPDATE_VPS 11 /* VPS should be updated */ +#define IVTV_F_I_DECODING_YUV 12 /* this stream is YUV frame decoding */ +#define IVTV_F_I_ENC_PAUSED 13 /* the encoder is paused */ +#define IVTV_F_I_VALID_DEC_TIMINGS 14 /* last_dec_timing is valid */ +#define IVTV_F_I_HAVE_WORK 15 /* used in the interrupt handler: there is work to be done */ +#define IVTV_F_I_WORK_HANDLER_VBI 16 /* there is work to be done for VBI */ +#define IVTV_F_I_WORK_HANDLER_YUV 17 /* there is work to be done for YUV */ +#define IVTV_F_I_WORK_HANDLER_PIO 18 /* there is work to be done for PIO */ +#define IVTV_F_I_PIO 19 /* PIO in progress */ +#define IVTV_F_I_DEC_PAUSED 20 /* the decoder is paused */ +#define IVTV_F_I_INITED 21 /* set after first open */ +#define IVTV_F_I_FAILED 22 /* set if first open failed */ +#define IVTV_F_I_WORK_HANDLER_PCM 23 /* there is work to be done for PCM */ + +/* Event notifications */ +#define IVTV_F_I_EV_DEC_STOPPED 28 /* decoder stopped event */ +#define IVTV_F_I_EV_VSYNC 29 /* VSYNC event */ +#define IVTV_F_I_EV_VSYNC_FIELD 30 /* VSYNC event field (0 = first, 1 = second field) */ +#define IVTV_F_I_EV_VSYNC_ENABLED 31 /* VSYNC event enabled */ + +/* Scatter-Gather array element, used in DMA transfers */ +struct ivtv_sg_element { + __le32 src; + __le32 dst; + __le32 size; +}; + +struct ivtv_sg_host_element { + u32 src; + u32 dst; + u32 size; +}; + +struct ivtv_user_dma { + struct mutex lock; + int page_count; + struct page *map[IVTV_DMA_SG_OSD_ENT]; + /* Needed when dealing with highmem userspace buffers */ + struct page *bouncemap[IVTV_DMA_SG_OSD_ENT]; + + /* Base Dev SG Array for cx23415/6 */ + struct ivtv_sg_element SGarray[IVTV_DMA_SG_OSD_ENT]; + dma_addr_t SG_handle; + int SG_length; + + /* SG List of Buffers */ + struct scatterlist SGlist[IVTV_DMA_SG_OSD_ENT]; +}; + +struct ivtv_dma_page_info { + unsigned long uaddr; + unsigned long first; + unsigned long last; + unsigned int offset; + unsigned int tail; + int page_count; +}; + +struct ivtv_buffer { + struct list_head list; + dma_addr_t dma_handle; + unsigned short b_flags; + unsigned short dma_xfer_cnt; + char *buf; + u32 bytesused; + u32 readpos; +}; + +struct ivtv_queue { + struct list_head list; /* the list of buffers in this queue */ + u32 buffers; /* number of buffers in this queue */ + u32 length; /* total number of bytes of available buffer space */ + u32 bytesused; /* total number of bytes used in this queue */ +}; + +struct ivtv; /* forward reference */ +struct ivtv_open_id; + +struct ivtv_stream { + /* These first four fields are always set, even if the stream + is not actually created. */ + struct video_device vdev; /* vdev.v4l2_dev is NULL if there is no device */ + struct ivtv *itv; /* for ease of use */ + const char *name; /* name of the stream */ + int type; /* stream type */ + + struct ivtv_open_id *id; /* pointer to the streaming ivtv_open_id */ + spinlock_t qlock; /* locks access to the queues */ + unsigned long s_flags; /* status flags, see above */ + int dma; /* can be PCI_DMA_TODEVICE, PCI_DMA_FROMDEVICE or PCI_DMA_NONE */ + u32 pending_offset; + u32 pending_backup; + u64 pending_pts; + + u32 dma_offset; + u32 dma_backup; + u64 dma_pts; + + int subtype; + wait_queue_head_t waitq; + u32 dma_last_offset; + + /* Buffer Stats */ + u32 buffers; + u32 buf_size; + u32 buffers_stolen; + + /* Buffer Queues */ + struct ivtv_queue q_free; /* free buffers */ + struct ivtv_queue q_full; /* full buffers */ + struct ivtv_queue q_io; /* waiting for I/O */ + struct ivtv_queue q_dma; /* waiting for DMA */ + struct ivtv_queue q_predma; /* waiting for DMA */ + + /* DMA xfer counter, buffers belonging to the same DMA + xfer will have the same dma_xfer_cnt. */ + u16 dma_xfer_cnt; + + /* Base Dev SG Array for cx23415/6 */ + struct ivtv_sg_host_element *sg_pending; + struct ivtv_sg_host_element *sg_processing; + struct ivtv_sg_element *sg_dma; + dma_addr_t sg_handle; + int sg_pending_size; + int sg_processing_size; + int sg_processed; + + /* SG List of Buffers */ + struct scatterlist *SGlist; +}; + +struct ivtv_open_id { + struct v4l2_fh fh; + int type; /* stream type */ + int yuv_frames; /* 1: started OUT_UDMA_YUV output mode */ + struct ivtv *itv; +}; + +static inline struct ivtv_open_id *file2id(struct file *filp) +{ + return container_of(file_to_v4l2_fh(filp), struct ivtv_open_id, fh); +} + +struct yuv_frame_info +{ + u32 update; + s32 src_x; + s32 src_y; + u32 src_w; + u32 src_h; + s32 dst_x; + s32 dst_y; + u32 dst_w; + u32 dst_h; + s32 pan_x; + s32 pan_y; + u32 vis_w; + u32 vis_h; + u32 interlaced_y; + u32 interlaced_uv; + s32 tru_x; + u32 tru_w; + u32 tru_h; + u32 offset_y; + s32 lace_mode; + u32 sync_field; + u32 delay; + u32 interlaced; +}; + +#define IVTV_YUV_MODE_INTERLACED 0x00 +#define IVTV_YUV_MODE_PROGRESSIVE 0x01 +#define IVTV_YUV_MODE_AUTO 0x02 +#define IVTV_YUV_MODE_MASK 0x03 + +#define IVTV_YUV_SYNC_EVEN 0x00 +#define IVTV_YUV_SYNC_ODD 0x04 +#define IVTV_YUV_SYNC_MASK 0x04 + +#define IVTV_YUV_BUFFERS 8 + +struct yuv_playback_info +{ + u32 reg_2834; + u32 reg_2838; + u32 reg_283c; + u32 reg_2840; + u32 reg_2844; + u32 reg_2848; + u32 reg_2854; + u32 reg_285c; + u32 reg_2864; + + u32 reg_2870; + u32 reg_2874; + u32 reg_2890; + u32 reg_2898; + u32 reg_289c; + + u32 reg_2918; + u32 reg_291c; + u32 reg_2920; + u32 reg_2924; + u32 reg_2928; + u32 reg_292c; + u32 reg_2930; + + u32 reg_2934; + + u32 reg_2938; + u32 reg_293c; + u32 reg_2940; + u32 reg_2944; + u32 reg_2948; + u32 reg_294c; + u32 reg_2950; + u32 reg_2954; + u32 reg_2958; + u32 reg_295c; + u32 reg_2960; + u32 reg_2964; + u32 reg_2968; + u32 reg_296c; + + u32 reg_2970; + + int v_filter_1; + int v_filter_2; + int h_filter; + + u8 track_osd; /* Should yuv output track the OSD size & position */ + + u32 osd_x_offset; + u32 osd_y_offset; + + u32 osd_x_pan; + u32 osd_y_pan; + + u32 osd_vis_w; + u32 osd_vis_h; + + u32 osd_full_w; + u32 osd_full_h; + + int decode_height; + + int lace_mode; + int lace_threshold; + int lace_sync_field; + + atomic_t next_dma_frame; + atomic_t next_fill_frame; + + u32 yuv_forced_update; + int update_frame; + + u8 fields_lapsed; /* Counter used when delaying a frame */ + + struct yuv_frame_info new_frame_info[IVTV_YUV_BUFFERS]; + struct yuv_frame_info old_frame_info; + struct yuv_frame_info old_frame_info_args; + + void *blanking_ptr; + dma_addr_t blanking_dmaptr; + + int stream_size; + + u8 draw_frame; /* PVR350 buffer to draw into */ + u8 max_frames_buffered; /* Maximum number of frames to buffer */ + + struct v4l2_rect main_rect; + u32 v4l2_src_w; + u32 v4l2_src_h; + + u8 running; /* Have any frames been displayed */ +}; + +#define IVTV_VBI_FRAMES 32 + +/* VBI data */ +struct vbi_cc { + u8 odd[2]; /* two-byte payload of odd field */ + u8 even[2]; /* two-byte payload of even field */; +}; + +struct vbi_vps { + u8 data[5]; /* five-byte VPS payload */ +}; + +struct vbi_info { + /* VBI general data, does not change during streaming */ + + u32 raw_decoder_line_size; /* raw VBI line size from digitizer */ + u8 raw_decoder_sav_odd_field; /* raw VBI Start Active Video digitizer code of odd field */ + u8 raw_decoder_sav_even_field; /* raw VBI Start Active Video digitizer code of even field */ + u32 sliced_decoder_line_size; /* sliced VBI line size from digitizer */ + u8 sliced_decoder_sav_odd_field; /* sliced VBI Start Active Video digitizer code of odd field */ + u8 sliced_decoder_sav_even_field; /* sliced VBI Start Active Video digitizer code of even field */ + + u32 start[2]; /* start of first VBI line in the odd/even fields */ + u32 count; /* number of VBI lines per field */ + u32 raw_size; /* size of raw VBI line from the digitizer */ + u32 sliced_size; /* size of sliced VBI line from the digitizer */ + + u32 dec_start; /* start in decoder memory of VBI re-insertion buffers */ + u32 enc_start; /* start in encoder memory of VBI capture buffers */ + u32 enc_size; /* size of VBI capture area */ + int fpi; /* number of VBI frames per interrupt */ + + struct v4l2_format in; /* current VBI capture format */ + struct v4l2_sliced_vbi_format *sliced_in; /* convenience pointer to sliced struct in vbi.in union */ + int insert_mpeg; /* if non-zero, then embed VBI data in MPEG stream */ + + /* Raw VBI compatibility hack */ + + u32 frame; /* frame counter hack needed for backwards compatibility + of old VBI software */ + + /* Sliced VBI output data */ + + struct vbi_cc cc_payload[256]; /* sliced VBI CC payload array: it is an array to + prevent dropping CC data if they couldn't be + processed fast enough */ + int cc_payload_idx; /* index in cc_payload */ + u8 cc_missing_cnt; /* counts number of frames without CC for passthrough mode */ + int wss_payload; /* sliced VBI WSS payload */ + u8 wss_missing_cnt; /* counts number of frames without WSS for passthrough mode */ + struct vbi_vps vps_payload; /* sliced VBI VPS payload */ + + /* Sliced VBI capture data */ + + struct v4l2_sliced_vbi_data sliced_data[36]; /* sliced VBI storage for VBI encoder stream */ + struct v4l2_sliced_vbi_data sliced_dec_data[36];/* sliced VBI storage for VBI decoder stream */ + + /* VBI Embedding data */ + + /* Buffer for VBI data inserted into MPEG stream. + The first byte is a dummy byte that's never used. + The next 16 bytes contain the MPEG header for the VBI data, + the remainder is the actual VBI data. + The max size accepted by the MPEG VBI reinsertion turns out + to be 1552 bytes, which happens to be 4 + (1 + 42) * (2 * 18) bytes, + where 4 is a four byte header, 42 is the max sliced VBI payload, 1 is + a single line header byte and 2 * 18 is the number of VBI lines per frame. + + However, it seems that the data must be 1K aligned, so we have to + pad the data until the 1 or 2 K boundary. + + This pointer array will allocate 2049 bytes to store each VBI frame. */ + u8 *sliced_mpeg_data[IVTV_VBI_FRAMES]; + u32 sliced_mpeg_size[IVTV_VBI_FRAMES]; + struct ivtv_buffer sliced_mpeg_buf; /* temporary buffer holding data from sliced_mpeg_data */ + u32 inserted_frame; /* index in sliced_mpeg_size of next sliced data + to be inserted in the MPEG stream */ +}; + +/* forward declaration of struct defined in ivtv-cards.h */ +struct ivtv_card; + +/* Struct to hold info about ivtv cards */ +struct ivtv { + /* General fixed card data */ + struct pci_dev *pdev; /* PCI device */ + const struct ivtv_card *card; /* card information */ + const char *card_name; /* full name of the card */ + const struct ivtv_card_tuner_i2c *card_i2c; /* i2c addresses to probe for tuner */ + u8 has_cx23415; /* 1 if it is a cx23415 based card, 0 for cx23416 */ + u8 pvr150_workaround; /* 1 if the cx25840 needs to workaround a PVR150 bug */ + u8 nof_inputs; /* number of video inputs */ + u8 nof_audio_inputs; /* number of audio inputs */ + u32 v4l2_cap; /* V4L2 capabilities of card */ + u32 hw_flags; /* hardware description of the board */ + v4l2_std_id tuner_std; /* the norm of the card's tuner (fixed) */ + struct v4l2_subdev *sd_video; /* controlling video decoder subdev */ + bool sd_video_is_streaming; /* is video already streaming? */ + struct v4l2_subdev *sd_audio; /* controlling audio subdev */ + struct v4l2_subdev *sd_muxer; /* controlling audio muxer subdev */ + resource_size_t base_addr; /* PCI resource base address */ + volatile void __iomem *enc_mem; /* pointer to mapped encoder memory */ + volatile void __iomem *dec_mem; /* pointer to mapped decoder memory */ + volatile void __iomem *reg_mem; /* pointer to mapped registers */ + struct ivtv_options options; /* user options */ + + struct v4l2_device v4l2_dev; + struct cx2341x_handler cxhdl; + struct { + /* PTS/Frame count control cluster */ + struct v4l2_ctrl *ctrl_pts; + struct v4l2_ctrl *ctrl_frame; + }; + struct { + /* Audio Playback control cluster */ + struct v4l2_ctrl *ctrl_audio_playback; + struct v4l2_ctrl *ctrl_audio_multilingual_playback; + }; + struct v4l2_ctrl_handler hdl_gpio; + struct v4l2_subdev sd_gpio; /* GPIO sub-device */ + u16 instance; + + /* High-level state info */ + unsigned long i_flags; /* global ivtv flags */ + u8 is_50hz; /* 1 if the current capture standard is 50 Hz */ + u8 is_60hz /* 1 if the current capture standard is 60 Hz */; + u8 is_out_50hz /* 1 if the current TV output standard is 50 Hz */; + u8 is_out_60hz /* 1 if the current TV output standard is 60 Hz */; + int output_mode; /* decoder output mode: NONE, MPG, YUV, UDMA YUV, passthrough */ + u32 audio_input; /* current audio input */ + u32 active_input; /* current video input */ + u32 active_output; /* current video output */ + v4l2_std_id std; /* current capture TV standard */ + v4l2_std_id std_out; /* current TV output standard */ + u8 audio_stereo_mode; /* decoder setting how to handle stereo MPEG audio */ + u8 audio_bilingual_mode; /* decoder setting how to handle bilingual MPEG audio */ + + /* Locking */ + spinlock_t lock; /* lock access to this struct */ + struct mutex serialize_lock; /* mutex used to serialize open/close/start/stop/ioctl operations */ + + /* Streams */ + int stream_buf_size[IVTV_MAX_STREAMS]; /* stream buffer size */ + struct ivtv_stream streams[IVTV_MAX_STREAMS]; /* stream data */ + atomic_t capturing; /* count number of active capture streams */ + atomic_t decoding; /* count number of active decoding streams */ + + /* ALSA interface for PCM capture stream */ + struct snd_ivtv_card *alsa; + void (*pcm_announce_callback)(struct snd_ivtv_card *card, u8 *pcm_data, + size_t num_bytes); + + /* Used for ivtv-alsa module loading */ + struct work_struct request_module_wk; + + /* Interrupts & DMA */ + u32 irqmask; /* active interrupts */ + u32 irq_rr_idx; /* round-robin stream index */ + struct kthread_worker irq_worker; /* kthread worker for PIO/YUV/VBI actions */ + struct task_struct *irq_worker_task; /* task for irq_worker */ + struct kthread_work irq_work; /* kthread work entry */ + spinlock_t dma_reg_lock; /* lock access to DMA engine registers */ + int cur_dma_stream; /* index of current stream doing DMA (-1 if none) */ + int cur_pio_stream; /* index of current stream doing PIO (-1 if none) */ + u32 dma_data_req_offset; /* store offset in decoder memory of current DMA request */ + u32 dma_data_req_size; /* store size of current DMA request */ + int dma_retries; /* current DMA retry attempt */ + struct ivtv_user_dma udma; /* user based DMA for OSD */ + struct timer_list dma_timer; /* timer used to catch unfinished DMAs */ + u32 last_vsync_field; /* last seen vsync field */ + wait_queue_head_t dma_waitq; /* wake up when the current DMA is finished */ + wait_queue_head_t eos_waitq; /* wake up when EOS arrives */ + wait_queue_head_t event_waitq; /* wake up when the next decoder event arrives */ + wait_queue_head_t vsync_waitq; /* wake up when the next decoder vsync arrives */ + + + /* Mailbox */ + struct ivtv_mailbox_data enc_mbox; /* encoder mailboxes */ + struct ivtv_mailbox_data dec_mbox; /* decoder mailboxes */ + struct ivtv_api_cache api_cache[256]; /* cached API commands */ + + + /* I2C */ + struct i2c_adapter i2c_adap; + struct i2c_algo_bit_data i2c_algo; + struct i2c_client i2c_client; + int i2c_state; /* i2c bit state */ + struct mutex i2c_bus_lock; /* lock i2c bus */ + + struct IR_i2c_init_data ir_i2c_init_data; + + /* Program Index information */ + u32 pgm_info_offset; /* start of pgm info in encoder memory */ + u32 pgm_info_num; /* number of elements in the pgm cyclic buffer in encoder memory */ + u32 pgm_info_write_idx; /* last index written by the card that was transferred to pgm_info[] */ + u32 pgm_info_read_idx; /* last index in pgm_info read by the application */ + struct v4l2_enc_idx_entry pgm_info[IVTV_MAX_PGM_INDEX]; /* filled from the pgm cyclic buffer on the card */ + + + /* Miscellaneous */ + u32 open_id; /* incremented each time an open occurs, is >= 1 */ + int search_pack_header; /* 1 if ivtv_copy_buf_to_user() is scanning for a pack header (0xba) */ + int speed; /* current playback speed setting */ + u8 speed_mute_audio; /* 1 if audio should be muted when fast forward */ + u64 mpg_data_received; /* number of bytes received from the MPEG stream */ + u64 vbi_data_inserted; /* number of VBI bytes inserted into the MPEG stream */ + u32 last_dec_timing[3]; /* cache last retrieved pts/scr/frame values */ + unsigned long dualwatch_jiffies;/* jiffies value of the previous dualwatch check */ + u32 dualwatch_stereo_mode; /* current detected dualwatch stereo mode */ + + + /* VBI state info */ + struct vbi_info vbi; /* VBI-specific data */ + + + /* YUV playback */ + struct yuv_playback_info yuv_info; /* YUV playback data */ + + + /* OSD support */ + unsigned long osd_video_pbase; + int osd_global_alpha_state; /* 1 = global alpha is on */ + int osd_local_alpha_state; /* 1 = local alpha is on */ + int osd_chroma_key_state; /* 1 = chroma-keying is on */ + u8 osd_global_alpha; /* current global alpha */ + u32 osd_chroma_key; /* current chroma key */ + struct v4l2_rect osd_rect; /* current OSD position and size */ + struct v4l2_rect main_rect; /* current Main window position and size */ + struct osd_info *osd_info; /* ivtvfb private OSD info */ + void (*ivtvfb_restore)(struct ivtv *itv); /* Used for a warm start */ +}; + +static inline struct ivtv *to_ivtv(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct ivtv, v4l2_dev); +} + +/* ivtv extensions to be loaded */ +extern int (*ivtv_ext_init)(struct ivtv *); + +/* Globals */ +extern int ivtv_first_minor; + +/*==============Prototypes==================*/ + +/* Hardware/IRQ */ +void ivtv_set_irq_mask(struct ivtv *itv, u32 mask); +void ivtv_clear_irq_mask(struct ivtv *itv, u32 mask); + +/* try to set output mode, return current mode. */ +int ivtv_set_output_mode(struct ivtv *itv, int mode); + +/* return current output stream based on current mode */ +struct ivtv_stream *ivtv_get_output_stream(struct ivtv *itv); + +/* Return non-zero if a signal is pending */ +int ivtv_msleep_timeout(unsigned int msecs, int intr); + +/* Wait on queue, returns -EINTR if interrupted */ +int ivtv_waitq(wait_queue_head_t *waitq); + +/* Read Hauppauge eeprom */ +struct tveeprom; /* forward reference */ +void ivtv_read_eeprom(struct ivtv *itv, struct tveeprom *tv); + +/* First-open initialization: load firmware, init cx25840, etc. */ +int ivtv_init_on_first_open(struct ivtv *itv); + +/* Test if the current VBI mode is raw (1) or sliced (0) */ +static inline int ivtv_raw_vbi(const struct ivtv *itv) +{ + return itv->vbi.in.type == V4L2_BUF_TYPE_VBI_CAPTURE; +} + +/* This is a PCI post thing, where if the pci register is not read, then + the write doesn't always take effect right away. By reading back the + register any pending PCI writes will be performed (in order), and so + you can be sure that the writes are guaranteed to be done. + + Rarely needed, only in some timing sensitive cases. + Apparently if this is not done some motherboards seem + to kill the firmware and get into the broken state until computer is + rebooted. */ +#define write_sync(val, reg) \ + do { writel(val, reg); readl(reg); } while (0) + +#define read_reg(reg) readl(itv->reg_mem + (reg)) +#define write_reg(val, reg) writel(val, itv->reg_mem + (reg)) +#define write_reg_sync(val, reg) \ + do { write_reg(val, reg); read_reg(reg); } while (0) + +#define read_enc(addr) readl(itv->enc_mem + (u32)(addr)) +#define write_enc(val, addr) writel(val, itv->enc_mem + (u32)(addr)) +#define write_enc_sync(val, addr) \ + do { write_enc(val, addr); read_enc(addr); } while (0) + +#define read_dec(addr) readl(itv->dec_mem + (u32)(addr)) +#define write_dec(val, addr) writel(val, itv->dec_mem + (u32)(addr)) +#define write_dec_sync(val, addr) \ + do { write_dec(val, addr); read_dec(addr); } while (0) + +/* Call the specified callback for all subdevs matching hw (if 0, then + match them all). Ignore any errors. */ +#define ivtv_call_hw(itv, hw, o, f, args...) \ + v4l2_device_mask_call_all(&(itv)->v4l2_dev, hw, o, f, ##args) + +#define ivtv_call_all(itv, o, f, args...) ivtv_call_hw(itv, 0, o, f , ##args) + +/* Call the specified callback for all subdevs matching hw (if 0, then + match them all). If the callback returns an error other than 0 or + -ENOIOCTLCMD, then return with that error code. */ +#define ivtv_call_hw_err(itv, hw, o, f, args...) \ + v4l2_device_mask_call_until_err(&(itv)->v4l2_dev, hw, o, f, ##args) + +#define ivtv_call_all_err(itv, o, f, args...) ivtv_call_hw_err(itv, 0, o, f , ##args) + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-fileops.c b/drivers/media/pci/ivtv/ivtv-fileops.c new file mode 100644 index 000000000..e7fed9698 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-fileops.c @@ -0,0 +1,1071 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + file operation functions + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#include "ivtv-driver.h" +#include "ivtv-fileops.h" +#include "ivtv-i2c.h" +#include "ivtv-queue.h" +#include "ivtv-udma.h" +#include "ivtv-irq.h" +#include "ivtv-vbi.h" +#include "ivtv-mailbox.h" +#include "ivtv-routing.h" +#include "ivtv-streams.h" +#include "ivtv-yuv.h" +#include "ivtv-ioctl.h" +#include "ivtv-cards.h" +#include "ivtv-firmware.h" +#include +#include +#include + +/* This function tries to claim the stream for a specific file descriptor. + If no one else is using this stream then the stream is claimed and + associated VBI streams are also automatically claimed. + Possible error returns: -EBUSY if someone else has claimed + the stream or 0 on success. */ +int ivtv_claim_stream(struct ivtv_open_id *id, int type) +{ + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[type]; + struct ivtv_stream *s_vbi; + int vbi_type; + + if (test_and_set_bit(IVTV_F_S_CLAIMED, &s->s_flags)) { + /* someone already claimed this stream */ + if (s->id == id) { + /* yes, this file descriptor did. So that's OK. */ + return 0; + } + if (s->id == NULL && (type == IVTV_DEC_STREAM_TYPE_VBI || + type == IVTV_ENC_STREAM_TYPE_VBI)) { + /* VBI is handled already internally, now also assign + the file descriptor to this stream for external + reading of the stream. */ + s->id = id; + IVTV_DEBUG_INFO("Start Read VBI\n"); + return 0; + } + /* someone else is using this stream already */ + IVTV_DEBUG_INFO("Stream %d is busy\n", type); + return -EBUSY; + } + s->id = id; + if (type == IVTV_DEC_STREAM_TYPE_VBI) { + /* Enable reinsertion interrupt */ + ivtv_clear_irq_mask(itv, IVTV_IRQ_DEC_VBI_RE_INSERT); + } + + /* IVTV_DEC_STREAM_TYPE_MPG needs to claim IVTV_DEC_STREAM_TYPE_VBI, + IVTV_ENC_STREAM_TYPE_MPG needs to claim IVTV_ENC_STREAM_TYPE_VBI + (provided VBI insertion is on and sliced VBI is selected), for all + other streams we're done */ + if (type == IVTV_DEC_STREAM_TYPE_MPG) { + vbi_type = IVTV_DEC_STREAM_TYPE_VBI; + } else if (type == IVTV_ENC_STREAM_TYPE_MPG && + itv->vbi.insert_mpeg && !ivtv_raw_vbi(itv)) { + vbi_type = IVTV_ENC_STREAM_TYPE_VBI; + } else { + return 0; + } + s_vbi = &itv->streams[vbi_type]; + + if (!test_and_set_bit(IVTV_F_S_CLAIMED, &s_vbi->s_flags)) { + /* Enable reinsertion interrupt */ + if (vbi_type == IVTV_DEC_STREAM_TYPE_VBI) + ivtv_clear_irq_mask(itv, IVTV_IRQ_DEC_VBI_RE_INSERT); + } + /* mark that it is used internally */ + set_bit(IVTV_F_S_INTERNAL_USE, &s_vbi->s_flags); + return 0; +} +EXPORT_SYMBOL(ivtv_claim_stream); + +/* This function releases a previously claimed stream. It will take into + account associated VBI streams. */ +void ivtv_release_stream(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + struct ivtv_stream *s_vbi; + + s->id = NULL; + if ((s->type == IVTV_DEC_STREAM_TYPE_VBI || s->type == IVTV_ENC_STREAM_TYPE_VBI) && + test_bit(IVTV_F_S_INTERNAL_USE, &s->s_flags)) { + /* this stream is still in use internally */ + return; + } + if (!test_and_clear_bit(IVTV_F_S_CLAIMED, &s->s_flags)) { + IVTV_DEBUG_WARN("Release stream %s not in use!\n", s->name); + return; + } + + ivtv_flush_queues(s); + + /* disable reinsertion interrupt */ + if (s->type == IVTV_DEC_STREAM_TYPE_VBI) + ivtv_set_irq_mask(itv, IVTV_IRQ_DEC_VBI_RE_INSERT); + + /* IVTV_DEC_STREAM_TYPE_MPG needs to release IVTV_DEC_STREAM_TYPE_VBI, + IVTV_ENC_STREAM_TYPE_MPG needs to release IVTV_ENC_STREAM_TYPE_VBI, + for all other streams we're done */ + if (s->type == IVTV_DEC_STREAM_TYPE_MPG) + s_vbi = &itv->streams[IVTV_DEC_STREAM_TYPE_VBI]; + else if (s->type == IVTV_ENC_STREAM_TYPE_MPG) + s_vbi = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + else + return; + + /* clear internal use flag */ + if (!test_and_clear_bit(IVTV_F_S_INTERNAL_USE, &s_vbi->s_flags)) { + /* was already cleared */ + return; + } + if (s_vbi->id) { + /* VBI stream still claimed by a file descriptor */ + return; + } + /* disable reinsertion interrupt */ + if (s_vbi->type == IVTV_DEC_STREAM_TYPE_VBI) + ivtv_set_irq_mask(itv, IVTV_IRQ_DEC_VBI_RE_INSERT); + clear_bit(IVTV_F_S_CLAIMED, &s_vbi->s_flags); + ivtv_flush_queues(s_vbi); +} +EXPORT_SYMBOL(ivtv_release_stream); + +static void ivtv_dualwatch(struct ivtv *itv) +{ + struct v4l2_tuner vt; + u32 new_stereo_mode; + const u32 dual = 0x02; + + new_stereo_mode = v4l2_ctrl_g_ctrl(itv->cxhdl.audio_mode); + memset(&vt, 0, sizeof(vt)); + ivtv_call_all(itv, tuner, g_tuner, &vt); + if (vt.audmode == V4L2_TUNER_MODE_LANG1_LANG2 && (vt.rxsubchans & V4L2_TUNER_SUB_LANG2)) + new_stereo_mode = dual; + + if (new_stereo_mode == itv->dualwatch_stereo_mode) + return; + + IVTV_DEBUG_INFO("dualwatch: change stereo flag from 0x%x to 0x%x.\n", + itv->dualwatch_stereo_mode, new_stereo_mode); + if (v4l2_ctrl_s_ctrl(itv->cxhdl.audio_mode, new_stereo_mode)) + IVTV_DEBUG_INFO("dualwatch: changing stereo flag failed\n"); +} + +static void ivtv_update_pgm_info(struct ivtv *itv) +{ + u32 wr_idx = (read_enc(itv->pgm_info_offset) - itv->pgm_info_offset - 4) / 24; + int cnt; + int i = 0; + + if (wr_idx >= itv->pgm_info_num) { + IVTV_DEBUG_WARN("Invalid PGM index %d (>= %d)\n", wr_idx, itv->pgm_info_num); + return; + } + cnt = (wr_idx + itv->pgm_info_num - itv->pgm_info_write_idx) % itv->pgm_info_num; + while (i < cnt) { + int idx = (itv->pgm_info_write_idx + i) % itv->pgm_info_num; + struct v4l2_enc_idx_entry *e = itv->pgm_info + idx; + u32 addr = itv->pgm_info_offset + 4 + idx * 24; + const int mapping[8] = { -1, V4L2_ENC_IDX_FRAME_I, V4L2_ENC_IDX_FRAME_P, -1, + V4L2_ENC_IDX_FRAME_B, -1, -1, -1 }; + // 1=I, 2=P, 4=B + + e->offset = read_enc(addr + 4) + ((u64)read_enc(addr + 8) << 32); + if (e->offset > itv->mpg_data_received) { + break; + } + e->offset += itv->vbi_data_inserted; + e->length = read_enc(addr); + e->pts = read_enc(addr + 16) + ((u64)(read_enc(addr + 20) & 1) << 32); + e->flags = mapping[read_enc(addr + 12) & 7]; + i++; + } + itv->pgm_info_write_idx = (itv->pgm_info_write_idx + i) % itv->pgm_info_num; +} + +static void ivtv_schedule(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + DEFINE_WAIT(wait); + + lockdep_assert_held(&itv->serialize_lock); + + mutex_unlock(&itv->serialize_lock); + prepare_to_wait(&s->waitq, &wait, TASK_INTERRUPTIBLE); + /* New buffers might have become free before we were added to the waitqueue */ + if (!s->q_free.buffers) + schedule(); + finish_wait(&s->waitq, &wait); + mutex_lock(&itv->serialize_lock); +} + +static struct ivtv_buffer *ivtv_get_buffer(struct ivtv_stream *s, int non_block, int *err) +{ + struct ivtv *itv = s->itv; + struct ivtv_stream *s_vbi = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + struct ivtv_buffer *buf; + + *err = 0; + while (1) { + if (s->type == IVTV_ENC_STREAM_TYPE_MPG) { + /* Process pending program info updates and pending VBI data */ + ivtv_update_pgm_info(itv); + + if (time_after(jiffies, + itv->dualwatch_jiffies + + msecs_to_jiffies(1000))) { + itv->dualwatch_jiffies = jiffies; + ivtv_dualwatch(itv); + } + + if (test_bit(IVTV_F_S_INTERNAL_USE, &s_vbi->s_flags) && + !test_bit(IVTV_F_S_APPL_IO, &s_vbi->s_flags)) { + while ((buf = ivtv_dequeue(s_vbi, &s_vbi->q_full))) { + /* byteswap and process VBI data */ + ivtv_process_vbi_data(itv, buf, s_vbi->dma_pts, s_vbi->type); + ivtv_enqueue(s_vbi, buf, &s_vbi->q_free); + } + } + buf = &itv->vbi.sliced_mpeg_buf; + if (buf->readpos != buf->bytesused) { + return buf; + } + } + + /* do we have leftover data? */ + buf = ivtv_dequeue(s, &s->q_io); + if (buf) + return buf; + + /* do we have new data? */ + buf = ivtv_dequeue(s, &s->q_full); + if (buf) { + if ((buf->b_flags & IVTV_F_B_NEED_BUF_SWAP) == 0) + return buf; + buf->b_flags &= ~IVTV_F_B_NEED_BUF_SWAP; + if (s->type == IVTV_ENC_STREAM_TYPE_MPG) + /* byteswap MPG data */ + ivtv_buf_swap(buf); + else if (s->type != IVTV_DEC_STREAM_TYPE_VBI) { + /* byteswap and process VBI data */ + ivtv_process_vbi_data(itv, buf, s->dma_pts, s->type); + } + return buf; + } + + /* return if end of stream */ + if (s->type != IVTV_DEC_STREAM_TYPE_VBI && !test_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + IVTV_DEBUG_INFO("EOS %s\n", s->name); + return NULL; + } + + /* return if file was opened with O_NONBLOCK */ + if (non_block) { + *err = -EAGAIN; + return NULL; + } + + /* wait for more data to arrive */ + ivtv_schedule(s); + if (signal_pending(current)) { + /* return if a signal was received */ + IVTV_DEBUG_INFO("User stopped %s\n", s->name); + *err = -EINTR; + return NULL; + } + } +} + +static void ivtv_setup_sliced_vbi_buf(struct ivtv *itv) +{ + int idx = itv->vbi.inserted_frame % IVTV_VBI_FRAMES; + + itv->vbi.sliced_mpeg_buf.buf = itv->vbi.sliced_mpeg_data[idx]; + itv->vbi.sliced_mpeg_buf.bytesused = itv->vbi.sliced_mpeg_size[idx]; + itv->vbi.sliced_mpeg_buf.readpos = 0; +} + +static size_t ivtv_copy_buf_to_user(struct ivtv_stream *s, struct ivtv_buffer *buf, + char __user *ubuf, size_t ucount) +{ + struct ivtv *itv = s->itv; + size_t len = buf->bytesused - buf->readpos; + + if (len > ucount) len = ucount; + if (itv->vbi.insert_mpeg && s->type == IVTV_ENC_STREAM_TYPE_MPG && + !ivtv_raw_vbi(itv) && buf != &itv->vbi.sliced_mpeg_buf) { + const char *start = buf->buf + buf->readpos; + const char *p = start + 1; + const u8 *q; + u8 ch = itv->search_pack_header ? 0xba : 0xe0; + int stuffing, i; + + while (start + len > p && (q = memchr(p, 0, start + len - p))) { + p = q + 1; + if ((char *)q + 15 >= buf->buf + buf->bytesused || + q[1] != 0 || q[2] != 1 || q[3] != ch) { + continue; + } + if (!itv->search_pack_header) { + if ((q[6] & 0xc0) != 0x80) + continue; + if (((q[7] & 0xc0) == 0x80 && (q[9] & 0xf0) == 0x20) || + ((q[7] & 0xc0) == 0xc0 && (q[9] & 0xf0) == 0x30)) { + ch = 0xba; + itv->search_pack_header = 1; + p = q + 9; + } + continue; + } + stuffing = q[13] & 7; + /* all stuffing bytes must be 0xff */ + for (i = 0; i < stuffing; i++) + if (q[14 + i] != 0xff) + break; + if (i == stuffing && (q[4] & 0xc4) == 0x44 && (q[12] & 3) == 3 && + q[14 + stuffing] == 0 && q[15 + stuffing] == 0 && + q[16 + stuffing] == 1) { + itv->search_pack_header = 0; + len = (char *)q - start; + ivtv_setup_sliced_vbi_buf(itv); + break; + } + } + } + if (copy_to_user(ubuf, (u8 *)buf->buf + buf->readpos, len)) { + IVTV_DEBUG_WARN("copy %zd bytes to user failed for %s\n", len, s->name); + return -EFAULT; + } + /*IVTV_INFO("copied %lld %d %d %d %d %d vbi %d\n", itv->mpg_data_received, len, ucount, + buf->readpos, buf->bytesused, buf->bytesused - buf->readpos - len, + buf == &itv->vbi.sliced_mpeg_buf); */ + buf->readpos += len; + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && buf != &itv->vbi.sliced_mpeg_buf) + itv->mpg_data_received += len; + return len; +} + +static ssize_t ivtv_read(struct ivtv_stream *s, char __user *ubuf, size_t tot_count, int non_block) +{ + struct ivtv *itv = s->itv; + size_t tot_written = 0; + int single_frame = 0; + + if (atomic_read(&itv->capturing) == 0 && s->id == NULL) { + /* shouldn't happen */ + IVTV_DEBUG_WARN("Stream %s not initialized before read\n", s->name); + return -EIO; + } + + /* Each VBI buffer is one frame, the v4l2 API says that for VBI the frames should + arrive one-by-one, so make sure we never output more than one VBI frame at a time */ + if (s->type == IVTV_DEC_STREAM_TYPE_VBI || + (s->type == IVTV_ENC_STREAM_TYPE_VBI && !ivtv_raw_vbi(itv))) + single_frame = 1; + + for (;;) { + struct ivtv_buffer *buf; + int rc; + + buf = ivtv_get_buffer(s, non_block, &rc); + /* if there is no data available... */ + if (buf == NULL) { + /* if we got data, then return that regardless */ + if (tot_written) + break; + /* EOS condition */ + if (rc == 0) { + clear_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + clear_bit(IVTV_F_S_APPL_IO, &s->s_flags); + ivtv_release_stream(s); + } + /* set errno */ + return rc; + } + rc = ivtv_copy_buf_to_user(s, buf, ubuf + tot_written, tot_count - tot_written); + if (buf != &itv->vbi.sliced_mpeg_buf) { + ivtv_enqueue(s, buf, (buf->readpos == buf->bytesused) ? &s->q_free : &s->q_io); + } + else if (buf->readpos == buf->bytesused) { + int idx = itv->vbi.inserted_frame % IVTV_VBI_FRAMES; + itv->vbi.sliced_mpeg_size[idx] = 0; + itv->vbi.inserted_frame++; + itv->vbi_data_inserted += buf->bytesused; + } + if (rc < 0) + return rc; + tot_written += rc; + + if (tot_written == tot_count || single_frame) + break; + } + return tot_written; +} + +static ssize_t ivtv_read_pos(struct ivtv_stream *s, char __user *ubuf, size_t count, + loff_t *pos, int non_block) +{ + ssize_t rc = count ? ivtv_read(s, ubuf, count, non_block) : 0; + struct ivtv *itv = s->itv; + + IVTV_DEBUG_HI_FILE("read %zd from %s, got %zd\n", count, s->name, rc); + if (rc > 0) + *pos += rc; + return rc; +} + +int ivtv_start_capture(struct ivtv_open_id *id) +{ + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + struct ivtv_stream *s_vbi; + + if (s->type == IVTV_ENC_STREAM_TYPE_RAD || + s->type == IVTV_DEC_STREAM_TYPE_MPG || + s->type == IVTV_DEC_STREAM_TYPE_YUV || + s->type == IVTV_DEC_STREAM_TYPE_VOUT) { + /* you cannot read from these stream types. */ + return -EINVAL; + } + + /* Try to claim this stream. */ + if (ivtv_claim_stream(id, s->type)) + return -EBUSY; + + /* This stream does not need to start capturing */ + if (s->type == IVTV_DEC_STREAM_TYPE_VBI) { + set_bit(IVTV_F_S_APPL_IO, &s->s_flags); + return 0; + } + + /* If capture is already in progress, then we also have to + do nothing extra. */ + if (test_bit(IVTV_F_S_STREAMOFF, &s->s_flags) || test_and_set_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + set_bit(IVTV_F_S_APPL_IO, &s->s_flags); + return 0; + } + + /* Start VBI capture if required */ + s_vbi = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && + test_bit(IVTV_F_S_INTERNAL_USE, &s_vbi->s_flags) && + !test_and_set_bit(IVTV_F_S_STREAMING, &s_vbi->s_flags)) { + /* Note: the IVTV_ENC_STREAM_TYPE_VBI is claimed + automatically when the MPG stream is claimed. + We only need to start the VBI capturing. */ + if (ivtv_start_v4l2_encode_stream(s_vbi)) { + IVTV_DEBUG_WARN("VBI capture start failed\n"); + + /* Failure, clean up and return an error */ + clear_bit(IVTV_F_S_STREAMING, &s_vbi->s_flags); + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + /* also releases the associated VBI stream */ + ivtv_release_stream(s); + return -EIO; + } + IVTV_DEBUG_INFO("VBI insertion started\n"); + } + + /* Tell the card to start capturing */ + if (!ivtv_start_v4l2_encode_stream(s)) { + /* We're done */ + set_bit(IVTV_F_S_APPL_IO, &s->s_flags); + /* Resume a possibly paused encoder */ + if (test_and_clear_bit(IVTV_F_I_ENC_PAUSED, &itv->i_flags)) + ivtv_vapi(itv, CX2341X_ENC_PAUSE_ENCODER, 1, 1); + return 0; + } + + /* failure, clean up */ + IVTV_DEBUG_WARN("Failed to start capturing for stream %s\n", s->name); + + /* Note: the IVTV_ENC_STREAM_TYPE_VBI is released + automatically when the MPG stream is released. + We only need to stop the VBI capturing. */ + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && + test_bit(IVTV_F_S_STREAMING, &s_vbi->s_flags)) { + ivtv_stop_v4l2_encode_stream(s_vbi, 0); + clear_bit(IVTV_F_S_STREAMING, &s_vbi->s_flags); + } + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + ivtv_release_stream(s); + return -EIO; +} + +ssize_t ivtv_v4l2_read(struct file * filp, char __user *buf, size_t count, loff_t * pos) +{ + struct ivtv_open_id *id = file2id(filp); + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + ssize_t rc; + + IVTV_DEBUG_HI_FILE("read %zd bytes from %s\n", count, s->name); + + if (mutex_lock_interruptible(&itv->serialize_lock)) + return -ERESTARTSYS; + rc = ivtv_start_capture(id); + if (!rc) + rc = ivtv_read_pos(s, buf, count, pos, filp->f_flags & O_NONBLOCK); + mutex_unlock(&itv->serialize_lock); + return rc; +} + +int ivtv_start_decoding(struct ivtv_open_id *id, int speed) +{ + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + int rc; + + if (atomic_read(&itv->decoding) == 0) { + if (ivtv_claim_stream(id, s->type)) { + /* someone else is using this stream already */ + IVTV_DEBUG_WARN("start decode, stream already claimed\n"); + return -EBUSY; + } + rc = ivtv_start_v4l2_decode_stream(s, 0); + if (rc < 0) { + if (rc == -EAGAIN) + rc = ivtv_start_v4l2_decode_stream(s, 0); + if (rc < 0) + return rc; + } + } + if (s->type == IVTV_DEC_STREAM_TYPE_MPG) + return ivtv_set_speed(itv, speed); + return 0; +} + +static int ivtv_schedule_dma(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + int got_sig; + DEFINE_WAIT(wait); + + lockdep_assert_held(&itv->serialize_lock); + + mutex_unlock(&itv->serialize_lock); + prepare_to_wait(&itv->dma_waitq, &wait, TASK_INTERRUPTIBLE); + while (!(got_sig = signal_pending(current)) && + test_bit(IVTV_F_S_DMA_PENDING, &s->s_flags)) + schedule(); + finish_wait(&itv->dma_waitq, &wait); + mutex_lock(&itv->serialize_lock); + + return got_sig; +} + +static ssize_t ivtv_write(struct file *filp, const char __user *user_buf, size_t count, loff_t *pos) +{ + struct ivtv_open_id *id = file2id(filp); + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + struct yuv_playback_info *yi = &itv->yuv_info; + struct ivtv_buffer *buf; + struct ivtv_queue q; + int bytes_written = 0; + int mode; + int rc; + + IVTV_DEBUG_HI_FILE("write %zd bytes to %s\n", count, s->name); + + if (s->type != IVTV_DEC_STREAM_TYPE_MPG && + s->type != IVTV_DEC_STREAM_TYPE_YUV && + s->type != IVTV_DEC_STREAM_TYPE_VOUT) + /* not decoder streams */ + return -EINVAL; + + /* Try to claim this stream */ + if (ivtv_claim_stream(id, s->type)) + return -EBUSY; + + /* This stream does not need to start any decoding */ + if (s->type == IVTV_DEC_STREAM_TYPE_VOUT) { + int elems = count / sizeof(struct v4l2_sliced_vbi_data); + + set_bit(IVTV_F_S_APPL_IO, &s->s_flags); + return ivtv_write_vbi_from_user(itv, + (const struct v4l2_sliced_vbi_data __user *)user_buf, elems); + } + + mode = s->type == IVTV_DEC_STREAM_TYPE_MPG ? OUT_MPG : OUT_YUV; + + if (ivtv_set_output_mode(itv, mode) != mode) { + ivtv_release_stream(s); + return -EBUSY; + } + ivtv_queue_init(&q); + set_bit(IVTV_F_S_APPL_IO, &s->s_flags); + + /* Start decoder (returns 0 if already started) */ + rc = ivtv_start_decoding(id, itv->speed); + if (rc) { + IVTV_DEBUG_WARN("Failed start decode stream %s\n", s->name); + + /* failure, clean up */ + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + clear_bit(IVTV_F_S_APPL_IO, &s->s_flags); + return rc; + } + +retry: + /* If possible, just DMA the entire frame - Check the data transfer size + since we may get here before the stream has been fully set-up */ + if (mode == OUT_YUV && s->q_full.length == 0 && itv->dma_data_req_size) { + while (count >= itv->dma_data_req_size) { + rc = ivtv_yuv_udma_stream_frame(itv, (void __user *)user_buf); + + if (rc < 0) + return rc; + + bytes_written += itv->dma_data_req_size; + user_buf += itv->dma_data_req_size; + count -= itv->dma_data_req_size; + } + if (count == 0) { + IVTV_DEBUG_HI_FILE("Wrote %d bytes to %s (%d)\n", bytes_written, s->name, s->q_full.bytesused); + return bytes_written; + } + } + + for (;;) { + /* Gather buffers */ + while (q.length - q.bytesused < count && (buf = ivtv_dequeue(s, &s->q_io))) + ivtv_enqueue(s, buf, &q); + while (q.length - q.bytesused < count && (buf = ivtv_dequeue(s, &s->q_free))) { + ivtv_enqueue(s, buf, &q); + } + if (q.buffers) + break; + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + ivtv_schedule(s); + if (signal_pending(current)) { + IVTV_DEBUG_INFO("User stopped %s\n", s->name); + return -EINTR; + } + } + + /* copy user data into buffers */ + while ((buf = ivtv_dequeue(s, &q))) { + /* yuv is a pain. Don't copy more data than needed for a single + frame, otherwise we lose sync with the incoming stream */ + if (s->type == IVTV_DEC_STREAM_TYPE_YUV && + yi->stream_size + count > itv->dma_data_req_size) + rc = ivtv_buf_copy_from_user(s, buf, user_buf, + itv->dma_data_req_size - yi->stream_size); + else + rc = ivtv_buf_copy_from_user(s, buf, user_buf, count); + + /* Make sure we really got all the user data */ + if (rc < 0) { + ivtv_queue_move(s, &q, NULL, &s->q_free, 0); + return rc; + } + user_buf += rc; + count -= rc; + bytes_written += rc; + + if (s->type == IVTV_DEC_STREAM_TYPE_YUV) { + yi->stream_size += rc; + /* If we have a complete yuv frame, break loop now */ + if (yi->stream_size == itv->dma_data_req_size) { + ivtv_enqueue(s, buf, &s->q_full); + yi->stream_size = 0; + break; + } + } + + if (buf->bytesused != s->buf_size) { + /* incomplete, leave in q_io for next time */ + ivtv_enqueue(s, buf, &s->q_io); + break; + } + /* Byteswap MPEG buffer */ + if (s->type == IVTV_DEC_STREAM_TYPE_MPG) + ivtv_buf_swap(buf); + ivtv_enqueue(s, buf, &s->q_full); + } + + if (test_bit(IVTV_F_S_NEEDS_DATA, &s->s_flags)) { + if (s->q_full.length >= itv->dma_data_req_size) { + if (mode == OUT_YUV) + ivtv_yuv_setup_stream_frame(itv); + + if (ivtv_schedule_dma(s)) { + IVTV_DEBUG_INFO("User interrupted %s\n", s->name); + return -EINTR; + } + + clear_bit(IVTV_F_S_NEEDS_DATA, &s->s_flags); + ivtv_queue_move(s, &s->q_full, NULL, &s->q_predma, itv->dma_data_req_size); + ivtv_dma_stream_dec_prepare(s, itv->dma_data_req_offset + IVTV_DECODER_OFFSET, 1); + } + } + /* more user data is available, wait until buffers become free + to transfer the rest. */ + if (count && !(filp->f_flags & O_NONBLOCK)) + goto retry; + IVTV_DEBUG_HI_FILE("Wrote %d bytes to %s (%d)\n", bytes_written, s->name, s->q_full.bytesused); + return bytes_written; +} + +ssize_t ivtv_v4l2_write(struct file *filp, const char __user *user_buf, size_t count, loff_t *pos) +{ + struct ivtv_open_id *id = file2id(filp); + struct ivtv *itv = id->itv; + ssize_t res; + + if (mutex_lock_interruptible(&itv->serialize_lock)) + return -ERESTARTSYS; + res = ivtv_write(filp, user_buf, count, pos); + mutex_unlock(&itv->serialize_lock); + return res; +} + +__poll_t ivtv_v4l2_dec_poll(struct file *filp, poll_table *wait) +{ + struct ivtv_open_id *id = file2id(filp); + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + __poll_t res = 0; + + /* add stream's waitq to the poll list */ + IVTV_DEBUG_HI_FILE("Decoder poll\n"); + + /* If there are subscribed events, then only use the new event + API instead of the old video.h based API. */ + if (!list_empty(&id->fh.subscribed)) { + poll_wait(filp, &id->fh.wait, wait); + /* Turn off the old-style vsync events */ + clear_bit(IVTV_F_I_EV_VSYNC_ENABLED, &itv->i_flags); + if (v4l2_event_pending(&id->fh)) + res = EPOLLPRI; + } else { + /* This is the old-style API which is here only for backwards + compatibility. */ + poll_wait(filp, &s->waitq, wait); + set_bit(IVTV_F_I_EV_VSYNC_ENABLED, &itv->i_flags); + if (test_bit(IVTV_F_I_EV_VSYNC, &itv->i_flags) || + test_bit(IVTV_F_I_EV_DEC_STOPPED, &itv->i_flags)) + res = EPOLLPRI; + } + + /* Allow write if buffers are available for writing */ + if (s->q_free.buffers) + res |= EPOLLOUT | EPOLLWRNORM; + return res; +} + +__poll_t ivtv_v4l2_enc_poll(struct file *filp, poll_table *wait) +{ + __poll_t req_events = poll_requested_events(wait); + struct ivtv_open_id *id = file2id(filp); + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + int eof = test_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + __poll_t res = 0; + + /* Start a capture if there is none */ + if (!eof && !test_bit(IVTV_F_S_STREAMING, &s->s_flags) && + s->type != IVTV_ENC_STREAM_TYPE_RAD && + (req_events & (EPOLLIN | EPOLLRDNORM))) { + int rc; + + mutex_lock(&itv->serialize_lock); + rc = ivtv_start_capture(id); + mutex_unlock(&itv->serialize_lock); + if (rc) { + IVTV_DEBUG_INFO("Could not start capture for %s (%d)\n", + s->name, rc); + return EPOLLERR; + } + IVTV_DEBUG_FILE("Encoder poll started capture\n"); + } + + /* add stream's waitq to the poll list */ + IVTV_DEBUG_HI_FILE("Encoder poll\n"); + poll_wait(filp, &s->waitq, wait); + if (v4l2_event_pending(&id->fh)) + res |= EPOLLPRI; + else + poll_wait(filp, &id->fh.wait, wait); + + if (s->q_full.length || s->q_io.length) + return res | EPOLLIN | EPOLLRDNORM; + if (eof) + return res | EPOLLHUP; + return res; +} + +void ivtv_stop_capture(struct ivtv_open_id *id, int gop_end) +{ + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + + IVTV_DEBUG_FILE("close() of %s\n", s->name); + + /* 'Unclaim' this stream */ + + /* Stop capturing */ + if (test_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + struct ivtv_stream *s_vbi = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + + IVTV_DEBUG_INFO("close stopping capture\n"); + /* Special case: a running VBI capture for VBI insertion + in the mpeg stream. Need to stop that too. */ + if (id->type == IVTV_ENC_STREAM_TYPE_MPG && + test_bit(IVTV_F_S_STREAMING, &s_vbi->s_flags) && + !test_bit(IVTV_F_S_APPL_IO, &s_vbi->s_flags)) { + IVTV_DEBUG_INFO("close stopping embedded VBI capture\n"); + ivtv_stop_v4l2_encode_stream(s_vbi, 0); + } + if ((id->type == IVTV_DEC_STREAM_TYPE_VBI || + id->type == IVTV_ENC_STREAM_TYPE_VBI) && + test_bit(IVTV_F_S_INTERNAL_USE, &s->s_flags)) { + /* Also used internally, don't stop capturing */ + s->id = NULL; + } + else { + ivtv_stop_v4l2_encode_stream(s, gop_end); + } + } + if (!gop_end) { + clear_bit(IVTV_F_S_APPL_IO, &s->s_flags); + clear_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + ivtv_release_stream(s); + } +} + +static void ivtv_stop_decoding(struct ivtv_open_id *id, int flags, u64 pts) +{ + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + + IVTV_DEBUG_FILE("close() of %s\n", s->name); + + if (id->type == IVTV_DEC_STREAM_TYPE_YUV && + test_bit(IVTV_F_I_DECODING_YUV, &itv->i_flags)) { + /* Restore registers we've changed & clean up any mess */ + ivtv_yuv_close(itv); + } + + /* Stop decoding */ + if (test_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + IVTV_DEBUG_INFO("close stopping decode\n"); + + ivtv_stop_v4l2_decode_stream(s, flags, pts); + itv->output_mode = OUT_NONE; + } + clear_bit(IVTV_F_S_APPL_IO, &s->s_flags); + clear_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + + if (itv->output_mode == OUT_UDMA_YUV && id->yuv_frames) + itv->output_mode = OUT_NONE; + + itv->speed = 0; + clear_bit(IVTV_F_I_DEC_PAUSED, &itv->i_flags); + ivtv_release_stream(s); +} + +int ivtv_v4l2_close(struct file *filp) +{ + struct v4l2_fh *fh = file_to_v4l2_fh(filp); + struct ivtv_open_id *id = file2id(filp); + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + + IVTV_DEBUG_FILE("close %s\n", s->name); + + mutex_lock(&itv->serialize_lock); + + /* Stop radio */ + if (id->type == IVTV_ENC_STREAM_TYPE_RAD && + v4l2_fh_is_singular_file(filp)) { + /* Closing radio device, return to TV mode */ + ivtv_mute(itv); + /* Mark that the radio is no longer in use */ + clear_bit(IVTV_F_I_RADIO_USER, &itv->i_flags); + /* Switch tuner to TV */ + ivtv_call_all(itv, video, s_std, itv->std); + /* Select correct audio input (i.e. TV tuner or Line in) */ + ivtv_audio_set_io(itv); + if (itv->hw_flags & IVTV_HW_SAA711X) { + ivtv_call_hw(itv, IVTV_HW_SAA711X, video, s_crystal_freq, + SAA7115_FREQ_32_11_MHZ, 0); + } + if (atomic_read(&itv->capturing) > 0) { + /* Undo video mute */ + ivtv_vapi(itv, CX2341X_ENC_MUTE_VIDEO, 1, + v4l2_ctrl_g_ctrl(itv->cxhdl.video_mute) | + (v4l2_ctrl_g_ctrl(itv->cxhdl.video_mute_yuv) << 8)); + } + /* Done! Unmute and continue. */ + ivtv_unmute(itv); + } + + v4l2_fh_del(fh, filp); + v4l2_fh_exit(fh); + + /* Easy case first: this stream was never claimed by us */ + if (s->id != id) + goto close_done; + + /* 'Unclaim' this stream */ + + if (s->type >= IVTV_DEC_STREAM_TYPE_MPG) { + struct ivtv_stream *s_vout = &itv->streams[IVTV_DEC_STREAM_TYPE_VOUT]; + + ivtv_stop_decoding(id, V4L2_DEC_CMD_STOP_TO_BLACK | V4L2_DEC_CMD_STOP_IMMEDIATELY, 0); + + /* If all output streams are closed, and if the user doesn't have + IVTV_DEC_STREAM_TYPE_VOUT open, then disable CC on TV-out. */ + if (itv->output_mode == OUT_NONE && !test_bit(IVTV_F_S_APPL_IO, &s_vout->s_flags)) { + /* disable CC on TV-out */ + ivtv_disable_cc(itv); + } + } else { + ivtv_stop_capture(id, 0); + } +close_done: + kfree(id); + mutex_unlock(&itv->serialize_lock); + return 0; +} + +static int ivtv_open(struct file *filp) +{ + struct video_device *vdev = video_devdata(filp); + struct ivtv_stream *s = video_get_drvdata(vdev); + struct ivtv *itv = s->itv; + struct ivtv_open_id *item; + int res = 0; + + IVTV_DEBUG_FILE("open %s\n", s->name); + + if (ivtv_init_on_first_open(itv)) { + IVTV_ERR("Failed to initialize on device %s\n", + video_device_node_name(vdev)); + return -ENXIO; + } + +#ifdef CONFIG_VIDEO_ADV_DEBUG + /* Unless ivtv_fw_debug is set, error out if firmware dead. */ + if (ivtv_fw_debug) { + IVTV_WARN("Opening %s with dead firmware lockout disabled\n", + video_device_node_name(vdev)); + IVTV_WARN("Selected firmware errors will be ignored\n"); + } else { +#else + if (1) { +#endif + res = ivtv_firmware_check(itv, "ivtv_serialized_open"); + if (res == -EAGAIN) + res = ivtv_firmware_check(itv, "ivtv_serialized_open"); + if (res < 0) + return -EIO; + } + + if (s->type == IVTV_DEC_STREAM_TYPE_MPG && + test_bit(IVTV_F_S_CLAIMED, &itv->streams[IVTV_DEC_STREAM_TYPE_YUV].s_flags)) + return -EBUSY; + + if (s->type == IVTV_DEC_STREAM_TYPE_YUV && + test_bit(IVTV_F_S_CLAIMED, &itv->streams[IVTV_DEC_STREAM_TYPE_MPG].s_flags)) + return -EBUSY; + + if (s->type == IVTV_DEC_STREAM_TYPE_YUV) { + if (read_reg(0x82c) == 0) { + IVTV_ERR("Tried to open YUV output device but need to send data to mpeg decoder before it can be used\n"); + /* return -ENODEV; */ + } + ivtv_udma_alloc(itv); + } + + /* Allocate memory */ + item = kzalloc_obj(struct ivtv_open_id); + if (NULL == item) { + IVTV_DEBUG_WARN("nomem on v4l2 open\n"); + return -ENOMEM; + } + v4l2_fh_init(&item->fh, &s->vdev); + item->itv = itv; + item->type = s->type; + v4l2_fh_add(&item->fh, filp); + + if (item->type == IVTV_ENC_STREAM_TYPE_RAD && + v4l2_fh_is_singular_file(filp)) { + if (!test_bit(IVTV_F_I_RADIO_USER, &itv->i_flags)) { + if (atomic_read(&itv->capturing) > 0) { + /* switching to radio while capture is + in progress is not polite */ + v4l2_fh_del(&item->fh, filp); + v4l2_fh_exit(&item->fh); + kfree(item); + return -EBUSY; + } + } + /* Mark that the radio is being used. */ + set_bit(IVTV_F_I_RADIO_USER, &itv->i_flags); + /* We have the radio */ + ivtv_mute(itv); + /* Switch tuner to radio */ + ivtv_call_all(itv, tuner, s_radio); + /* Select the correct audio input (i.e. radio tuner) */ + ivtv_audio_set_io(itv); + if (itv->hw_flags & IVTV_HW_SAA711X) { + ivtv_call_hw(itv, IVTV_HW_SAA711X, video, s_crystal_freq, + SAA7115_FREQ_32_11_MHZ, SAA7115_FREQ_FL_APLL); + } + /* Done! Unmute and continue. */ + ivtv_unmute(itv); + } + + /* YUV or MPG Decoding Mode? */ + if (s->type == IVTV_DEC_STREAM_TYPE_MPG) { + clear_bit(IVTV_F_I_DEC_YUV, &itv->i_flags); + } else if (s->type == IVTV_DEC_STREAM_TYPE_YUV) { + set_bit(IVTV_F_I_DEC_YUV, &itv->i_flags); + /* For yuv, we need to know the dma size before we start */ + itv->dma_data_req_size = + 1080 * ((itv->yuv_info.v4l2_src_h + 31) & ~31); + itv->yuv_info.stream_size = 0; + } + return 0; +} + +int ivtv_v4l2_open(struct file *filp) +{ + struct video_device *vdev = video_devdata(filp); + int res; + + if (mutex_lock_interruptible(vdev->lock)) + return -ERESTARTSYS; + res = ivtv_open(filp); + mutex_unlock(vdev->lock); + return res; +} + +void ivtv_mute(struct ivtv *itv) +{ + if (atomic_read(&itv->capturing)) + ivtv_vapi(itv, CX2341X_ENC_MUTE_AUDIO, 1, 1); + IVTV_DEBUG_INFO("Mute\n"); +} + +void ivtv_unmute(struct ivtv *itv) +{ + if (atomic_read(&itv->capturing)) { + ivtv_msleep_timeout(100, 0); + ivtv_vapi(itv, CX2341X_ENC_MISC, 1, 12); + ivtv_vapi(itv, CX2341X_ENC_MUTE_AUDIO, 1, 0); + } + IVTV_DEBUG_INFO("Unmute\n"); +} diff --git a/drivers/media/pci/ivtv/ivtv-fileops.h b/drivers/media/pci/ivtv/ivtv-fileops.h new file mode 100644 index 000000000..7bbe42b00 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-fileops.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + file operation functions + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#ifndef IVTV_FILEOPS_H +#define IVTV_FILEOPS_H + +/* Testing/Debugging */ +int ivtv_v4l2_open(struct file *filp); +ssize_t ivtv_v4l2_read(struct file *filp, char __user *buf, size_t count, + loff_t * pos); +ssize_t ivtv_v4l2_write(struct file *filp, const char __user *buf, size_t count, + loff_t * pos); +int ivtv_v4l2_close(struct file *filp); +__poll_t ivtv_v4l2_enc_poll(struct file *filp, poll_table * wait); +__poll_t ivtv_v4l2_dec_poll(struct file *filp, poll_table * wait); +int ivtv_start_capture(struct ivtv_open_id *id); +void ivtv_stop_capture(struct ivtv_open_id *id, int gop_end); +int ivtv_start_decoding(struct ivtv_open_id *id, int speed); +void ivtv_mute(struct ivtv *itv); +void ivtv_unmute(struct ivtv *itv); + +/* Utilities */ +/* Shared with ivtv-alsa module */ +int ivtv_claim_stream(struct ivtv_open_id *id, int type); +void ivtv_release_stream(struct ivtv_stream *s); + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-firmware.c b/drivers/media/pci/ivtv/ivtv-firmware.c new file mode 100644 index 000000000..1bc873ebe --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-firmware.c @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + ivtv firmware functions. + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#include "ivtv-driver.h" +#include "ivtv-mailbox.h" +#include "ivtv-firmware.h" +#include "ivtv-yuv.h" +#include "ivtv-ioctl.h" +#include "ivtv-cards.h" +#include +#include + +#define IVTV_MASK_SPU_ENABLE 0xFFFFFFFE +#define IVTV_MASK_VPU_ENABLE15 0xFFFFFFF6 +#define IVTV_MASK_VPU_ENABLE16 0xFFFFFFFB +#define IVTV_CMD_VDM_STOP 0x00000000 +#define IVTV_CMD_AO_STOP 0x00000005 +#define IVTV_CMD_APU_PING 0x00000000 +#define IVTV_CMD_VPU_STOP15 0xFFFFFFFE +#define IVTV_CMD_VPU_STOP16 0xFFFFFFEE +#define IVTV_CMD_HW_BLOCKS_RST 0xFFFFFFFF +#define IVTV_CMD_SPU_STOP 0x00000001 +#define IVTV_CMD_SDRAM_PRECHARGE_INIT 0x0000001A +#define IVTV_CMD_SDRAM_REFRESH_INIT 0x80000640 +#define IVTV_SDRAM_SLEEPTIME 600 + +#define IVTV_DECODE_INIT_MPEG_FILENAME "v4l-cx2341x-init.mpg" +#define IVTV_DECODE_INIT_MPEG_SIZE (152*1024) + +/* Encoder/decoder firmware sizes */ +#define IVTV_FW_ENC_SIZE (376836) +#define IVTV_FW_DEC_SIZE (256*1024) + +static int load_fw_direct(const char *fn, volatile u8 __iomem *mem, struct ivtv *itv, long size) +{ + const struct firmware *fw = NULL; + int retries = 3; + +retry: + if (retries && request_firmware(&fw, fn, &itv->pdev->dev) == 0) { + int i; + volatile u32 __iomem *dst = (volatile u32 __iomem *)mem; + const u32 *src = (const u32 *)fw->data; + + if (fw->size != size) { + /* Due to race conditions in firmware loading (esp. with udev <0.95) + the wrong file was sometimes loaded. So we check filesizes to + see if at least the right-sized file was loaded. If not, then we + retry. */ + IVTV_INFO("Retry: file loaded was not %s (expected size %ld, got %zu)\n", fn, size, fw->size); + release_firmware(fw); + retries--; + goto retry; + } + for (i = 0; i < fw->size; i += 4) { + /* no need for endianness conversion on the ppc */ + __raw_writel(*src, dst); + dst++; + src++; + } + IVTV_INFO("Loaded %s firmware (%zu bytes)\n", fn, fw->size); + release_firmware(fw); + return size; + } + IVTV_ERR("Unable to open firmware %s (must be %ld bytes)\n", fn, size); + IVTV_ERR("Did you put the firmware in the hotplug firmware directory?\n"); + return -ENOMEM; +} + +void ivtv_halt_firmware(struct ivtv *itv) +{ + IVTV_DEBUG_INFO("Preparing for firmware halt.\n"); + if (itv->has_cx23415 && itv->dec_mbox.mbox) + ivtv_vapi(itv, CX2341X_DEC_HALT_FW, 0); + if (itv->enc_mbox.mbox) + ivtv_vapi(itv, CX2341X_ENC_HALT_FW, 0); + + ivtv_msleep_timeout(10, 0); + itv->enc_mbox.mbox = itv->dec_mbox.mbox = NULL; + + IVTV_DEBUG_INFO("Stopping VDM\n"); + write_reg(IVTV_CMD_VDM_STOP, IVTV_REG_VDM); + + IVTV_DEBUG_INFO("Stopping AO\n"); + write_reg(IVTV_CMD_AO_STOP, IVTV_REG_AO); + + IVTV_DEBUG_INFO("pinging (?) APU\n"); + write_reg(IVTV_CMD_APU_PING, IVTV_REG_APU); + + IVTV_DEBUG_INFO("Stopping VPU\n"); + if (!itv->has_cx23415) + write_reg(IVTV_CMD_VPU_STOP16, IVTV_REG_VPU); + else + write_reg(IVTV_CMD_VPU_STOP15, IVTV_REG_VPU); + + IVTV_DEBUG_INFO("Resetting Hw Blocks\n"); + write_reg(IVTV_CMD_HW_BLOCKS_RST, IVTV_REG_HW_BLOCKS); + + IVTV_DEBUG_INFO("Stopping SPU\n"); + write_reg(IVTV_CMD_SPU_STOP, IVTV_REG_SPU); + + ivtv_msleep_timeout(10, 0); + + IVTV_DEBUG_INFO("init Encoder SDRAM pre-charge\n"); + write_reg(IVTV_CMD_SDRAM_PRECHARGE_INIT, IVTV_REG_ENC_SDRAM_PRECHARGE); + + IVTV_DEBUG_INFO("init Encoder SDRAM refresh to 1us\n"); + write_reg(IVTV_CMD_SDRAM_REFRESH_INIT, IVTV_REG_ENC_SDRAM_REFRESH); + + if (itv->has_cx23415) { + IVTV_DEBUG_INFO("init Decoder SDRAM pre-charge\n"); + write_reg(IVTV_CMD_SDRAM_PRECHARGE_INIT, IVTV_REG_DEC_SDRAM_PRECHARGE); + + IVTV_DEBUG_INFO("init Decoder SDRAM refresh to 1us\n"); + write_reg(IVTV_CMD_SDRAM_REFRESH_INIT, IVTV_REG_DEC_SDRAM_REFRESH); + } + + IVTV_DEBUG_INFO("Sleeping for %dms\n", IVTV_SDRAM_SLEEPTIME); + ivtv_msleep_timeout(IVTV_SDRAM_SLEEPTIME, 0); +} + +void ivtv_firmware_versions(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + + /* Encoder */ + ivtv_vapi_result(itv, data, CX2341X_ENC_GET_VERSION, 0); + IVTV_INFO("Encoder revision: 0x%08x\n", data[0]); + + if (data[0] != 0x02060039) + IVTV_WARN("Recommended firmware version is 0x02060039.\n"); + + if (itv->has_cx23415) { + /* Decoder */ + ivtv_vapi_result(itv, data, CX2341X_DEC_GET_VERSION, 0); + IVTV_INFO("Decoder revision: 0x%08x\n", data[0]); + } +} + +static int ivtv_firmware_copy(struct ivtv *itv) +{ + IVTV_DEBUG_INFO("Loading encoder image\n"); + if (load_fw_direct(CX2341X_FIRM_ENC_FILENAME, + itv->enc_mem, itv, IVTV_FW_ENC_SIZE) != IVTV_FW_ENC_SIZE) { + IVTV_DEBUG_WARN("failed loading encoder firmware\n"); + return -3; + } + if (!itv->has_cx23415) + return 0; + + IVTV_DEBUG_INFO("Loading decoder image\n"); + if (load_fw_direct(CX2341X_FIRM_DEC_FILENAME, + itv->dec_mem, itv, IVTV_FW_DEC_SIZE) != IVTV_FW_DEC_SIZE) { + IVTV_DEBUG_WARN("failed loading decoder firmware\n"); + return -1; + } + return 0; +} + +static volatile struct ivtv_mailbox __iomem *ivtv_search_mailbox(const volatile u8 __iomem *mem, u32 size) +{ + int i; + + /* mailbox is preceded by a 16 byte 'magic cookie' starting at a 256-byte + address boundary */ + for (i = 0; i < size; i += 0x100) { + if (readl(mem + i) == 0x12345678 && + readl(mem + i + 4) == 0x34567812 && + readl(mem + i + 8) == 0x56781234 && + readl(mem + i + 12) == 0x78123456) { + return (volatile struct ivtv_mailbox __iomem *)(mem + i + 16); + } + } + return NULL; +} + +int ivtv_firmware_init(struct ivtv *itv) +{ + int err; + + ivtv_halt_firmware(itv); + + /* load firmware */ + err = ivtv_firmware_copy(itv); + if (err) { + IVTV_DEBUG_WARN("Error %d loading firmware\n", err); + return err; + } + + /* start firmware */ + write_reg(read_reg(IVTV_REG_SPU) & IVTV_MASK_SPU_ENABLE, IVTV_REG_SPU); + ivtv_msleep_timeout(100, 0); + if (itv->has_cx23415) + write_reg(read_reg(IVTV_REG_VPU) & IVTV_MASK_VPU_ENABLE15, IVTV_REG_VPU); + else + write_reg(read_reg(IVTV_REG_VPU) & IVTV_MASK_VPU_ENABLE16, IVTV_REG_VPU); + ivtv_msleep_timeout(100, 0); + + /* find mailboxes and ping firmware */ + itv->enc_mbox.mbox = ivtv_search_mailbox(itv->enc_mem, IVTV_ENCODER_SIZE); + if (itv->enc_mbox.mbox == NULL) + IVTV_ERR("Encoder mailbox not found\n"); + else if (ivtv_vapi(itv, CX2341X_ENC_PING_FW, 0)) { + IVTV_ERR("Encoder firmware dead!\n"); + itv->enc_mbox.mbox = NULL; + } + if (itv->enc_mbox.mbox == NULL) + return -ENODEV; + + if (!itv->has_cx23415) + return 0; + + itv->dec_mbox.mbox = ivtv_search_mailbox(itv->dec_mem, IVTV_DECODER_SIZE); + if (itv->dec_mbox.mbox == NULL) { + IVTV_ERR("Decoder mailbox not found\n"); + } else if (itv->has_cx23415 && ivtv_vapi(itv, CX2341X_DEC_PING_FW, 0)) { + IVTV_ERR("Decoder firmware dead!\n"); + itv->dec_mbox.mbox = NULL; + } else { + /* Firmware okay, so check yuv output filter table */ + ivtv_yuv_filter_check(itv); + } + return itv->dec_mbox.mbox ? 0 : -ENODEV; +} + +void ivtv_init_mpeg_decoder(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + long readbytes; + volatile u8 __iomem *mem_offset; + + data[0] = 0; + data[1] = itv->cxhdl.width; /* YUV source width */ + data[2] = itv->cxhdl.height; + data[3] = itv->cxhdl.audio_properties; /* Audio settings to use, + bitmap. see docs. */ + if (ivtv_api(itv, CX2341X_DEC_SET_DECODER_SOURCE, 4, data)) { + IVTV_ERR("ivtv_init_mpeg_decoder failed to set decoder source\n"); + return; + } + + if (ivtv_vapi(itv, CX2341X_DEC_START_PLAYBACK, 2, 0, 1) != 0) { + IVTV_ERR("ivtv_init_mpeg_decoder failed to start playback\n"); + return; + } + ivtv_api_get_data(&itv->dec_mbox, IVTV_MBOX_DMA, 2, data); + mem_offset = itv->dec_mem + data[1]; + + if ((readbytes = load_fw_direct(IVTV_DECODE_INIT_MPEG_FILENAME, + mem_offset, itv, IVTV_DECODE_INIT_MPEG_SIZE)) <= 0) { + IVTV_DEBUG_WARN("failed to read mpeg decoder initialisation file %s\n", + IVTV_DECODE_INIT_MPEG_FILENAME); + } else { + ivtv_vapi(itv, CX2341X_DEC_SCHED_DMA_FROM_HOST, 3, 0, readbytes, 0); + ivtv_msleep_timeout(100, 0); + } + ivtv_vapi(itv, CX2341X_DEC_STOP_PLAYBACK, 4, 0, 0, 0, 1); +} + +/* Try to restart the card & restore previous settings */ +static int ivtv_firmware_restart(struct ivtv *itv) +{ + int rc = 0; + v4l2_std_id std; + + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) + /* Display test image during restart */ + ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_routing, + SAA7127_INPUT_TYPE_TEST_IMAGE, + itv->card->video_outputs[itv->active_output].video_output, + 0); + + mutex_lock(&itv->udma.lock); + + rc = ivtv_firmware_init(itv); + if (rc) { + mutex_unlock(&itv->udma.lock); + return rc; + } + + /* Allow settings to reload */ + ivtv_mailbox_cache_invalidate(itv); + + /* Restore encoder video standard */ + std = itv->std; + itv->std = 0; + ivtv_s_std_enc(itv, std); + + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) { + ivtv_init_mpeg_decoder(itv); + + /* Restore decoder video standard */ + std = itv->std_out; + itv->std_out = 0; + ivtv_s_std_dec(itv, std); + + /* Restore framebuffer if active */ + if (itv->ivtvfb_restore) + itv->ivtvfb_restore(itv); + + /* Restore alpha settings */ + ivtv_set_osd_alpha(itv); + + /* Restore normal output */ + ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_routing, + SAA7127_INPUT_TYPE_NORMAL, + itv->card->video_outputs[itv->active_output].video_output, + 0); + } + + mutex_unlock(&itv->udma.lock); + return rc; +} + +/* Check firmware running state. The checks fall through + allowing multiple failures to be logged. */ +int ivtv_firmware_check(struct ivtv *itv, char *where) +{ + int res = 0; + + /* Check encoder is still running */ + if (ivtv_vapi(itv, CX2341X_ENC_PING_FW, 0) < 0) { + IVTV_WARN("Encoder has died : %s\n", where); + res = -1; + } + + /* Also check audio. Only check if not in use & encoder is okay */ + if (!res && !atomic_read(&itv->capturing) && + (!atomic_read(&itv->decoding) || + (atomic_read(&itv->decoding) < 2 && test_bit(IVTV_F_I_DEC_YUV, + &itv->i_flags)))) { + + if (ivtv_vapi(itv, CX2341X_ENC_MISC, 1, 12) < 0) { + IVTV_WARN("Audio has died (Encoder OK) : %s\n", where); + res = -2; + } + } + + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) { + /* Second audio check. Skip if audio already failed */ + if (res != -2 && read_dec(0x100) != read_dec(0x104)) { + /* Wait & try again to be certain. */ + ivtv_msleep_timeout(14, 0); + if (read_dec(0x100) != read_dec(0x104)) { + IVTV_WARN("Audio has died (Decoder) : %s\n", + where); + res = -1; + } + } + + /* Check decoder is still running */ + if (ivtv_vapi(itv, CX2341X_DEC_PING_FW, 0) < 0) { + IVTV_WARN("Decoder has died : %s\n", where); + res = -1; + } + } + + /* If something failed & currently idle, try to reload */ + if (res && !atomic_read(&itv->capturing) && + !atomic_read(&itv->decoding)) { + IVTV_INFO("Detected in %s that firmware had failed - Reloading\n", + where); + res = ivtv_firmware_restart(itv); + /* + * Even if restarted ok, still signal a problem had occurred. + * The caller can come through this function again to check + * if things are really ok after the restart. + */ + if (!res) { + IVTV_INFO("Firmware restart okay\n"); + res = -EAGAIN; + } else { + IVTV_INFO("Firmware restart failed\n"); + } + } else if (res) { + res = -EIO; + } + + return res; +} + +MODULE_FIRMWARE(CX2341X_FIRM_ENC_FILENAME); +MODULE_FIRMWARE(CX2341X_FIRM_DEC_FILENAME); +MODULE_FIRMWARE(IVTV_DECODE_INIT_MPEG_FILENAME); diff --git a/drivers/media/pci/ivtv/ivtv-firmware.h b/drivers/media/pci/ivtv/ivtv-firmware.h new file mode 100644 index 000000000..ce94b6c38 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-firmware.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + ivtv firmware functions. + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#ifndef IVTV_FIRMWARE_H +#define IVTV_FIRMWARE_H + +int ivtv_firmware_init(struct ivtv *itv); +void ivtv_firmware_versions(struct ivtv *itv); +void ivtv_halt_firmware(struct ivtv *itv); +void ivtv_init_mpeg_decoder(struct ivtv *itv); +int ivtv_firmware_check(struct ivtv *itv, char *where); + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-gpio.c b/drivers/media/pci/ivtv/ivtv-gpio.c new file mode 100644 index 000000000..d3477e152 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-gpio.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + gpio functions. + Merging GPIO support into driver: + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#include "ivtv-driver.h" +#include "ivtv-cards.h" +#include "ivtv-gpio.h" +#include "xc2028.h" +#include +#include + +/* + * GPIO assignment of Yuan MPG600/MPG160 + * + * bit 15 14 13 12 | 11 10 9 8 | 7 6 5 4 | 3 2 1 0 + * OUTPUT IN1 IN0 AM3 AM2 AM1 AM0 + * INPUT DM1 DM0 + * + * IN* : Input selection + * IN1 IN0 + * 1 1 N/A + * 1 0 Line + * 0 1 N/A + * 0 0 Tuner + * + * AM* : Audio Mode + * AM3 0: Normal 1: Mixed(Sub+Main channel) + * AM2 0: Subchannel 1: Main channel + * AM1 0: Stereo 1: Mono + * AM0 0: Normal 1: Mute + * + * DM* : Detected tuner audio Mode + * DM1 0: Stereo 1: Mono + * DM0 0: Multiplex 1: Normal + * + * GPIO Initial Settings + * MPG600 MPG160 + * DIR 0x3080 0x7080 + * OUTPUT 0x000C 0x400C + * + * Special thanks to Makoto Iguchi and Mr. Anonymous + * for analyzing GPIO of MPG160. + * + ***************************************************************************** + * + * GPIO assignment of Avermedia M179 (per information direct from AVerMedia) + * + * bit 15 14 13 12 | 11 10 9 8 | 7 6 5 4 | 3 2 1 0 + * OUTPUT IN0 AM0 IN1 AM1 AM2 IN2 BR0 BR1 + * INPUT + * + * IN* : Input selection + * IN0 IN1 IN2 + * * 1 * Mute + * 0 0 0 Line-In + * 1 0 0 TV Tuner Audio + * 0 0 1 FM Audio + * 1 0 1 Mute + * + * AM* : Audio Mode + * AM0 AM1 AM2 + * 0 0 0 TV Tuner Audio: L_OUT=(L+R)/2, R_OUT=SAP + * 0 0 1 TV Tuner Audio: L_OUT=R_OUT=SAP (SAP) + * 0 1 0 TV Tuner Audio: L_OUT=L, R_OUT=R (stereo) + * 0 1 1 TV Tuner Audio: mute + * 1 * * TV Tuner Audio: L_OUT=R_OUT=(L+R)/2 (mono) + * + * BR* : Audio Sample Rate (BR stands for bitrate for some reason) + * BR0 BR1 + * 0 0 32 kHz + * 0 1 44.1 kHz + * 1 0 48 kHz + * + * DM* : Detected tuner audio Mode + * Unknown currently + * + * Special thanks to AVerMedia Technologies, Inc. and Jiun-Kuei Jung at + * AVerMedia for providing the GPIO information used to add support + * for the M179 cards. + */ + +/********************* GPIO stuffs *********************/ + +/* GPIO registers */ +#define IVTV_REG_GPIO_IN 0x9008 +#define IVTV_REG_GPIO_OUT 0x900c +#define IVTV_REG_GPIO_DIR 0x9020 + +void ivtv_reset_ir_gpio(struct ivtv *itv) +{ + int curdir, curout; + + if (itv->card->type != IVTV_CARD_PVR_150) + return; + IVTV_DEBUG_INFO("Resetting PVR150 IR\n"); + curout = read_reg(IVTV_REG_GPIO_OUT); + curdir = read_reg(IVTV_REG_GPIO_DIR); + curdir |= 0x80; + write_reg(curdir, IVTV_REG_GPIO_DIR); + curout = (curout & ~0xF) | 1; + write_reg(curout, IVTV_REG_GPIO_OUT); + /* We could use something else for smaller time */ + schedule_timeout_interruptible(msecs_to_jiffies(1)); + curout |= 2; + write_reg(curout, IVTV_REG_GPIO_OUT); + curdir &= ~0x80; + write_reg(curdir, IVTV_REG_GPIO_DIR); +} + +/* Xceive tuner reset function */ +int ivtv_reset_tuner_gpio(void *dev, int component, int cmd, int value) +{ + struct i2c_algo_bit_data *algo = dev; + struct ivtv *itv = algo->data; + u32 curout; + + if (cmd != XC2028_TUNER_RESET) + return 0; + IVTV_DEBUG_INFO("Resetting tuner\n"); + curout = read_reg(IVTV_REG_GPIO_OUT); + curout &= ~(1 << itv->card->xceive_pin); + write_reg(curout, IVTV_REG_GPIO_OUT); + schedule_timeout_interruptible(msecs_to_jiffies(1)); + + curout |= 1 << itv->card->xceive_pin; + write_reg(curout, IVTV_REG_GPIO_OUT); + schedule_timeout_interruptible(msecs_to_jiffies(1)); + return 0; +} + +static inline struct ivtv *sd_to_ivtv(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ivtv, sd_gpio); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct ivtv, hdl_gpio)->sd_gpio; +} + +static int subdev_s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + struct ivtv *itv = sd_to_ivtv(sd); + u16 mask, data; + + mask = itv->card->gpio_audio_freq.mask; + switch (freq) { + case 32000: + data = itv->card->gpio_audio_freq.f32000; + break; + case 44100: + data = itv->card->gpio_audio_freq.f44100; + break; + case 48000: + default: + data = itv->card->gpio_audio_freq.f48000; + break; + } + if (mask) + write_reg((read_reg(IVTV_REG_GPIO_OUT) & ~mask) | (data & mask), IVTV_REG_GPIO_OUT); + return 0; +} + +static int subdev_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct ivtv *itv = sd_to_ivtv(sd); + u16 mask; + + mask = itv->card->gpio_audio_detect.mask; + if (mask == 0 || (read_reg(IVTV_REG_GPIO_IN) & mask)) + vt->rxsubchans = V4L2_TUNER_SUB_STEREO | + V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + else + vt->rxsubchans = V4L2_TUNER_SUB_MONO; + return 0; +} + +static int subdev_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt) +{ + struct ivtv *itv = sd_to_ivtv(sd); + u16 mask, data; + + mask = itv->card->gpio_audio_mode.mask; + switch (vt->audmode) { + case V4L2_TUNER_MODE_LANG1: + data = itv->card->gpio_audio_mode.lang1; + break; + case V4L2_TUNER_MODE_LANG2: + data = itv->card->gpio_audio_mode.lang2; + break; + case V4L2_TUNER_MODE_MONO: + data = itv->card->gpio_audio_mode.mono; + break; + case V4L2_TUNER_MODE_STEREO: + case V4L2_TUNER_MODE_LANG1_LANG2: + default: + data = itv->card->gpio_audio_mode.stereo; + break; + } + if (mask) + write_reg((read_reg(IVTV_REG_GPIO_OUT) & ~mask) | (data & mask), IVTV_REG_GPIO_OUT); + return 0; +} + +static int subdev_s_radio(struct v4l2_subdev *sd) +{ + struct ivtv *itv = sd_to_ivtv(sd); + u16 mask, data; + + mask = itv->card->gpio_audio_input.mask; + data = itv->card->gpio_audio_input.radio; + if (mask) + write_reg((read_reg(IVTV_REG_GPIO_OUT) & ~mask) | (data & mask), IVTV_REG_GPIO_OUT); + return 0; +} + +static int subdev_s_audio_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct ivtv *itv = sd_to_ivtv(sd); + u16 mask, data; + + if (input > 2) + return -EINVAL; + mask = itv->card->gpio_audio_input.mask; + switch (input) { + case 0: + data = itv->card->gpio_audio_input.tuner; + break; + case 1: + data = itv->card->gpio_audio_input.linein; + break; + case 2: + default: + data = itv->card->gpio_audio_input.radio; + break; + } + if (mask) + write_reg((read_reg(IVTV_REG_GPIO_OUT) & ~mask) | (data & mask), IVTV_REG_GPIO_OUT); + return 0; +} + +static int subdev_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct ivtv *itv = sd_to_ivtv(sd); + u16 mask, data; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + mask = itv->card->gpio_audio_mute.mask; + data = ctrl->val ? itv->card->gpio_audio_mute.mute : 0; + if (mask) + write_reg((read_reg(IVTV_REG_GPIO_OUT) & ~mask) | + (data & mask), IVTV_REG_GPIO_OUT); + return 0; + } + return -EINVAL; +} + + +static int subdev_log_status(struct v4l2_subdev *sd) +{ + struct ivtv *itv = sd_to_ivtv(sd); + + IVTV_INFO("GPIO status: DIR=0x%04x OUT=0x%04x IN=0x%04x\n", + read_reg(IVTV_REG_GPIO_DIR), read_reg(IVTV_REG_GPIO_OUT), + read_reg(IVTV_REG_GPIO_IN)); + v4l2_ctrl_handler_log_status(&itv->hdl_gpio, sd->name); + return 0; +} + +static int subdev_s_video_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct ivtv *itv = sd_to_ivtv(sd); + u16 mask, data; + + if (input > 2) /* 0:Tuner 1:Composite 2:S-Video */ + return -EINVAL; + mask = itv->card->gpio_video_input.mask; + if (input == 0) + data = itv->card->gpio_video_input.tuner; + else if (input == 1) + data = itv->card->gpio_video_input.composite; + else + data = itv->card->gpio_video_input.svideo; + if (mask) + write_reg((read_reg(IVTV_REG_GPIO_OUT) & ~mask) | (data & mask), IVTV_REG_GPIO_OUT); + return 0; +} + +static const struct v4l2_ctrl_ops gpio_ctrl_ops = { + .s_ctrl = subdev_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops subdev_core_ops = { + .log_status = subdev_log_status, +}; + +static const struct v4l2_subdev_tuner_ops subdev_tuner_ops = { + .s_radio = subdev_s_radio, + .g_tuner = subdev_g_tuner, + .s_tuner = subdev_s_tuner, +}; + +static const struct v4l2_subdev_audio_ops subdev_audio_ops = { + .s_clock_freq = subdev_s_clock_freq, + .s_routing = subdev_s_audio_routing, +}; + +static const struct v4l2_subdev_video_ops subdev_video_ops = { + .s_routing = subdev_s_video_routing, +}; + +static const struct v4l2_subdev_ops subdev_ops = { + .core = &subdev_core_ops, + .tuner = &subdev_tuner_ops, + .audio = &subdev_audio_ops, + .video = &subdev_video_ops, +}; + +int ivtv_gpio_init(struct ivtv *itv) +{ + u16 pin = 0; + + if (itv->card->xceive_pin) + pin = 1 << itv->card->xceive_pin; + + if ((itv->card->gpio_init.direction | pin) == 0) + return 0; + + IVTV_DEBUG_INFO("GPIO initial dir: %08x out: %08x\n", + read_reg(IVTV_REG_GPIO_DIR), read_reg(IVTV_REG_GPIO_OUT)); + + /* init output data then direction */ + write_reg(itv->card->gpio_init.initial_value | pin, IVTV_REG_GPIO_OUT); + write_reg(itv->card->gpio_init.direction | pin, IVTV_REG_GPIO_DIR); + v4l2_subdev_init(&itv->sd_gpio, &subdev_ops); + snprintf(itv->sd_gpio.name, sizeof(itv->sd_gpio.name), "%s-gpio", itv->v4l2_dev.name); + itv->sd_gpio.grp_id = IVTV_HW_GPIO; + v4l2_ctrl_handler_init(&itv->hdl_gpio, 1); + v4l2_ctrl_new_std(&itv->hdl_gpio, &gpio_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + if (itv->hdl_gpio.error) + return itv->hdl_gpio.error; + itv->sd_gpio.ctrl_handler = &itv->hdl_gpio; + v4l2_ctrl_handler_setup(&itv->hdl_gpio); + return v4l2_device_register_subdev(&itv->v4l2_dev, &itv->sd_gpio); +} diff --git a/drivers/media/pci/ivtv/ivtv-gpio.h b/drivers/media/pci/ivtv/ivtv-gpio.h new file mode 100644 index 000000000..686c9b5e9 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-gpio.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + gpio functions. + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#ifndef IVTV_GPIO_H +#define IVTV_GPIO_H + +/* GPIO stuff */ +int ivtv_gpio_init(struct ivtv *itv); +void ivtv_reset_ir_gpio(struct ivtv *itv); +int ivtv_reset_tuner_gpio(void *dev, int component, int cmd, int value); + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-i2c.c b/drivers/media/pci/ivtv/ivtv-i2c.c new file mode 100644 index 000000000..28cb22d6a --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-i2c.c @@ -0,0 +1,739 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + I2C functions + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2005-2007 Hans Verkuil + + */ + +/* + This file includes an i2c implementation that was reverse engineered + from the Hauppauge windows driver. Older ivtv versions used i2c-algo-bit, + which whilst fine under most circumstances, had trouble with the Zilog + CPU on the PVR-150 which handles IR functions (occasional inability to + communicate with the chip until it was reset) and also with the i2c + bus being completely unreachable when multiple PVR cards were present. + + The implementation is very similar to i2c-algo-bit, but there are enough + subtle differences that the two are hard to merge. The general strategy + employed by i2c-algo-bit is to use udelay() to implement the timing + when putting out bits on the scl/sda lines. The general strategy taken + here is to poll the lines for state changes (see ivtv_waitscl and + ivtv_waitsda). In addition there are small delays at various locations + which poll the SCL line 5 times (ivtv_scldelay). I would guess that + since this is memory mapped I/O that the length of those delays is tied + to the PCI bus clock. There is some extra code to do with recovery + and retries. Since it is not known what causes the actual i2c problems + in the first place, the only goal if one was to attempt to use + i2c-algo-bit would be to try to make it follow the same code path. + This would be a lot of work, and I'm also not convinced that it would + provide a generic benefit to i2c-algo-bit. Therefore consider this + an engineering solution -- not pretty, but it works. + + Some more general comments about what we are doing: + + The i2c bus is a 2 wire serial bus, with clock (SCL) and data (SDA) + lines. To communicate on the bus (as a master, we don't act as a slave), + we first initiate a start condition (ivtv_start). We then write the + address of the device that we want to communicate with, along with a flag + that indicates whether this is a read or a write. The slave then issues + an ACK signal (ivtv_ack), which tells us that it is ready for reading / + writing. We then proceed with reading or writing (ivtv_read/ivtv_write), + and finally issue a stop condition (ivtv_stop) to make the bus available + to other masters. + + There is an additional form of transaction where a write may be + immediately followed by a read. In this case, there is no intervening + stop condition. (Only the msp3400 chip uses this method of data transfer). + */ + +#include "ivtv-driver.h" +#include "ivtv-cards.h" +#include "ivtv-gpio.h" +#include "ivtv-i2c.h" +#include + +/* i2c implementation for cx23415/6 chip, ivtv project. + * Author: Kevin Thayer (nufan_wfk at yahoo.com) + */ +/* i2c stuff */ +#define IVTV_REG_I2C_SETSCL_OFFSET 0x7000 +#define IVTV_REG_I2C_SETSDA_OFFSET 0x7004 +#define IVTV_REG_I2C_GETSCL_OFFSET 0x7008 +#define IVTV_REG_I2C_GETSDA_OFFSET 0x700c + +#define IVTV_CS53L32A_I2C_ADDR 0x11 +#define IVTV_M52790_I2C_ADDR 0x48 +#define IVTV_CX25840_I2C_ADDR 0x44 +#define IVTV_SAA7115_I2C_ADDR 0x21 +#define IVTV_SAA7127_I2C_ADDR 0x44 +#define IVTV_SAA717x_I2C_ADDR 0x21 +#define IVTV_MSP3400_I2C_ADDR 0x40 +#define IVTV_HAUPPAUGE_I2C_ADDR 0x50 +#define IVTV_WM8739_I2C_ADDR 0x1a +#define IVTV_WM8775_I2C_ADDR 0x1b +#define IVTV_TEA5767_I2C_ADDR 0x60 +#define IVTV_UPD64031A_I2C_ADDR 0x12 +#define IVTV_UPD64083_I2C_ADDR 0x5c +#define IVTV_VP27SMPX_I2C_ADDR 0x5b +#define IVTV_M52790_I2C_ADDR 0x48 +#define IVTV_AVERMEDIA_IR_RX_I2C_ADDR 0x40 +#define IVTV_HAUP_EXT_IR_RX_I2C_ADDR 0x1a +#define IVTV_HAUP_INT_IR_RX_I2C_ADDR 0x18 +#define IVTV_Z8F0811_IR_TX_I2C_ADDR 0x70 +#define IVTV_Z8F0811_IR_RX_I2C_ADDR 0x71 +#define IVTV_ADAPTEC_IR_ADDR 0x6b + +/* This array should match the IVTV_HW_ defines */ +static const u8 hw_addrs[IVTV_HW_MAX_BITS] = { + IVTV_CX25840_I2C_ADDR, + IVTV_SAA7115_I2C_ADDR, + IVTV_SAA7127_I2C_ADDR, + IVTV_MSP3400_I2C_ADDR, + 0, + IVTV_WM8775_I2C_ADDR, + IVTV_CS53L32A_I2C_ADDR, + 0, + IVTV_SAA7115_I2C_ADDR, + IVTV_UPD64031A_I2C_ADDR, + IVTV_UPD64083_I2C_ADDR, + IVTV_SAA717x_I2C_ADDR, + IVTV_WM8739_I2C_ADDR, + IVTV_VP27SMPX_I2C_ADDR, + IVTV_M52790_I2C_ADDR, + 0, /* IVTV_HW_GPIO dummy driver ID */ + IVTV_AVERMEDIA_IR_RX_I2C_ADDR, /* IVTV_HW_I2C_IR_RX_AVER */ + IVTV_HAUP_EXT_IR_RX_I2C_ADDR, /* IVTV_HW_I2C_IR_RX_HAUP_EXT */ + IVTV_HAUP_INT_IR_RX_I2C_ADDR, /* IVTV_HW_I2C_IR_RX_HAUP_INT */ + IVTV_Z8F0811_IR_RX_I2C_ADDR, /* IVTV_HW_Z8F0811_IR_HAUP */ + IVTV_ADAPTEC_IR_ADDR, /* IVTV_HW_I2C_IR_RX_ADAPTEC */ +}; + +/* This array should match the IVTV_HW_ defines */ +static const char * const hw_devicenames[IVTV_HW_MAX_BITS] = { + "cx25840", + "saa7115", + "saa7127_auto", /* saa7127 or saa7129 */ + "msp3400", + "tuner", + "wm8775", + "cs53l32a", + "tveeprom", + "saa7114", + "upd64031a", + "upd64083", + "saa717x", + "wm8739", + "vp27smpx", + "m52790", + "gpio", + "ir_video", /* IVTV_HW_I2C_IR_RX_AVER */ + "ir_video", /* IVTV_HW_I2C_IR_RX_HAUP_EXT */ + "ir_video", /* IVTV_HW_I2C_IR_RX_HAUP_INT */ + "ir_z8f0811_haup", /* IVTV_HW_Z8F0811_IR_HAUP */ + "ir_video", /* IVTV_HW_I2C_IR_RX_ADAPTEC */ +}; + +static int get_key_adaptec(struct IR_i2c *ir, enum rc_proto *protocol, + u32 *scancode, u8 *toggle) +{ + unsigned char keybuf[4]; + + keybuf[0] = 0x00; + i2c_master_send(ir->c, keybuf, 1); + /* poll IR chip */ + if (i2c_master_recv(ir->c, keybuf, sizeof(keybuf)) != sizeof(keybuf)) { + return 0; + } + + /* key pressed ? */ + if (keybuf[2] == 0xff) + return 0; + + /* remove repeat bit */ + keybuf[2] &= 0x7f; + keybuf[3] |= 0x80; + + *protocol = RC_PROTO_UNKNOWN; + *scancode = keybuf[3] | keybuf[2] << 8 | keybuf[1] << 16 |keybuf[0] << 24; + *toggle = 0; + return 1; +} + +static int ivtv_i2c_new_ir(struct ivtv *itv, u32 hw, const char *type, u8 addr) +{ + struct i2c_board_info info; + struct i2c_adapter *adap = &itv->i2c_adap; + struct IR_i2c_init_data *init_data = &itv->ir_i2c_init_data; + unsigned short addr_list[2] = { addr, I2C_CLIENT_END }; + + /* Only allow one IR receiver to be registered per board */ + if (itv->hw_flags & IVTV_HW_IR_ANY) + return -1; + + /* Our default information for ir-kbd-i2c.c to use */ + switch (hw) { + case IVTV_HW_I2C_IR_RX_AVER: + init_data->ir_codes = RC_MAP_AVERMEDIA_CARDBUS; + init_data->internal_get_key_func = + IR_KBD_GET_KEY_AVERMEDIA_CARDBUS; + init_data->type = RC_PROTO_BIT_OTHER; + init_data->name = "AVerMedia AVerTV card"; + break; + case IVTV_HW_I2C_IR_RX_HAUP_EXT: + case IVTV_HW_I2C_IR_RX_HAUP_INT: + init_data->ir_codes = RC_MAP_HAUPPAUGE; + init_data->internal_get_key_func = IR_KBD_GET_KEY_HAUP; + init_data->type = RC_PROTO_BIT_RC5; + init_data->name = itv->card_name; + break; + case IVTV_HW_Z8F0811_IR_HAUP: + /* Default to grey remote */ + init_data->ir_codes = RC_MAP_HAUPPAUGE; + init_data->internal_get_key_func = IR_KBD_GET_KEY_HAUP_XVR; + init_data->type = RC_PROTO_BIT_RC5 | RC_PROTO_BIT_RC6_MCE | + RC_PROTO_BIT_RC6_6A_32; + init_data->name = itv->card_name; + break; + case IVTV_HW_I2C_IR_RX_ADAPTEC: + init_data->get_key = get_key_adaptec; + init_data->name = itv->card_name; + /* FIXME: The protocol and RC_MAP needs to be corrected */ + init_data->ir_codes = RC_MAP_EMPTY; + init_data->type = RC_PROTO_BIT_UNKNOWN; + break; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.platform_data = init_data; + strscpy(info.type, type, I2C_NAME_SIZE); + + return IS_ERR(i2c_new_scanned_device(adap, &info, addr_list, NULL)) ? + -1 : 0; +} + +/* Instantiate the IR receiver device using probing -- undesirable */ +void ivtv_i2c_new_ir_legacy(struct ivtv *itv) +{ + struct i2c_board_info info; + /* + * The external IR receiver is at i2c address 0x34. + * The internal IR receiver is at i2c address 0x30. + * + * In theory, both can be fitted, and Hauppauge suggests an external + * overrides an internal. That's why we probe 0x1a (~0x34) first. CB + * + * Some of these addresses we probe may collide with other i2c address + * allocations, so this function must be called after all other i2c + * devices we care about are registered. + */ + static const unsigned short addr_list[] = { + 0x1a, /* Hauppauge IR external - collides with WM8739 */ + 0x18, /* Hauppauge IR internal */ + I2C_CLIENT_END + }; + + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "ir_video", I2C_NAME_SIZE); + i2c_new_scanned_device(&itv->i2c_adap, &info, addr_list, NULL); +} + +int ivtv_i2c_register(struct ivtv *itv, unsigned idx) +{ + struct i2c_adapter *adap = &itv->i2c_adap; + struct v4l2_subdev *sd; + const char *type; + u32 hw; + + if (idx >= IVTV_HW_MAX_BITS) + return -ENODEV; + + type = hw_devicenames[idx]; + hw = 1 << idx; + + if (hw == IVTV_HW_TUNER) { + /* special tuner handling */ + sd = v4l2_i2c_new_subdev(&itv->v4l2_dev, adap, type, 0, + itv->card_i2c->radio); + if (sd) + sd->grp_id = 1 << idx; + sd = v4l2_i2c_new_subdev(&itv->v4l2_dev, adap, type, 0, + itv->card_i2c->demod); + if (sd) + sd->grp_id = 1 << idx; + sd = v4l2_i2c_new_subdev(&itv->v4l2_dev, adap, type, 0, + itv->card_i2c->tv); + if (sd) + sd->grp_id = 1 << idx; + return sd ? 0 : -1; + } + + if (hw & IVTV_HW_IR_ANY) + return ivtv_i2c_new_ir(itv, hw, type, hw_addrs[idx]); + + /* Is it not an I2C device or one we do not wish to register? */ + if (!hw_addrs[idx]) + return -1; + + /* It's an I2C device other than an analog tuner or IR chip */ + if (hw == IVTV_HW_UPD64031A || hw == IVTV_HW_UPD6408X) { + sd = v4l2_i2c_new_subdev(&itv->v4l2_dev, + adap, type, 0, I2C_ADDRS(hw_addrs[idx])); + } else if (hw == IVTV_HW_CX25840) { + struct cx25840_platform_data pdata; + struct i2c_board_info cx25840_info = { + .type = "cx25840", + .addr = hw_addrs[idx], + .platform_data = &pdata, + }; + + memset(&pdata, 0, sizeof(pdata)); + pdata.pvr150_workaround = itv->pvr150_workaround; + sd = v4l2_i2c_new_subdev_board(&itv->v4l2_dev, adap, + &cx25840_info, NULL); + } else { + sd = v4l2_i2c_new_subdev(&itv->v4l2_dev, + adap, type, hw_addrs[idx], NULL); + } + if (sd) + sd->grp_id = 1 << idx; + return sd ? 0 : -1; +} + +struct v4l2_subdev *ivtv_find_hw(struct ivtv *itv, u32 hw) +{ + struct v4l2_subdev *result = NULL; + struct v4l2_subdev *sd; + + spin_lock(&itv->v4l2_dev.lock); + v4l2_device_for_each_subdev(sd, &itv->v4l2_dev) { + if (sd->grp_id == hw) { + result = sd; + break; + } + } + spin_unlock(&itv->v4l2_dev.lock); + return result; +} + +/* Set the serial clock line to the desired state */ +static void ivtv_setscl(struct ivtv *itv, int state) +{ + /* write them out */ + /* write bits are inverted */ + write_reg(~state, IVTV_REG_I2C_SETSCL_OFFSET); +} + +/* Set the serial data line to the desired state */ +static void ivtv_setsda(struct ivtv *itv, int state) +{ + /* write them out */ + /* write bits are inverted */ + write_reg(~state & 1, IVTV_REG_I2C_SETSDA_OFFSET); +} + +/* Read the serial clock line */ +static int ivtv_getscl(struct ivtv *itv) +{ + return read_reg(IVTV_REG_I2C_GETSCL_OFFSET) & 1; +} + +/* Read the serial data line */ +static int ivtv_getsda(struct ivtv *itv) +{ + return read_reg(IVTV_REG_I2C_GETSDA_OFFSET) & 1; +} + +/* Implement a short delay by polling the serial clock line */ +static void ivtv_scldelay(struct ivtv *itv) +{ + int i; + + for (i = 0; i < 5; ++i) + ivtv_getscl(itv); +} + +/* Wait for the serial clock line to become set to a specific value */ +static int ivtv_waitscl(struct ivtv *itv, int val) +{ + int i; + + ivtv_scldelay(itv); + for (i = 0; i < 1000; ++i) { + if (ivtv_getscl(itv) == val) + return 1; + } + return 0; +} + +/* Wait for the serial data line to become set to a specific value */ +static int ivtv_waitsda(struct ivtv *itv, int val) +{ + int i; + + ivtv_scldelay(itv); + for (i = 0; i < 1000; ++i) { + if (ivtv_getsda(itv) == val) + return 1; + } + return 0; +} + +/* Wait for the slave to issue an ACK */ +static int ivtv_ack(struct ivtv *itv) +{ + int ret = 0; + + if (ivtv_getscl(itv) == 1) { + IVTV_DEBUG_HI_I2C("SCL was high starting an ack\n"); + ivtv_setscl(itv, 0); + if (!ivtv_waitscl(itv, 0)) { + IVTV_DEBUG_I2C("Could not set SCL low starting an ack\n"); + return -EREMOTEIO; + } + } + ivtv_setsda(itv, 1); + ivtv_scldelay(itv); + ivtv_setscl(itv, 1); + if (!ivtv_waitsda(itv, 0)) { + IVTV_DEBUG_I2C("Slave did not ack\n"); + ret = -EREMOTEIO; + } + ivtv_setscl(itv, 0); + if (!ivtv_waitscl(itv, 0)) { + IVTV_DEBUG_I2C("Failed to set SCL low after ACK\n"); + ret = -EREMOTEIO; + } + return ret; +} + +/* Write a single byte to the i2c bus and wait for the slave to ACK */ +static int ivtv_sendbyte(struct ivtv *itv, unsigned char byte) +{ + int i, bit; + + IVTV_DEBUG_HI_I2C("write %x\n",byte); + for (i = 0; i < 8; ++i, byte<<=1) { + ivtv_setscl(itv, 0); + if (!ivtv_waitscl(itv, 0)) { + IVTV_DEBUG_I2C("Error setting SCL low\n"); + return -EREMOTEIO; + } + bit = (byte>>7)&1; + ivtv_setsda(itv, bit); + if (!ivtv_waitsda(itv, bit)) { + IVTV_DEBUG_I2C("Error setting SDA\n"); + return -EREMOTEIO; + } + ivtv_setscl(itv, 1); + if (!ivtv_waitscl(itv, 1)) { + IVTV_DEBUG_I2C("Slave not ready for bit\n"); + return -EREMOTEIO; + } + } + ivtv_setscl(itv, 0); + if (!ivtv_waitscl(itv, 0)) { + IVTV_DEBUG_I2C("Error setting SCL low\n"); + return -EREMOTEIO; + } + return ivtv_ack(itv); +} + +/* Read a byte from the i2c bus and send a NACK if applicable (i.e. for the + final byte) */ +static int ivtv_readbyte(struct ivtv *itv, unsigned char *byte, int nack) +{ + int i; + + *byte = 0; + + ivtv_setsda(itv, 1); + ivtv_scldelay(itv); + for (i = 0; i < 8; ++i) { + ivtv_setscl(itv, 0); + ivtv_scldelay(itv); + ivtv_setscl(itv, 1); + if (!ivtv_waitscl(itv, 1)) { + IVTV_DEBUG_I2C("Error setting SCL high\n"); + return -EREMOTEIO; + } + *byte = ((*byte)<<1)|ivtv_getsda(itv); + } + ivtv_setscl(itv, 0); + ivtv_scldelay(itv); + ivtv_setsda(itv, nack); + ivtv_scldelay(itv); + ivtv_setscl(itv, 1); + ivtv_scldelay(itv); + ivtv_setscl(itv, 0); + ivtv_scldelay(itv); + IVTV_DEBUG_HI_I2C("read %x\n",*byte); + return 0; +} + +/* Issue a start condition on the i2c bus to alert slaves to prepare for + an address write */ +static int ivtv_start(struct ivtv *itv) +{ + int sda; + + sda = ivtv_getsda(itv); + if (sda != 1) { + IVTV_DEBUG_HI_I2C("SDA was low at start\n"); + ivtv_setsda(itv, 1); + if (!ivtv_waitsda(itv, 1)) { + IVTV_DEBUG_I2C("SDA stuck low\n"); + return -EREMOTEIO; + } + } + if (ivtv_getscl(itv) != 1) { + ivtv_setscl(itv, 1); + if (!ivtv_waitscl(itv, 1)) { + IVTV_DEBUG_I2C("SCL stuck low at start\n"); + return -EREMOTEIO; + } + } + ivtv_setsda(itv, 0); + ivtv_scldelay(itv); + return 0; +} + +/* Issue a stop condition on the i2c bus to release it */ +static int ivtv_stop(struct ivtv *itv) +{ + int i; + + if (ivtv_getscl(itv) != 0) { + IVTV_DEBUG_HI_I2C("SCL not low when stopping\n"); + ivtv_setscl(itv, 0); + if (!ivtv_waitscl(itv, 0)) { + IVTV_DEBUG_I2C("SCL could not be set low\n"); + } + } + ivtv_setsda(itv, 0); + ivtv_scldelay(itv); + ivtv_setscl(itv, 1); + if (!ivtv_waitscl(itv, 1)) { + IVTV_DEBUG_I2C("SCL could not be set high\n"); + return -EREMOTEIO; + } + ivtv_scldelay(itv); + ivtv_setsda(itv, 1); + if (!ivtv_waitsda(itv, 1)) { + IVTV_DEBUG_I2C("resetting I2C\n"); + for (i = 0; i < 16; ++i) { + ivtv_setscl(itv, 0); + ivtv_scldelay(itv); + ivtv_setscl(itv, 1); + ivtv_scldelay(itv); + ivtv_setsda(itv, 1); + } + ivtv_waitsda(itv, 1); + return -EREMOTEIO; + } + return 0; +} + +/* Write a message to the given i2c slave. do_stop may be 0 to prevent + issuing the i2c stop condition (when following with a read) */ +static int ivtv_write(struct ivtv *itv, unsigned char addr, unsigned char *data, u32 len, int do_stop) +{ + int retry, ret = -EREMOTEIO; + u32 i; + + for (retry = 0; ret != 0 && retry < 8; ++retry) { + ret = ivtv_start(itv); + + if (ret == 0) { + ret = ivtv_sendbyte(itv, addr<<1); + for (i = 0; ret == 0 && i < len; ++i) + ret = ivtv_sendbyte(itv, data[i]); + } + if (ret != 0 || do_stop) { + ivtv_stop(itv); + } + } + if (ret) + IVTV_DEBUG_I2C("i2c write to %x failed\n", addr); + return ret; +} + +/* Read data from the given i2c slave. A stop condition is always issued. */ +static int ivtv_read(struct ivtv *itv, unsigned char addr, unsigned char *data, u32 len) +{ + int retry, ret = -EREMOTEIO; + u32 i; + + for (retry = 0; ret != 0 && retry < 8; ++retry) { + ret = ivtv_start(itv); + if (ret == 0) + ret = ivtv_sendbyte(itv, (addr << 1) | 1); + for (i = 0; ret == 0 && i < len; ++i) { + ret = ivtv_readbyte(itv, &data[i], i == len - 1); + } + ivtv_stop(itv); + } + if (ret) + IVTV_DEBUG_I2C("i2c read from %x failed\n", addr); + return ret; +} + +/* Kernel i2c transfer implementation. Takes a number of messages to be read + or written. If a read follows a write, this will occur without an + intervening stop condition */ +static int ivtv_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, int num) +{ + struct v4l2_device *v4l2_dev = i2c_get_adapdata(i2c_adap); + struct ivtv *itv = to_ivtv(v4l2_dev); + int retval; + int i; + + mutex_lock(&itv->i2c_bus_lock); + for (i = retval = 0; retval == 0 && i < num; i++) { + if (msgs[i].flags & I2C_M_RD) + retval = ivtv_read(itv, msgs[i].addr, msgs[i].buf, msgs[i].len); + else { + /* if followed by a read, don't stop */ + int stop = !(i + 1 < num && msgs[i + 1].flags == I2C_M_RD); + + retval = ivtv_write(itv, msgs[i].addr, msgs[i].buf, msgs[i].len, stop); + } + } + mutex_unlock(&itv->i2c_bus_lock); + return retval ? retval : num; +} + +/* Kernel i2c capabilities */ +static u32 ivtv_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm ivtv_algo = { + .master_xfer = ivtv_xfer, + .functionality = ivtv_functionality, +}; + +/* template for our-bit banger */ +static const struct i2c_adapter ivtv_i2c_adap_hw_template = { + .name = "ivtv i2c driver", + .algo = &ivtv_algo, + .algo_data = NULL, /* filled from template */ + .owner = THIS_MODULE, +}; + +static void ivtv_setscl_old(void *data, int state) +{ + struct ivtv *itv = (struct ivtv *)data; + + if (state) + itv->i2c_state |= 0x01; + else + itv->i2c_state &= ~0x01; + + /* write them out */ + /* write bits are inverted */ + write_reg(~itv->i2c_state, IVTV_REG_I2C_SETSCL_OFFSET); +} + +static void ivtv_setsda_old(void *data, int state) +{ + struct ivtv *itv = (struct ivtv *)data; + + if (state) + itv->i2c_state |= 0x01; + else + itv->i2c_state &= ~0x01; + + /* write them out */ + /* write bits are inverted */ + write_reg(~itv->i2c_state, IVTV_REG_I2C_SETSDA_OFFSET); +} + +static int ivtv_getscl_old(void *data) +{ + struct ivtv *itv = (struct ivtv *)data; + + return read_reg(IVTV_REG_I2C_GETSCL_OFFSET) & 1; +} + +static int ivtv_getsda_old(void *data) +{ + struct ivtv *itv = (struct ivtv *)data; + + return read_reg(IVTV_REG_I2C_GETSDA_OFFSET) & 1; +} + +/* template for i2c-bit-algo */ +static const struct i2c_adapter ivtv_i2c_adap_template = { + .name = "ivtv i2c driver", + .algo = NULL, /* set by i2c-algo-bit */ + .algo_data = NULL, /* filled from template */ + .owner = THIS_MODULE, +}; + +#define IVTV_ALGO_BIT_TIMEOUT (2) /* seconds */ + +static const struct i2c_algo_bit_data ivtv_i2c_algo_template = { + .setsda = ivtv_setsda_old, + .setscl = ivtv_setscl_old, + .getsda = ivtv_getsda_old, + .getscl = ivtv_getscl_old, + .udelay = IVTV_DEFAULT_I2C_CLOCK_PERIOD / 2, /* microseconds */ + .timeout = IVTV_ALGO_BIT_TIMEOUT * HZ, /* jiffies */ +}; + +static const struct i2c_client ivtv_i2c_client_template = { + .name = "ivtv internal", +}; + +/* init + register i2c adapter */ +int init_ivtv_i2c(struct ivtv *itv) +{ + int retval; + + IVTV_DEBUG_I2C("i2c init\n"); + + /* Sanity checks for the I2C hardware arrays. They must be the + * same size. + */ + if (ARRAY_SIZE(hw_devicenames) != ARRAY_SIZE(hw_addrs)) { + IVTV_ERR("Mismatched I2C hardware arrays\n"); + return -ENODEV; + } + if (itv->options.newi2c > 0) { + itv->i2c_adap = ivtv_i2c_adap_hw_template; + } else { + itv->i2c_adap = ivtv_i2c_adap_template; + itv->i2c_algo = ivtv_i2c_algo_template; + } + itv->i2c_algo.udelay = itv->options.i2c_clock_period / 2; + itv->i2c_algo.data = itv; + itv->i2c_adap.algo_data = &itv->i2c_algo; + + sprintf(itv->i2c_adap.name + strlen(itv->i2c_adap.name), " #%d", + itv->instance); + i2c_set_adapdata(&itv->i2c_adap, &itv->v4l2_dev); + + itv->i2c_client = ivtv_i2c_client_template; + itv->i2c_client.adapter = &itv->i2c_adap; + itv->i2c_adap.dev.parent = &itv->pdev->dev; + + IVTV_DEBUG_I2C("setting scl and sda to 1\n"); + ivtv_setscl(itv, 1); + ivtv_setsda(itv, 1); + + if (itv->options.newi2c > 0) + retval = i2c_add_adapter(&itv->i2c_adap); + else + retval = i2c_bit_add_bus(&itv->i2c_adap); + + return retval; +} + +void exit_ivtv_i2c(struct ivtv *itv) +{ + IVTV_DEBUG_I2C("i2c exit\n"); + + i2c_del_adapter(&itv->i2c_adap); +} diff --git a/drivers/media/pci/ivtv/ivtv-i2c.h b/drivers/media/pci/ivtv/ivtv-i2c.h new file mode 100644 index 000000000..504bbc1de --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-i2c.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + I2C functions + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#ifndef IVTV_I2C_H +#define IVTV_I2C_H + +void ivtv_i2c_new_ir_legacy(struct ivtv *itv); +int ivtv_i2c_register(struct ivtv *itv, unsigned idx); +struct v4l2_subdev *ivtv_find_hw(struct ivtv *itv, u32 hw); + +/* init + register i2c adapter */ +int init_ivtv_i2c(struct ivtv *itv); +void exit_ivtv_i2c(struct ivtv *itv); + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-ioctl.c b/drivers/media/pci/ivtv/ivtv-ioctl.c new file mode 100644 index 000000000..fc95f0bf4 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-ioctl.c @@ -0,0 +1,1751 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + ioctl system call + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#include "ivtv-driver.h" +#include "ivtv-version.h" +#include "ivtv-mailbox.h" +#include "ivtv-i2c.h" +#include "ivtv-queue.h" +#include "ivtv-fileops.h" +#include "ivtv-vbi.h" +#include "ivtv-routing.h" +#include "ivtv-streams.h" +#include "ivtv-yuv.h" +#include "ivtv-ioctl.h" +#include "ivtv-gpio.h" +#include "ivtv-controls.h" +#include "ivtv-cards.h" +#include +#include +#include + +u16 ivtv_service2vbi(int type) +{ + switch (type) { + case V4L2_SLICED_TELETEXT_B: + return IVTV_SLICED_TYPE_TELETEXT_B; + case V4L2_SLICED_CAPTION_525: + return IVTV_SLICED_TYPE_CAPTION_525; + case V4L2_SLICED_WSS_625: + return IVTV_SLICED_TYPE_WSS_625; + case V4L2_SLICED_VPS: + return IVTV_SLICED_TYPE_VPS; + default: + return 0; + } +} + +static int valid_service_line(int field, int line, int is_pal) +{ + return (is_pal && line >= 6 && (line != 23 || field == 0)) || + (!is_pal && line >= 10 && line < 22); +} + +static u16 select_service_from_set(int field, int line, u16 set, int is_pal) +{ + u16 valid_set = (is_pal ? V4L2_SLICED_VBI_625 : V4L2_SLICED_VBI_525); + int i; + + set = set & valid_set; + if (set == 0 || !valid_service_line(field, line, is_pal)) { + return 0; + } + if (!is_pal) { + if (line == 21 && (set & V4L2_SLICED_CAPTION_525)) + return V4L2_SLICED_CAPTION_525; + } + else { + if (line == 16 && field == 0 && (set & V4L2_SLICED_VPS)) + return V4L2_SLICED_VPS; + if (line == 23 && field == 0 && (set & V4L2_SLICED_WSS_625)) + return V4L2_SLICED_WSS_625; + if (line == 23) + return 0; + } + for (i = 0; i < 32; i++) { + if (BIT(i) & set) + return BIT(i); + } + return 0; +} + +void ivtv_expand_service_set(struct v4l2_sliced_vbi_format *fmt, int is_pal) +{ + u16 set = fmt->service_set; + int f, l; + + fmt->service_set = 0; + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) { + fmt->service_lines[f][l] = select_service_from_set(f, l, set, is_pal); + } + } +} + +static void check_service_set(struct v4l2_sliced_vbi_format *fmt, int is_pal) +{ + int f, l; + + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) { + fmt->service_lines[f][l] = select_service_from_set(f, l, fmt->service_lines[f][l], is_pal); + } + } +} + +u16 ivtv_get_service_set(struct v4l2_sliced_vbi_format *fmt) +{ + int f, l; + u16 set = 0; + + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) { + set |= fmt->service_lines[f][l]; + } + } + return set; +} + +void ivtv_set_osd_alpha(struct ivtv *itv) +{ + ivtv_vapi(itv, CX2341X_OSD_SET_GLOBAL_ALPHA, 3, + itv->osd_global_alpha_state, itv->osd_global_alpha, !itv->osd_local_alpha_state); + ivtv_vapi(itv, CX2341X_OSD_SET_CHROMA_KEY, 2, itv->osd_chroma_key_state, itv->osd_chroma_key); +} + +int ivtv_set_speed(struct ivtv *itv, int speed) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + int single_step = (speed == 1 || speed == -1); + DEFINE_WAIT(wait); + + if (speed == 0) speed = 1000; + + /* No change? */ + if (speed == itv->speed && !single_step) + return 0; + + if (single_step && (speed < 0) == (itv->speed < 0)) { + /* Single step video and no need to change direction */ + ivtv_vapi(itv, CX2341X_DEC_STEP_VIDEO, 1, 0); + itv->speed = speed; + return 0; + } + if (single_step) + /* Need to change direction */ + speed = speed < 0 ? -1000 : 1000; + + data[0] = (speed > 1000 || speed < -1000) ? 0x80000000 : 0; + data[0] |= (speed > 1000 || speed < -1500) ? 0x40000000 : 0; + data[1] = (speed < 0); + data[2] = speed < 0 ? 3 : 7; + data[3] = v4l2_ctrl_g_ctrl(itv->cxhdl.video_b_frames); + data[4] = (speed == 1500 || speed == 500) ? itv->speed_mute_audio : 0; + data[5] = 0; + data[6] = 0; + + if (speed == 1500 || speed == -1500) data[0] |= 1; + else if (speed == 2000 || speed == -2000) data[0] |= 2; + else if (speed > -1000 && speed < 0) data[0] |= (-1000 / speed); + else if (speed < 1000 && speed > 0) data[0] |= (1000 / speed); + + /* If not decoding, just change speed setting */ + if (atomic_read(&itv->decoding) > 0) { + int got_sig = 0; + + /* Stop all DMA and decoding activity */ + ivtv_vapi(itv, CX2341X_DEC_PAUSE_PLAYBACK, 1, 0); + + /* Wait for any DMA to finish */ + mutex_unlock(&itv->serialize_lock); + prepare_to_wait(&itv->dma_waitq, &wait, TASK_INTERRUPTIBLE); + while (test_bit(IVTV_F_I_DMA, &itv->i_flags)) { + got_sig = signal_pending(current); + if (got_sig) + break; + got_sig = 0; + schedule(); + } + finish_wait(&itv->dma_waitq, &wait); + mutex_lock(&itv->serialize_lock); + if (got_sig) + return -EINTR; + + /* Change Speed safely */ + ivtv_api(itv, CX2341X_DEC_SET_PLAYBACK_SPEED, 7, data); + IVTV_DEBUG_INFO("Setting Speed to 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n", + data[0], data[1], data[2], data[3], data[4], data[5], data[6]); + } + if (single_step) { + speed = (speed < 0) ? -1 : 1; + ivtv_vapi(itv, CX2341X_DEC_STEP_VIDEO, 1, 0); + } + itv->speed = speed; + return 0; +} + +static int ivtv_validate_speed(int cur_speed, int new_speed) +{ + int fact = new_speed < 0 ? -1 : 1; + int s; + + if (cur_speed == 0) + cur_speed = 1000; + if (new_speed < 0) + new_speed = -new_speed; + if (cur_speed < 0) + cur_speed = -cur_speed; + + if (cur_speed <= new_speed) { + if (new_speed > 1500) + return fact * 2000; + if (new_speed > 1000) + return fact * 1500; + } + else { + if (new_speed >= 2000) + return fact * 2000; + if (new_speed >= 1500) + return fact * 1500; + if (new_speed >= 1000) + return fact * 1000; + } + if (new_speed == 0) + return 1000; + if (new_speed == 1 || new_speed == 1000) + return fact * new_speed; + + s = new_speed; + new_speed = 1000 / new_speed; + if (1000 / cur_speed == new_speed) + new_speed += (cur_speed < s) ? -1 : 1; + if (new_speed > 60) return 1000 / (fact * 60); + return 1000 / (fact * new_speed); +} + +static int ivtv_video_command(struct ivtv *itv, struct ivtv_open_id *id, + struct v4l2_decoder_cmd *dc, int try) +{ + struct ivtv_stream *s = &itv->streams[IVTV_DEC_STREAM_TYPE_MPG]; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + + switch (dc->cmd) { + case V4L2_DEC_CMD_START: { + dc->flags &= V4L2_DEC_CMD_START_MUTE_AUDIO; + dc->start.speed = ivtv_validate_speed(itv->speed, dc->start.speed); + if (dc->start.speed < 0) + dc->start.format = V4L2_DEC_START_FMT_GOP; + else + dc->start.format = V4L2_DEC_START_FMT_NONE; + if (dc->start.speed != 500 && dc->start.speed != 1500) + dc->flags = dc->start.speed == 1000 ? 0 : + V4L2_DEC_CMD_START_MUTE_AUDIO; + if (try) break; + + itv->speed_mute_audio = dc->flags & V4L2_DEC_CMD_START_MUTE_AUDIO; + if (ivtv_set_output_mode(itv, OUT_MPG) != OUT_MPG) + return -EBUSY; + if (test_and_clear_bit(IVTV_F_I_DEC_PAUSED, &itv->i_flags)) { + /* forces ivtv_set_speed to be called */ + itv->speed = 0; + } + return ivtv_start_decoding(id, dc->start.speed); + } + + case V4L2_DEC_CMD_STOP: + dc->flags &= V4L2_DEC_CMD_STOP_IMMEDIATELY | V4L2_DEC_CMD_STOP_TO_BLACK; + if (dc->flags & V4L2_DEC_CMD_STOP_IMMEDIATELY) + dc->stop.pts = 0; + if (try) break; + if (atomic_read(&itv->decoding) == 0) + return 0; + if (itv->output_mode != OUT_MPG) + return -EBUSY; + + itv->output_mode = OUT_NONE; + return ivtv_stop_v4l2_decode_stream(s, dc->flags, dc->stop.pts); + + case V4L2_DEC_CMD_PAUSE: + dc->flags &= V4L2_DEC_CMD_PAUSE_TO_BLACK; + if (try) break; + if (!atomic_read(&itv->decoding)) + return -EPERM; + if (itv->output_mode != OUT_MPG) + return -EBUSY; + if (atomic_read(&itv->decoding) > 0) { + ivtv_vapi(itv, CX2341X_DEC_PAUSE_PLAYBACK, 1, + (dc->flags & V4L2_DEC_CMD_PAUSE_TO_BLACK) ? 1 : 0); + set_bit(IVTV_F_I_DEC_PAUSED, &itv->i_flags); + } + break; + + case V4L2_DEC_CMD_RESUME: + dc->flags = 0; + if (try) break; + if (!atomic_read(&itv->decoding)) + return -EPERM; + if (itv->output_mode != OUT_MPG) + return -EBUSY; + if (test_and_clear_bit(IVTV_F_I_DEC_PAUSED, &itv->i_flags)) { + int speed = itv->speed; + itv->speed = 0; + return ivtv_start_decoding(id, speed); + } + break; + + default: + return -EINVAL; + } + return 0; +} + +static int ivtv_g_fmt_sliced_vbi_out(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv *itv = file2id(file)->itv; + struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced; + + vbifmt->reserved[0] = 0; + vbifmt->reserved[1] = 0; + if (!(itv->v4l2_cap & V4L2_CAP_SLICED_VBI_OUTPUT)) + return -EINVAL; + vbifmt->io_size = sizeof(struct v4l2_sliced_vbi_data) * 36; + memset(vbifmt->service_lines, 0, sizeof(vbifmt->service_lines)); + if (itv->is_60hz) { + vbifmt->service_lines[0][21] = V4L2_SLICED_CAPTION_525; + vbifmt->service_lines[1][21] = V4L2_SLICED_CAPTION_525; + } else { + vbifmt->service_lines[0][23] = V4L2_SLICED_WSS_625; + vbifmt->service_lines[0][16] = V4L2_SLICED_VPS; + } + vbifmt->service_set = ivtv_get_service_set(vbifmt); + return 0; +} + +static int ivtv_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + + pixfmt->width = itv->cxhdl.width; + pixfmt->height = itv->cxhdl.height; + pixfmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + pixfmt->field = V4L2_FIELD_INTERLACED; + if (id->type == IVTV_ENC_STREAM_TYPE_YUV) { + pixfmt->pixelformat = V4L2_PIX_FMT_NV12_16L16; + /* YUV size is (Y=(h*720) + UV=(h*(720/2))) */ + pixfmt->sizeimage = pixfmt->height * 720 * 3 / 2; + pixfmt->bytesperline = 720; + } else { + pixfmt->pixelformat = V4L2_PIX_FMT_MPEG; + pixfmt->sizeimage = 128 * 1024; + pixfmt->bytesperline = 0; + } + return 0; +} + +static int ivtv_g_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv *itv = file2id(file)->itv; + struct v4l2_vbi_format *vbifmt = &fmt->fmt.vbi; + + vbifmt->sampling_rate = 27000000; + vbifmt->offset = 248; + vbifmt->samples_per_line = itv->vbi.raw_decoder_line_size - 4; + vbifmt->sample_format = V4L2_PIX_FMT_GREY; + vbifmt->start[0] = itv->vbi.start[0]; + vbifmt->start[1] = itv->vbi.start[1]; + vbifmt->count[0] = vbifmt->count[1] = itv->vbi.count; + vbifmt->flags = 0; + vbifmt->reserved[0] = 0; + vbifmt->reserved[1] = 0; + return 0; +} + +static int ivtv_g_fmt_sliced_vbi_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced; + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + + vbifmt->reserved[0] = 0; + vbifmt->reserved[1] = 0; + vbifmt->io_size = sizeof(struct v4l2_sliced_vbi_data) * 36; + + if (id->type == IVTV_DEC_STREAM_TYPE_VBI) { + vbifmt->service_set = itv->is_50hz ? V4L2_SLICED_VBI_625 : + V4L2_SLICED_VBI_525; + ivtv_expand_service_set(vbifmt, itv->is_50hz); + vbifmt->service_set = ivtv_get_service_set(vbifmt); + return 0; + } + + v4l2_subdev_call(itv->sd_video, vbi, g_sliced_fmt, vbifmt); + vbifmt->service_set = ivtv_get_service_set(vbifmt); + return 0; +} + +static int ivtv_g_fmt_vid_out(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + pixfmt->width = itv->main_rect.width; + pixfmt->height = itv->main_rect.height; + pixfmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + pixfmt->field = V4L2_FIELD_INTERLACED; + if (id->type == IVTV_DEC_STREAM_TYPE_YUV) { + switch (itv->yuv_info.lace_mode & IVTV_YUV_MODE_MASK) { + case IVTV_YUV_MODE_INTERLACED: + pixfmt->field = (itv->yuv_info.lace_mode & IVTV_YUV_SYNC_MASK) ? + V4L2_FIELD_INTERLACED_BT : V4L2_FIELD_INTERLACED_TB; + break; + case IVTV_YUV_MODE_PROGRESSIVE: + pixfmt->field = V4L2_FIELD_NONE; + break; + default: + pixfmt->field = V4L2_FIELD_ANY; + break; + } + pixfmt->pixelformat = V4L2_PIX_FMT_NV12_16L16; + pixfmt->bytesperline = 720; + pixfmt->width = itv->yuv_info.v4l2_src_w; + pixfmt->height = itv->yuv_info.v4l2_src_h; + /* YUV size is (Y=(h*w) + UV=(h*(w/2))) */ + pixfmt->sizeimage = + 1080 * ((pixfmt->height + 31) & ~31); + } else { + pixfmt->pixelformat = V4L2_PIX_FMT_MPEG; + pixfmt->sizeimage = 128 * 1024; + pixfmt->bytesperline = 0; + } + return 0; +} + +static int ivtv_g_fmt_vid_out_overlay(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv *itv = file2id(file)->itv; + struct ivtv_stream *s = &itv->streams[file2id(file)->type]; + struct v4l2_window *winfmt = &fmt->fmt.win; + + if (!(s->vdev.device_caps & V4L2_CAP_VIDEO_OUTPUT_OVERLAY)) + return -EINVAL; + if (!itv->osd_video_pbase) + return -EINVAL; + winfmt->chromakey = itv->osd_chroma_key; + winfmt->global_alpha = itv->osd_global_alpha; + winfmt->field = V4L2_FIELD_INTERLACED; + winfmt->clips = NULL; + winfmt->clipcount = 0; + winfmt->bitmap = NULL; + winfmt->w.top = winfmt->w.left = 0; + winfmt->w.width = itv->osd_rect.width; + winfmt->w.height = itv->osd_rect.height; + return 0; +} + +static int ivtv_try_fmt_sliced_vbi_out(struct file *file, void *fh, struct v4l2_format *fmt) +{ + return ivtv_g_fmt_sliced_vbi_out(file, fh, fmt); +} + +static int ivtv_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + int w = fmt->fmt.pix.width; + int h = fmt->fmt.pix.height; + int min_h = 2; + + w = clamp(w, 2, 720); + if (id->type == IVTV_ENC_STREAM_TYPE_YUV) { + /* YUV height must be a multiple of 32 */ + h &= ~0x1f; + min_h = 32; + } + h = clamp(h, min_h, itv->is_50hz ? 576 : 480); + ivtv_g_fmt_vid_cap(file, fh, fmt); + fmt->fmt.pix.width = w; + fmt->fmt.pix.height = h; + return 0; +} + +static int ivtv_try_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + return ivtv_g_fmt_vbi_cap(file, fh, fmt); +} + +static int ivtv_try_fmt_sliced_vbi_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced; + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + + if (id->type == IVTV_DEC_STREAM_TYPE_VBI) + return ivtv_g_fmt_sliced_vbi_cap(file, fh, fmt); + + /* set sliced VBI capture format */ + vbifmt->io_size = sizeof(struct v4l2_sliced_vbi_data) * 36; + vbifmt->reserved[0] = 0; + vbifmt->reserved[1] = 0; + + if (vbifmt->service_set) + ivtv_expand_service_set(vbifmt, itv->is_50hz); + check_service_set(vbifmt, itv->is_50hz); + vbifmt->service_set = ivtv_get_service_set(vbifmt); + return 0; +} + +static int ivtv_try_fmt_vid_out(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv_open_id *id = file2id(file); + s32 w = fmt->fmt.pix.width; + s32 h = fmt->fmt.pix.height; + int field = fmt->fmt.pix.field; + int ret = ivtv_g_fmt_vid_out(file, fh, fmt); + + w = clamp(w, 2, 720); + /* Why can the height be 576 even when the output is NTSC? + + Internally the buffers of the PVR350 are always set to 720x576. The + decoded video frame will always be placed in the top left corner of + this buffer. For any video which is not 720x576, the buffer will + then be cropped to remove the unused right and lower areas, with + the remaining image being scaled by the hardware to fit the display + area. The video can be scaled both up and down, so a 720x480 video + can be displayed full-screen on PAL and a 720x576 video can be + displayed without cropping on NTSC. + + Note that the scaling only occurs on the video stream, the osd + resolution is locked to the broadcast standard and not scaled. + + Thanks to Ian Armstrong for this explanation. */ + h = clamp(h, 2, 576); + if (id->type == IVTV_DEC_STREAM_TYPE_YUV) + fmt->fmt.pix.field = field; + fmt->fmt.pix.width = w; + fmt->fmt.pix.height = h; + return ret; +} + +static int ivtv_try_fmt_vid_out_overlay(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv *itv = file2id(file)->itv; + struct ivtv_stream *s = &itv->streams[file2id(file)->type]; + u32 chromakey = fmt->fmt.win.chromakey; + u8 global_alpha = fmt->fmt.win.global_alpha; + + if (!(s->vdev.device_caps & V4L2_CAP_VIDEO_OUTPUT_OVERLAY)) + return -EINVAL; + if (!itv->osd_video_pbase) + return -EINVAL; + ivtv_g_fmt_vid_out_overlay(file, fh, fmt); + fmt->fmt.win.chromakey = chromakey; + fmt->fmt.win.global_alpha = global_alpha; + return 0; +} + +static int ivtv_s_fmt_sliced_vbi_out(struct file *file, void *fh, struct v4l2_format *fmt) +{ + return ivtv_g_fmt_sliced_vbi_out(file, fh, fmt); +} + +static int ivtv_s_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret = ivtv_try_fmt_vid_cap(file, fh, fmt); + int w = fmt->fmt.pix.width; + int h = fmt->fmt.pix.height; + + if (ret) + return ret; + + if (itv->cxhdl.width == w && itv->cxhdl.height == h) + return 0; + + if (atomic_read(&itv->capturing) > 0) + return -EBUSY; + + itv->cxhdl.width = w; + itv->cxhdl.height = h; + if (v4l2_ctrl_g_ctrl(itv->cxhdl.video_encoding) == V4L2_MPEG_VIDEO_ENCODING_MPEG_1) + fmt->fmt.pix.width /= 2; + format.format.width = fmt->fmt.pix.width; + format.format.height = h; + format.format.code = MEDIA_BUS_FMT_FIXED; + v4l2_subdev_call(itv->sd_video, pad, set_fmt, NULL, &format); + return ivtv_g_fmt_vid_cap(file, fh, fmt); +} + +static int ivtv_s_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv *itv = file2id(file)->itv; + + if (!ivtv_raw_vbi(itv) && atomic_read(&itv->capturing) > 0) + return -EBUSY; + itv->vbi.sliced_in->service_set = 0; + itv->vbi.in.type = V4L2_BUF_TYPE_VBI_CAPTURE; + v4l2_subdev_call(itv->sd_video, vbi, s_raw_fmt, &fmt->fmt.vbi); + return ivtv_g_fmt_vbi_cap(file, fh, fmt); +} + +static int ivtv_s_fmt_sliced_vbi_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced; + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + int ret = ivtv_try_fmt_sliced_vbi_cap(file, fh, fmt); + + if (ret || id->type == IVTV_DEC_STREAM_TYPE_VBI) + return ret; + + check_service_set(vbifmt, itv->is_50hz); + if (ivtv_raw_vbi(itv) && atomic_read(&itv->capturing) > 0) + return -EBUSY; + itv->vbi.in.type = V4L2_BUF_TYPE_SLICED_VBI_CAPTURE; + v4l2_subdev_call(itv->sd_video, vbi, s_sliced_fmt, vbifmt); + memcpy(itv->vbi.sliced_in, vbifmt, sizeof(*itv->vbi.sliced_in)); + return 0; +} + +static int ivtv_s_fmt_vid_out(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + struct yuv_playback_info *yi = &itv->yuv_info; + int ret = ivtv_try_fmt_vid_out(file, fh, fmt); + + if (ret) + return ret; + + if (id->type != IVTV_DEC_STREAM_TYPE_YUV) + return 0; + + /* Return now if we already have some frame data */ + if (yi->stream_size) + return -EBUSY; + + yi->v4l2_src_w = fmt->fmt.pix.width; + yi->v4l2_src_h = fmt->fmt.pix.height; + + switch (fmt->fmt.pix.field) { + case V4L2_FIELD_NONE: + yi->lace_mode = IVTV_YUV_MODE_PROGRESSIVE; + break; + case V4L2_FIELD_ANY: + yi->lace_mode = IVTV_YUV_MODE_AUTO; + break; + case V4L2_FIELD_INTERLACED_BT: + yi->lace_mode = + IVTV_YUV_MODE_INTERLACED|IVTV_YUV_SYNC_ODD; + break; + case V4L2_FIELD_INTERLACED_TB: + default: + yi->lace_mode = IVTV_YUV_MODE_INTERLACED; + break; + } + yi->lace_sync_field = (yi->lace_mode & IVTV_YUV_SYNC_MASK) == IVTV_YUV_SYNC_EVEN ? 0 : 1; + + if (test_bit(IVTV_F_I_DEC_YUV, &itv->i_flags)) + itv->dma_data_req_size = + 1080 * ((yi->v4l2_src_h + 31) & ~31); + + return 0; +} + +static int ivtv_s_fmt_vid_out_overlay(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv *itv = file2id(file)->itv; + int ret = ivtv_try_fmt_vid_out_overlay(file, fh, fmt); + + if (ret == 0) { + itv->osd_chroma_key = fmt->fmt.win.chromakey; + itv->osd_global_alpha = fmt->fmt.win.global_alpha; + ivtv_set_osd_alpha(itv); + } + return ret; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ivtv_itvc(struct ivtv *itv, bool get, u64 reg, u64 *val) +{ + volatile u8 __iomem *reg_start; + + if (reg & 0x3) + return -EINVAL; + if (reg >= IVTV_REG_OFFSET && reg < IVTV_REG_OFFSET + IVTV_REG_SIZE) + reg_start = itv->reg_mem - IVTV_REG_OFFSET; + else if (itv->has_cx23415 && reg >= IVTV_DECODER_OFFSET && + reg < IVTV_DECODER_OFFSET + IVTV_DECODER_SIZE) + reg_start = itv->dec_mem - IVTV_DECODER_OFFSET; + else if (reg < IVTV_ENCODER_SIZE) + reg_start = itv->enc_mem; + else + return -EINVAL; + + if (get) + *val = readl(reg + reg_start); + else + writel(*val, reg + reg_start); + return 0; +} + +static int ivtv_g_register(struct file *file, void *fh, struct v4l2_dbg_register *reg) +{ + struct ivtv *itv = file2id(file)->itv; + + reg->size = 4; + return ivtv_itvc(itv, true, reg->reg, ®->val); +} + +static int ivtv_s_register(struct file *file, void *fh, const struct v4l2_dbg_register *reg) +{ + struct ivtv *itv = file2id(file)->itv; + u64 val = reg->val; + + return ivtv_itvc(itv, false, reg->reg, &val); +} +#endif + +static int ivtv_querycap(struct file *file, void *fh, struct v4l2_capability *vcap) +{ + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + + strscpy(vcap->driver, IVTV_DRIVER_NAME, sizeof(vcap->driver)); + strscpy(vcap->card, itv->card_name, sizeof(vcap->card)); + vcap->capabilities = itv->v4l2_cap | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int ivtv_enumaudio(struct file *file, void *fh, struct v4l2_audio *vin) +{ + struct ivtv *itv = file2id(file)->itv; + + return ivtv_get_audio_input(itv, vin->index, vin); +} + +static int ivtv_g_audio(struct file *file, void *fh, struct v4l2_audio *vin) +{ + struct ivtv *itv = file2id(file)->itv; + + vin->index = itv->audio_input; + return ivtv_get_audio_input(itv, vin->index, vin); +} + +static int ivtv_s_audio(struct file *file, void *fh, const struct v4l2_audio *vout) +{ + struct ivtv *itv = file2id(file)->itv; + + if (vout->index >= itv->nof_audio_inputs) + return -EINVAL; + + itv->audio_input = vout->index; + ivtv_audio_set_io(itv); + + return 0; +} + +static int ivtv_enumaudout(struct file *file, void *fh, struct v4l2_audioout *vin) +{ + struct ivtv *itv = file2id(file)->itv; + + /* set it to defaults from our table */ + return ivtv_get_audio_output(itv, vin->index, vin); +} + +static int ivtv_g_audout(struct file *file, void *fh, struct v4l2_audioout *vin) +{ + struct ivtv *itv = file2id(file)->itv; + + vin->index = 0; + return ivtv_get_audio_output(itv, vin->index, vin); +} + +static int ivtv_s_audout(struct file *file, void *fh, const struct v4l2_audioout *vout) +{ + struct ivtv *itv = file2id(file)->itv; + + if (itv->card->video_outputs == NULL || vout->index != 0) + return -EINVAL; + return 0; +} + +static int ivtv_enum_input(struct file *file, void *fh, struct v4l2_input *vin) +{ + struct ivtv *itv = file2id(file)->itv; + + /* set it to defaults from our table */ + return ivtv_get_input(itv, vin->index, vin); +} + +static int ivtv_enum_output(struct file *file, void *fh, struct v4l2_output *vout) +{ + struct ivtv *itv = file2id(file)->itv; + + return ivtv_get_output(itv, vout->index, vout); +} + +static int ivtv_g_pixelaspect(struct file *file, void *fh, + int type, struct v4l2_fract *f) +{ + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + f->numerator = itv->is_50hz ? 54 : 11; + f->denominator = itv->is_50hz ? 59 : 10; + } else if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + f->numerator = itv->is_out_50hz ? 54 : 11; + f->denominator = itv->is_out_50hz ? 59 : 10; + } else { + return -EINVAL; + } + return 0; +} + +static int ivtv_s_selection(struct file *file, void *fh, + struct v4l2_selection *sel) +{ + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + struct yuv_playback_info *yi = &itv->yuv_info; + struct v4l2_rect r = { 0, 0, 720, 0 }; + int streamtype = id->type; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || + !(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + + if (sel->target != V4L2_SEL_TGT_COMPOSE) + return -EINVAL; + + + if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || + !(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + + r.height = itv->is_out_50hz ? 576 : 480; + if (streamtype == IVTV_DEC_STREAM_TYPE_YUV && yi->track_osd) { + r.width = yi->osd_full_w; + r.height = yi->osd_full_h; + } + sel->r.width = clamp(sel->r.width, 16U, r.width); + sel->r.height = clamp(sel->r.height, 16U, r.height); + sel->r.left = clamp_t(unsigned, sel->r.left, 0, r.width - sel->r.width); + sel->r.top = clamp_t(unsigned, sel->r.top, 0, r.height - sel->r.height); + + if (streamtype == IVTV_DEC_STREAM_TYPE_YUV) { + yi->main_rect = sel->r; + return 0; + } + if (!ivtv_vapi(itv, CX2341X_OSD_SET_FRAMEBUFFER_WINDOW, 4, + sel->r.width, sel->r.height, sel->r.left, sel->r.top)) { + itv->main_rect = sel->r; + return 0; + } + return -EINVAL; +} + +static int ivtv_g_selection(struct file *file, void *fh, + struct v4l2_selection *sel) +{ + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + struct yuv_playback_info *yi = &itv->yuv_info; + struct v4l2_rect r = { 0, 0, 720, 0 }; + int streamtype = id->type; + + if (sel->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + switch (sel->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = sel->r.left = 0; + sel->r.width = 720; + sel->r.height = itv->is_50hz ? 576 : 480; + return 0; + default: + return -EINVAL; + } + } + + if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || + !(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_COMPOSE: + if (streamtype == IVTV_DEC_STREAM_TYPE_YUV) + sel->r = yi->main_rect; + else + sel->r = itv->main_rect; + return 0; + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + r.height = itv->is_out_50hz ? 576 : 480; + if (streamtype == IVTV_DEC_STREAM_TYPE_YUV && yi->track_osd) { + r.width = yi->osd_full_w; + r.height = yi->osd_full_h; + } + sel->r = r; + return 0; + } + return -EINVAL; +} + +static int ivtv_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *fmt) +{ + static const struct v4l2_fmtdesc hm12 = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .description = "HM12 (YUV 4:2:0)", + .pixelformat = V4L2_PIX_FMT_NV12_16L16, + }; + static const struct v4l2_fmtdesc mpeg = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = V4L2_FMT_FLAG_COMPRESSED, + .description = "MPEG", + .pixelformat = V4L2_PIX_FMT_MPEG, + }; + struct ivtv *itv = file2id(file)->itv; + struct ivtv_stream *s = &itv->streams[file2id(file)->type]; + + if (fmt->index) + return -EINVAL; + if (s->type == IVTV_ENC_STREAM_TYPE_MPG) + *fmt = mpeg; + else if (s->type == IVTV_ENC_STREAM_TYPE_YUV) + *fmt = hm12; + else + return -EINVAL; + return 0; +} + +static int ivtv_enum_fmt_vid_out(struct file *file, void *fh, struct v4l2_fmtdesc *fmt) +{ + static const struct v4l2_fmtdesc hm12 = { + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT, + .description = "HM12 (YUV 4:2:0)", + .pixelformat = V4L2_PIX_FMT_NV12_16L16, + }; + static const struct v4l2_fmtdesc mpeg = { + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT, + .flags = V4L2_FMT_FLAG_COMPRESSED, + .description = "MPEG", + .pixelformat = V4L2_PIX_FMT_MPEG, + }; + struct ivtv *itv = file2id(file)->itv; + struct ivtv_stream *s = &itv->streams[file2id(file)->type]; + + if (fmt->index) + return -EINVAL; + if (s->type == IVTV_DEC_STREAM_TYPE_MPG) + *fmt = mpeg; + else if (s->type == IVTV_DEC_STREAM_TYPE_YUV) + *fmt = hm12; + else + return -EINVAL; + return 0; +} + +static int ivtv_g_input(struct file *file, void *fh, unsigned int *i) +{ + struct ivtv *itv = file2id(file)->itv; + + *i = itv->active_input; + + return 0; +} + +int ivtv_do_s_input(struct ivtv *itv, unsigned int inp) +{ + v4l2_std_id std; + int i; + + if (inp >= itv->nof_inputs) + return -EINVAL; + + if (inp == itv->active_input) { + IVTV_DEBUG_INFO("Input unchanged\n"); + return 0; + } + + if (atomic_read(&itv->capturing) > 0) { + return -EBUSY; + } + + IVTV_DEBUG_INFO("Changing input from %d to %d\n", + itv->active_input, inp); + + itv->active_input = inp; + /* Set the audio input to whatever is appropriate for the + input type. */ + itv->audio_input = itv->card->video_inputs[inp].audio_index; + + if (itv->card->video_inputs[inp].video_type == IVTV_CARD_INPUT_VID_TUNER) + std = itv->tuner_std; + else + std = V4L2_STD_ALL; + for (i = 0; i <= IVTV_ENC_STREAM_TYPE_VBI; i++) + itv->streams[i].vdev.tvnorms = std; + + /* prevent others from messing with the streams until + we're finished changing inputs. */ + ivtv_mute(itv); + ivtv_video_set_io(itv); + ivtv_audio_set_io(itv); + ivtv_unmute(itv); + + return 0; +} + +static int ivtv_s_input(struct file *file, void *fh, unsigned int inp) +{ + return ivtv_do_s_input(file2id(file)->itv, inp); +} + +static int ivtv_g_output(struct file *file, void *fh, unsigned int *i) +{ + struct ivtv *itv = file2id(file)->itv; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + + *i = itv->active_output; + + return 0; +} + +static int ivtv_s_output(struct file *file, void *fh, unsigned int outp) +{ + struct ivtv *itv = file2id(file)->itv; + + if (outp >= itv->card->nof_outputs) + return -EINVAL; + + if (outp == itv->active_output) { + IVTV_DEBUG_INFO("Output unchanged\n"); + return 0; + } + IVTV_DEBUG_INFO("Changing output from %d to %d\n", + itv->active_output, outp); + + itv->active_output = outp; + ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_routing, + SAA7127_INPUT_TYPE_NORMAL, + itv->card->video_outputs[outp].video_output, 0); + + return 0; +} + +static int ivtv_g_frequency(struct file *file, void *fh, struct v4l2_frequency *vf) +{ + struct ivtv *itv = file2id(file)->itv; + struct ivtv_stream *s = &itv->streams[file2id(file)->type]; + + if (s->vdev.vfl_dir) + return -ENOTTY; + if (vf->tuner != 0) + return -EINVAL; + + ivtv_call_all(itv, tuner, g_frequency, vf); + return 0; +} + +int ivtv_do_s_frequency(struct ivtv_stream *s, const struct v4l2_frequency *vf) +{ + struct ivtv *itv = s->itv; + + if (s->vdev.vfl_dir) + return -ENOTTY; + if (vf->tuner != 0) + return -EINVAL; + + ivtv_mute(itv); + IVTV_DEBUG_INFO("v4l2 ioctl: set frequency %d\n", vf->frequency); + ivtv_call_all(itv, tuner, s_frequency, vf); + ivtv_unmute(itv); + return 0; +} + +static int ivtv_s_frequency(struct file *file, void *fh, + const struct v4l2_frequency *vf) +{ + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + + return ivtv_do_s_frequency(&itv->streams[id->type], vf); +} + +static int ivtv_g_std(struct file *file, void *fh, v4l2_std_id *std) +{ + struct ivtv *itv = file2id(file)->itv; + + *std = itv->std; + return 0; +} + +void ivtv_s_std_enc(struct ivtv *itv, v4l2_std_id std) +{ + itv->std = std; + itv->is_60hz = (std & V4L2_STD_525_60) ? 1 : 0; + itv->is_50hz = !itv->is_60hz; + cx2341x_handler_set_50hz(&itv->cxhdl, itv->is_50hz); + itv->cxhdl.width = 720; + itv->cxhdl.height = itv->is_50hz ? 576 : 480; + itv->vbi.count = itv->is_50hz ? 18 : 12; + itv->vbi.start[0] = itv->is_50hz ? 6 : 10; + itv->vbi.start[1] = itv->is_50hz ? 318 : 273; + + if (itv->hw_flags & IVTV_HW_CX25840) + itv->vbi.sliced_decoder_line_size = itv->is_60hz ? 272 : 284; + + /* Tuner */ + ivtv_call_all(itv, video, s_std, itv->std); +} + +void ivtv_s_std_dec(struct ivtv *itv, v4l2_std_id std) +{ + struct yuv_playback_info *yi = &itv->yuv_info; + DEFINE_WAIT(wait); + int f; + + /* set display standard */ + itv->std_out = std; + itv->is_out_60hz = (std & V4L2_STD_525_60) ? 1 : 0; + itv->is_out_50hz = !itv->is_out_60hz; + ivtv_call_all(itv, video, s_std_output, itv->std_out); + + /* + * The next firmware call is time sensitive. Time it to + * avoid risk of a hard lock, by trying to ensure the call + * happens within the first 100 lines of the top field. + * Make 4 attempts to sync to the decoder before giving up. + */ + mutex_unlock(&itv->serialize_lock); + for (f = 0; f < 4; f++) { + prepare_to_wait(&itv->vsync_waitq, &wait, + TASK_UNINTERRUPTIBLE); + if ((read_reg(IVTV_REG_DEC_LINE_FIELD) >> 16) < 100) + break; + schedule_timeout(msecs_to_jiffies(25)); + } + finish_wait(&itv->vsync_waitq, &wait); + mutex_lock(&itv->serialize_lock); + + if (f == 4) + IVTV_WARN("Mode change failed to sync to decoder\n"); + + ivtv_vapi(itv, CX2341X_DEC_SET_STANDARD, 1, itv->is_out_50hz); + itv->main_rect.left = 0; + itv->main_rect.top = 0; + itv->main_rect.width = 720; + itv->main_rect.height = itv->is_out_50hz ? 576 : 480; + ivtv_vapi(itv, CX2341X_OSD_SET_FRAMEBUFFER_WINDOW, 4, + 720, itv->main_rect.height, 0, 0); + yi->main_rect = itv->main_rect; + if (!itv->osd_info) { + yi->osd_full_w = 720; + yi->osd_full_h = itv->is_out_50hz ? 576 : 480; + } +} + +static int ivtv_s_std(struct file *file, void *fh, v4l2_std_id std) +{ + struct ivtv *itv = file2id(file)->itv; + + if ((std & V4L2_STD_ALL) == 0) + return -EINVAL; + + if (std == itv->std) + return 0; + + if (test_bit(IVTV_F_I_RADIO_USER, &itv->i_flags) || + atomic_read(&itv->capturing) > 0 || + atomic_read(&itv->decoding) > 0) { + /* Switching standard would mess with already running + streams, prevent that by returning EBUSY. */ + return -EBUSY; + } + + IVTV_DEBUG_INFO("Switching standard to %llx.\n", + (unsigned long long)itv->std); + + ivtv_s_std_enc(itv, std); + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) + ivtv_s_std_dec(itv, std); + + return 0; +} + +static int ivtv_s_tuner(struct file *file, void *fh, const struct v4l2_tuner *vt) +{ + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + + if (vt->index != 0) + return -EINVAL; + + ivtv_call_all(itv, tuner, s_tuner, vt); + + return 0; +} + +static int ivtv_g_tuner(struct file *file, void *fh, struct v4l2_tuner *vt) +{ + struct ivtv *itv = file2id(file)->itv; + + if (vt->index != 0) + return -EINVAL; + + ivtv_call_all(itv, tuner, g_tuner, vt); + + if (vt->type == V4L2_TUNER_RADIO) + strscpy(vt->name, "ivtv Radio Tuner", sizeof(vt->name)); + else + strscpy(vt->name, "ivtv TV Tuner", sizeof(vt->name)); + return 0; +} + +static int ivtv_g_sliced_vbi_cap(struct file *file, void *fh, struct v4l2_sliced_vbi_cap *cap) +{ + struct ivtv *itv = file2id(file)->itv; + int set = itv->is_50hz ? V4L2_SLICED_VBI_625 : V4L2_SLICED_VBI_525; + int f, l; + + if (cap->type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) { + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) { + if (valid_service_line(f, l, itv->is_50hz)) + cap->service_lines[f][l] = set; + } + } + } else if (cap->type == V4L2_BUF_TYPE_SLICED_VBI_OUTPUT) { + if (!(itv->v4l2_cap & V4L2_CAP_SLICED_VBI_OUTPUT)) + return -EINVAL; + if (itv->is_60hz) { + cap->service_lines[0][21] = V4L2_SLICED_CAPTION_525; + cap->service_lines[1][21] = V4L2_SLICED_CAPTION_525; + } else { + cap->service_lines[0][23] = V4L2_SLICED_WSS_625; + cap->service_lines[0][16] = V4L2_SLICED_VPS; + } + } else { + return -EINVAL; + } + + set = 0; + for (f = 0; f < 2; f++) + for (l = 0; l < 24; l++) + set |= cap->service_lines[f][l]; + cap->service_set = set; + return 0; +} + +static int ivtv_g_enc_index(struct file *file, void *fh, struct v4l2_enc_idx *idx) +{ + struct ivtv *itv = file2id(file)->itv; + struct v4l2_enc_idx_entry *e = idx->entry; + int entries; + int i; + + entries = (itv->pgm_info_write_idx + IVTV_MAX_PGM_INDEX - itv->pgm_info_read_idx) % + IVTV_MAX_PGM_INDEX; + if (entries > V4L2_ENC_IDX_ENTRIES) + entries = V4L2_ENC_IDX_ENTRIES; + idx->entries = 0; + idx->entries_cap = IVTV_MAX_PGM_INDEX; + if (!atomic_read(&itv->capturing)) + return 0; + for (i = 0; i < entries; i++) { + *e = itv->pgm_info[(itv->pgm_info_read_idx + i) % IVTV_MAX_PGM_INDEX]; + if ((e->flags & V4L2_ENC_IDX_FRAME_MASK) <= V4L2_ENC_IDX_FRAME_B) { + idx->entries++; + e++; + } + } + itv->pgm_info_read_idx = (itv->pgm_info_read_idx + idx->entries) % IVTV_MAX_PGM_INDEX; + return 0; +} + +static int ivtv_encoder_cmd(struct file *file, void *fh, struct v4l2_encoder_cmd *enc) +{ + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + + + switch (enc->cmd) { + case V4L2_ENC_CMD_START: + IVTV_DEBUG_IOCTL("V4L2_ENC_CMD_START\n"); + enc->flags = 0; + return ivtv_start_capture(id); + + case V4L2_ENC_CMD_STOP: + IVTV_DEBUG_IOCTL("V4L2_ENC_CMD_STOP\n"); + enc->flags &= V4L2_ENC_CMD_STOP_AT_GOP_END; + ivtv_stop_capture(id, enc->flags & V4L2_ENC_CMD_STOP_AT_GOP_END); + return 0; + + case V4L2_ENC_CMD_PAUSE: + IVTV_DEBUG_IOCTL("V4L2_ENC_CMD_PAUSE\n"); + enc->flags = 0; + + if (!atomic_read(&itv->capturing)) + return -EPERM; + if (test_and_set_bit(IVTV_F_I_ENC_PAUSED, &itv->i_flags)) + return 0; + + ivtv_mute(itv); + ivtv_vapi(itv, CX2341X_ENC_PAUSE_ENCODER, 1, 0); + break; + + case V4L2_ENC_CMD_RESUME: + IVTV_DEBUG_IOCTL("V4L2_ENC_CMD_RESUME\n"); + enc->flags = 0; + + if (!atomic_read(&itv->capturing)) + return -EPERM; + + if (!test_and_clear_bit(IVTV_F_I_ENC_PAUSED, &itv->i_flags)) + return 0; + + ivtv_vapi(itv, CX2341X_ENC_PAUSE_ENCODER, 1, 1); + ivtv_unmute(itv); + break; + default: + IVTV_DEBUG_IOCTL("Unknown cmd %d\n", enc->cmd); + return -EINVAL; + } + + return 0; +} + +static int ivtv_try_encoder_cmd(struct file *file, void *fh, struct v4l2_encoder_cmd *enc) +{ + struct ivtv *itv = file2id(file)->itv; + + switch (enc->cmd) { + case V4L2_ENC_CMD_START: + IVTV_DEBUG_IOCTL("V4L2_ENC_CMD_START\n"); + enc->flags = 0; + return 0; + + case V4L2_ENC_CMD_STOP: + IVTV_DEBUG_IOCTL("V4L2_ENC_CMD_STOP\n"); + enc->flags &= V4L2_ENC_CMD_STOP_AT_GOP_END; + return 0; + + case V4L2_ENC_CMD_PAUSE: + IVTV_DEBUG_IOCTL("V4L2_ENC_CMD_PAUSE\n"); + enc->flags = 0; + return 0; + + case V4L2_ENC_CMD_RESUME: + IVTV_DEBUG_IOCTL("V4L2_ENC_CMD_RESUME\n"); + enc->flags = 0; + return 0; + default: + IVTV_DEBUG_IOCTL("Unknown cmd %d\n", enc->cmd); + return -EINVAL; + } +} + +static int ivtv_g_fbuf(struct file *file, void *fh, struct v4l2_framebuffer *fb) +{ + struct ivtv *itv = file2id(file)->itv; + struct ivtv_stream *s = &itv->streams[file2id(file)->type]; + u32 data[CX2341X_MBOX_MAX_DATA]; + struct yuv_playback_info *yi = &itv->yuv_info; + + int pixfmt; + static u32 pixel_format[16] = { + V4L2_PIX_FMT_PAL8, /* Uses a 256-entry RGB colormap */ + V4L2_PIX_FMT_RGB565, + V4L2_PIX_FMT_RGB555, + V4L2_PIX_FMT_RGB444, + V4L2_PIX_FMT_RGB32, + 0, + 0, + 0, + V4L2_PIX_FMT_PAL8, /* Uses a 256-entry YUV colormap */ + V4L2_PIX_FMT_YUV565, + V4L2_PIX_FMT_YUV555, + V4L2_PIX_FMT_YUV444, + V4L2_PIX_FMT_YUV32, + 0, + 0, + 0, + }; + + if (!(s->vdev.device_caps & V4L2_CAP_VIDEO_OUTPUT_OVERLAY)) + return -ENOTTY; + if (!itv->osd_video_pbase) + return -ENOTTY; + + fb->capability = V4L2_FBUF_CAP_EXTERNOVERLAY | V4L2_FBUF_CAP_CHROMAKEY | + V4L2_FBUF_CAP_GLOBAL_ALPHA; + + ivtv_vapi_result(itv, data, CX2341X_OSD_GET_STATE, 0); + data[0] |= (read_reg(0x2a00) >> 7) & 0x40; + pixfmt = (data[0] >> 3) & 0xf; + + fb->fmt.pixelformat = pixel_format[pixfmt]; + fb->fmt.width = itv->osd_rect.width; + fb->fmt.height = itv->osd_rect.height; + fb->fmt.field = V4L2_FIELD_INTERLACED; + fb->fmt.bytesperline = fb->fmt.width; + fb->fmt.colorspace = V4L2_COLORSPACE_SMPTE170M; + fb->fmt.field = V4L2_FIELD_INTERLACED; + if (fb->fmt.pixelformat != V4L2_PIX_FMT_PAL8) + fb->fmt.bytesperline *= 2; + if (fb->fmt.pixelformat == V4L2_PIX_FMT_RGB32 || + fb->fmt.pixelformat == V4L2_PIX_FMT_YUV32) + fb->fmt.bytesperline *= 2; + fb->fmt.sizeimage = fb->fmt.bytesperline * fb->fmt.height; + fb->base = (void *)itv->osd_video_pbase; + fb->flags = 0; + + if (itv->osd_chroma_key_state) + fb->flags |= V4L2_FBUF_FLAG_CHROMAKEY; + + if (itv->osd_global_alpha_state) + fb->flags |= V4L2_FBUF_FLAG_GLOBAL_ALPHA; + + if (yi->track_osd) + fb->flags |= V4L2_FBUF_FLAG_OVERLAY; + + pixfmt &= 7; + + /* no local alpha for RGB565 or unknown formats */ + if (pixfmt == 1 || pixfmt > 4) + return 0; + + /* 16-bit formats have inverted local alpha */ + if (pixfmt == 2 || pixfmt == 3) + fb->capability |= V4L2_FBUF_CAP_LOCAL_INV_ALPHA; + else + fb->capability |= V4L2_FBUF_CAP_LOCAL_ALPHA; + + if (itv->osd_local_alpha_state) { + /* 16-bit formats have inverted local alpha */ + if (pixfmt == 2 || pixfmt == 3) + fb->flags |= V4L2_FBUF_FLAG_LOCAL_INV_ALPHA; + else + fb->flags |= V4L2_FBUF_FLAG_LOCAL_ALPHA; + } + + return 0; +} + +static int ivtv_s_fbuf(struct file *file, void *fh, const struct v4l2_framebuffer *fb) +{ + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[file2id(file)->type]; + struct yuv_playback_info *yi = &itv->yuv_info; + + if (!(s->vdev.device_caps & V4L2_CAP_VIDEO_OUTPUT_OVERLAY)) + return -ENOTTY; + if (!itv->osd_video_pbase) + return -ENOTTY; + + itv->osd_global_alpha_state = (fb->flags & V4L2_FBUF_FLAG_GLOBAL_ALPHA) != 0; + itv->osd_local_alpha_state = + (fb->flags & (V4L2_FBUF_FLAG_LOCAL_ALPHA|V4L2_FBUF_FLAG_LOCAL_INV_ALPHA)) != 0; + itv->osd_chroma_key_state = (fb->flags & V4L2_FBUF_FLAG_CHROMAKEY) != 0; + ivtv_set_osd_alpha(itv); + yi->track_osd = (fb->flags & V4L2_FBUF_FLAG_OVERLAY) != 0; + return 0; +} + +static int ivtv_overlay(struct file *file, void *fh, unsigned int on) +{ + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[file2id(file)->type]; + + if (!(s->vdev.device_caps & V4L2_CAP_VIDEO_OUTPUT_OVERLAY)) + return -ENOTTY; + if (!itv->osd_video_pbase) + return -ENOTTY; + + ivtv_vapi(itv, CX2341X_OSD_SET_STATE, 1, on != 0); + + return 0; +} + +static int ivtv_subscribe_event(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_VSYNC: + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 0, NULL); + default: + return v4l2_ctrl_subscribe_event(fh, sub); + } +} + +static int ivtv_log_status(struct file *file, void *fh) +{ + struct ivtv *itv = file2id(file)->itv; + u32 data[CX2341X_MBOX_MAX_DATA]; + + int has_output = itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT; + struct v4l2_input vidin; + struct v4l2_audio audin; + int i; + + IVTV_INFO("Version: %s Card: %s\n", IVTV_VERSION, itv->card_name); + if (itv->hw_flags & IVTV_HW_TVEEPROM) { + struct tveeprom tv; + + ivtv_read_eeprom(itv, &tv); + } + ivtv_call_all(itv, core, log_status); + ivtv_get_input(itv, itv->active_input, &vidin); + ivtv_get_audio_input(itv, itv->audio_input, &audin); + IVTV_INFO("Video Input: %s\n", vidin.name); + IVTV_INFO("Audio Input: %s%s\n", audin.name, + itv->dualwatch_stereo_mode == V4L2_MPEG_AUDIO_MODE_DUAL ? + " (Bilingual)" : ""); + if (has_output) { + struct v4l2_output vidout; + struct v4l2_audioout audout; + int mode = itv->output_mode; + static const char * const output_modes[5] = { + "None", + "MPEG Streaming", + "YUV Streaming", + "YUV Frames", + "Passthrough", + }; + static const char * const alpha_mode[4] = { + "None", + "Global", + "Local", + "Global and Local" + }; + static const char * const pixel_format[16] = { + "ARGB Indexed", + "RGB 5:6:5", + "ARGB 1:5:5:5", + "ARGB 1:4:4:4", + "ARGB 8:8:8:8", + "5", + "6", + "7", + "AYUV Indexed", + "YUV 5:6:5", + "AYUV 1:5:5:5", + "AYUV 1:4:4:4", + "AYUV 8:8:8:8", + "13", + "14", + "15", + }; + + ivtv_get_output(itv, itv->active_output, &vidout); + ivtv_get_audio_output(itv, 0, &audout); + IVTV_INFO("Video Output: %s\n", vidout.name); + if (mode < 0 || mode > OUT_PASSTHROUGH) + mode = OUT_NONE; + IVTV_INFO("Output Mode: %s\n", output_modes[mode]); + ivtv_vapi_result(itv, data, CX2341X_OSD_GET_STATE, 0); + data[0] |= (read_reg(0x2a00) >> 7) & 0x40; + IVTV_INFO("Overlay: %s, Alpha: %s, Pixel Format: %s\n", + data[0] & 1 ? "On" : "Off", + alpha_mode[(data[0] >> 1) & 0x3], + pixel_format[(data[0] >> 3) & 0xf]); + } + IVTV_INFO("Tuner: %s\n", + test_bit(IVTV_F_I_RADIO_USER, &itv->i_flags) ? "Radio" : "TV"); + v4l2_ctrl_handler_log_status(&itv->cxhdl.hdl, itv->v4l2_dev.name); + IVTV_INFO("Status flags: 0x%08lx\n", itv->i_flags); + for (i = 0; i < IVTV_MAX_STREAMS; i++) { + struct ivtv_stream *s = &itv->streams[i]; + + if (s->vdev.v4l2_dev == NULL || s->buffers == 0) + continue; + IVTV_INFO("Stream %s: status 0x%04lx, %d%% of %d KiB (%d buffers) in use\n", s->name, s->s_flags, + (s->buffers - s->q_free.buffers) * 100 / s->buffers, + (s->buffers * s->buf_size) / 1024, s->buffers); + } + + IVTV_INFO("Read MPG/VBI: %lld/%lld bytes\n", + (long long)itv->mpg_data_received, + (long long)itv->vbi_data_inserted); + return 0; +} + +static int ivtv_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *dec) +{ + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + + IVTV_DEBUG_IOCTL("VIDIOC_DECODER_CMD %d\n", dec->cmd); + return ivtv_video_command(itv, id, dec, false); +} + +static int ivtv_try_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *dec) +{ + struct ivtv_open_id *id = file2id(file); + struct ivtv *itv = id->itv; + + IVTV_DEBUG_IOCTL("VIDIOC_TRY_DECODER_CMD %d\n", dec->cmd); + return ivtv_video_command(itv, id, dec, true); +} + +static int ivtv_decoder_ioctls(struct file *filp, unsigned int cmd, void *arg) +{ + struct ivtv_open_id *id = file2id(filp); + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + + switch (cmd) { + case IVTV_IOC_DMA_FRAME: { + struct ivtv_dma_frame *args = arg; + + IVTV_DEBUG_IOCTL("IVTV_IOC_DMA_FRAME\n"); + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + if (args->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + if (itv->output_mode == OUT_UDMA_YUV && args->y_source == NULL) + return 0; + if (ivtv_start_decoding(id, id->type)) { + return -EBUSY; + } + if (ivtv_set_output_mode(itv, OUT_UDMA_YUV) != OUT_UDMA_YUV) { + ivtv_release_stream(s); + return -EBUSY; + } + /* Mark that this file handle started the UDMA_YUV mode */ + id->yuv_frames = 1; + if (args->y_source == NULL) + return 0; + return ivtv_yuv_prep_frame(itv, args); + } + + case IVTV_IOC_PASSTHROUGH_MODE: + IVTV_DEBUG_IOCTL("IVTV_IOC_PASSTHROUGH_MODE\n"); + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + return ivtv_passthrough_mode(itv, *(int *)arg != 0); + default: + return -EINVAL; + } + return 0; +} + +static long ivtv_default(struct file *file, void *fh, bool valid_prio, + unsigned int cmd, void *arg) +{ + struct ivtv *itv = file2id(file)->itv; + + if (!valid_prio) { + switch (cmd) { + case IVTV_IOC_PASSTHROUGH_MODE: + return -EBUSY; + } + } + + switch (cmd) { + case VIDIOC_INT_RESET: { + u32 val = *(u32 *)arg; + + if ((val == 0 && itv->options.newi2c) || (val & 0x01)) + ivtv_reset_ir_gpio(itv); + if (val & 0x02) + v4l2_subdev_call(itv->sd_video, core, reset, 0); + break; + } + + case IVTV_IOC_DMA_FRAME: + case IVTV_IOC_PASSTHROUGH_MODE: + return ivtv_decoder_ioctls(file, cmd, (void *)arg); + + default: + return -ENOTTY; + } + return 0; +} + +static const struct v4l2_ioctl_ops ivtv_ioctl_ops = { + .vidioc_querycap = ivtv_querycap, + .vidioc_s_audio = ivtv_s_audio, + .vidioc_g_audio = ivtv_g_audio, + .vidioc_enumaudio = ivtv_enumaudio, + .vidioc_s_audout = ivtv_s_audout, + .vidioc_g_audout = ivtv_g_audout, + .vidioc_enum_input = ivtv_enum_input, + .vidioc_enum_output = ivtv_enum_output, + .vidioc_enumaudout = ivtv_enumaudout, + .vidioc_g_pixelaspect = ivtv_g_pixelaspect, + .vidioc_s_selection = ivtv_s_selection, + .vidioc_g_selection = ivtv_g_selection, + .vidioc_g_input = ivtv_g_input, + .vidioc_s_input = ivtv_s_input, + .vidioc_g_output = ivtv_g_output, + .vidioc_s_output = ivtv_s_output, + .vidioc_g_frequency = ivtv_g_frequency, + .vidioc_s_frequency = ivtv_s_frequency, + .vidioc_s_tuner = ivtv_s_tuner, + .vidioc_g_tuner = ivtv_g_tuner, + .vidioc_g_enc_index = ivtv_g_enc_index, + .vidioc_g_fbuf = ivtv_g_fbuf, + .vidioc_s_fbuf = ivtv_s_fbuf, + .vidioc_g_std = ivtv_g_std, + .vidioc_s_std = ivtv_s_std, + .vidioc_overlay = ivtv_overlay, + .vidioc_log_status = ivtv_log_status, + .vidioc_enum_fmt_vid_cap = ivtv_enum_fmt_vid_cap, + .vidioc_encoder_cmd = ivtv_encoder_cmd, + .vidioc_try_encoder_cmd = ivtv_try_encoder_cmd, + .vidioc_decoder_cmd = ivtv_decoder_cmd, + .vidioc_try_decoder_cmd = ivtv_try_decoder_cmd, + .vidioc_enum_fmt_vid_out = ivtv_enum_fmt_vid_out, + .vidioc_g_fmt_vid_cap = ivtv_g_fmt_vid_cap, + .vidioc_g_fmt_vbi_cap = ivtv_g_fmt_vbi_cap, + .vidioc_g_fmt_sliced_vbi_cap = ivtv_g_fmt_sliced_vbi_cap, + .vidioc_g_fmt_vid_out = ivtv_g_fmt_vid_out, + .vidioc_g_fmt_vid_out_overlay = ivtv_g_fmt_vid_out_overlay, + .vidioc_g_fmt_sliced_vbi_out = ivtv_g_fmt_sliced_vbi_out, + .vidioc_s_fmt_vid_cap = ivtv_s_fmt_vid_cap, + .vidioc_s_fmt_vbi_cap = ivtv_s_fmt_vbi_cap, + .vidioc_s_fmt_sliced_vbi_cap = ivtv_s_fmt_sliced_vbi_cap, + .vidioc_s_fmt_vid_out = ivtv_s_fmt_vid_out, + .vidioc_s_fmt_vid_out_overlay = ivtv_s_fmt_vid_out_overlay, + .vidioc_s_fmt_sliced_vbi_out = ivtv_s_fmt_sliced_vbi_out, + .vidioc_try_fmt_vid_cap = ivtv_try_fmt_vid_cap, + .vidioc_try_fmt_vbi_cap = ivtv_try_fmt_vbi_cap, + .vidioc_try_fmt_sliced_vbi_cap = ivtv_try_fmt_sliced_vbi_cap, + .vidioc_try_fmt_vid_out = ivtv_try_fmt_vid_out, + .vidioc_try_fmt_vid_out_overlay = ivtv_try_fmt_vid_out_overlay, + .vidioc_try_fmt_sliced_vbi_out = ivtv_try_fmt_sliced_vbi_out, + .vidioc_g_sliced_vbi_cap = ivtv_g_sliced_vbi_cap, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = ivtv_g_register, + .vidioc_s_register = ivtv_s_register, +#endif + .vidioc_default = ivtv_default, + .vidioc_subscribe_event = ivtv_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +void ivtv_set_funcs(struct video_device *vdev) +{ + vdev->ioctl_ops = &ivtv_ioctl_ops; +} diff --git a/drivers/media/pci/ivtv/ivtv-ioctl.h b/drivers/media/pci/ivtv/ivtv-ioctl.h new file mode 100644 index 000000000..96ca7e2ef --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-ioctl.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + ioctl system call + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#ifndef IVTV_IOCTL_H +#define IVTV_IOCTL_H + +struct ivtv; + +u16 ivtv_service2vbi(int type); +void ivtv_expand_service_set(struct v4l2_sliced_vbi_format *fmt, int is_pal); +u16 ivtv_get_service_set(struct v4l2_sliced_vbi_format *fmt); +void ivtv_set_osd_alpha(struct ivtv *itv); +int ivtv_set_speed(struct ivtv *itv, int speed); +void ivtv_set_funcs(struct video_device *vdev); +void ivtv_s_std_enc(struct ivtv *itv, v4l2_std_id std); +void ivtv_s_std_dec(struct ivtv *itv, v4l2_std_id std); +int ivtv_do_s_frequency(struct ivtv_stream *s, const struct v4l2_frequency *vf); +int ivtv_do_s_input(struct ivtv *itv, unsigned int inp); + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-irq.c b/drivers/media/pci/ivtv/ivtv-irq.c new file mode 100644 index 000000000..05e0293b4 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-irq.c @@ -0,0 +1,1078 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* interrupt handling + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#include "ivtv-driver.h" +#include "ivtv-queue.h" +#include "ivtv-udma.h" +#include "ivtv-irq.h" +#include "ivtv-mailbox.h" +#include "ivtv-vbi.h" +#include "ivtv-yuv.h" +#include + +#define DMA_MAGIC_COOKIE 0x000001fe + +static void ivtv_dma_dec_start(struct ivtv_stream *s); + +static const int ivtv_stream_map[] = { + IVTV_ENC_STREAM_TYPE_MPG, + IVTV_ENC_STREAM_TYPE_YUV, + IVTV_ENC_STREAM_TYPE_PCM, + IVTV_ENC_STREAM_TYPE_VBI, +}; + +static void ivtv_pcm_work_handler(struct ivtv *itv) +{ + struct ivtv_stream *s = &itv->streams[IVTV_ENC_STREAM_TYPE_PCM]; + struct ivtv_buffer *buf; + + /* Pass the PCM data to ivtv-alsa */ + + while (1) { + /* + * Users should not be using both the ALSA and V4L2 PCM audio + * capture interfaces at the same time. If the user is doing + * this, there maybe a buffer in q_io to grab, use, and put + * back in rotation. + */ + buf = ivtv_dequeue(s, &s->q_io); + if (buf == NULL) + buf = ivtv_dequeue(s, &s->q_full); + if (buf == NULL) + break; + + if (buf->readpos < buf->bytesused) + itv->pcm_announce_callback(itv->alsa, + (u8 *)(buf->buf + buf->readpos), + (size_t)(buf->bytesused - buf->readpos)); + + ivtv_enqueue(s, buf, &s->q_free); + } +} + +static void ivtv_pio_work_handler(struct ivtv *itv) +{ + struct ivtv_stream *s = &itv->streams[itv->cur_pio_stream]; + struct ivtv_buffer *buf; + int i = 0; + + IVTV_DEBUG_HI_DMA("ivtv_pio_work_handler\n"); + if (itv->cur_pio_stream < 0 || itv->cur_pio_stream >= IVTV_MAX_STREAMS || + s->vdev.v4l2_dev == NULL || !ivtv_use_pio(s)) { + itv->cur_pio_stream = -1; + /* trigger PIO complete user interrupt */ + write_reg(IVTV_IRQ_ENC_PIO_COMPLETE, 0x44); + return; + } + IVTV_DEBUG_HI_DMA("Process PIO %s\n", s->name); + list_for_each_entry(buf, &s->q_dma.list, list) { + u32 size = s->sg_processing[i].size & 0x3ffff; + + /* Copy the data from the card to the buffer */ + if (s->type == IVTV_DEC_STREAM_TYPE_VBI) { + memcpy_fromio(buf->buf, itv->dec_mem + s->sg_processing[i].src - IVTV_DECODER_OFFSET, size); + } + else { + memcpy_fromio(buf->buf, itv->enc_mem + s->sg_processing[i].src, size); + } + i++; + if (i == s->sg_processing_size) + break; + } + write_reg(IVTV_IRQ_ENC_PIO_COMPLETE, 0x44); +} + +void ivtv_irq_work_handler(struct kthread_work *work) +{ + struct ivtv *itv = container_of(work, struct ivtv, irq_work); + + if (test_and_clear_bit(IVTV_F_I_WORK_HANDLER_PIO, &itv->i_flags)) + ivtv_pio_work_handler(itv); + + if (test_and_clear_bit(IVTV_F_I_WORK_HANDLER_VBI, &itv->i_flags)) + ivtv_vbi_work_handler(itv); + + if (test_and_clear_bit(IVTV_F_I_WORK_HANDLER_YUV, &itv->i_flags)) + ivtv_yuv_work_handler(itv); + + if (test_and_clear_bit(IVTV_F_I_WORK_HANDLER_PCM, &itv->i_flags)) + ivtv_pcm_work_handler(itv); +} + +/* Determine the required DMA size, setup enough buffers in the predma queue and + actually copy the data from the card to the buffers in case a PIO transfer is + required for this stream. + */ +static int stream_enc_dma_append(struct ivtv_stream *s, u32 data[CX2341X_MBOX_MAX_DATA]) +{ + struct ivtv *itv = s->itv; + struct ivtv_buffer *buf; + u32 bytes_needed = 0; + u32 offset, size; + u32 UVoffset = 0, UVsize = 0; + int skip_bufs = s->q_predma.buffers; + int idx = s->sg_pending_size; + int rc; + + /* sanity checks */ + if (s->vdev.v4l2_dev == NULL) { + IVTV_DEBUG_WARN("Stream %s not started\n", s->name); + return -1; + } + if (!test_bit(IVTV_F_S_CLAIMED, &s->s_flags)) { + IVTV_DEBUG_WARN("Stream %s not open\n", s->name); + return -1; + } + + /* determine offset, size and PTS for the various streams */ + switch (s->type) { + case IVTV_ENC_STREAM_TYPE_MPG: + offset = data[1]; + size = data[2]; + s->pending_pts = 0; + break; + + case IVTV_ENC_STREAM_TYPE_YUV: + offset = data[1]; + size = data[2]; + UVoffset = data[3]; + UVsize = data[4]; + s->pending_pts = ((u64) data[5] << 32) | data[6]; + break; + + case IVTV_ENC_STREAM_TYPE_PCM: + offset = data[1] + 12; + size = data[2] - 12; + s->pending_pts = read_dec(offset - 8) | + ((u64)(read_dec(offset - 12)) << 32); + if (itv->has_cx23415) + offset += IVTV_DECODER_OFFSET; + break; + + case IVTV_ENC_STREAM_TYPE_VBI: + size = itv->vbi.enc_size * itv->vbi.fpi; + offset = read_enc(itv->vbi.enc_start - 4) + 12; + if (offset == 12) { + IVTV_DEBUG_INFO("VBI offset == 0\n"); + return -1; + } + s->pending_pts = read_enc(offset - 4) | ((u64)read_enc(offset - 8) << 32); + break; + + case IVTV_DEC_STREAM_TYPE_VBI: + size = read_dec(itv->vbi.dec_start + 4) + 8; + offset = read_dec(itv->vbi.dec_start) + itv->vbi.dec_start; + s->pending_pts = 0; + offset += IVTV_DECODER_OFFSET; + break; + default: + /* shouldn't happen */ + return -1; + } + + /* if this is the start of the DMA then fill in the magic cookie */ + if (s->sg_pending_size == 0 && ivtv_use_dma(s)) { + if (itv->has_cx23415 && (s->type == IVTV_ENC_STREAM_TYPE_PCM || + s->type == IVTV_DEC_STREAM_TYPE_VBI)) { + s->pending_backup = read_dec(offset - IVTV_DECODER_OFFSET); + write_dec_sync(DMA_MAGIC_COOKIE, offset - IVTV_DECODER_OFFSET); + } + else { + s->pending_backup = read_enc(offset); + write_enc_sync(DMA_MAGIC_COOKIE, offset); + } + s->pending_offset = offset; + } + + bytes_needed = size; + if (s->type == IVTV_ENC_STREAM_TYPE_YUV) { + /* The size for the Y samples needs to be rounded upwards to a + multiple of the buf_size. The UV samples then start in the + next buffer. */ + bytes_needed = s->buf_size * ((bytes_needed + s->buf_size - 1) / s->buf_size); + bytes_needed += UVsize; + } + + IVTV_DEBUG_HI_DMA("%s %s: 0x%08x bytes at 0x%08x\n", + ivtv_use_pio(s) ? "PIO" : "DMA", s->name, bytes_needed, offset); + + rc = ivtv_queue_move(s, &s->q_free, &s->q_full, &s->q_predma, bytes_needed); + if (rc < 0) { /* Insufficient buffers */ + IVTV_DEBUG_WARN("Cannot obtain %d bytes for %s data transfer\n", + bytes_needed, s->name); + return -1; + } + if (rc && !s->buffers_stolen && test_bit(IVTV_F_S_APPL_IO, &s->s_flags)) { + IVTV_WARN("All %s stream buffers are full. Dropping data.\n", s->name); + IVTV_WARN("Cause: the application is not reading fast enough.\n"); + } + s->buffers_stolen = rc; + + /* got the buffers, now fill in sg_pending */ + buf = list_entry(s->q_predma.list.next, struct ivtv_buffer, list); + memset(buf->buf, 0, 128); + list_for_each_entry(buf, &s->q_predma.list, list) { + if (skip_bufs-- > 0) + continue; + s->sg_pending[idx].dst = buf->dma_handle; + s->sg_pending[idx].src = offset; + s->sg_pending[idx].size = s->buf_size; + buf->bytesused = min(size, s->buf_size); + buf->dma_xfer_cnt = s->dma_xfer_cnt; + + s->q_predma.bytesused += buf->bytesused; + size -= buf->bytesused; + offset += s->buf_size; + + /* Sync SG buffers */ + ivtv_buf_sync_for_device(s, buf); + + if (size == 0) { /* YUV */ + /* process the UV section */ + offset = UVoffset; + size = UVsize; + } + idx++; + } + s->sg_pending_size = idx; + return 0; +} + +static void dma_post(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + struct ivtv_buffer *buf = NULL; + struct list_head *p; + u32 offset; + __le32 *u32buf; + int x = 0; + + IVTV_DEBUG_HI_DMA("%s %s completed (%x)\n", ivtv_use_pio(s) ? "PIO" : "DMA", + s->name, s->dma_offset); + list_for_each(p, &s->q_dma.list) { + buf = list_entry(p, struct ivtv_buffer, list); + u32buf = (__le32 *)buf->buf; + + /* Sync Buffer */ + ivtv_buf_sync_for_cpu(s, buf); + + if (x == 0 && ivtv_use_dma(s)) { + offset = s->dma_last_offset; + if (le32_to_cpu(u32buf[offset / 4]) != DMA_MAGIC_COOKIE) + { + for (offset = 0; offset < 64; offset++) + if (le32_to_cpu(u32buf[offset]) == DMA_MAGIC_COOKIE) + break; + offset *= 4; + if (offset == 256) { + IVTV_DEBUG_WARN("%s: Couldn't find start of buffer within the first 256 bytes\n", s->name); + offset = s->dma_last_offset; + } + if (s->dma_last_offset != offset) + IVTV_DEBUG_WARN("%s: offset %d -> %d\n", s->name, s->dma_last_offset, offset); + s->dma_last_offset = offset; + } + if (itv->has_cx23415 && (s->type == IVTV_ENC_STREAM_TYPE_PCM || + s->type == IVTV_DEC_STREAM_TYPE_VBI)) { + write_dec_sync(0, s->dma_offset - IVTV_DECODER_OFFSET); + } + else { + write_enc_sync(0, s->dma_offset); + } + if (offset) { + buf->bytesused -= offset; + memcpy(buf->buf, buf->buf + offset, buf->bytesused + offset); + } + *u32buf = cpu_to_le32(s->dma_backup); + } + x++; + /* flag byteswap ABCD -> DCBA for MPG & VBI data outside irq */ + if (s->type == IVTV_ENC_STREAM_TYPE_MPG || + s->type == IVTV_ENC_STREAM_TYPE_VBI) + buf->b_flags |= IVTV_F_B_NEED_BUF_SWAP; + } + if (buf) + buf->bytesused += s->dma_last_offset; + if (buf && s->type == IVTV_DEC_STREAM_TYPE_VBI) { + list_for_each_entry(buf, &s->q_dma.list, list) { + /* Parse and Groom VBI Data */ + s->q_dma.bytesused -= buf->bytesused; + ivtv_process_vbi_data(itv, buf, 0, s->type); + s->q_dma.bytesused += buf->bytesused; + } + if (s->id == NULL) { + ivtv_queue_move(s, &s->q_dma, NULL, &s->q_free, 0); + return; + } + } + + ivtv_queue_move(s, &s->q_dma, NULL, &s->q_full, s->q_dma.bytesused); + + if (s->type == IVTV_ENC_STREAM_TYPE_PCM && + itv->pcm_announce_callback != NULL) { + /* + * Set up the work handler to pass the data to ivtv-alsa. + * + * We just use q_full and let the work handler race with users + * making ivtv-fileops.c calls on the PCM device node. + * + * Users should not be using both the ALSA and V4L2 PCM audio + * capture interfaces at the same time. If the user does this, + * fragments of data will just go out each interface as they + * race for PCM data. + */ + set_bit(IVTV_F_I_WORK_HANDLER_PCM, &itv->i_flags); + set_bit(IVTV_F_I_HAVE_WORK, &itv->i_flags); + } + + if (s->id) + wake_up(&s->waitq); +} + +void ivtv_dma_stream_dec_prepare(struct ivtv_stream *s, u32 offset, int lock) +{ + struct ivtv *itv = s->itv; + struct yuv_playback_info *yi = &itv->yuv_info; + u8 frame = yi->draw_frame; + struct yuv_frame_info *f = &yi->new_frame_info[frame]; + struct ivtv_buffer *buf; + u32 y_size = 720 * ((f->src_h + 31) & ~31); + u32 uv_offset = offset + IVTV_YUV_BUFFER_UV_OFFSET; + int y_done = 0; + int bytes_written = 0; + int idx = 0; + + IVTV_DEBUG_HI_DMA("DEC PREPARE DMA %s: %08x %08x\n", s->name, s->q_predma.bytesused, offset); + + /* Insert buffer block for YUV if needed */ + if (s->type == IVTV_DEC_STREAM_TYPE_YUV && f->offset_y) { + if (yi->blanking_ptr) { + s->sg_pending[idx].src = yi->blanking_dmaptr; + s->sg_pending[idx].dst = offset; + s->sg_pending[idx].size = 720 * 16; + } + offset += 720 * 16; + idx++; + } + + list_for_each_entry(buf, &s->q_predma.list, list) { + /* YUV UV Offset from Y Buffer */ + if (s->type == IVTV_DEC_STREAM_TYPE_YUV && !y_done && + (bytes_written + buf->bytesused) >= y_size) { + s->sg_pending[idx].src = buf->dma_handle; + s->sg_pending[idx].dst = offset; + s->sg_pending[idx].size = y_size - bytes_written; + offset = uv_offset; + if (s->sg_pending[idx].size != buf->bytesused) { + idx++; + s->sg_pending[idx].src = + buf->dma_handle + s->sg_pending[idx - 1].size; + s->sg_pending[idx].dst = offset; + s->sg_pending[idx].size = + buf->bytesused - s->sg_pending[idx - 1].size; + offset += s->sg_pending[idx].size; + } + y_done = 1; + } else { + s->sg_pending[idx].src = buf->dma_handle; + s->sg_pending[idx].dst = offset; + s->sg_pending[idx].size = buf->bytesused; + offset += buf->bytesused; + } + bytes_written += buf->bytesused; + + /* Sync SG buffers */ + ivtv_buf_sync_for_device(s, buf); + idx++; + } + s->sg_pending_size = idx; + + /* Sync Hardware SG List of buffers */ + ivtv_stream_sync_for_device(s); + if (lock) { + unsigned long flags = 0; + + spin_lock_irqsave(&itv->dma_reg_lock, flags); + if (!test_bit(IVTV_F_I_DMA, &itv->i_flags)) + ivtv_dma_dec_start(s); + else + set_bit(IVTV_F_S_DMA_PENDING, &s->s_flags); + spin_unlock_irqrestore(&itv->dma_reg_lock, flags); + } else { + if (!test_bit(IVTV_F_I_DMA, &itv->i_flags)) + ivtv_dma_dec_start(s); + else + set_bit(IVTV_F_S_DMA_PENDING, &s->s_flags); + } +} + +static void ivtv_dma_enc_start_xfer(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + + s->sg_dma->src = cpu_to_le32(s->sg_processing[s->sg_processed].src); + s->sg_dma->dst = cpu_to_le32(s->sg_processing[s->sg_processed].dst); + s->sg_dma->size = cpu_to_le32(s->sg_processing[s->sg_processed].size | 0x80000000); + s->sg_processed++; + /* Sync Hardware SG List of buffers */ + ivtv_stream_sync_for_device(s); + write_reg(s->sg_handle, IVTV_REG_ENCDMAADDR); + write_reg_sync(read_reg(IVTV_REG_DMAXFER) | 0x02, IVTV_REG_DMAXFER); + itv->dma_timer.expires = jiffies + msecs_to_jiffies(300); + add_timer(&itv->dma_timer); +} + +static void ivtv_dma_dec_start_xfer(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + + s->sg_dma->src = cpu_to_le32(s->sg_processing[s->sg_processed].src); + s->sg_dma->dst = cpu_to_le32(s->sg_processing[s->sg_processed].dst); + s->sg_dma->size = cpu_to_le32(s->sg_processing[s->sg_processed].size | 0x80000000); + s->sg_processed++; + /* Sync Hardware SG List of buffers */ + ivtv_stream_sync_for_device(s); + write_reg(s->sg_handle, IVTV_REG_DECDMAADDR); + write_reg_sync(read_reg(IVTV_REG_DMAXFER) | 0x01, IVTV_REG_DMAXFER); + itv->dma_timer.expires = jiffies + msecs_to_jiffies(300); + add_timer(&itv->dma_timer); +} + +/* start the encoder DMA */ +static void ivtv_dma_enc_start(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + struct ivtv_stream *s_vbi = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + int i; + + IVTV_DEBUG_HI_DMA("start %s for %s\n", ivtv_use_dma(s) ? "DMA" : "PIO", s->name); + + if (s->q_predma.bytesused) + ivtv_queue_move(s, &s->q_predma, NULL, &s->q_dma, s->q_predma.bytesused); + + if (ivtv_use_dma(s)) + s->sg_pending[s->sg_pending_size - 1].size += 256; + + /* If this is an MPEG stream, and VBI data is also pending, then append the + VBI DMA to the MPEG DMA and transfer both sets of data at once. + + VBI DMA is a second class citizen compared to MPEG and mixing them together + will confuse the firmware (the end of a VBI DMA is seen as the end of a + MPEG DMA, thus effectively dropping an MPEG frame). So instead we make + sure we only use the MPEG DMA to transfer the VBI DMA if both are in + use. This way no conflicts occur. */ + clear_bit(IVTV_F_S_DMA_HAS_VBI, &s->s_flags); + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && s_vbi->sg_pending_size && + s->sg_pending_size + s_vbi->sg_pending_size <= s->buffers) { + ivtv_queue_move(s_vbi, &s_vbi->q_predma, NULL, &s_vbi->q_dma, s_vbi->q_predma.bytesused); + if (ivtv_use_dma(s_vbi)) + s_vbi->sg_pending[s_vbi->sg_pending_size - 1].size += 256; + for (i = 0; i < s_vbi->sg_pending_size; i++) { + s->sg_pending[s->sg_pending_size++] = s_vbi->sg_pending[i]; + } + s_vbi->dma_offset = s_vbi->pending_offset; + s_vbi->sg_pending_size = 0; + s_vbi->dma_xfer_cnt++; + set_bit(IVTV_F_S_DMA_HAS_VBI, &s->s_flags); + IVTV_DEBUG_HI_DMA("include DMA for %s\n", s_vbi->name); + } + + s->dma_xfer_cnt++; + memcpy(s->sg_processing, s->sg_pending, sizeof(struct ivtv_sg_host_element) * s->sg_pending_size); + s->sg_processing_size = s->sg_pending_size; + s->sg_pending_size = 0; + s->sg_processed = 0; + s->dma_offset = s->pending_offset; + s->dma_backup = s->pending_backup; + s->dma_pts = s->pending_pts; + + if (ivtv_use_pio(s)) { + set_bit(IVTV_F_I_WORK_HANDLER_PIO, &itv->i_flags); + set_bit(IVTV_F_I_HAVE_WORK, &itv->i_flags); + set_bit(IVTV_F_I_PIO, &itv->i_flags); + itv->cur_pio_stream = s->type; + } + else { + itv->dma_retries = 0; + ivtv_dma_enc_start_xfer(s); + set_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = s->type; + } +} + +static void ivtv_dma_dec_start(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + + if (s->q_predma.bytesused) + ivtv_queue_move(s, &s->q_predma, NULL, &s->q_dma, s->q_predma.bytesused); + s->dma_xfer_cnt++; + memcpy(s->sg_processing, s->sg_pending, sizeof(struct ivtv_sg_host_element) * s->sg_pending_size); + s->sg_processing_size = s->sg_pending_size; + s->sg_pending_size = 0; + s->sg_processed = 0; + + IVTV_DEBUG_HI_DMA("start DMA for %s\n", s->name); + itv->dma_retries = 0; + ivtv_dma_dec_start_xfer(s); + set_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = s->type; +} + +static void ivtv_irq_dma_read(struct ivtv *itv) +{ + struct ivtv_stream *s = NULL; + struct ivtv_buffer *buf; + int hw_stream_type = 0; + + IVTV_DEBUG_HI_IRQ("DEC DMA READ\n"); + + timer_delete(&itv->dma_timer); + + if (!test_bit(IVTV_F_I_UDMA, &itv->i_flags) && itv->cur_dma_stream < 0) + return; + + if (!test_bit(IVTV_F_I_UDMA, &itv->i_flags)) { + s = &itv->streams[itv->cur_dma_stream]; + ivtv_stream_sync_for_cpu(s); + + if (read_reg(IVTV_REG_DMASTATUS) & 0x14) { + IVTV_DEBUG_WARN("DEC DMA ERROR %x (xfer %d of %d, retry %d)\n", + read_reg(IVTV_REG_DMASTATUS), + s->sg_processed, s->sg_processing_size, itv->dma_retries); + write_reg(read_reg(IVTV_REG_DMASTATUS) & 3, IVTV_REG_DMASTATUS); + if (itv->dma_retries == 3) { + /* Too many retries, give up on this frame */ + itv->dma_retries = 0; + s->sg_processed = s->sg_processing_size; + } + else { + /* Retry, starting with the first xfer segment. + Just retrying the current segment is not sufficient. */ + s->sg_processed = 0; + itv->dma_retries++; + } + } + if (s->sg_processed < s->sg_processing_size) { + /* DMA next buffer */ + ivtv_dma_dec_start_xfer(s); + return; + } + if (s->type == IVTV_DEC_STREAM_TYPE_YUV) + hw_stream_type = 2; + IVTV_DEBUG_HI_DMA("DEC DATA READ %s: %d\n", s->name, s->q_dma.bytesused); + + /* For some reason must kick the firmware, like PIO mode, + I think this tells the firmware we are done and the size + of the xfer so it can calculate what we need next. + I think we can do this part ourselves but would have to + fully calculate xfer info ourselves and not use interrupts + */ + ivtv_vapi(itv, CX2341X_DEC_SCHED_DMA_FROM_HOST, 3, 0, s->q_dma.bytesused, + hw_stream_type); + + /* Free last DMA call */ + while ((buf = ivtv_dequeue(s, &s->q_dma)) != NULL) { + ivtv_buf_sync_for_cpu(s, buf); + ivtv_enqueue(s, buf, &s->q_free); + } + wake_up(&s->waitq); + } + clear_bit(IVTV_F_I_UDMA, &itv->i_flags); + clear_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = -1; + wake_up(&itv->dma_waitq); +} + +static void ivtv_irq_enc_dma_complete(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv_stream *s; + + ivtv_api_get_data(&itv->enc_mbox, IVTV_MBOX_DMA_END, 2, data); + IVTV_DEBUG_HI_IRQ("ENC DMA COMPLETE %x %d (%d)\n", data[0], data[1], itv->cur_dma_stream); + + timer_delete(&itv->dma_timer); + + if (itv->cur_dma_stream < 0) + return; + + s = &itv->streams[itv->cur_dma_stream]; + ivtv_stream_sync_for_cpu(s); + + if (data[0] & 0x18) { + IVTV_DEBUG_WARN("ENC DMA ERROR %x (offset %08x, xfer %d of %d, retry %d)\n", data[0], + s->dma_offset, s->sg_processed, s->sg_processing_size, itv->dma_retries); + write_reg(read_reg(IVTV_REG_DMASTATUS) & 3, IVTV_REG_DMASTATUS); + if (itv->dma_retries == 3) { + /* Too many retries, give up on this frame */ + itv->dma_retries = 0; + s->sg_processed = s->sg_processing_size; + } + else { + /* Retry, starting with the first xfer segment. + Just retrying the current segment is not sufficient. */ + s->sg_processed = 0; + itv->dma_retries++; + } + } + if (s->sg_processed < s->sg_processing_size) { + /* DMA next buffer */ + ivtv_dma_enc_start_xfer(s); + return; + } + clear_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = -1; + dma_post(s); + if (test_and_clear_bit(IVTV_F_S_DMA_HAS_VBI, &s->s_flags)) { + s = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + dma_post(s); + } + s->sg_processing_size = 0; + s->sg_processed = 0; + wake_up(&itv->dma_waitq); +} + +static void ivtv_irq_enc_pio_complete(struct ivtv *itv) +{ + struct ivtv_stream *s; + + if (itv->cur_pio_stream < 0 || itv->cur_pio_stream >= IVTV_MAX_STREAMS) { + itv->cur_pio_stream = -1; + return; + } + s = &itv->streams[itv->cur_pio_stream]; + IVTV_DEBUG_HI_IRQ("ENC PIO COMPLETE %s\n", s->name); + clear_bit(IVTV_F_I_PIO, &itv->i_flags); + itv->cur_pio_stream = -1; + dma_post(s); + if (s->type == IVTV_ENC_STREAM_TYPE_MPG) + ivtv_vapi(itv, CX2341X_ENC_SCHED_DMA_TO_HOST, 3, 0, 0, 0); + else if (s->type == IVTV_ENC_STREAM_TYPE_YUV) + ivtv_vapi(itv, CX2341X_ENC_SCHED_DMA_TO_HOST, 3, 0, 0, 1); + else if (s->type == IVTV_ENC_STREAM_TYPE_PCM) + ivtv_vapi(itv, CX2341X_ENC_SCHED_DMA_TO_HOST, 3, 0, 0, 2); + clear_bit(IVTV_F_I_PIO, &itv->i_flags); + if (test_and_clear_bit(IVTV_F_S_DMA_HAS_VBI, &s->s_flags)) { + s = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + dma_post(s); + } + wake_up(&itv->dma_waitq); +} + +static void ivtv_irq_dma_err(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + u32 status; + + timer_delete(&itv->dma_timer); + + ivtv_api_get_data(&itv->enc_mbox, IVTV_MBOX_DMA_END, 2, data); + status = read_reg(IVTV_REG_DMASTATUS); + IVTV_DEBUG_WARN("DMA ERROR %08x %08x %08x %d\n", data[0], data[1], + status, itv->cur_dma_stream); + /* + * We do *not* write back to the IVTV_REG_DMASTATUS register to + * clear the error status, if either the encoder write (0x02) or + * decoder read (0x01) bus master DMA operation do not indicate + * completed. We can race with the DMA engine, which may have + * transitioned to completed status *after* we read the register. + * Setting a IVTV_REG_DMASTATUS flag back to "busy" status, after the + * DMA engine has completed, will cause the DMA engine to stop working. + */ + status &= 0x3; + if (status == 0x3) + write_reg(status, IVTV_REG_DMASTATUS); + + if (!test_bit(IVTV_F_I_UDMA, &itv->i_flags) && + itv->cur_dma_stream >= 0 && itv->cur_dma_stream < IVTV_MAX_STREAMS) { + struct ivtv_stream *s = &itv->streams[itv->cur_dma_stream]; + + if (s->type >= IVTV_DEC_STREAM_TYPE_MPG) { + /* retry */ + /* + * FIXME - handle cases of DMA error similar to + * encoder below, except conditioned on status & 0x1 + */ + ivtv_dma_dec_start(s); + return; + } else { + if ((status & 0x2) == 0) { + /* + * CX2341x Bus Master DMA write is ongoing. + * Reset the timer and let it complete. + */ + itv->dma_timer.expires = + jiffies + msecs_to_jiffies(600); + add_timer(&itv->dma_timer); + return; + } + + if (itv->dma_retries < 3) { + /* + * CX2341x Bus Master DMA write has ended. + * Retry the write, starting with the first + * xfer segment. Just retrying the current + * segment is not sufficient. + */ + s->sg_processed = 0; + itv->dma_retries++; + ivtv_dma_enc_start_xfer(s); + return; + } + /* Too many retries, give up on this one */ + } + + } + if (test_bit(IVTV_F_I_UDMA, &itv->i_flags)) { + ivtv_udma_start(itv); + return; + } + clear_bit(IVTV_F_I_UDMA, &itv->i_flags); + clear_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = -1; + wake_up(&itv->dma_waitq); +} + +static void ivtv_irq_enc_start_cap(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv_stream *s; + + /* Get DMA destination and size arguments from card */ + ivtv_api_get_data(&itv->enc_mbox, IVTV_MBOX_DMA, 7, data); + IVTV_DEBUG_HI_IRQ("ENC START CAP %d: %08x %08x\n", data[0], data[1], data[2]); + + if (data[0] > 2 || data[1] == 0 || data[2] == 0) { + IVTV_DEBUG_WARN("Unknown input: %08x %08x %08x\n", + data[0], data[1], data[2]); + return; + } + s = &itv->streams[ivtv_stream_map[data[0]]]; + if (!stream_enc_dma_append(s, data)) { + set_bit(ivtv_use_pio(s) ? IVTV_F_S_PIO_PENDING : IVTV_F_S_DMA_PENDING, &s->s_flags); + } +} + +static void ivtv_irq_enc_vbi_cap(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv_stream *s; + + IVTV_DEBUG_HI_IRQ("ENC START VBI CAP\n"); + s = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + + if (!stream_enc_dma_append(s, data)) + set_bit(ivtv_use_pio(s) ? IVTV_F_S_PIO_PENDING : IVTV_F_S_DMA_PENDING, &s->s_flags); +} + +static void ivtv_irq_dec_vbi_reinsert(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv_stream *s = &itv->streams[IVTV_DEC_STREAM_TYPE_VBI]; + + IVTV_DEBUG_HI_IRQ("DEC VBI REINSERT\n"); + if (test_bit(IVTV_F_S_CLAIMED, &s->s_flags) && + !stream_enc_dma_append(s, data)) { + set_bit(IVTV_F_S_PIO_PENDING, &s->s_flags); + } +} + +static void ivtv_irq_dec_data_req(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv_stream *s; + + /* YUV or MPG */ + + if (test_bit(IVTV_F_I_DEC_YUV, &itv->i_flags)) { + ivtv_api_get_data(&itv->dec_mbox, IVTV_MBOX_DMA, 2, data); + itv->dma_data_req_size = + 1080 * ((itv->yuv_info.v4l2_src_h + 31) & ~31); + itv->dma_data_req_offset = data[1]; + if (atomic_read(&itv->yuv_info.next_dma_frame) >= 0) + ivtv_yuv_frame_complete(itv); + s = &itv->streams[IVTV_DEC_STREAM_TYPE_YUV]; + } + else { + ivtv_api_get_data(&itv->dec_mbox, IVTV_MBOX_DMA, 3, data); + itv->dma_data_req_size = min_t(u32, data[2], 0x10000); + itv->dma_data_req_offset = data[1]; + s = &itv->streams[IVTV_DEC_STREAM_TYPE_MPG]; + } + IVTV_DEBUG_HI_IRQ("DEC DATA REQ %s: %d %08x %u\n", s->name, s->q_full.bytesused, + itv->dma_data_req_offset, itv->dma_data_req_size); + if (itv->dma_data_req_size == 0 || s->q_full.bytesused < itv->dma_data_req_size) { + set_bit(IVTV_F_S_NEEDS_DATA, &s->s_flags); + } + else { + if (test_bit(IVTV_F_I_DEC_YUV, &itv->i_flags)) + ivtv_yuv_setup_stream_frame(itv); + clear_bit(IVTV_F_S_NEEDS_DATA, &s->s_flags); + ivtv_queue_move(s, &s->q_full, NULL, &s->q_predma, itv->dma_data_req_size); + ivtv_dma_stream_dec_prepare(s, itv->dma_data_req_offset + IVTV_DECODER_OFFSET, 0); + } +} + +static void ivtv_irq_vsync(struct ivtv *itv) +{ + /* The vsync interrupt is unusual in that it won't clear until + * the end of the first line for the current field, at which + * point it clears itself. This can result in repeated vsync + * interrupts, or a missed vsync. Read some of the registers + * to determine the line being displayed and ensure we handle + * one vsync per frame. + */ + unsigned int frame = read_reg(IVTV_REG_DEC_LINE_FIELD) & 1; + struct yuv_playback_info *yi = &itv->yuv_info; + int last_dma_frame = atomic_read(&yi->next_dma_frame); + struct yuv_frame_info *f = &yi->new_frame_info[last_dma_frame]; + + if (0) IVTV_DEBUG_IRQ("DEC VSYNC\n"); + + if (((frame ^ f->sync_field) == 0 && + ((itv->last_vsync_field & 1) ^ f->sync_field)) || + (frame != (itv->last_vsync_field & 1) && !f->interlaced)) { + int next_dma_frame = last_dma_frame; + + if (!(f->interlaced && f->delay && yi->fields_lapsed < 1)) { + if (next_dma_frame >= 0 && next_dma_frame != atomic_read(&yi->next_fill_frame)) { + write_reg(yuv_offset[next_dma_frame] >> 4, 0x82c); + write_reg((yuv_offset[next_dma_frame] + IVTV_YUV_BUFFER_UV_OFFSET) >> 4, 0x830); + write_reg(yuv_offset[next_dma_frame] >> 4, 0x834); + write_reg((yuv_offset[next_dma_frame] + IVTV_YUV_BUFFER_UV_OFFSET) >> 4, 0x838); + next_dma_frame = (next_dma_frame + 1) % IVTV_YUV_BUFFERS; + atomic_set(&yi->next_dma_frame, next_dma_frame); + yi->fields_lapsed = -1; + yi->running = 1; + } + } + } + if (frame != (itv->last_vsync_field & 1)) { + static const struct v4l2_event evtop = { + .type = V4L2_EVENT_VSYNC, + .u.vsync.field = V4L2_FIELD_TOP, + }; + static const struct v4l2_event evbottom = { + .type = V4L2_EVENT_VSYNC, + .u.vsync.field = V4L2_FIELD_BOTTOM, + }; + struct ivtv_stream *s = ivtv_get_output_stream(itv); + + itv->last_vsync_field += 1; + if (frame == 0) { + clear_bit(IVTV_F_I_VALID_DEC_TIMINGS, &itv->i_flags); + clear_bit(IVTV_F_I_EV_VSYNC_FIELD, &itv->i_flags); + } + else { + set_bit(IVTV_F_I_EV_VSYNC_FIELD, &itv->i_flags); + } + if (test_bit(IVTV_F_I_EV_VSYNC_ENABLED, &itv->i_flags)) { + set_bit(IVTV_F_I_EV_VSYNC, &itv->i_flags); + wake_up(&itv->event_waitq); + if (s) + wake_up(&s->waitq); + } + if (s && s->vdev.v4l2_dev) + v4l2_event_queue(&s->vdev, frame ? &evtop : &evbottom); + wake_up(&itv->vsync_waitq); + + /* Send VBI to saa7127 */ + if (frame && (itv->output_mode == OUT_PASSTHROUGH || + test_bit(IVTV_F_I_UPDATE_WSS, &itv->i_flags) || + test_bit(IVTV_F_I_UPDATE_VPS, &itv->i_flags) || + test_bit(IVTV_F_I_UPDATE_CC, &itv->i_flags))) { + set_bit(IVTV_F_I_WORK_HANDLER_VBI, &itv->i_flags); + set_bit(IVTV_F_I_HAVE_WORK, &itv->i_flags); + } + + /* Check if we need to update the yuv registers */ + if (yi->running && (yi->yuv_forced_update || f->update)) { + if (!f->update) { + last_dma_frame = + (u8)(atomic_read(&yi->next_dma_frame) - + 1) % IVTV_YUV_BUFFERS; + f = &yi->new_frame_info[last_dma_frame]; + } + + if (f->src_w) { + yi->update_frame = last_dma_frame; + f->update = 0; + yi->yuv_forced_update = 0; + set_bit(IVTV_F_I_WORK_HANDLER_YUV, &itv->i_flags); + set_bit(IVTV_F_I_HAVE_WORK, &itv->i_flags); + } + } + + yi->fields_lapsed++; + } +} + +#define IVTV_IRQ_DMA (IVTV_IRQ_DMA_READ | IVTV_IRQ_ENC_DMA_COMPLETE | IVTV_IRQ_DMA_ERR | IVTV_IRQ_ENC_START_CAP | IVTV_IRQ_ENC_VBI_CAP | IVTV_IRQ_DEC_DATA_REQ | IVTV_IRQ_DEC_VBI_RE_INSERT) + +irqreturn_t ivtv_irq_handler(int irq, void *dev_id) +{ + struct ivtv *itv = (struct ivtv *)dev_id; + u32 combo; + u32 stat; + int i; + u8 vsync_force = 0; + + spin_lock(&itv->dma_reg_lock); + /* get contents of irq status register */ + stat = read_reg(IVTV_REG_IRQSTATUS); + + combo = ~itv->irqmask & stat; + + /* Clear out IRQ */ + if (combo) write_reg(combo, IVTV_REG_IRQSTATUS); + + if (0 == combo) { + /* The vsync interrupt is unusual and clears itself. If we + * took too long, we may have missed it. Do some checks + */ + if (~itv->irqmask & IVTV_IRQ_DEC_VSYNC) { + /* vsync is enabled, see if we're in a new field */ + if ((itv->last_vsync_field & 1) != + (read_reg(IVTV_REG_DEC_LINE_FIELD) & 1)) { + /* New field, looks like we missed it */ + IVTV_DEBUG_YUV("VSync interrupt missed %d\n", + read_reg(IVTV_REG_DEC_LINE_FIELD) >> 16); + vsync_force = 1; + } + } + + if (!vsync_force) { + /* No Vsync expected, wasn't for us */ + spin_unlock(&itv->dma_reg_lock); + return IRQ_NONE; + } + } + + /* Exclude interrupts noted below from the output, otherwise the log is flooded with + these messages */ + if (combo & ~0xff6d0400) + IVTV_DEBUG_HI_IRQ("======= valid IRQ bits: 0x%08x ======\n", combo); + + if (combo & IVTV_IRQ_DEC_DMA_COMPLETE) { + IVTV_DEBUG_HI_IRQ("DEC DMA COMPLETE\n"); + } + + if (combo & IVTV_IRQ_DMA_READ) { + ivtv_irq_dma_read(itv); + } + + if (combo & IVTV_IRQ_ENC_DMA_COMPLETE) { + ivtv_irq_enc_dma_complete(itv); + } + + if (combo & IVTV_IRQ_ENC_PIO_COMPLETE) { + ivtv_irq_enc_pio_complete(itv); + } + + if (combo & IVTV_IRQ_DMA_ERR) { + ivtv_irq_dma_err(itv); + } + + if (combo & IVTV_IRQ_ENC_START_CAP) { + ivtv_irq_enc_start_cap(itv); + } + + if (combo & IVTV_IRQ_ENC_VBI_CAP) { + ivtv_irq_enc_vbi_cap(itv); + } + + if (combo & IVTV_IRQ_DEC_VBI_RE_INSERT) { + ivtv_irq_dec_vbi_reinsert(itv); + } + + if (combo & IVTV_IRQ_ENC_EOS) { + IVTV_DEBUG_IRQ("ENC EOS\n"); + set_bit(IVTV_F_I_EOS, &itv->i_flags); + wake_up(&itv->eos_waitq); + } + + if (combo & IVTV_IRQ_DEC_DATA_REQ) { + ivtv_irq_dec_data_req(itv); + } + + /* Decoder Vertical Sync - We can't rely on 'combo', so check if vsync enabled */ + if (~itv->irqmask & IVTV_IRQ_DEC_VSYNC) { + ivtv_irq_vsync(itv); + } + + if (combo & IVTV_IRQ_ENC_VIM_RST) { + IVTV_DEBUG_IRQ("VIM RST\n"); + /*ivtv_vapi(itv, CX2341X_ENC_REFRESH_INPUT, 0); */ + } + + if (combo & IVTV_IRQ_DEC_AUD_MODE_CHG) { + IVTV_DEBUG_INFO("Stereo mode changed\n"); + } + + if ((combo & IVTV_IRQ_DMA) && !test_bit(IVTV_F_I_DMA, &itv->i_flags)) { + itv->irq_rr_idx++; + for (i = 0; i < IVTV_MAX_STREAMS; i++) { + int idx = (i + itv->irq_rr_idx) % IVTV_MAX_STREAMS; + struct ivtv_stream *s = &itv->streams[idx]; + + if (!test_and_clear_bit(IVTV_F_S_DMA_PENDING, &s->s_flags)) + continue; + if (s->type >= IVTV_DEC_STREAM_TYPE_MPG) + ivtv_dma_dec_start(s); + else + ivtv_dma_enc_start(s); + break; + } + + if (i == IVTV_MAX_STREAMS && + test_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags)) + ivtv_udma_start(itv); + } + + if ((combo & IVTV_IRQ_DMA) && !test_bit(IVTV_F_I_PIO, &itv->i_flags)) { + itv->irq_rr_idx++; + for (i = 0; i < IVTV_MAX_STREAMS; i++) { + int idx = (i + itv->irq_rr_idx) % IVTV_MAX_STREAMS; + struct ivtv_stream *s = &itv->streams[idx]; + + if (!test_and_clear_bit(IVTV_F_S_PIO_PENDING, &s->s_flags)) + continue; + if (s->type == IVTV_DEC_STREAM_TYPE_VBI || s->type < IVTV_DEC_STREAM_TYPE_MPG) + ivtv_dma_enc_start(s); + break; + } + } + + if (test_and_clear_bit(IVTV_F_I_HAVE_WORK, &itv->i_flags)) { + kthread_queue_work(&itv->irq_worker, &itv->irq_work); + } + + spin_unlock(&itv->dma_reg_lock); + + /* If we've just handled a 'forced' vsync, it's safest to say it + * wasn't ours. Another device may have triggered it at just + * the right time. + */ + return vsync_force ? IRQ_NONE : IRQ_HANDLED; +} + +void ivtv_unfinished_dma(struct timer_list *t) +{ + struct ivtv *itv = timer_container_of(itv, t, dma_timer); + + if (!test_bit(IVTV_F_I_DMA, &itv->i_flags)) + return; + IVTV_ERR("DMA TIMEOUT %08x %d\n", read_reg(IVTV_REG_DMASTATUS), itv->cur_dma_stream); + + write_reg(read_reg(IVTV_REG_DMASTATUS) & 3, IVTV_REG_DMASTATUS); + clear_bit(IVTV_F_I_UDMA, &itv->i_flags); + clear_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = -1; + wake_up(&itv->dma_waitq); +} diff --git a/drivers/media/pci/ivtv/ivtv-irq.h b/drivers/media/pci/ivtv/ivtv-irq.h new file mode 100644 index 000000000..8a780bea7 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-irq.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + interrupt handling + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#ifndef IVTV_IRQ_H +#define IVTV_IRQ_H + +#define IVTV_IRQ_ENC_START_CAP BIT(31) +#define IVTV_IRQ_ENC_EOS BIT(30) +#define IVTV_IRQ_ENC_VBI_CAP BIT(29) +#define IVTV_IRQ_ENC_VIM_RST BIT(28) +#define IVTV_IRQ_ENC_DMA_COMPLETE BIT(27) +#define IVTV_IRQ_ENC_PIO_COMPLETE BIT(25) +#define IVTV_IRQ_DEC_AUD_MODE_CHG BIT(24) +#define IVTV_IRQ_DEC_DATA_REQ BIT(22) +#define IVTV_IRQ_DEC_DMA_COMPLETE BIT(20) +#define IVTV_IRQ_DEC_VBI_RE_INSERT BIT(19) +#define IVTV_IRQ_DMA_ERR BIT(18) +#define IVTV_IRQ_DMA_WRITE BIT(17) +#define IVTV_IRQ_DMA_READ BIT(16) +#define IVTV_IRQ_DEC_VSYNC BIT(10) + +/* IRQ Masks */ +#define IVTV_IRQ_MASK_INIT (IVTV_IRQ_DMA_ERR|IVTV_IRQ_ENC_DMA_COMPLETE|\ + IVTV_IRQ_DMA_READ|IVTV_IRQ_ENC_PIO_COMPLETE) + +#define IVTV_IRQ_MASK_CAPTURE (IVTV_IRQ_ENC_START_CAP | IVTV_IRQ_ENC_EOS) +#define IVTV_IRQ_MASK_DECODE (IVTV_IRQ_DEC_DATA_REQ|IVTV_IRQ_DEC_AUD_MODE_CHG) + +irqreturn_t ivtv_irq_handler(int irq, void *dev_id); + +void ivtv_irq_work_handler(struct kthread_work *work); +void ivtv_dma_stream_dec_prepare(struct ivtv_stream *s, u32 offset, int lock); +void ivtv_unfinished_dma(struct timer_list *t); + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-mailbox.c b/drivers/media/pci/ivtv/ivtv-mailbox.c new file mode 100644 index 000000000..cd7c9f2d4 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-mailbox.c @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + mailbox functions + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#include "ivtv-driver.h" +#include "ivtv-mailbox.h" + +/* Firmware mailbox flags*/ +#define IVTV_MBOX_FIRMWARE_DONE 0x00000004 +#define IVTV_MBOX_DRIVER_DONE 0x00000002 +#define IVTV_MBOX_DRIVER_BUSY 0x00000001 +#define IVTV_MBOX_FREE 0x00000000 + +/* Firmware mailbox standard timeout */ +#define IVTV_API_STD_TIMEOUT 0x02000000 + +#define API_CACHE (1 << 0) /* Allow the command to be stored in the cache */ +#define API_RESULT (1 << 1) /* Allow 1 second for this cmd to end */ +#define API_FAST_RESULT (3 << 1) /* Allow 0.1 second for this cmd to end */ +#define API_DMA (1 << 3) /* DMA mailbox, has special handling */ +#define API_HIGH_VOL (1 << 5) /* High volume command (i.e. called during encoding or decoding) */ +#define API_NO_WAIT_MB (1 << 4) /* Command may not wait for a free mailbox */ +#define API_NO_WAIT_RES (1 << 5) /* Command may not wait for the result */ +#define API_NO_POLL (1 << 6) /* Avoid pointless polling */ + +struct ivtv_api_info { + int flags; /* Flags, see above */ + const char *name; /* The name of the command */ +}; + +#define API_ENTRY(x, f) [x] = { (f), #x } + +static const struct ivtv_api_info api_info[256] = { + /* MPEG encoder API */ + API_ENTRY(CX2341X_ENC_PING_FW, API_FAST_RESULT), + API_ENTRY(CX2341X_ENC_START_CAPTURE, API_RESULT | API_NO_POLL), + API_ENTRY(CX2341X_ENC_STOP_CAPTURE, API_RESULT), + API_ENTRY(CX2341X_ENC_SET_AUDIO_ID, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_VIDEO_ID, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_PCR_ID, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_FRAME_RATE, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_FRAME_SIZE, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_BIT_RATE, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_GOP_PROPERTIES, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_ASPECT_RATIO, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_DNR_FILTER_MODE, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_DNR_FILTER_PROPS, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_CORING_LEVELS, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_SPATIAL_FILTER_TYPE, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_VBI_LINE, API_RESULT), + API_ENTRY(CX2341X_ENC_SET_STREAM_TYPE, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_OUTPUT_PORT, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_AUDIO_PROPERTIES, API_CACHE), + API_ENTRY(CX2341X_ENC_HALT_FW, API_FAST_RESULT), + API_ENTRY(CX2341X_ENC_GET_VERSION, API_FAST_RESULT), + API_ENTRY(CX2341X_ENC_SET_GOP_CLOSURE, API_CACHE), + API_ENTRY(CX2341X_ENC_GET_SEQ_END, API_RESULT), + API_ENTRY(CX2341X_ENC_SET_PGM_INDEX_INFO, API_FAST_RESULT), + API_ENTRY(CX2341X_ENC_SET_VBI_CONFIG, API_RESULT), + API_ENTRY(CX2341X_ENC_SET_DMA_BLOCK_SIZE, API_CACHE), + API_ENTRY(CX2341X_ENC_GET_PREV_DMA_INFO_MB_10, API_FAST_RESULT), + API_ENTRY(CX2341X_ENC_GET_PREV_DMA_INFO_MB_9, API_FAST_RESULT), + API_ENTRY(CX2341X_ENC_SCHED_DMA_TO_HOST, API_DMA | API_HIGH_VOL), + API_ENTRY(CX2341X_ENC_INITIALIZE_INPUT, API_RESULT), + API_ENTRY(CX2341X_ENC_SET_FRAME_DROP_RATE, API_CACHE), + API_ENTRY(CX2341X_ENC_PAUSE_ENCODER, API_RESULT), + API_ENTRY(CX2341X_ENC_REFRESH_INPUT, API_NO_WAIT_MB | API_HIGH_VOL), + API_ENTRY(CX2341X_ENC_SET_COPYRIGHT, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_EVENT_NOTIFICATION, API_RESULT), + API_ENTRY(CX2341X_ENC_SET_NUM_VSYNC_LINES, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_PLACEHOLDER, API_CACHE), + API_ENTRY(CX2341X_ENC_MUTE_VIDEO, API_RESULT), + API_ENTRY(CX2341X_ENC_MUTE_AUDIO, API_RESULT), + API_ENTRY(CX2341X_ENC_SET_VERT_CROP_LINE, API_FAST_RESULT), + API_ENTRY(CX2341X_ENC_MISC, API_FAST_RESULT), + /* Obsolete PULLDOWN API command */ + API_ENTRY(0xb1, API_CACHE), + + /* MPEG decoder API */ + API_ENTRY(CX2341X_DEC_PING_FW, API_FAST_RESULT), + API_ENTRY(CX2341X_DEC_START_PLAYBACK, API_RESULT | API_NO_POLL), + API_ENTRY(CX2341X_DEC_STOP_PLAYBACK, API_RESULT), + API_ENTRY(CX2341X_DEC_SET_PLAYBACK_SPEED, API_RESULT), + API_ENTRY(CX2341X_DEC_STEP_VIDEO, API_RESULT), + API_ENTRY(CX2341X_DEC_SET_DMA_BLOCK_SIZE, API_CACHE), + API_ENTRY(CX2341X_DEC_GET_XFER_INFO, API_FAST_RESULT), + API_ENTRY(CX2341X_DEC_GET_DMA_STATUS, API_FAST_RESULT), + API_ENTRY(CX2341X_DEC_SCHED_DMA_FROM_HOST, API_DMA | API_HIGH_VOL), + API_ENTRY(CX2341X_DEC_PAUSE_PLAYBACK, API_RESULT), + API_ENTRY(CX2341X_DEC_HALT_FW, API_FAST_RESULT), + API_ENTRY(CX2341X_DEC_SET_STANDARD, API_CACHE), + API_ENTRY(CX2341X_DEC_GET_VERSION, API_FAST_RESULT), + API_ENTRY(CX2341X_DEC_SET_STREAM_INPUT, API_CACHE), + API_ENTRY(CX2341X_DEC_GET_TIMING_INFO, API_RESULT /*| API_NO_WAIT_RES*/), + API_ENTRY(CX2341X_DEC_SET_AUDIO_MODE, API_CACHE), + API_ENTRY(CX2341X_DEC_SET_EVENT_NOTIFICATION, API_RESULT), + API_ENTRY(CX2341X_DEC_SET_DISPLAY_BUFFERS, API_CACHE), + API_ENTRY(CX2341X_DEC_EXTRACT_VBI, API_RESULT), + API_ENTRY(CX2341X_DEC_SET_DECODER_SOURCE, API_FAST_RESULT), + API_ENTRY(CX2341X_DEC_SET_PREBUFFERING, API_CACHE), + + /* OSD API */ + API_ENTRY(CX2341X_OSD_GET_FRAMEBUFFER, API_FAST_RESULT), + API_ENTRY(CX2341X_OSD_GET_PIXEL_FORMAT, API_FAST_RESULT), + API_ENTRY(CX2341X_OSD_SET_PIXEL_FORMAT, API_CACHE), + API_ENTRY(CX2341X_OSD_GET_STATE, API_FAST_RESULT), + API_ENTRY(CX2341X_OSD_SET_STATE, API_CACHE), + API_ENTRY(CX2341X_OSD_GET_OSD_COORDS, API_FAST_RESULT), + API_ENTRY(CX2341X_OSD_SET_OSD_COORDS, API_CACHE), + API_ENTRY(CX2341X_OSD_GET_SCREEN_COORDS, API_FAST_RESULT), + API_ENTRY(CX2341X_OSD_SET_SCREEN_COORDS, API_CACHE), + API_ENTRY(CX2341X_OSD_GET_GLOBAL_ALPHA, API_FAST_RESULT), + API_ENTRY(CX2341X_OSD_SET_GLOBAL_ALPHA, API_CACHE), + API_ENTRY(CX2341X_OSD_SET_BLEND_COORDS, API_CACHE), + API_ENTRY(CX2341X_OSD_GET_FLICKER_STATE, API_FAST_RESULT), + API_ENTRY(CX2341X_OSD_SET_FLICKER_STATE, API_CACHE), + API_ENTRY(CX2341X_OSD_BLT_COPY, API_RESULT), + API_ENTRY(CX2341X_OSD_BLT_FILL, API_RESULT), + API_ENTRY(CX2341X_OSD_BLT_TEXT, API_RESULT), + API_ENTRY(CX2341X_OSD_SET_FRAMEBUFFER_WINDOW, API_CACHE), + API_ENTRY(CX2341X_OSD_SET_CHROMA_KEY, API_CACHE), + API_ENTRY(CX2341X_OSD_GET_ALPHA_CONTENT_INDEX, API_FAST_RESULT), + API_ENTRY(CX2341X_OSD_SET_ALPHA_CONTENT_INDEX, API_CACHE) +}; + +static int try_mailbox(struct ivtv *itv, struct ivtv_mailbox_data *mbdata, int mb) +{ + u32 flags = readl(&mbdata->mbox[mb].flags); + int is_free = flags == IVTV_MBOX_FREE || (flags & IVTV_MBOX_FIRMWARE_DONE); + + /* if the mailbox is free, then try to claim it */ + if (is_free && !test_and_set_bit(mb, &mbdata->busy)) { + write_sync(IVTV_MBOX_DRIVER_BUSY, &mbdata->mbox[mb].flags); + return 1; + } + return 0; +} + +/* Try to find a free mailbox. Note mailbox 0 is reserved for DMA and so is not + attempted here. */ +static int get_mailbox(struct ivtv *itv, struct ivtv_mailbox_data *mbdata, int flags) +{ + unsigned long then = jiffies; + int i, mb; + int max_mbox = mbdata->max_mbox; + int retries = 100; + + /* All slow commands use the same mailbox, serializing them and also + leaving the other mailbox free for simple fast commands. */ + if ((flags & API_FAST_RESULT) == API_RESULT) + max_mbox = 1; + + /* find free non-DMA mailbox */ + for (i = 0; i < retries; i++) { + for (mb = 1; mb <= max_mbox; mb++) + if (try_mailbox(itv, mbdata, mb)) + return mb; + + /* Sleep before a retry, if not atomic */ + if (!(flags & API_NO_WAIT_MB)) { + if (time_after(jiffies, + then + msecs_to_jiffies(10*retries))) + break; + ivtv_msleep_timeout(10, 0); + } + } + return -ENODEV; +} + +static void write_mailbox(volatile struct ivtv_mailbox __iomem *mbox, int cmd, int args, u32 data[]) +{ + int i; + + write_sync(cmd, &mbox->cmd); + write_sync(IVTV_API_STD_TIMEOUT, &mbox->timeout); + + for (i = 0; i < CX2341X_MBOX_MAX_DATA; i++) + write_sync(data[i], &mbox->data[i]); + + write_sync(IVTV_MBOX_DRIVER_DONE | IVTV_MBOX_DRIVER_BUSY, &mbox->flags); +} + +static void clear_all_mailboxes(struct ivtv *itv, struct ivtv_mailbox_data *mbdata) +{ + int i; + + for (i = 0; i <= mbdata->max_mbox; i++) { + IVTV_DEBUG_WARN("Clearing mailbox %d: cmd 0x%08x flags 0x%08x\n", + i, readl(&mbdata->mbox[i].cmd), readl(&mbdata->mbox[i].flags)); + write_sync(0, &mbdata->mbox[i].flags); + clear_bit(i, &mbdata->busy); + } +} + +static int ivtv_api_call(struct ivtv *itv, int cmd, int args, u32 data[]) +{ + struct ivtv_mailbox_data *mbdata = (cmd >= 128) ? &itv->enc_mbox : &itv->dec_mbox; + volatile struct ivtv_mailbox __iomem *mbox; + int api_timeout = msecs_to_jiffies(1000); + int flags, mb, i; + unsigned long then; + + /* sanity checks */ + if (NULL == mbdata) { + IVTV_ERR("No mailbox allocated\n"); + return -ENODEV; + } + if (args < 0 || args > CX2341X_MBOX_MAX_DATA || + cmd < 0 || cmd > 255 || api_info[cmd].name == NULL) { + IVTV_ERR("Invalid MB call: cmd = 0x%02x, args = %d\n", cmd, args); + return -EINVAL; + } + + if (api_info[cmd].flags & API_HIGH_VOL) { + IVTV_DEBUG_HI_MB("MB Call: %s\n", api_info[cmd].name); + } + else { + IVTV_DEBUG_MB("MB Call: %s\n", api_info[cmd].name); + } + + /* clear possibly uninitialized part of data array */ + for (i = args; i < CX2341X_MBOX_MAX_DATA; i++) + data[i] = 0; + + /* If this command was issued within the last 30 minutes and with identical + data, then just return 0 as there is no need to issue this command again. + Just an optimization to prevent unnecessary use of mailboxes. */ + if (itv->api_cache[cmd].last_jiffies && + time_before(jiffies, + itv->api_cache[cmd].last_jiffies + + msecs_to_jiffies(1800000)) && + !memcmp(data, itv->api_cache[cmd].data, sizeof(itv->api_cache[cmd].data))) { + itv->api_cache[cmd].last_jiffies = jiffies; + return 0; + } + + flags = api_info[cmd].flags; + + if (flags & API_DMA) { + for (i = 0; i < 100; i++) { + mb = i % (mbdata->max_mbox + 1); + if (try_mailbox(itv, mbdata, mb)) { + write_mailbox(&mbdata->mbox[mb], cmd, args, data); + clear_bit(mb, &mbdata->busy); + return 0; + } + IVTV_DEBUG_WARN("%s: mailbox %d not free %08x\n", + api_info[cmd].name, mb, readl(&mbdata->mbox[mb].flags)); + } + IVTV_WARN("Could not find free DMA mailbox for %s\n", api_info[cmd].name); + clear_all_mailboxes(itv, mbdata); + return -EBUSY; + } + + if ((flags & API_FAST_RESULT) == API_FAST_RESULT) + api_timeout = msecs_to_jiffies(100); + + mb = get_mailbox(itv, mbdata, flags); + if (mb < 0) { + IVTV_DEBUG_WARN("No free mailbox found (%s)\n", api_info[cmd].name); + clear_all_mailboxes(itv, mbdata); + return -EBUSY; + } + mbox = &mbdata->mbox[mb]; + write_mailbox(mbox, cmd, args, data); + if (flags & API_CACHE) { + memcpy(itv->api_cache[cmd].data, data, sizeof(itv->api_cache[cmd].data)); + itv->api_cache[cmd].last_jiffies = jiffies; + } + if ((flags & API_RESULT) == 0) { + clear_bit(mb, &mbdata->busy); + return 0; + } + + /* Get results */ + then = jiffies; + + if (!(flags & API_NO_POLL)) { + /* First try to poll, then switch to delays */ + for (i = 0; i < 100; i++) { + if (readl(&mbox->flags) & IVTV_MBOX_FIRMWARE_DONE) + break; + } + } + while (!(readl(&mbox->flags) & IVTV_MBOX_FIRMWARE_DONE)) { + if (time_after(jiffies, then + api_timeout)) { + IVTV_DEBUG_WARN("Could not get result (%s)\n", api_info[cmd].name); + /* reset the mailbox, but it is likely too late already */ + write_sync(0, &mbox->flags); + clear_bit(mb, &mbdata->busy); + return -EIO; + } + if (flags & API_NO_WAIT_RES) + mdelay(1); + else + ivtv_msleep_timeout(1, 0); + } + if (time_after(jiffies, then + msecs_to_jiffies(100))) + IVTV_DEBUG_WARN("%s took %u jiffies\n", + api_info[cmd].name, + jiffies_to_msecs(jiffies - then)); + + for (i = 0; i < CX2341X_MBOX_MAX_DATA; i++) + data[i] = readl(&mbox->data[i]); + write_sync(0, &mbox->flags); + clear_bit(mb, &mbdata->busy); + return 0; +} + +int ivtv_api(struct ivtv *itv, int cmd, int args, u32 data[]) +{ + int res = ivtv_api_call(itv, cmd, args, data); + + /* Allow a single retry, probably already too late though. + If there is no free mailbox then that is usually an indication + of a more serious problem. */ + return (res == -EBUSY) ? ivtv_api_call(itv, cmd, args, data) : res; +} + +int ivtv_api_func(void *priv, u32 cmd, int in, int out, u32 data[CX2341X_MBOX_MAX_DATA]) +{ + return ivtv_api(priv, cmd, in, data); +} + +int ivtv_vapi_result(struct ivtv *itv, u32 data[CX2341X_MBOX_MAX_DATA], int cmd, int args, ...) +{ + va_list ap; + int i; + + va_start(ap, args); + for (i = 0; i < args; i++) { + data[i] = va_arg(ap, u32); + } + va_end(ap); + return ivtv_api(itv, cmd, args, data); +} + +int ivtv_vapi(struct ivtv *itv, int cmd, int args, ...) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + va_list ap; + int i; + + va_start(ap, args); + for (i = 0; i < args; i++) { + data[i] = va_arg(ap, u32); + } + va_end(ap); + return ivtv_api(itv, cmd, args, data); +} + +/* This one is for stuff that can't sleep.. irq handlers, etc.. */ +void ivtv_api_get_data(struct ivtv_mailbox_data *mbdata, int mb, + int argc, u32 data[]) +{ + volatile u32 __iomem *p = mbdata->mbox[mb].data; + int i; + for (i = 0; i < argc; i++, p++) + data[i] = readl(p); +} + +/* Wipe api cache */ +void ivtv_mailbox_cache_invalidate(struct ivtv *itv) +{ + int i; + for (i = 0; i < 256; i++) + itv->api_cache[i].last_jiffies = 0; +} diff --git a/drivers/media/pci/ivtv/ivtv-mailbox.h b/drivers/media/pci/ivtv/ivtv-mailbox.h new file mode 100644 index 000000000..364e7f515 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-mailbox.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + mailbox functions + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#ifndef IVTV_MAILBOX_H +#define IVTV_MAILBOX_H + +#define IVTV_MBOX_DMA_END 8 +#define IVTV_MBOX_DMA 9 + +void ivtv_api_get_data(struct ivtv_mailbox_data *mbdata, int mb, + int argc, u32 data[]); +int ivtv_api(struct ivtv *itv, int cmd, int args, u32 data[]); +int ivtv_vapi_result(struct ivtv *itv, u32 data[CX2341X_MBOX_MAX_DATA], int cmd, int args, ...); +int ivtv_vapi(struct ivtv *itv, int cmd, int args, ...); +int ivtv_api_func(void *priv, u32 cmd, int in, int out, u32 data[CX2341X_MBOX_MAX_DATA]); +void ivtv_mailbox_cache_invalidate(struct ivtv *itv); + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-queue.c b/drivers/media/pci/ivtv/ivtv-queue.c new file mode 100644 index 000000000..9e339ef0c --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-queue.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + buffer queues. + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#include "ivtv-driver.h" +#include "ivtv-queue.h" + +int ivtv_buf_copy_from_user(struct ivtv_stream *s, struct ivtv_buffer *buf, const char __user *src, int copybytes) +{ + if (s->buf_size - buf->bytesused < copybytes) + copybytes = s->buf_size - buf->bytesused; + if (copy_from_user(buf->buf + buf->bytesused, src, copybytes)) { + return -EFAULT; + } + buf->bytesused += copybytes; + return copybytes; +} + +void ivtv_buf_swap(struct ivtv_buffer *buf) +{ + int i; + + for (i = 0; i < buf->bytesused; i += 4) + swab32s((u32 *)(buf->buf + i)); +} + +void ivtv_queue_init(struct ivtv_queue *q) +{ + INIT_LIST_HEAD(&q->list); + q->buffers = 0; + q->length = 0; + q->bytesused = 0; +} + +void ivtv_enqueue(struct ivtv_stream *s, struct ivtv_buffer *buf, struct ivtv_queue *q) +{ + unsigned long flags; + + /* clear the buffer if it is going to be enqueued to the free queue */ + if (q == &s->q_free) { + buf->bytesused = 0; + buf->readpos = 0; + buf->b_flags = 0; + buf->dma_xfer_cnt = 0; + } + spin_lock_irqsave(&s->qlock, flags); + list_add_tail(&buf->list, &q->list); + q->buffers++; + q->length += s->buf_size; + q->bytesused += buf->bytesused - buf->readpos; + spin_unlock_irqrestore(&s->qlock, flags); +} + +struct ivtv_buffer *ivtv_dequeue(struct ivtv_stream *s, struct ivtv_queue *q) +{ + struct ivtv_buffer *buf = NULL; + unsigned long flags; + + spin_lock_irqsave(&s->qlock, flags); + if (!list_empty(&q->list)) { + buf = list_entry(q->list.next, struct ivtv_buffer, list); + list_del_init(q->list.next); + q->buffers--; + q->length -= s->buf_size; + q->bytesused -= buf->bytesused - buf->readpos; + } + spin_unlock_irqrestore(&s->qlock, flags); + return buf; +} + +static void ivtv_queue_move_buf(struct ivtv_stream *s, struct ivtv_queue *from, + struct ivtv_queue *to, int clear) +{ + struct ivtv_buffer *buf = list_entry(from->list.next, struct ivtv_buffer, list); + + list_move_tail(from->list.next, &to->list); + from->buffers--; + from->length -= s->buf_size; + from->bytesused -= buf->bytesused - buf->readpos; + /* special handling for q_free */ + if (clear) + buf->bytesused = buf->readpos = buf->b_flags = buf->dma_xfer_cnt = 0; + to->buffers++; + to->length += s->buf_size; + to->bytesused += buf->bytesused - buf->readpos; +} + +/* Move 'needed_bytes' worth of buffers from queue 'from' into queue 'to'. + If 'needed_bytes' == 0, then move all buffers from 'from' into 'to'. + If 'steal' != NULL, then buffers may also taken from that queue if + needed, but only if 'from' is the free queue. + + The buffer is automatically cleared if it goes to the free queue. It is + also cleared if buffers need to be taken from the 'steal' queue and + the 'from' queue is the free queue. + + When 'from' is q_free, then needed_bytes is compared to the total + available buffer length, otherwise needed_bytes is compared to the + bytesused value. For the 'steal' queue the total available buffer + length is always used. + + -ENOMEM is returned if the buffers could not be obtained, 0 if all + buffers where obtained from the 'from' list and if non-zero then + the number of stolen buffers is returned. */ +int ivtv_queue_move(struct ivtv_stream *s, struct ivtv_queue *from, struct ivtv_queue *steal, + struct ivtv_queue *to, int needed_bytes) +{ + unsigned long flags; + int rc = 0; + int from_free = from == &s->q_free; + int to_free = to == &s->q_free; + int bytes_available, bytes_steal; + + spin_lock_irqsave(&s->qlock, flags); + if (needed_bytes == 0) { + from_free = 1; + needed_bytes = from->length; + } + + bytes_available = from_free ? from->length : from->bytesused; + bytes_steal = (from_free && steal) ? steal->length : 0; + + if (bytes_available + bytes_steal < needed_bytes) { + spin_unlock_irqrestore(&s->qlock, flags); + return -ENOMEM; + } + while (steal && bytes_available < needed_bytes) { + struct ivtv_buffer *buf = list_entry(steal->list.prev, struct ivtv_buffer, list); + u16 dma_xfer_cnt = buf->dma_xfer_cnt; + + /* move buffers from the tail of the 'steal' queue to the tail of the + 'from' queue. Always copy all the buffers with the same dma_xfer_cnt + value, this ensures that you do not end up with partial frame data + if one frame is stored in multiple buffers. */ + while (dma_xfer_cnt == buf->dma_xfer_cnt) { + list_move_tail(steal->list.prev, &from->list); + rc++; + steal->buffers--; + steal->length -= s->buf_size; + steal->bytesused -= buf->bytesused - buf->readpos; + buf->bytesused = buf->readpos = buf->b_flags = buf->dma_xfer_cnt = 0; + from->buffers++; + from->length += s->buf_size; + bytes_available += s->buf_size; + if (list_empty(&steal->list)) + break; + buf = list_entry(steal->list.prev, struct ivtv_buffer, list); + } + } + if (from_free) { + u32 old_length = to->length; + + while (to->length - old_length < needed_bytes) { + ivtv_queue_move_buf(s, from, to, 1); + } + } + else { + u32 old_bytesused = to->bytesused; + + while (to->bytesused - old_bytesused < needed_bytes) { + ivtv_queue_move_buf(s, from, to, to_free); + } + } + spin_unlock_irqrestore(&s->qlock, flags); + return rc; +} + +void ivtv_flush_queues(struct ivtv_stream *s) +{ + ivtv_queue_move(s, &s->q_io, NULL, &s->q_free, 0); + ivtv_queue_move(s, &s->q_full, NULL, &s->q_free, 0); + ivtv_queue_move(s, &s->q_dma, NULL, &s->q_free, 0); + ivtv_queue_move(s, &s->q_predma, NULL, &s->q_free, 0); +} + +int ivtv_stream_alloc(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + int SGsize = sizeof(struct ivtv_sg_host_element) * s->buffers; + int i; + + if (s->buffers == 0) + return 0; + + IVTV_DEBUG_INFO("Allocate %s%s stream: %d x %d buffers (%dkB total)\n", + s->dma != DMA_NONE ? "DMA " : "", + s->name, s->buffers, s->buf_size, s->buffers * s->buf_size / 1024); + + s->sg_pending = kzalloc(SGsize, GFP_KERNEL|__GFP_NOWARN); + if (s->sg_pending == NULL) { + IVTV_ERR("Could not allocate sg_pending for %s stream\n", s->name); + return -ENOMEM; + } + s->sg_pending_size = 0; + + s->sg_processing = kzalloc(SGsize, GFP_KERNEL|__GFP_NOWARN); + if (s->sg_processing == NULL) { + IVTV_ERR("Could not allocate sg_processing for %s stream\n", s->name); + kfree(s->sg_pending); + s->sg_pending = NULL; + return -ENOMEM; + } + s->sg_processing_size = 0; + + s->sg_dma = kzalloc_obj(struct ivtv_sg_element, + GFP_KERNEL | __GFP_NOWARN); + if (s->sg_dma == NULL) { + IVTV_ERR("Could not allocate sg_dma for %s stream\n", s->name); + kfree(s->sg_pending); + s->sg_pending = NULL; + kfree(s->sg_processing); + s->sg_processing = NULL; + return -ENOMEM; + } + if (ivtv_might_use_dma(s)) { + s->sg_handle = dma_map_single(&itv->pdev->dev, s->sg_dma, + sizeof(struct ivtv_sg_element), + DMA_TO_DEVICE); + ivtv_stream_sync_for_cpu(s); + } + + /* allocate stream buffers. Initially all buffers are in q_free. */ + for (i = 0; i < s->buffers; i++) { + struct ivtv_buffer *buf = kzalloc_obj(struct ivtv_buffer, + GFP_KERNEL | __GFP_NOWARN); + + if (buf == NULL) + break; + buf->buf = kmalloc(s->buf_size + 256, GFP_KERNEL|__GFP_NOWARN); + if (buf->buf == NULL) { + kfree(buf); + break; + } + INIT_LIST_HEAD(&buf->list); + if (ivtv_might_use_dma(s)) { + buf->dma_handle = dma_map_single(&s->itv->pdev->dev, + buf->buf, s->buf_size + 256, s->dma); + ivtv_buf_sync_for_cpu(s, buf); + } + ivtv_enqueue(s, buf, &s->q_free); + } + if (i == s->buffers) + return 0; + IVTV_ERR("Couldn't allocate buffers for %s stream\n", s->name); + ivtv_stream_free(s); + return -ENOMEM; +} + +void ivtv_stream_free(struct ivtv_stream *s) +{ + struct ivtv_buffer *buf; + + /* move all buffers to q_free */ + ivtv_flush_queues(s); + + /* empty q_free */ + while ((buf = ivtv_dequeue(s, &s->q_free))) { + if (ivtv_might_use_dma(s)) + dma_unmap_single(&s->itv->pdev->dev, buf->dma_handle, + s->buf_size + 256, s->dma); + kfree(buf->buf); + kfree(buf); + } + + /* Free SG Array/Lists */ + if (s->sg_dma != NULL) { + if (s->sg_handle != IVTV_DMA_UNMAPPED) { + dma_unmap_single(&s->itv->pdev->dev, s->sg_handle, + sizeof(struct ivtv_sg_element), + DMA_TO_DEVICE); + s->sg_handle = IVTV_DMA_UNMAPPED; + } + kfree(s->sg_pending); + kfree(s->sg_processing); + kfree(s->sg_dma); + s->sg_pending = NULL; + s->sg_processing = NULL; + s->sg_dma = NULL; + s->sg_pending_size = 0; + s->sg_processing_size = 0; + } +} diff --git a/drivers/media/pci/ivtv/ivtv-queue.h b/drivers/media/pci/ivtv/ivtv-queue.h new file mode 100644 index 000000000..9619745d6 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-queue.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + buffer queues. + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#ifndef IVTV_QUEUE_H +#define IVTV_QUEUE_H + +#define IVTV_DMA_UNMAPPED ((u32) -1) +#define SLICED_VBI_PIO 0 + +/* ivtv_buffer utility functions */ + +static inline int ivtv_might_use_pio(struct ivtv_stream *s) +{ + return s->dma == DMA_NONE || (SLICED_VBI_PIO && s->type == IVTV_ENC_STREAM_TYPE_VBI); +} + +static inline int ivtv_use_pio(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + + return s->dma == DMA_NONE || + (SLICED_VBI_PIO && s->type == IVTV_ENC_STREAM_TYPE_VBI && itv->vbi.sliced_in->service_set); +} + +static inline int ivtv_might_use_dma(struct ivtv_stream *s) +{ + return s->dma != DMA_NONE; +} + +static inline int ivtv_use_dma(struct ivtv_stream *s) +{ + return !ivtv_use_pio(s); +} + +static inline void ivtv_buf_sync_for_cpu(struct ivtv_stream *s, struct ivtv_buffer *buf) +{ + if (ivtv_use_dma(s)) + dma_sync_single_for_cpu(&s->itv->pdev->dev, buf->dma_handle, + s->buf_size + 256, s->dma); +} + +static inline void ivtv_buf_sync_for_device(struct ivtv_stream *s, struct ivtv_buffer *buf) +{ + if (ivtv_use_dma(s)) + dma_sync_single_for_device(&s->itv->pdev->dev, + buf->dma_handle, s->buf_size + 256, + s->dma); +} + +int ivtv_buf_copy_from_user(struct ivtv_stream *s, struct ivtv_buffer *buf, const char __user *src, int copybytes); +void ivtv_buf_swap(struct ivtv_buffer *buf); + +/* ivtv_queue utility functions */ +void ivtv_queue_init(struct ivtv_queue *q); +void ivtv_enqueue(struct ivtv_stream *s, struct ivtv_buffer *buf, struct ivtv_queue *q); +struct ivtv_buffer *ivtv_dequeue(struct ivtv_stream *s, struct ivtv_queue *q); +int ivtv_queue_move(struct ivtv_stream *s, struct ivtv_queue *from, struct ivtv_queue *steal, + struct ivtv_queue *to, int needed_bytes); +void ivtv_flush_queues(struct ivtv_stream *s); + +/* ivtv_stream utility functions */ +int ivtv_stream_alloc(struct ivtv_stream *s); +void ivtv_stream_free(struct ivtv_stream *s); + +static inline void ivtv_stream_sync_for_cpu(struct ivtv_stream *s) +{ + if (ivtv_use_dma(s)) + dma_sync_single_for_cpu(&s->itv->pdev->dev, s->sg_handle, + sizeof(struct ivtv_sg_element), + DMA_TO_DEVICE); +} + +static inline void ivtv_stream_sync_for_device(struct ivtv_stream *s) +{ + if (ivtv_use_dma(s)) + dma_sync_single_for_device(&s->itv->pdev->dev, s->sg_handle, + sizeof(struct ivtv_sg_element), + DMA_TO_DEVICE); +} + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-routing.c b/drivers/media/pci/ivtv/ivtv-routing.c new file mode 100644 index 000000000..b1dfc2e96 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-routing.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Audio/video-routing-related ivtv functions. + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#include "ivtv-driver.h" +#include "ivtv-i2c.h" +#include "ivtv-cards.h" +#include "ivtv-gpio.h" +#include "ivtv-routing.h" + +#include +#include +#include +#include + +/* Selects the audio input and output according to the current + settings. */ +void ivtv_audio_set_io(struct ivtv *itv) +{ + const struct ivtv_card_audio_input *in; + u32 input, output = 0; + + /* Determine which input to use */ + if (test_bit(IVTV_F_I_RADIO_USER, &itv->i_flags)) + in = &itv->card->radio_input; + else + in = &itv->card->audio_inputs[itv->audio_input]; + + /* handle muxer chips */ + input = in->muxer_input; + if (itv->card->hw_muxer & IVTV_HW_M52790) + output = M52790_OUT_STEREO; + v4l2_subdev_call(itv->sd_muxer, audio, s_routing, + input, output, 0); + + input = in->audio_input; + output = 0; + if (itv->card->hw_audio & IVTV_HW_MSP34XX) + output = MSP_OUTPUT(MSP_SC_IN_DSP_SCART1); + ivtv_call_hw(itv, itv->card->hw_audio, audio, s_routing, + input, output, 0); +} + +/* Selects the video input and output according to the current + settings. */ +void ivtv_video_set_io(struct ivtv *itv) +{ + int inp = itv->active_input; + u32 input; + u32 type; + + v4l2_subdev_call(itv->sd_video, video, s_routing, + itv->card->video_inputs[inp].video_input, 0, 0); + + type = itv->card->video_inputs[inp].video_type; + + if (type == IVTV_CARD_INPUT_VID_TUNER) { + input = 0; /* Tuner */ + } else if (type < IVTV_CARD_INPUT_COMPOSITE1) { + input = 2; /* S-Video */ + } else { + input = 1; /* Composite */ + } + + if (itv->card->hw_video & IVTV_HW_GPIO) + ivtv_call_hw(itv, IVTV_HW_GPIO, video, s_routing, + input, 0, 0); + + if (itv->card->hw_video & IVTV_HW_UPD64031A) { + if (type == IVTV_CARD_INPUT_VID_TUNER || + type >= IVTV_CARD_INPUT_COMPOSITE1) { + /* Composite: GR on, connect to 3DYCS */ + input = UPD64031A_GR_ON | UPD64031A_3DYCS_COMPOSITE; + } else { + /* S-Video: GR bypassed, turn it off */ + input = UPD64031A_GR_OFF | UPD64031A_3DYCS_DISABLE; + } + input |= itv->card->gr_config; + + ivtv_call_hw(itv, IVTV_HW_UPD64031A, video, s_routing, + input, 0, 0); + } + + if (itv->card->hw_video & IVTV_HW_UPD6408X) { + input = UPD64083_YCS_MODE; + if (type > IVTV_CARD_INPUT_VID_TUNER && + type < IVTV_CARD_INPUT_COMPOSITE1) { + /* S-Video uses YCNR mode and internal Y-ADC, the + upd64031a is not used. */ + input |= UPD64083_YCNR_MODE; + } + else if (itv->card->hw_video & IVTV_HW_UPD64031A) { + /* Use upd64031a output for tuner and + composite(CX23416GYC only) inputs */ + if (type == IVTV_CARD_INPUT_VID_TUNER || + itv->card->type == IVTV_CARD_CX23416GYC) { + input |= UPD64083_EXT_Y_ADC; + } + } + ivtv_call_hw(itv, IVTV_HW_UPD6408X, video, s_routing, + input, 0, 0); + } +} diff --git a/drivers/media/pci/ivtv/ivtv-routing.h b/drivers/media/pci/ivtv/ivtv-routing.h new file mode 100644 index 000000000..69ddb66ef --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-routing.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Audio/video-routing-related ivtv functions. + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#ifndef IVTV_ROUTING_H +#define IVTV_ROUTING_H + +void ivtv_audio_set_io(struct ivtv *itv); +void ivtv_video_set_io(struct ivtv *itv); + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-streams.c b/drivers/media/pci/ivtv/ivtv-streams.c new file mode 100644 index 000000000..d98fe0c9d --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-streams.c @@ -0,0 +1,1026 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + init/start/stop/exit stream functions + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + */ + +/* License: GPL + * Author: Kevin Thayer + * + * This file will hold API related functions, both internal (firmware api) + * and external (v4l2, etc) + * + * ----- + * MPG600/MPG160 support by T.Adachi + * and Takeru KOMORIYA + * + * AVerMedia M179 GPIO info by Chris Pinkham + * using information provided by Jiun-Kuei Jung @ AVerMedia. + */ + +#include "ivtv-driver.h" +#include "ivtv-fileops.h" +#include "ivtv-queue.h" +#include "ivtv-mailbox.h" +#include "ivtv-ioctl.h" +#include "ivtv-irq.h" +#include "ivtv-yuv.h" +#include "ivtv-cards.h" +#include "ivtv-streams.h" +#include "ivtv-firmware.h" +#include + +static const struct v4l2_file_operations ivtv_v4l2_enc_fops = { + .owner = THIS_MODULE, + .read = ivtv_v4l2_read, + .write = ivtv_v4l2_write, + .open = ivtv_v4l2_open, + .unlocked_ioctl = video_ioctl2, +#ifdef CONFIG_COMPAT + .compat_ioctl32 = video_ioctl2, /* for ivtv_default() */ +#endif + .release = ivtv_v4l2_close, + .poll = ivtv_v4l2_enc_poll, +}; + +static const struct v4l2_file_operations ivtv_v4l2_dec_fops = { + .owner = THIS_MODULE, + .read = ivtv_v4l2_read, + .write = ivtv_v4l2_write, + .open = ivtv_v4l2_open, + .unlocked_ioctl = video_ioctl2, +#ifdef CONFIG_COMPAT + .compat_ioctl32 = video_ioctl2, /* for ivtv_default() */ +#endif + .release = ivtv_v4l2_close, + .poll = ivtv_v4l2_dec_poll, +}; + +static const struct v4l2_file_operations ivtv_v4l2_radio_fops = { + .owner = THIS_MODULE, + .open = ivtv_v4l2_open, + .unlocked_ioctl = video_ioctl2, +#ifdef CONFIG_COMPAT + .compat_ioctl32 = video_ioctl2, /* for ivtv_default() */ +#endif + .release = ivtv_v4l2_close, + .poll = ivtv_v4l2_enc_poll, +}; + +#define IVTV_V4L2_DEC_MPG_OFFSET 16 /* offset from 0 to register decoder mpg v4l2 minors on */ +#define IVTV_V4L2_ENC_PCM_OFFSET 24 /* offset from 0 to register pcm v4l2 minors on */ +#define IVTV_V4L2_ENC_YUV_OFFSET 32 /* offset from 0 to register yuv v4l2 minors on */ +#define IVTV_V4L2_DEC_YUV_OFFSET 48 /* offset from 0 to register decoder yuv v4l2 minors on */ +#define IVTV_V4L2_DEC_VBI_OFFSET 8 /* offset from 0 to register decoder vbi input v4l2 minors on */ +#define IVTV_V4L2_DEC_VOUT_OFFSET 16 /* offset from 0 to register vbi output v4l2 minors on */ + +static struct { + const char *name; + int vfl_type; + int num_offset; + int dma, pio; + u32 v4l2_caps; + const struct v4l2_file_operations *fops; +} ivtv_stream_info[] = { + { /* IVTV_ENC_STREAM_TYPE_MPG */ + "encoder MPG", + VFL_TYPE_VIDEO, 0, + DMA_FROM_DEVICE, 0, + V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER | + V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, + &ivtv_v4l2_enc_fops + }, + { /* IVTV_ENC_STREAM_TYPE_YUV */ + "encoder YUV", + VFL_TYPE_VIDEO, IVTV_V4L2_ENC_YUV_OFFSET, + DMA_FROM_DEVICE, 0, + V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER | + V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, + &ivtv_v4l2_enc_fops + }, + { /* IVTV_ENC_STREAM_TYPE_VBI */ + "encoder VBI", + VFL_TYPE_VBI, 0, + DMA_FROM_DEVICE, 0, + V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_CAPTURE | V4L2_CAP_TUNER | + V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, + &ivtv_v4l2_enc_fops + }, + { /* IVTV_ENC_STREAM_TYPE_PCM */ + "encoder PCM", + VFL_TYPE_VIDEO, IVTV_V4L2_ENC_PCM_OFFSET, + DMA_FROM_DEVICE, 0, + V4L2_CAP_TUNER | V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, + &ivtv_v4l2_enc_fops + }, + { /* IVTV_ENC_STREAM_TYPE_RAD */ + "encoder radio", + VFL_TYPE_RADIO, 0, + DMA_NONE, 1, + V4L2_CAP_RADIO | V4L2_CAP_TUNER, + &ivtv_v4l2_radio_fops + }, + { /* IVTV_DEC_STREAM_TYPE_MPG */ + "decoder MPG", + VFL_TYPE_VIDEO, IVTV_V4L2_DEC_MPG_OFFSET, + DMA_TO_DEVICE, 0, + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, + &ivtv_v4l2_dec_fops + }, + { /* IVTV_DEC_STREAM_TYPE_VBI */ + "decoder VBI", + VFL_TYPE_VBI, IVTV_V4L2_DEC_VBI_OFFSET, + DMA_NONE, 1, + V4L2_CAP_SLICED_VBI_CAPTURE | V4L2_CAP_READWRITE, + &ivtv_v4l2_enc_fops + }, + { /* IVTV_DEC_STREAM_TYPE_VOUT */ + "decoder VOUT", + VFL_TYPE_VBI, IVTV_V4L2_DEC_VOUT_OFFSET, + DMA_NONE, 1, + V4L2_CAP_SLICED_VBI_OUTPUT | V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, + &ivtv_v4l2_dec_fops + }, + { /* IVTV_DEC_STREAM_TYPE_YUV */ + "decoder YUV", + VFL_TYPE_VIDEO, IVTV_V4L2_DEC_YUV_OFFSET, + DMA_TO_DEVICE, 0, + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, + &ivtv_v4l2_dec_fops + } +}; + +static void ivtv_stream_init(struct ivtv *itv, int type) +{ + struct ivtv_stream *s = &itv->streams[type]; + + /* we need to keep vdev, so restore it afterwards */ + memset(s, 0, sizeof(*s)); + + /* initialize ivtv_stream fields */ + s->itv = itv; + s->type = type; + s->name = ivtv_stream_info[type].name; + s->vdev.device_caps = ivtv_stream_info[type].v4l2_caps; + + if (ivtv_stream_info[type].pio) + s->dma = DMA_NONE; + else + s->dma = ivtv_stream_info[type].dma; + s->buf_size = itv->stream_buf_size[type]; + if (s->buf_size) + s->buffers = (itv->options.kilobytes[type] * 1024 + s->buf_size - 1) / s->buf_size; + spin_lock_init(&s->qlock); + init_waitqueue_head(&s->waitq); + s->sg_handle = IVTV_DMA_UNMAPPED; + ivtv_queue_init(&s->q_free); + ivtv_queue_init(&s->q_full); + ivtv_queue_init(&s->q_dma); + ivtv_queue_init(&s->q_predma); + ivtv_queue_init(&s->q_io); +} + +static int ivtv_prep_dev(struct ivtv *itv, int type) +{ + struct ivtv_stream *s = &itv->streams[type]; + int num_offset = ivtv_stream_info[type].num_offset; + int num = itv->instance + ivtv_first_minor + num_offset; + + /* These four fields are always initialized. If vdev.v4l2_dev == NULL, then + this stream is not in use. In that case no other fields but these + four can be used. */ + s->vdev.v4l2_dev = NULL; + s->itv = itv; + s->type = type; + s->name = ivtv_stream_info[type].name; + + /* Check whether the radio is supported */ + if (type == IVTV_ENC_STREAM_TYPE_RAD && !(itv->v4l2_cap & V4L2_CAP_RADIO)) + return 0; + if (type >= IVTV_DEC_STREAM_TYPE_MPG && !(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return 0; + + /* User explicitly selected 0 buffers for these streams, so don't + create them. */ + if (ivtv_stream_info[type].dma != DMA_NONE && + itv->options.kilobytes[type] == 0) { + IVTV_INFO("Disabled %s device\n", ivtv_stream_info[type].name); + return 0; + } + + ivtv_stream_init(itv, type); + + snprintf(s->vdev.name, sizeof(s->vdev.name), "%s %s", + itv->v4l2_dev.name, s->name); + + s->vdev.num = num; + s->vdev.v4l2_dev = &itv->v4l2_dev; + if (ivtv_stream_info[type].v4l2_caps & + (V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_SLICED_VBI_OUTPUT)) + s->vdev.vfl_dir = VFL_DIR_TX; + s->vdev.fops = ivtv_stream_info[type].fops; + s->vdev.ctrl_handler = itv->v4l2_dev.ctrl_handler; + s->vdev.release = video_device_release_empty; + s->vdev.tvnorms = V4L2_STD_ALL; + s->vdev.lock = &itv->serialize_lock; + if (s->type == IVTV_DEC_STREAM_TYPE_VBI) { + v4l2_disable_ioctl(&s->vdev, VIDIOC_S_AUDIO); + v4l2_disable_ioctl(&s->vdev, VIDIOC_G_AUDIO); + v4l2_disable_ioctl(&s->vdev, VIDIOC_ENUMAUDIO); + v4l2_disable_ioctl(&s->vdev, VIDIOC_ENUMINPUT); + v4l2_disable_ioctl(&s->vdev, VIDIOC_S_INPUT); + v4l2_disable_ioctl(&s->vdev, VIDIOC_G_INPUT); + v4l2_disable_ioctl(&s->vdev, VIDIOC_S_FREQUENCY); + v4l2_disable_ioctl(&s->vdev, VIDIOC_G_FREQUENCY); + v4l2_disable_ioctl(&s->vdev, VIDIOC_S_TUNER); + v4l2_disable_ioctl(&s->vdev, VIDIOC_G_TUNER); + v4l2_disable_ioctl(&s->vdev, VIDIOC_S_STD); + } + ivtv_set_funcs(&s->vdev); + return 0; +} + +/* Initialize v4l2 variables and prepare v4l2 devices */ +int ivtv_streams_setup(struct ivtv *itv) +{ + int type; + + /* Setup V4L2 Devices */ + for (type = 0; type < IVTV_MAX_STREAMS; type++) { + /* Prepare device */ + if (ivtv_prep_dev(itv, type)) + break; + + if (itv->streams[type].vdev.v4l2_dev == NULL) + continue; + + /* Allocate Stream */ + if (ivtv_stream_alloc(&itv->streams[type])) + break; + } + if (type == IVTV_MAX_STREAMS) + return 0; + + /* One or more streams could not be initialized. Clean 'em all up. */ + ivtv_streams_cleanup(itv); + return -ENOMEM; +} + +static int ivtv_reg_dev(struct ivtv *itv, int type) +{ + struct ivtv_stream *s = &itv->streams[type]; + int vfl_type = ivtv_stream_info[type].vfl_type; + const char *name; + int num; + + if (s->vdev.v4l2_dev == NULL) + return 0; + + num = s->vdev.num; + /* card number + user defined offset + device offset */ + if (type != IVTV_ENC_STREAM_TYPE_MPG) { + struct ivtv_stream *s_mpg = &itv->streams[IVTV_ENC_STREAM_TYPE_MPG]; + + if (s_mpg->vdev.v4l2_dev) + num = s_mpg->vdev.num + ivtv_stream_info[type].num_offset; + } + if (itv->osd_video_pbase && (type == IVTV_DEC_STREAM_TYPE_YUV || + type == IVTV_DEC_STREAM_TYPE_MPG)) { + s->vdev.device_caps |= V4L2_CAP_VIDEO_OUTPUT_OVERLAY; + itv->v4l2_cap |= V4L2_CAP_VIDEO_OUTPUT_OVERLAY; + } + video_set_drvdata(&s->vdev, s); + + /* Register device. First try the desired minor, then any free one. */ + if (video_register_device_no_warn(&s->vdev, vfl_type, num)) { + IVTV_ERR("Couldn't register v4l2 device for %s (device node number %d)\n", + s->name, num); + return -ENOMEM; + } + name = video_device_node_name(&s->vdev); + + switch (vfl_type) { + case VFL_TYPE_VIDEO: + IVTV_INFO("Registered device %s for %s (%d kB)\n", + name, s->name, itv->options.kilobytes[type]); + break; + case VFL_TYPE_RADIO: + IVTV_INFO("Registered device %s for %s\n", + name, s->name); + break; + case VFL_TYPE_VBI: + if (itv->options.kilobytes[type]) + IVTV_INFO("Registered device %s for %s (%d kB)\n", + name, s->name, itv->options.kilobytes[type]); + else + IVTV_INFO("Registered device %s for %s\n", + name, s->name); + break; + } + return 0; +} + +/* Register v4l2 devices */ +int ivtv_streams_register(struct ivtv *itv) +{ + int type; + int err = 0; + + /* Register V4L2 devices */ + for (type = 0; type < IVTV_MAX_STREAMS; type++) + err |= ivtv_reg_dev(itv, type); + + if (err == 0) + return 0; + + /* One or more streams could not be initialized. Clean 'em all up. */ + ivtv_streams_cleanup(itv); + return -ENOMEM; +} + +/* Unregister v4l2 devices */ +void ivtv_streams_cleanup(struct ivtv *itv) +{ + int type; + + /* Teardown all streams */ + for (type = 0; type < IVTV_MAX_STREAMS; type++) { + struct video_device *vdev = &itv->streams[type].vdev; + + if (vdev->v4l2_dev == NULL) + continue; + + video_unregister_device(vdev); + ivtv_stream_free(&itv->streams[type]); + itv->streams[type].vdev.v4l2_dev = NULL; + } +} + +static void ivtv_vbi_setup(struct ivtv *itv) +{ + int raw = ivtv_raw_vbi(itv); + u32 data[CX2341X_MBOX_MAX_DATA]; + int lines; + int i; + + /* Reset VBI */ + ivtv_vapi(itv, CX2341X_ENC_SET_VBI_LINE, 5, 0xffff , 0, 0, 0, 0); + + /* setup VBI registers */ + if (raw) + v4l2_subdev_call(itv->sd_video, vbi, s_raw_fmt, &itv->vbi.in.fmt.vbi); + else + v4l2_subdev_call(itv->sd_video, vbi, s_sliced_fmt, &itv->vbi.in.fmt.sliced); + + /* determine number of lines and total number of VBI bytes. + A raw line takes 1443 bytes: 2 * 720 + 4 byte frame header - 1 + The '- 1' byte is probably an unused U or V byte. Or something... + A sliced line takes 51 bytes: 4 byte frame header, 4 byte internal + header, 42 data bytes + checksum (to be confirmed) */ + if (raw) { + lines = itv->vbi.count * 2; + } else { + lines = itv->is_60hz ? 24 : 38; + if (itv->is_60hz && (itv->hw_flags & IVTV_HW_CX25840)) + lines += 2; + } + + itv->vbi.enc_size = lines * (raw ? itv->vbi.raw_size : itv->vbi.sliced_size); + + /* Note: sliced vs raw flag doesn't seem to have any effect + TODO: check mode (0x02) value with older ivtv versions. */ + data[0] = raw | 0x02 | (0xbd << 8); + + /* Every X number of frames a VBI interrupt arrives (frames as in 25 or 30 fps) */ + data[1] = 1; + /* The VBI frames are stored in a ringbuffer with this size (with a VBI frame as unit) */ + data[2] = raw ? 4 : 4 * (itv->vbi.raw_size / itv->vbi.enc_size); + /* The start/stop codes determine which VBI lines end up in the raw VBI data area. + The codes are from table 24 in the saa7115 datasheet. Each raw/sliced/video line + is framed with codes FF0000XX where XX is the SAV/EAV (Start/End of Active Video) + code. These values for raw VBI are obtained from a driver disassembly. The sliced + start/stop codes was deduced from this, but they do not appear in the driver. + Other code pairs that I found are: 0x250E6249/0x13545454 and 0x25256262/0x38137F54. + However, I have no idea what these values are for. */ + if (itv->hw_flags & IVTV_HW_CX25840) { + /* Setup VBI for the cx25840 digitizer */ + if (raw) { + data[3] = 0x20602060; + data[4] = 0x30703070; + } else { + data[3] = 0xB0F0B0F0; + data[4] = 0xA0E0A0E0; + } + /* Lines per frame */ + data[5] = lines; + /* bytes per line */ + data[6] = (raw ? itv->vbi.raw_size : itv->vbi.sliced_size); + } else { + /* Setup VBI for the saa7115 digitizer */ + if (raw) { + data[3] = 0x25256262; + data[4] = 0x387F7F7F; + } else { + data[3] = 0xABABECEC; + data[4] = 0xB6F1F1F1; + } + /* Lines per frame */ + data[5] = lines; + /* bytes per line */ + data[6] = itv->vbi.enc_size / lines; + } + + IVTV_DEBUG_INFO( + "Setup VBI API header 0x%08x pkts %d buffs %d ln %d sz %d\n", + data[0], data[1], data[2], data[5], data[6]); + + ivtv_api(itv, CX2341X_ENC_SET_VBI_CONFIG, 7, data); + + /* returns the VBI encoder memory area. */ + itv->vbi.enc_start = data[2]; + itv->vbi.fpi = data[0]; + if (!itv->vbi.fpi) + itv->vbi.fpi = 1; + + IVTV_DEBUG_INFO("Setup VBI start 0x%08x frames %d fpi %d\n", + itv->vbi.enc_start, data[1], itv->vbi.fpi); + + /* select VBI lines. + Note that the sliced argument seems to have no effect. */ + for (i = 2; i <= 24; i++) { + int valid; + + if (itv->is_60hz) { + valid = i >= 10 && i < 22; + } else { + valid = i >= 6 && i < 24; + } + ivtv_vapi(itv, CX2341X_ENC_SET_VBI_LINE, 5, i - 1, + valid, 0 , 0, 0); + ivtv_vapi(itv, CX2341X_ENC_SET_VBI_LINE, 5, (i - 1) | 0x80000000, + valid, 0, 0, 0); + } + + /* Remaining VBI questions: + - Is it possible to select particular VBI lines only for inclusion in the MPEG + stream? Currently you can only get the first X lines. + - Is mixed raw and sliced VBI possible? + - What's the meaning of the raw/sliced flag? + - What's the meaning of params 2, 3 & 4 of the Select VBI command? */ +} + +int ivtv_start_v4l2_encode_stream(struct ivtv_stream *s) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv *itv = s->itv; + int captype = 0, subtype = 0; + int enable_passthrough = 0; + + if (s->vdev.v4l2_dev == NULL) + return -EINVAL; + + IVTV_DEBUG_INFO("Start encoder stream %s\n", s->name); + + switch (s->type) { + case IVTV_ENC_STREAM_TYPE_MPG: + captype = 0; + subtype = 3; + + /* Stop Passthrough */ + if (itv->output_mode == OUT_PASSTHROUGH) { + ivtv_passthrough_mode(itv, 0); + enable_passthrough = 1; + } + itv->mpg_data_received = itv->vbi_data_inserted = 0; + itv->dualwatch_jiffies = jiffies; + itv->dualwatch_stereo_mode = v4l2_ctrl_g_ctrl(itv->cxhdl.audio_mode); + itv->search_pack_header = 0; + break; + + case IVTV_ENC_STREAM_TYPE_YUV: + if (itv->output_mode == OUT_PASSTHROUGH) { + captype = 2; + subtype = 11; /* video+audio+decoder */ + break; + } + captype = 1; + subtype = 1; + break; + case IVTV_ENC_STREAM_TYPE_PCM: + captype = 1; + subtype = 2; + break; + case IVTV_ENC_STREAM_TYPE_VBI: + captype = 1; + subtype = 4; + + itv->vbi.frame = 0; + itv->vbi.inserted_frame = 0; + memset(itv->vbi.sliced_mpeg_size, + 0, sizeof(itv->vbi.sliced_mpeg_size)); + break; + default: + return -EINVAL; + } + s->subtype = subtype; + s->buffers_stolen = 0; + + /* Clear Streamoff flags in case left from last capture */ + clear_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + + if (atomic_read(&itv->capturing) == 0) { + int digitizer; + + /* Always use frame based mode. Experiments have demonstrated that byte + stream based mode results in dropped frames and corruption. Not often, + but occasionally. Many thanks go to Leonard Orb who spent a lot of + effort and time trying to trace the cause of the drop outs. */ + /* 1 frame per DMA */ + /*ivtv_vapi(itv, CX2341X_ENC_SET_DMA_BLOCK_SIZE, 2, 128, 0); */ + ivtv_vapi(itv, CX2341X_ENC_SET_DMA_BLOCK_SIZE, 2, 1, 1); + + /* Stuff from Windows, we don't know what it is */ + ivtv_vapi(itv, CX2341X_ENC_SET_VERT_CROP_LINE, 1, 0); + /* According to the docs, this should be correct. However, this is + untested. I don't dare enable this without having tested it. + Only very few old cards actually have this hardware combination. + ivtv_vapi(itv, CX2341X_ENC_SET_VERT_CROP_LINE, 1, + ((itv->hw_flags & IVTV_HW_SAA7114) && itv->is_60hz) ? 10001 : 0); + */ + ivtv_vapi(itv, CX2341X_ENC_MISC, 2, 3, !itv->has_cx23415); + ivtv_vapi(itv, CX2341X_ENC_MISC, 2, 8, 0); + ivtv_vapi(itv, CX2341X_ENC_MISC, 2, 4, 1); + ivtv_vapi(itv, CX2341X_ENC_MISC, 1, 12); + + /* assign placeholder */ + ivtv_vapi(itv, CX2341X_ENC_SET_PLACEHOLDER, 12, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + if (itv->card->hw_all & (IVTV_HW_SAA7115 | IVTV_HW_SAA717X)) + digitizer = 0xF1; + else if (itv->card->hw_all & IVTV_HW_SAA7114) + digitizer = 0xEF; + else /* cx25840 */ + digitizer = 0x140; + + ivtv_vapi(itv, CX2341X_ENC_SET_NUM_VSYNC_LINES, 2, digitizer, digitizer); + + /* Setup VBI */ + if (itv->v4l2_cap & V4L2_CAP_VBI_CAPTURE) { + ivtv_vbi_setup(itv); + } + + /* assign program index info. Mask 7: select I/P/B, Num_req: 400 max */ + ivtv_vapi_result(itv, data, CX2341X_ENC_SET_PGM_INDEX_INFO, 2, 7, 400); + itv->pgm_info_offset = data[0]; + itv->pgm_info_num = data[1]; + itv->pgm_info_write_idx = 0; + itv->pgm_info_read_idx = 0; + + IVTV_DEBUG_INFO("PGM Index at 0x%08x with %d elements\n", + itv->pgm_info_offset, itv->pgm_info_num); + + /* Setup API for Stream */ + cx2341x_handler_setup(&itv->cxhdl); + + /* mute if capturing radio */ + if (test_bit(IVTV_F_I_RADIO_USER, &itv->i_flags)) + ivtv_vapi(itv, CX2341X_ENC_MUTE_VIDEO, 1, + 1 | (v4l2_ctrl_g_ctrl(itv->cxhdl.video_mute_yuv) << 8)); + } + + /* Vsync Setup */ + if (itv->has_cx23415 && !test_and_set_bit(IVTV_F_I_DIG_RST, &itv->i_flags)) { + /* event notification (on) */ + ivtv_vapi(itv, CX2341X_ENC_SET_EVENT_NOTIFICATION, 4, 0, 1, IVTV_IRQ_ENC_VIM_RST, -1); + ivtv_clear_irq_mask(itv, IVTV_IRQ_ENC_VIM_RST); + } + + if (atomic_read(&itv->capturing) == 0) { + /* Clear all Pending Interrupts */ + ivtv_set_irq_mask(itv, IVTV_IRQ_MASK_CAPTURE); + + clear_bit(IVTV_F_I_EOS, &itv->i_flags); + + cx2341x_handler_set_busy(&itv->cxhdl, 1); + + /* Initialize Digitizer for Capture */ + /* Avoid tinny audio problem - ensure audio clocks are going */ + v4l2_subdev_call(itv->sd_audio, audio, s_stream, 1); + /* Avoid unpredictable PCI bus hang - disable video clocks */ + if (itv->sd_video_is_streaming) + v4l2_subdev_call(itv->sd_video, video, s_stream, 0); + ivtv_msleep_timeout(300, 0); + ivtv_vapi(itv, CX2341X_ENC_INITIALIZE_INPUT, 0); + v4l2_subdev_call(itv->sd_video, video, s_stream, 1); + itv->sd_video_is_streaming = true; + } + + /* begin_capture */ + if (ivtv_vapi(itv, CX2341X_ENC_START_CAPTURE, 2, captype, subtype)) + { + IVTV_DEBUG_WARN( "Error starting capture!\n"); + return -EINVAL; + } + + /* Start Passthrough */ + if (enable_passthrough) { + ivtv_passthrough_mode(itv, 1); + } + + if (s->type == IVTV_ENC_STREAM_TYPE_VBI) + ivtv_clear_irq_mask(itv, IVTV_IRQ_ENC_VBI_CAP); + else + ivtv_clear_irq_mask(itv, IVTV_IRQ_MASK_CAPTURE); + + /* you're live! sit back and await interrupts :) */ + atomic_inc(&itv->capturing); + return 0; +} +EXPORT_SYMBOL(ivtv_start_v4l2_encode_stream); + +static int ivtv_setup_v4l2_decode_stream(struct ivtv_stream *s) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv *itv = s->itv; + int datatype; + u16 width; + u16 height; + + if (s->vdev.v4l2_dev == NULL) + return -EINVAL; + + IVTV_DEBUG_INFO("Setting some initial decoder settings\n"); + + width = itv->cxhdl.width; + height = itv->cxhdl.height; + + /* set audio mode to left/stereo for dual/stereo mode. */ + ivtv_vapi(itv, CX2341X_DEC_SET_AUDIO_MODE, 2, itv->audio_bilingual_mode, itv->audio_stereo_mode); + + /* set number of internal decoder buffers */ + ivtv_vapi(itv, CX2341X_DEC_SET_DISPLAY_BUFFERS, 1, 0); + + /* prebuffering */ + ivtv_vapi(itv, CX2341X_DEC_SET_PREBUFFERING, 1, 1); + + /* extract from user packets */ + ivtv_vapi_result(itv, data, CX2341X_DEC_EXTRACT_VBI, 1, 1); + itv->vbi.dec_start = data[0]; + + IVTV_DEBUG_INFO("Decoder VBI RE-Insert start 0x%08x size 0x%08x\n", + itv->vbi.dec_start, data[1]); + + /* set decoder source settings */ + /* Data type: 0 = mpeg from host, + 1 = yuv from encoder, + 2 = yuv_from_host */ + switch (s->type) { + case IVTV_DEC_STREAM_TYPE_YUV: + if (itv->output_mode == OUT_PASSTHROUGH) { + datatype = 1; + } else { + /* Fake size to avoid switching video standard */ + datatype = 2; + width = 720; + height = itv->is_out_50hz ? 576 : 480; + } + IVTV_DEBUG_INFO("Setup DEC YUV Stream data[0] = %d\n", datatype); + break; + case IVTV_DEC_STREAM_TYPE_MPG: + default: + datatype = 0; + break; + } + if (ivtv_vapi(itv, CX2341X_DEC_SET_DECODER_SOURCE, 4, datatype, + width, height, itv->cxhdl.audio_properties)) { + IVTV_DEBUG_WARN("Couldn't initialize decoder source\n"); + } + + /* Decoder sometimes dies here, so wait a moment */ + ivtv_msleep_timeout(10, 0); + + /* Known failure point for firmware, so check */ + return ivtv_firmware_check(itv, "ivtv_setup_v4l2_decode_stream"); +} + +int ivtv_start_v4l2_decode_stream(struct ivtv_stream *s, int gop_offset) +{ + struct ivtv *itv = s->itv; + int rc; + + if (s->vdev.v4l2_dev == NULL) + return -EINVAL; + + if (test_and_set_bit(IVTV_F_S_STREAMING, &s->s_flags)) + return 0; /* already started */ + + IVTV_DEBUG_INFO("Starting decode stream %s (gop_offset %d)\n", s->name, gop_offset); + + rc = ivtv_setup_v4l2_decode_stream(s); + if (rc < 0) { + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + return rc; + } + + /* set dma size to 65536 bytes */ + ivtv_vapi(itv, CX2341X_DEC_SET_DMA_BLOCK_SIZE, 1, 65536); + + /* Clear Streamoff */ + clear_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + + /* Zero out decoder counters */ + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA_END].data[0]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA_END].data[1]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA_END].data[2]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA_END].data[3]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA].data[0]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA].data[1]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA].data[2]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA].data[3]); + + /* turn on notification of dual/stereo mode change */ + ivtv_vapi(itv, CX2341X_DEC_SET_EVENT_NOTIFICATION, 4, 0, 1, IVTV_IRQ_DEC_AUD_MODE_CHG, -1); + + /* start playback */ + ivtv_vapi(itv, CX2341X_DEC_START_PLAYBACK, 2, gop_offset, 0); + + /* Let things settle before we actually start */ + ivtv_msleep_timeout(10, 0); + + /* Clear the following Interrupt mask bits for decoding */ + ivtv_clear_irq_mask(itv, IVTV_IRQ_MASK_DECODE); + IVTV_DEBUG_IRQ("IRQ Mask is now: 0x%08x\n", itv->irqmask); + + /* you're live! sit back and await interrupts :) */ + atomic_inc(&itv->decoding); + return 0; +} + +void ivtv_stop_all_captures(struct ivtv *itv) +{ + int i; + + for (i = IVTV_MAX_STREAMS - 1; i >= 0; i--) { + struct ivtv_stream *s = &itv->streams[i]; + + if (s->vdev.v4l2_dev == NULL) + continue; + if (test_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + ivtv_stop_v4l2_encode_stream(s, 0); + } + } +} + +int ivtv_stop_v4l2_encode_stream(struct ivtv_stream *s, int gop_end) +{ + struct ivtv *itv = s->itv; + DECLARE_WAITQUEUE(wait, current); + int cap_type; + int stopmode; + + if (s->vdev.v4l2_dev == NULL) + return -EINVAL; + + /* This function assumes that you are allowed to stop the capture + and that we are actually capturing */ + + IVTV_DEBUG_INFO("Stop Capture\n"); + + if (s->type == IVTV_DEC_STREAM_TYPE_VOUT) + return 0; + if (atomic_read(&itv->capturing) == 0) + return 0; + + switch (s->type) { + case IVTV_ENC_STREAM_TYPE_YUV: + cap_type = 1; + break; + case IVTV_ENC_STREAM_TYPE_PCM: + cap_type = 1; + break; + case IVTV_ENC_STREAM_TYPE_VBI: + cap_type = 1; + break; + case IVTV_ENC_STREAM_TYPE_MPG: + default: + cap_type = 0; + break; + } + + /* Stop Capture Mode */ + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && gop_end) { + stopmode = 0; + } else { + stopmode = 1; + } + + /* end_capture */ + /* when: 0 = end of GOP 1 = NOW!, type: 0 = mpeg, subtype: 3 = video+audio */ + ivtv_vapi(itv, CX2341X_ENC_STOP_CAPTURE, 3, stopmode, cap_type, s->subtype); + + if (!test_bit(IVTV_F_S_PASSTHROUGH, &s->s_flags)) { + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && gop_end) { + /* only run these if we're shutting down the last cap */ + unsigned long duration; + unsigned long then = jiffies; + + add_wait_queue(&itv->eos_waitq, &wait); + + set_current_state(TASK_INTERRUPTIBLE); + + /* wait 2s for EOS interrupt */ + while (!test_bit(IVTV_F_I_EOS, &itv->i_flags) && + time_before(jiffies, + then + msecs_to_jiffies(2000))) { + schedule_timeout(msecs_to_jiffies(10)); + } + + /* To convert jiffies to ms, we must multiply by 1000 + * and divide by HZ. To avoid runtime division, we + * convert this to multiplication by 1000/HZ. + * Since integer division truncates, we get the best + * accuracy if we do a rounding calculation of the constant. + * Think of the case where HZ is 1024. + */ + duration = ((1000 + HZ / 2) / HZ) * (jiffies - then); + + if (!test_bit(IVTV_F_I_EOS, &itv->i_flags)) { + IVTV_DEBUG_WARN("%s: EOS interrupt not received! stopping anyway.\n", s->name); + IVTV_DEBUG_WARN("%s: waited %lu ms.\n", s->name, duration); + } else { + IVTV_DEBUG_INFO("%s: EOS took %lu ms to occur.\n", s->name, duration); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&itv->eos_waitq, &wait); + set_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + } + + /* Handle any pending interrupts */ + ivtv_msleep_timeout(100, 0); + } + + atomic_dec(&itv->capturing); + + /* Clear capture and no-read bits */ + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + + if (s->type == IVTV_ENC_STREAM_TYPE_VBI) + ivtv_set_irq_mask(itv, IVTV_IRQ_ENC_VBI_CAP); + + if (atomic_read(&itv->capturing) > 0) { + return 0; + } + + cx2341x_handler_set_busy(&itv->cxhdl, 0); + + /* Set the following Interrupt mask bits for capture */ + ivtv_set_irq_mask(itv, IVTV_IRQ_MASK_CAPTURE); + timer_delete(&itv->dma_timer); + + /* event notification (off) */ + if (test_and_clear_bit(IVTV_F_I_DIG_RST, &itv->i_flags)) { + /* type: 0 = refresh */ + /* on/off: 0 = off, intr: 0x10000000, mbox_id: -1: none */ + ivtv_vapi(itv, CX2341X_ENC_SET_EVENT_NOTIFICATION, 4, 0, 0, IVTV_IRQ_ENC_VIM_RST, -1); + ivtv_set_irq_mask(itv, IVTV_IRQ_ENC_VIM_RST); + } + + /* Raw-passthrough is implied on start. Make sure it's stopped so + the encoder will re-initialize when next started */ + ivtv_vapi(itv, CX2341X_ENC_STOP_CAPTURE, 3, 1, 2, 7); + + wake_up(&s->waitq); + + return 0; +} +EXPORT_SYMBOL(ivtv_stop_v4l2_encode_stream); + +int ivtv_stop_v4l2_decode_stream(struct ivtv_stream *s, int flags, u64 pts) +{ + static const struct v4l2_event ev = { + .type = V4L2_EVENT_EOS, + }; + struct ivtv *itv = s->itv; + + if (s->vdev.v4l2_dev == NULL) + return -EINVAL; + + if (s->type != IVTV_DEC_STREAM_TYPE_YUV && s->type != IVTV_DEC_STREAM_TYPE_MPG) + return -EINVAL; + + if (!test_bit(IVTV_F_S_STREAMING, &s->s_flags)) + return 0; + + IVTV_DEBUG_INFO("Stop Decode at %llu, flags: %x\n", (unsigned long long)pts, flags); + + /* Stop Decoder */ + if (!(flags & V4L2_DEC_CMD_STOP_IMMEDIATELY) || pts) { + u32 tmp = 0; + + /* Wait until the decoder is no longer running */ + if (pts) { + ivtv_vapi(itv, CX2341X_DEC_STOP_PLAYBACK, 3, + 0, (u32)(pts & 0xffffffff), (u32)(pts >> 32)); + } + while (1) { + u32 data[CX2341X_MBOX_MAX_DATA]; + ivtv_vapi_result(itv, data, CX2341X_DEC_GET_XFER_INFO, 0); + if (s->q_full.buffers + s->q_dma.buffers == 0) { + if (tmp == data[3]) + break; + tmp = data[3]; + } + if (ivtv_msleep_timeout(100, 1)) + break; + } + } + ivtv_vapi(itv, CX2341X_DEC_STOP_PLAYBACK, 3, flags & V4L2_DEC_CMD_STOP_TO_BLACK, 0, 0); + + /* turn off notification of dual/stereo mode change */ + ivtv_vapi(itv, CX2341X_DEC_SET_EVENT_NOTIFICATION, 4, 0, 0, IVTV_IRQ_DEC_AUD_MODE_CHG, -1); + + ivtv_set_irq_mask(itv, IVTV_IRQ_MASK_DECODE); + timer_delete(&itv->dma_timer); + + clear_bit(IVTV_F_S_NEEDS_DATA, &s->s_flags); + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + ivtv_flush_queues(s); + + /* decoder needs time to settle */ + ivtv_msleep_timeout(40, 0); + + /* decrement decoding */ + atomic_dec(&itv->decoding); + + set_bit(IVTV_F_I_EV_DEC_STOPPED, &itv->i_flags); + wake_up(&itv->event_waitq); + v4l2_event_queue(&s->vdev, &ev); + + /* wake up wait queues */ + wake_up(&s->waitq); + + return 0; +} + +int ivtv_passthrough_mode(struct ivtv *itv, int enable) +{ + struct ivtv_stream *yuv_stream = &itv->streams[IVTV_ENC_STREAM_TYPE_YUV]; + struct ivtv_stream *dec_stream = &itv->streams[IVTV_DEC_STREAM_TYPE_YUV]; + + if (yuv_stream->vdev.v4l2_dev == NULL || dec_stream->vdev.v4l2_dev == NULL) + return -EINVAL; + + IVTV_DEBUG_INFO("ivtv ioctl: Select passthrough mode\n"); + + /* Prevent others from starting/stopping streams while we + initiate/terminate passthrough mode */ + if (enable) { + if (itv->output_mode == OUT_PASSTHROUGH) { + return 0; + } + if (ivtv_set_output_mode(itv, OUT_PASSTHROUGH) != OUT_PASSTHROUGH) + return -EBUSY; + + /* Fully initialize stream, and then unflag init */ + set_bit(IVTV_F_S_PASSTHROUGH, &dec_stream->s_flags); + set_bit(IVTV_F_S_STREAMING, &dec_stream->s_flags); + + /* Setup YUV Decoder */ + ivtv_setup_v4l2_decode_stream(dec_stream); + + /* Start Decoder */ + ivtv_vapi(itv, CX2341X_DEC_START_PLAYBACK, 2, 0, 1); + atomic_inc(&itv->decoding); + + /* Setup capture if not already done */ + if (atomic_read(&itv->capturing) == 0) { + cx2341x_handler_setup(&itv->cxhdl); + cx2341x_handler_set_busy(&itv->cxhdl, 1); + } + + /* Start Passthrough Mode */ + ivtv_vapi(itv, CX2341X_ENC_START_CAPTURE, 2, 2, 11); + atomic_inc(&itv->capturing); + return 0; + } + + if (itv->output_mode != OUT_PASSTHROUGH) + return 0; + + /* Stop Passthrough Mode */ + ivtv_vapi(itv, CX2341X_ENC_STOP_CAPTURE, 3, 1, 2, 11); + ivtv_vapi(itv, CX2341X_DEC_STOP_PLAYBACK, 3, 1, 0, 0); + + atomic_dec(&itv->capturing); + atomic_dec(&itv->decoding); + clear_bit(IVTV_F_S_PASSTHROUGH, &dec_stream->s_flags); + clear_bit(IVTV_F_S_STREAMING, &dec_stream->s_flags); + itv->output_mode = OUT_NONE; + if (atomic_read(&itv->capturing) == 0) + cx2341x_handler_set_busy(&itv->cxhdl, 0); + + return 0; +} diff --git a/drivers/media/pci/ivtv/ivtv-streams.h b/drivers/media/pci/ivtv/ivtv-streams.h new file mode 100644 index 000000000..43d4ecd6d --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-streams.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + init/start/stop/exit stream functions + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#ifndef IVTV_STREAMS_H +#define IVTV_STREAMS_H + +int ivtv_streams_setup(struct ivtv *itv); +int ivtv_streams_register(struct ivtv *itv); +void ivtv_streams_cleanup(struct ivtv *itv); + +/* Capture related */ +int ivtv_start_v4l2_encode_stream(struct ivtv_stream *s); +int ivtv_stop_v4l2_encode_stream(struct ivtv_stream *s, int gop_end); +int ivtv_start_v4l2_decode_stream(struct ivtv_stream *s, int gop_offset); +int ivtv_stop_v4l2_decode_stream(struct ivtv_stream *s, int flags, u64 pts); + +void ivtv_stop_all_captures(struct ivtv *itv); +int ivtv_passthrough_mode(struct ivtv *itv, int enable); + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-udma.c b/drivers/media/pci/ivtv/ivtv-udma.c new file mode 100644 index 000000000..7dedf04f9 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-udma.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + User DMA + + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#include "ivtv-driver.h" +#include "ivtv-udma.h" + +void ivtv_udma_get_page_info(struct ivtv_dma_page_info *dma_page, unsigned long first, unsigned long size) +{ + dma_page->uaddr = first & PAGE_MASK; + dma_page->offset = first & ~PAGE_MASK; + dma_page->tail = 1 + ((first+size-1) & ~PAGE_MASK); + dma_page->first = (first & PAGE_MASK) >> PAGE_SHIFT; + dma_page->last = ((first+size-1) & PAGE_MASK) >> PAGE_SHIFT; + dma_page->page_count = dma_page->last - dma_page->first + 1; + if (dma_page->page_count == 1) dma_page->tail -= dma_page->offset; +} + +int ivtv_udma_fill_sg_list (struct ivtv_user_dma *dma, struct ivtv_dma_page_info *dma_page, int map_offset) +{ + int i, offset; + unsigned long flags; + + if (map_offset < 0) + return map_offset; + + offset = dma_page->offset; + + /* Fill SG Array with new values */ + for (i = 0; i < dma_page->page_count; i++) { + unsigned int len = (i == dma_page->page_count - 1) ? + dma_page->tail : PAGE_SIZE - offset; + + if (PageHighMem(dma->map[map_offset])) { + void *src; + + if (dma->bouncemap[map_offset] == NULL) + dma->bouncemap[map_offset] = alloc_page(GFP_KERNEL); + if (dma->bouncemap[map_offset] == NULL) + return -1; + local_irq_save(flags); + src = kmap_atomic(dma->map[map_offset]) + offset; + memcpy(page_address(dma->bouncemap[map_offset]) + offset, src, len); + kunmap_atomic(src); + local_irq_restore(flags); + sg_set_page(&dma->SGlist[map_offset], dma->bouncemap[map_offset], len, offset); + } + else { + sg_set_page(&dma->SGlist[map_offset], dma->map[map_offset], len, offset); + } + offset = 0; + map_offset++; + } + return map_offset; +} + +void ivtv_udma_fill_sg_array (struct ivtv_user_dma *dma, u32 buffer_offset, u32 buffer_offset_2, u32 split) { + int i; + struct scatterlist *sg; + + for_each_sg(dma->SGlist, sg, dma->SG_length, i) { + dma->SGarray[i].size = cpu_to_le32(sg_dma_len(sg)); + dma->SGarray[i].src = cpu_to_le32(sg_dma_address(sg)); + dma->SGarray[i].dst = cpu_to_le32(buffer_offset); + buffer_offset += sg_dma_len(sg); + + split -= sg_dma_len(sg); + if (split == 0) + buffer_offset = buffer_offset_2; + } +} + +/* User DMA Buffers */ +void ivtv_udma_alloc(struct ivtv *itv) +{ + if (itv->udma.SG_handle == 0) { + /* Map DMA Page Array Buffer */ + itv->udma.SG_handle = dma_map_single(&itv->pdev->dev, + itv->udma.SGarray, + sizeof(itv->udma.SGarray), + DMA_TO_DEVICE); + ivtv_udma_sync_for_cpu(itv); + } +} + +int ivtv_udma_setup(struct ivtv *itv, unsigned long ivtv_dest_addr, + void __user *userbuf, int size_in_bytes) +{ + struct ivtv_dma_page_info user_dma; + struct ivtv_user_dma *dma = &itv->udma; + int err; + + IVTV_DEBUG_DMA("ivtv_udma_setup, dst: 0x%08x\n", (unsigned int)ivtv_dest_addr); + + /* Still in USE */ + if (dma->SG_length || dma->page_count) { + IVTV_DEBUG_WARN("ivtv_udma_setup: SG_length %d page_count %d still full?\n", + dma->SG_length, dma->page_count); + return -EBUSY; + } + + ivtv_udma_get_page_info(&user_dma, (unsigned long)userbuf, size_in_bytes); + + if (user_dma.page_count <= 0) { + IVTV_DEBUG_WARN("ivtv_udma_setup: Error %d page_count from %d bytes %d offset\n", + user_dma.page_count, size_in_bytes, user_dma.offset); + return -EINVAL; + } + + /* Pin user pages for DMA Xfer */ + err = pin_user_pages_unlocked(user_dma.uaddr, user_dma.page_count, + dma->map, 0); + + if (user_dma.page_count != err) { + IVTV_DEBUG_WARN("failed to map user pages, returned %d instead of %d\n", + err, user_dma.page_count); + if (err >= 0) { + unpin_user_pages(dma->map, err); + return -EINVAL; + } + return err; + } + + dma->page_count = user_dma.page_count; + + /* Fill SG List with new values */ + if (ivtv_udma_fill_sg_list(dma, &user_dma, 0) < 0) { + IVTV_DEBUG_WARN("%s: could not allocate bounce buffers for highmem userspace buffers\n", + __func__); + unpin_user_pages(dma->map, dma->page_count); + dma->page_count = 0; + return -ENOMEM; + } + + /* Map SG List */ + dma->SG_length = dma_map_sg(&itv->pdev->dev, dma->SGlist, + dma->page_count, DMA_TO_DEVICE); + if (!dma->SG_length) { + IVTV_DEBUG_WARN("%s: DMA map error, SG_length is 0\n", __func__); + unpin_user_pages(dma->map, dma->page_count); + dma->page_count = 0; + return -EINVAL; + } + + /* Fill SG Array with new values */ + ivtv_udma_fill_sg_array (dma, ivtv_dest_addr, 0, -1); + + /* Tag SG Array with Interrupt Bit */ + dma->SGarray[dma->SG_length - 1].size |= cpu_to_le32(0x80000000); + + ivtv_udma_sync_for_device(itv); + return dma->page_count; +} + +void ivtv_udma_unmap(struct ivtv *itv) +{ + struct ivtv_user_dma *dma = &itv->udma; + + IVTV_DEBUG_INFO("ivtv_unmap_user_dma\n"); + + /* Nothing to free */ + if (dma->page_count == 0) + return; + + /* Unmap Scatterlist */ + if (dma->SG_length) { + dma_unmap_sg(&itv->pdev->dev, dma->SGlist, dma->page_count, + DMA_TO_DEVICE); + dma->SG_length = 0; + } + /* sync DMA */ + ivtv_udma_sync_for_cpu(itv); + + unpin_user_pages(dma->map, dma->page_count); + dma->page_count = 0; +} + +void ivtv_udma_free(struct ivtv *itv) +{ + int i; + + /* Unmap SG Array */ + if (itv->udma.SG_handle) { + dma_unmap_single(&itv->pdev->dev, itv->udma.SG_handle, + sizeof(itv->udma.SGarray), DMA_TO_DEVICE); + } + + /* Unmap Scatterlist */ + if (itv->udma.SG_length) { + dma_unmap_sg(&itv->pdev->dev, itv->udma.SGlist, + itv->udma.page_count, DMA_TO_DEVICE); + } + + for (i = 0; i < IVTV_DMA_SG_OSD_ENT; i++) { + if (itv->udma.bouncemap[i]) + __free_page(itv->udma.bouncemap[i]); + } +} + +void ivtv_udma_start(struct ivtv *itv) +{ + IVTV_DEBUG_DMA("start UDMA\n"); + write_reg(itv->udma.SG_handle, IVTV_REG_DECDMAADDR); + write_reg_sync(read_reg(IVTV_REG_DMAXFER) | 0x01, IVTV_REG_DMAXFER); + set_bit(IVTV_F_I_DMA, &itv->i_flags); + set_bit(IVTV_F_I_UDMA, &itv->i_flags); + clear_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags); +} + +void ivtv_udma_prepare(struct ivtv *itv) +{ + unsigned long flags; + + spin_lock_irqsave(&itv->dma_reg_lock, flags); + if (!test_bit(IVTV_F_I_DMA, &itv->i_flags)) + ivtv_udma_start(itv); + else + set_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags); + spin_unlock_irqrestore(&itv->dma_reg_lock, flags); +} diff --git a/drivers/media/pci/ivtv/ivtv-udma.h b/drivers/media/pci/ivtv/ivtv-udma.h new file mode 100644 index 000000000..3030fadfd --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-udma.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2006-2007 Hans Verkuil + + */ + +#ifndef IVTV_UDMA_H +#define IVTV_UDMA_H + +/* User DMA functions */ +void ivtv_udma_get_page_info(struct ivtv_dma_page_info *dma_page, unsigned long first, unsigned long size); +int ivtv_udma_fill_sg_list(struct ivtv_user_dma *dma, struct ivtv_dma_page_info *dma_page, int map_offset); +void ivtv_udma_fill_sg_array(struct ivtv_user_dma *dma, u32 buffer_offset, u32 buffer_offset_2, u32 split); +int ivtv_udma_setup(struct ivtv *itv, unsigned long ivtv_dest_addr, + void __user *userbuf, int size_in_bytes); +void ivtv_udma_unmap(struct ivtv *itv); +void ivtv_udma_free(struct ivtv *itv); +void ivtv_udma_alloc(struct ivtv *itv); +void ivtv_udma_prepare(struct ivtv *itv); +void ivtv_udma_start(struct ivtv *itv); + +static inline void ivtv_udma_sync_for_device(struct ivtv *itv) +{ + dma_sync_single_for_device(&itv->pdev->dev, itv->udma.SG_handle, + sizeof(itv->udma.SGarray), DMA_TO_DEVICE); +} + +static inline void ivtv_udma_sync_for_cpu(struct ivtv *itv) +{ + dma_sync_single_for_cpu(&itv->pdev->dev, itv->udma.SG_handle, + sizeof(itv->udma.SGarray), DMA_TO_DEVICE); +} + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-vbi.c b/drivers/media/pci/ivtv/ivtv-vbi.c new file mode 100644 index 000000000..af086c4cb --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-vbi.c @@ -0,0 +1,537 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Vertical Blank Interval support functions + Copyright (C) 2004-2007 Hans Verkuil + + */ + +#include "ivtv-driver.h" +#include "ivtv-i2c.h" +#include "ivtv-ioctl.h" +#include "ivtv-queue.h" +#include "ivtv-cards.h" +#include "ivtv-vbi.h" + +static void ivtv_set_vps(struct ivtv *itv, int enabled) +{ + struct v4l2_sliced_vbi_data data; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return; + data.id = V4L2_SLICED_VPS; + data.field = 0; + data.line = enabled ? 16 : 0; + data.data[2] = itv->vbi.vps_payload.data[0]; + data.data[8] = itv->vbi.vps_payload.data[1]; + data.data[9] = itv->vbi.vps_payload.data[2]; + data.data[10] = itv->vbi.vps_payload.data[3]; + data.data[11] = itv->vbi.vps_payload.data[4]; + ivtv_call_hw(itv, IVTV_HW_SAA7127, vbi, s_vbi_data, &data); +} + +static void ivtv_set_cc(struct ivtv *itv, int mode, const struct vbi_cc *cc) +{ + struct v4l2_sliced_vbi_data data; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return; + data.id = V4L2_SLICED_CAPTION_525; + data.field = 0; + data.line = (mode & 1) ? 21 : 0; + data.data[0] = cc->odd[0]; + data.data[1] = cc->odd[1]; + ivtv_call_hw(itv, IVTV_HW_SAA7127, vbi, s_vbi_data, &data); + data.field = 1; + data.line = (mode & 2) ? 21 : 0; + data.data[0] = cc->even[0]; + data.data[1] = cc->even[1]; + ivtv_call_hw(itv, IVTV_HW_SAA7127, vbi, s_vbi_data, &data); +} + +static void ivtv_set_wss(struct ivtv *itv, int enabled, int mode) +{ + struct v4l2_sliced_vbi_data data; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return; + /* When using a 50 Hz system, always turn on the + wide screen signal with 4x3 ratio as the default. + Turning this signal on and off can confuse certain + TVs. As far as I can tell there is no reason not to + transmit this signal. */ + if ((itv->std_out & V4L2_STD_625_50) && !enabled) { + enabled = 1; + mode = 0x08; /* 4x3 full format */ + } + data.id = V4L2_SLICED_WSS_625; + data.field = 0; + data.line = enabled ? 23 : 0; + data.data[0] = mode & 0xff; + data.data[1] = (mode >> 8) & 0xff; + ivtv_call_hw(itv, IVTV_HW_SAA7127, vbi, s_vbi_data, &data); +} + +static int odd_parity(u8 c) +{ + c ^= (c >> 4); + c ^= (c >> 2); + c ^= (c >> 1); + + return c & 1; +} + +static void ivtv_write_vbi_line(struct ivtv *itv, + const struct v4l2_sliced_vbi_data *d, + struct vbi_cc *cc, int *found_cc) +{ + struct vbi_info *vi = &itv->vbi; + + if (d->id == V4L2_SLICED_CAPTION_525 && d->line == 21) { + if (d->field) { + cc->even[0] = d->data[0]; + cc->even[1] = d->data[1]; + } else { + cc->odd[0] = d->data[0]; + cc->odd[1] = d->data[1]; + } + *found_cc = 1; + } else if (d->id == V4L2_SLICED_VPS && d->line == 16 && d->field == 0) { + struct vbi_vps vps; + + vps.data[0] = d->data[2]; + vps.data[1] = d->data[8]; + vps.data[2] = d->data[9]; + vps.data[3] = d->data[10]; + vps.data[4] = d->data[11]; + if (memcmp(&vps, &vi->vps_payload, sizeof(vps))) { + vi->vps_payload = vps; + set_bit(IVTV_F_I_UPDATE_VPS, &itv->i_flags); + } + } else if (d->id == V4L2_SLICED_WSS_625 && + d->line == 23 && d->field == 0) { + int wss = d->data[0] | d->data[1] << 8; + + if (vi->wss_payload != wss) { + vi->wss_payload = wss; + set_bit(IVTV_F_I_UPDATE_WSS, &itv->i_flags); + } + } +} + +static void ivtv_write_vbi_cc_lines(struct ivtv *itv, const struct vbi_cc *cc) +{ + struct vbi_info *vi = &itv->vbi; + + if (vi->cc_payload_idx < ARRAY_SIZE(vi->cc_payload)) { + memcpy(&vi->cc_payload[vi->cc_payload_idx], cc, + sizeof(struct vbi_cc)); + vi->cc_payload_idx++; + set_bit(IVTV_F_I_UPDATE_CC, &itv->i_flags); + } +} + +static void ivtv_write_vbi(struct ivtv *itv, + const struct v4l2_sliced_vbi_data *sliced, + size_t cnt) +{ + struct vbi_cc cc = { .odd = { 0x80, 0x80 }, .even = { 0x80, 0x80 } }; + int found_cc = 0; + size_t i; + + for (i = 0; i < cnt; i++) + ivtv_write_vbi_line(itv, sliced + i, &cc, &found_cc); + + if (found_cc) + ivtv_write_vbi_cc_lines(itv, &cc); +} + +ssize_t +ivtv_write_vbi_from_user(struct ivtv *itv, + const struct v4l2_sliced_vbi_data __user *sliced, + size_t cnt) +{ + struct vbi_cc cc = { .odd = { 0x80, 0x80 }, .even = { 0x80, 0x80 } }; + int found_cc = 0; + size_t i; + struct v4l2_sliced_vbi_data d; + ssize_t ret = cnt * sizeof(struct v4l2_sliced_vbi_data); + + for (i = 0; i < cnt; i++) { + if (copy_from_user(&d, sliced + i, + sizeof(struct v4l2_sliced_vbi_data))) { + ret = -EFAULT; + break; + } + ivtv_write_vbi_line(itv, &d, &cc, &found_cc); + } + + if (found_cc) + ivtv_write_vbi_cc_lines(itv, &cc); + + return ret; +} + +static void copy_vbi_data(struct ivtv *itv, int lines, u32 pts_stamp) +{ + int line = 0; + int i; + u32 linemask[2] = { 0, 0 }; + unsigned short size; + static const u8 mpeg_hdr_data[] = { + 0x00, 0x00, 0x01, 0xba, 0x44, 0x00, 0x0c, 0x66, + 0x24, 0x01, 0x01, 0xd1, 0xd3, 0xfa, 0xff, 0xff, + 0x00, 0x00, 0x01, 0xbd, 0x00, 0x1a, 0x84, 0x80, + 0x07, 0x21, 0x00, 0x5d, 0x63, 0xa7, 0xff, 0xff + }; + const int sd = sizeof(mpeg_hdr_data); /* start of vbi data */ + int idx = itv->vbi.frame % IVTV_VBI_FRAMES; + u8 *dst = &itv->vbi.sliced_mpeg_data[idx][0]; + + for (i = 0; i < lines; i++) { + int f, l; + + if (itv->vbi.sliced_data[i].id == 0) + continue; + + l = itv->vbi.sliced_data[i].line - 6; + f = itv->vbi.sliced_data[i].field; + if (f) + l += 18; + if (l < 32) + linemask[0] |= (1 << l); + else + linemask[1] |= (1 << (l - 32)); + dst[sd + 12 + line * 43] = + ivtv_service2vbi(itv->vbi.sliced_data[i].id); + memcpy(dst + sd + 12 + line * 43 + 1, itv->vbi.sliced_data[i].data, 42); + line++; + } + memcpy(dst, mpeg_hdr_data, sizeof(mpeg_hdr_data)); + if (line == 36) { + /* All lines are used, so there is no space for the linemask + (the max size of the VBI data is 36 * 43 + 4 bytes). + So in this case we use the magic number 'ITV0'. */ + memcpy(dst + sd, "ITV0", 4); + memmove(dst + sd + 4, dst + sd + 12, line * 43); + size = 4 + ((43 * line + 3) & ~3); + } else { + memcpy(dst + sd, "itv0", 4); + cpu_to_le32s(&linemask[0]); + cpu_to_le32s(&linemask[1]); + memcpy(dst + sd + 4, &linemask[0], 8); + size = 12 + ((43 * line + 3) & ~3); + } + dst[4+16] = (size + 10) >> 8; + dst[5+16] = (size + 10) & 0xff; + dst[9+16] = 0x21 | ((pts_stamp >> 29) & 0x6); + dst[10+16] = (pts_stamp >> 22) & 0xff; + dst[11+16] = 1 | ((pts_stamp >> 14) & 0xff); + dst[12+16] = (pts_stamp >> 7) & 0xff; + dst[13+16] = 1 | ((pts_stamp & 0x7f) << 1); + itv->vbi.sliced_mpeg_size[idx] = sd + size; +} + +static int ivtv_convert_ivtv_vbi(struct ivtv *itv, u8 *p) +{ + u32 linemask[2]; + int i, l, id2; + int line = 0; + + if (!memcmp(p, "itv0", 4)) { + memcpy(linemask, p + 4, 8); + p += 12; + } else if (!memcmp(p, "ITV0", 4)) { + linemask[0] = 0xffffffff; + linemask[1] = 0xf; + p += 4; + } else { + /* unknown VBI data, convert to empty VBI frame */ + linemask[0] = linemask[1] = 0; + } + for (i = 0; i < 36; i++) { + int err = 0; + + if (i < 32 && !(linemask[0] & (1 << i))) + continue; + if (i >= 32 && !(linemask[1] & (1 << (i - 32)))) + continue; + id2 = *p & 0xf; + switch (id2) { + case IVTV_SLICED_TYPE_TELETEXT_B: + id2 = V4L2_SLICED_TELETEXT_B; + break; + case IVTV_SLICED_TYPE_CAPTION_525: + id2 = V4L2_SLICED_CAPTION_525; + err = !odd_parity(p[1]) || !odd_parity(p[2]); + break; + case IVTV_SLICED_TYPE_VPS: + id2 = V4L2_SLICED_VPS; + break; + case IVTV_SLICED_TYPE_WSS_625: + id2 = V4L2_SLICED_WSS_625; + break; + default: + id2 = 0; + break; + } + if (err == 0) { + l = (i < 18) ? i + 6 : i - 18 + 6; + itv->vbi.sliced_dec_data[line].line = l; + itv->vbi.sliced_dec_data[line].field = i >= 18; + itv->vbi.sliced_dec_data[line].id = id2; + memcpy(itv->vbi.sliced_dec_data[line].data, p + 1, 42); + line++; + } + p += 43; + } + while (line < 36) { + itv->vbi.sliced_dec_data[line].id = 0; + itv->vbi.sliced_dec_data[line].line = 0; + itv->vbi.sliced_dec_data[line].field = 0; + line++; + } + return line * sizeof(itv->vbi.sliced_dec_data[0]); +} + +/* Compress raw VBI format, removes leading SAV codes and surplus space after the + field. + Returns new compressed size. */ +static u32 compress_raw_buf(struct ivtv *itv, u8 *buf, u32 size) +{ + u32 line_size = itv->vbi.raw_decoder_line_size; + u32 lines = itv->vbi.count; + u8 sav1 = itv->vbi.raw_decoder_sav_odd_field; + u8 sav2 = itv->vbi.raw_decoder_sav_even_field; + u8 *q = buf; + u8 *p; + int i; + + for (i = 0; i < lines; i++) { + p = buf + i * line_size; + + /* Look for SAV code */ + if (p[0] != 0xff || p[1] || p[2] || (p[3] != sav1 && p[3] != sav2)) { + break; + } + memcpy(q, p + 4, line_size - 4); + q += line_size - 4; + } + return lines * (line_size - 4); +} + + +/* Compressed VBI format, all found sliced blocks put next to one another + Returns new compressed size */ +static u32 compress_sliced_buf(struct ivtv *itv, u32 line, u8 *buf, u32 size, u8 sav) +{ + u32 line_size = itv->vbi.sliced_decoder_line_size; + struct v4l2_decode_vbi_line vbi = {}; + int i; + unsigned lines = 0; + + /* find the first valid line */ + for (i = 0; i + 3 < size; i++, buf++) { + if (buf[0] == 0xff && !buf[1] && !buf[2] && buf[3] == sav) + break; + } + + size -= i; + if (size < line_size) { + return line; + } + for (i = 0; i < size / line_size; i++) { + u8 *p = buf + i * line_size; + + /* Look for SAV code */ + if (p[0] != 0xff || p[1] || p[2] || p[3] != sav) { + continue; + } + vbi.p = p + 4; + v4l2_subdev_call(itv->sd_video, vbi, decode_vbi_line, &vbi); + if (vbi.type && !(lines & (1 << vbi.line))) { + lines |= 1 << vbi.line; + itv->vbi.sliced_data[line].id = vbi.type; + itv->vbi.sliced_data[line].field = vbi.is_second_field; + itv->vbi.sliced_data[line].line = vbi.line; + memcpy(itv->vbi.sliced_data[line].data, vbi.p, 42); + line++; + } + } + return line; +} + +void ivtv_process_vbi_data(struct ivtv *itv, struct ivtv_buffer *buf, + u64 pts_stamp, int streamtype) +{ + u8 *p = (u8 *) buf->buf; + u32 size = buf->bytesused; + int y; + + /* Raw VBI data */ + if (streamtype == IVTV_ENC_STREAM_TYPE_VBI && ivtv_raw_vbi(itv)) { + u8 type; + + ivtv_buf_swap(buf); + + type = p[3]; + + size = buf->bytesused = compress_raw_buf(itv, p, size); + + /* second field of the frame? */ + if (type == itv->vbi.raw_decoder_sav_even_field) { + /* Dirty hack needed for backwards + compatibility of old VBI software. */ + p += size - 4; + memcpy(p, &itv->vbi.frame, 4); + itv->vbi.frame++; + } + return; + } + + /* Sliced VBI data with data insertion */ + if (streamtype == IVTV_ENC_STREAM_TYPE_VBI) { + int lines; + + ivtv_buf_swap(buf); + + /* first field */ + lines = compress_sliced_buf(itv, 0, p, size / 2, + itv->vbi.sliced_decoder_sav_odd_field); + /* second field */ + /* experimentation shows that the second half does not always begin + at the exact address. So start a bit earlier (hence 32). */ + lines = compress_sliced_buf(itv, lines, p + size / 2 - 32, size / 2 + 32, + itv->vbi.sliced_decoder_sav_even_field); + /* always return at least one empty line */ + if (lines == 0) { + itv->vbi.sliced_data[0].id = 0; + itv->vbi.sliced_data[0].line = 0; + itv->vbi.sliced_data[0].field = 0; + lines = 1; + } + buf->bytesused = size = lines * sizeof(itv->vbi.sliced_data[0]); + memcpy(p, &itv->vbi.sliced_data[0], size); + + if (itv->vbi.insert_mpeg) { + copy_vbi_data(itv, lines, pts_stamp); + } + itv->vbi.frame++; + return; + } + + /* Sliced VBI re-inserted from an MPEG stream */ + if (streamtype == IVTV_DEC_STREAM_TYPE_VBI) { + /* If the size is not 4-byte aligned, then the starting address + for the swapping is also shifted. After swapping the data the + real start address of the VBI data is exactly 4 bytes after the + original start. It's a bit fiddly but it works like a charm. + Non-4-byte alignment happens when an lseek is done on the input + mpeg file to a non-4-byte aligned position. So on arrival here + the VBI data is also non-4-byte aligned. */ + int offset = size & 3; + int cnt; + + if (offset) { + p += 4 - offset; + } + /* Swap Buffer */ + for (y = 0; y < size; y += 4) { + swab32s((u32 *)(p + y)); + } + + cnt = ivtv_convert_ivtv_vbi(itv, p + offset); + memcpy(buf->buf, itv->vbi.sliced_dec_data, cnt); + buf->bytesused = cnt; + + ivtv_write_vbi(itv, itv->vbi.sliced_dec_data, + cnt / sizeof(itv->vbi.sliced_dec_data[0])); + return; + } +} + +void ivtv_disable_cc(struct ivtv *itv) +{ + struct vbi_cc cc = { .odd = { 0x80, 0x80 }, .even = { 0x80, 0x80 } }; + + clear_bit(IVTV_F_I_UPDATE_CC, &itv->i_flags); + ivtv_set_cc(itv, 0, &cc); + itv->vbi.cc_payload_idx = 0; +} + + +void ivtv_vbi_work_handler(struct ivtv *itv) +{ + struct vbi_info *vi = &itv->vbi; + struct v4l2_sliced_vbi_data data; + struct vbi_cc cc = { .odd = { 0x80, 0x80 }, .even = { 0x80, 0x80 } }; + + /* Lock */ + if (itv->output_mode == OUT_PASSTHROUGH) { + if (itv->is_50hz) { + data.id = V4L2_SLICED_WSS_625; + data.field = 0; + + if (v4l2_subdev_call(itv->sd_video, vbi, g_vbi_data, &data) == 0) { + ivtv_set_wss(itv, 1, data.data[0] & 0xf); + vi->wss_missing_cnt = 0; + } else if (vi->wss_missing_cnt == 4) { + ivtv_set_wss(itv, 1, 0x8); /* 4x3 full format */ + } else { + vi->wss_missing_cnt++; + } + } + else { + int mode = 0; + + data.id = V4L2_SLICED_CAPTION_525; + data.field = 0; + if (v4l2_subdev_call(itv->sd_video, vbi, g_vbi_data, &data) == 0) { + mode |= 1; + cc.odd[0] = data.data[0]; + cc.odd[1] = data.data[1]; + } + data.field = 1; + if (v4l2_subdev_call(itv->sd_video, vbi, g_vbi_data, &data) == 0) { + mode |= 2; + cc.even[0] = data.data[0]; + cc.even[1] = data.data[1]; + } + if (mode) { + vi->cc_missing_cnt = 0; + ivtv_set_cc(itv, mode, &cc); + } else if (vi->cc_missing_cnt == 4) { + ivtv_set_cc(itv, 0, &cc); + } else { + vi->cc_missing_cnt++; + } + } + return; + } + + if (test_and_clear_bit(IVTV_F_I_UPDATE_WSS, &itv->i_flags)) { + ivtv_set_wss(itv, 1, vi->wss_payload & 0xf); + } + + if (test_bit(IVTV_F_I_UPDATE_CC, &itv->i_flags)) { + if (vi->cc_payload_idx == 0) { + clear_bit(IVTV_F_I_UPDATE_CC, &itv->i_flags); + ivtv_set_cc(itv, 3, &cc); + } + while (vi->cc_payload_idx) { + cc = vi->cc_payload[0]; + + memmove(vi->cc_payload, vi->cc_payload + 1, + sizeof(vi->cc_payload) - sizeof(vi->cc_payload[0])); + vi->cc_payload_idx--; + if (vi->cc_payload_idx && cc.odd[0] == 0x80 && cc.odd[1] == 0x80) + continue; + + ivtv_set_cc(itv, 3, &cc); + break; + } + } + + if (test_and_clear_bit(IVTV_F_I_UPDATE_VPS, &itv->i_flags)) { + ivtv_set_vps(itv, 1); + } +} diff --git a/drivers/media/pci/ivtv/ivtv-vbi.h b/drivers/media/pci/ivtv/ivtv-vbi.h new file mode 100644 index 000000000..12fe27da5 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-vbi.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Vertical Blank Interval support functions + Copyright (C) 2004-2007 Hans Verkuil + + */ + +#ifndef IVTV_VBI_H +#define IVTV_VBI_H + +ssize_t +ivtv_write_vbi_from_user(struct ivtv *itv, + const struct v4l2_sliced_vbi_data __user *sliced, + size_t count); +void ivtv_process_vbi_data(struct ivtv *itv, struct ivtv_buffer *buf, + u64 pts_stamp, int streamtype); +int ivtv_used_line(struct ivtv *itv, int line, int field); +void ivtv_disable_cc(struct ivtv *itv); +void ivtv_set_vbi(unsigned long arg); +void ivtv_vbi_work_handler(struct ivtv *itv); + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-version.h b/drivers/media/pci/ivtv/ivtv-version.h new file mode 100644 index 000000000..21e26d1f6 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-version.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + ivtv driver version information + Copyright (C) 2005-2007 Hans Verkuil + + */ + +#ifndef IVTV_VERSION_H +#define IVTV_VERSION_H + +#define IVTV_DRIVER_NAME "ivtv" +#define IVTV_VERSION "1.4.3" + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-yuv.c b/drivers/media/pci/ivtv/ivtv-yuv.c new file mode 100644 index 000000000..71f040106 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-yuv.c @@ -0,0 +1,1293 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + yuv support + + Copyright (C) 2007 Ian Armstrong + + */ + +#include "ivtv-driver.h" +#include "ivtv-udma.h" +#include "ivtv-yuv.h" + +/* YUV buffer offsets */ +const u32 yuv_offset[IVTV_YUV_BUFFERS] = { + 0x001a8600, + 0x00240400, + 0x002d8200, + 0x00370000, + 0x00029000, + 0x000C0E00, + 0x006B0400, + 0x00748200 +}; + +static int ivtv_yuv_prep_user_dma(struct ivtv *itv, struct ivtv_user_dma *dma, + struct ivtv_dma_frame *args) +{ + struct ivtv_dma_page_info y_dma; + struct ivtv_dma_page_info uv_dma; + struct yuv_playback_info *yi = &itv->yuv_info; + u8 frame = yi->draw_frame; + struct yuv_frame_info *f = &yi->new_frame_info[frame]; + int y_pages, uv_pages; + unsigned long y_buffer_offset, uv_buffer_offset; + int y_decode_height, uv_decode_height, y_size; + + y_buffer_offset = IVTV_DECODER_OFFSET + yuv_offset[frame]; + uv_buffer_offset = y_buffer_offset + IVTV_YUV_BUFFER_UV_OFFSET; + + y_decode_height = uv_decode_height = f->src_h + f->src_y; + + if (f->offset_y) + y_buffer_offset += 720 * 16; + + if (y_decode_height & 15) + y_decode_height = (y_decode_height + 16) & ~15; + + if (uv_decode_height & 31) + uv_decode_height = (uv_decode_height + 32) & ~31; + + y_size = 720 * y_decode_height; + + /* Still in USE */ + if (dma->SG_length || dma->page_count) { + IVTV_DEBUG_WARN + ("prep_user_dma: SG_length %d page_count %d still full?\n", + dma->SG_length, dma->page_count); + return -EBUSY; + } + + ivtv_udma_get_page_info (&y_dma, (unsigned long)args->y_source, 720 * y_decode_height); + ivtv_udma_get_page_info (&uv_dma, (unsigned long)args->uv_source, 360 * uv_decode_height); + + /* Pin user pages for DMA Xfer */ + y_pages = pin_user_pages_unlocked(y_dma.uaddr, + y_dma.page_count, &dma->map[0], 0); + uv_pages = 0; /* silence gcc. value is set and consumed only if: */ + if (y_pages == y_dma.page_count) { + uv_pages = pin_user_pages_unlocked(uv_dma.uaddr, + uv_dma.page_count, &dma->map[y_pages], 0); + } + + if (y_pages != y_dma.page_count || uv_pages != uv_dma.page_count) { + int rc = -EFAULT; + + if (y_pages == y_dma.page_count) { + IVTV_DEBUG_WARN + ("failed to map uv user pages, returned %d expecting %d\n", + uv_pages, uv_dma.page_count); + + if (uv_pages >= 0) { + unpin_user_pages(&dma->map[y_pages], uv_pages); + rc = -EFAULT; + } else { + rc = uv_pages; + } + } else { + IVTV_DEBUG_WARN + ("failed to map y user pages, returned %d expecting %d\n", + y_pages, y_dma.page_count); + } + if (y_pages >= 0) { + unpin_user_pages(dma->map, y_pages); + /* + * Inherit the -EFAULT from rc's + * initialization, but allow it to be + * overridden by uv_pages above if it was an + * actual errno. + */ + } else { + rc = y_pages; + } + return rc; + } + + dma->page_count = y_pages + uv_pages; + + /* Fill & map SG List */ + if (ivtv_udma_fill_sg_list (dma, &uv_dma, ivtv_udma_fill_sg_list (dma, &y_dma, 0)) < 0) { + IVTV_DEBUG_WARN("could not allocate bounce buffers for highmem userspace buffers\n"); + unpin_user_pages(dma->map, dma->page_count); + dma->page_count = 0; + return -ENOMEM; + } + dma->SG_length = dma_map_sg(&itv->pdev->dev, dma->SGlist, + dma->page_count, DMA_TO_DEVICE); + if (!dma->SG_length) { + IVTV_DEBUG_WARN("%s: DMA map error, SG_length is 0\n", __func__); + unpin_user_pages(dma->map, dma->page_count); + dma->page_count = 0; + return -EINVAL; + } + + /* Fill SG Array with new values */ + ivtv_udma_fill_sg_array(dma, y_buffer_offset, uv_buffer_offset, y_size); + + /* If we've offset the y plane, ensure top area is blanked */ + if (f->offset_y && yi->blanking_ptr) { + dma->SGarray[dma->SG_length].size = cpu_to_le32(720*16); + dma->SGarray[dma->SG_length].src = cpu_to_le32(yi->blanking_dmaptr); + dma->SGarray[dma->SG_length].dst = cpu_to_le32(IVTV_DECODER_OFFSET + yuv_offset[frame]); + dma->SG_length++; + } + + /* Tag SG Array with Interrupt Bit */ + dma->SGarray[dma->SG_length - 1].size |= cpu_to_le32(0x80000000); + + ivtv_udma_sync_for_device(itv); + return 0; +} + +/* We rely on a table held in the firmware - Quick check. */ +int ivtv_yuv_filter_check(struct ivtv *itv) +{ + int i, y, uv; + + for (i = 0, y = 16, uv = 4; i < 16; i++, y += 24, uv += 12) { + if ((read_dec(IVTV_YUV_HORIZONTAL_FILTER_OFFSET + y) != i << 16) || + (read_dec(IVTV_YUV_VERTICAL_FILTER_OFFSET + uv) != i << 16)) { + IVTV_WARN ("YUV filter table not found in firmware.\n"); + return -1; + } + } + return 0; +} + +static void ivtv_yuv_filter(struct ivtv *itv, int h_filter, int v_filter_1, int v_filter_2) +{ + u32 i, line; + + /* If any filter is -1, then don't update it */ + if (h_filter > -1) { + if (h_filter > 4) + h_filter = 4; + i = IVTV_YUV_HORIZONTAL_FILTER_OFFSET + (h_filter * 384); + for (line = 0; line < 16; line++) { + write_reg(read_dec(i), 0x02804); + write_reg(read_dec(i), 0x0281c); + i += 4; + write_reg(read_dec(i), 0x02808); + write_reg(read_dec(i), 0x02820); + i += 4; + write_reg(read_dec(i), 0x0280c); + write_reg(read_dec(i), 0x02824); + i += 4; + write_reg(read_dec(i), 0x02810); + write_reg(read_dec(i), 0x02828); + i += 4; + write_reg(read_dec(i), 0x02814); + write_reg(read_dec(i), 0x0282c); + i += 8; + write_reg(0, 0x02818); + write_reg(0, 0x02830); + } + IVTV_DEBUG_YUV("h_filter -> %d\n", h_filter); + } + + if (v_filter_1 > -1) { + if (v_filter_1 > 4) + v_filter_1 = 4; + i = IVTV_YUV_VERTICAL_FILTER_OFFSET + (v_filter_1 * 192); + for (line = 0; line < 16; line++) { + write_reg(read_dec(i), 0x02900); + i += 4; + write_reg(read_dec(i), 0x02904); + i += 8; + write_reg(0, 0x02908); + } + IVTV_DEBUG_YUV("v_filter_1 -> %d\n", v_filter_1); + } + + if (v_filter_2 > -1) { + if (v_filter_2 > 4) + v_filter_2 = 4; + i = IVTV_YUV_VERTICAL_FILTER_OFFSET + (v_filter_2 * 192); + for (line = 0; line < 16; line++) { + write_reg(read_dec(i), 0x0290c); + i += 4; + write_reg(read_dec(i), 0x02910); + i += 8; + write_reg(0, 0x02914); + } + IVTV_DEBUG_YUV("v_filter_2 -> %d\n", v_filter_2); + } +} + +static void ivtv_yuv_handle_horizontal(struct ivtv *itv, struct yuv_frame_info *f) +{ + struct yuv_playback_info *yi = &itv->yuv_info; + u32 reg_2834, reg_2838, reg_283c; + u32 reg_2844, reg_2854, reg_285c; + u32 reg_2864, reg_2874, reg_2890; + u32 reg_2870, reg_2870_base, reg_2870_offset; + int x_cutoff; + int h_filter; + u32 master_width; + + IVTV_DEBUG_WARN + ("Adjust to width %d src_w %d dst_w %d src_x %d dst_x %d\n", + f->tru_w, f->src_w, f->dst_w, f->src_x, f->dst_x); + + /* How wide is the src image */ + x_cutoff = f->src_w + f->src_x; + + /* Set the display width */ + reg_2834 = f->dst_w; + reg_2838 = reg_2834; + + /* Set the display position */ + reg_2890 = f->dst_x; + + /* Index into the image horizontally */ + reg_2870 = 0; + + /* 2870 is normally fudged to align video coords with osd coords. + If running full screen, it causes an unwanted left shift + Remove the fudge if we almost fill the screen. + Gradually adjust the offset to avoid the video 'snapping' + left/right if it gets dragged through this region. + Only do this if osd is full width. */ + if (f->vis_w == 720) { + if ((f->tru_x - f->pan_x > -1) && (f->tru_x - f->pan_x <= 40) && (f->dst_w >= 680)) + reg_2870 = 10 - (f->tru_x - f->pan_x) / 4; + else if ((f->tru_x - f->pan_x < 0) && (f->tru_x - f->pan_x >= -20) && (f->dst_w >= 660)) + reg_2870 = (10 + (f->tru_x - f->pan_x) / 2); + + if (f->dst_w >= f->src_w) + reg_2870 = reg_2870 << 16 | reg_2870; + else + reg_2870 = ((reg_2870 & ~1) << 15) | (reg_2870 & ~1); + } + + if (f->dst_w < f->src_w) + reg_2870 = 0x000d000e - reg_2870; + else + reg_2870 = 0x0012000e - reg_2870; + + /* We're also using 2870 to shift the image left (src_x & negative dst_x) */ + reg_2870_offset = (f->src_x * ((f->dst_w << 21) / f->src_w)) >> 19; + + if (f->dst_w >= f->src_w) { + x_cutoff &= ~1; + master_width = (f->src_w * 0x00200000) / (f->dst_w); + if (master_width * f->dst_w != f->src_w * 0x00200000) + master_width++; + reg_2834 = (reg_2834 << 16) | x_cutoff; + reg_2838 = (reg_2838 << 16) | x_cutoff; + reg_283c = master_width >> 2; + reg_2844 = master_width >> 2; + reg_2854 = master_width; + reg_285c = master_width >> 1; + reg_2864 = master_width >> 1; + + /* We also need to factor in the scaling + (src_w - dst_w) / (src_w / 4) */ + if (f->dst_w > f->src_w) + reg_2870_base = ((f->dst_w - f->src_w)<<16) / (f->src_w <<14); + else + reg_2870_base = 0; + + reg_2870 += (((reg_2870_offset << 14) & 0xFFFF0000) | reg_2870_offset >> 2) + (reg_2870_base << 17 | reg_2870_base); + reg_2874 = 0; + } else if (f->dst_w < f->src_w / 2) { + master_width = (f->src_w * 0x00080000) / f->dst_w; + if (master_width * f->dst_w != f->src_w * 0x00080000) + master_width++; + reg_2834 = (reg_2834 << 16) | x_cutoff; + reg_2838 = (reg_2838 << 16) | x_cutoff; + reg_283c = master_width >> 2; + reg_2844 = master_width >> 1; + reg_2854 = master_width; + reg_285c = master_width >> 1; + reg_2864 = master_width >> 1; + reg_2870 += ((reg_2870_offset << 15) & 0xFFFF0000) | reg_2870_offset; + reg_2870 += (5 - (((f->src_w + f->src_w / 2) - 1) / f->dst_w)) << 16; + reg_2874 = 0x00000012; + } else { + master_width = (f->src_w * 0x00100000) / f->dst_w; + if (master_width * f->dst_w != f->src_w * 0x00100000) + master_width++; + reg_2834 = (reg_2834 << 16) | x_cutoff; + reg_2838 = (reg_2838 << 16) | x_cutoff; + reg_283c = master_width >> 2; + reg_2844 = master_width >> 1; + reg_2854 = master_width; + reg_285c = master_width >> 1; + reg_2864 = master_width >> 1; + reg_2870 += ((reg_2870_offset << 14) & 0xFFFF0000) | reg_2870_offset >> 1; + reg_2870 += (5 - (((f->src_w * 3) - 1) / f->dst_w)) << 16; + reg_2874 = 0x00000001; + } + + /* Select the horizontal filter */ + if (f->src_w == f->dst_w) { + /* An exact size match uses filter 0 */ + h_filter = 0; + } else { + /* Figure out which filter to use */ + h_filter = ((f->src_w << 16) / f->dst_w) >> 15; + h_filter = (h_filter >> 1) + (h_filter & 1); + /* Only an exact size match can use filter 0 */ + h_filter += !h_filter; + } + + write_reg(reg_2834, 0x02834); + write_reg(reg_2838, 0x02838); + IVTV_DEBUG_YUV("Update reg 0x2834 %08x->%08x 0x2838 %08x->%08x\n", + yi->reg_2834, reg_2834, yi->reg_2838, reg_2838); + + write_reg(reg_283c, 0x0283c); + write_reg(reg_2844, 0x02844); + + IVTV_DEBUG_YUV("Update reg 0x283c %08x->%08x 0x2844 %08x->%08x\n", + yi->reg_283c, reg_283c, yi->reg_2844, reg_2844); + + write_reg(0x00080514, 0x02840); + write_reg(0x00100514, 0x02848); + IVTV_DEBUG_YUV("Update reg 0x2840 %08x->%08x 0x2848 %08x->%08x\n", + yi->reg_2840, 0x00080514, yi->reg_2848, 0x00100514); + + write_reg(reg_2854, 0x02854); + IVTV_DEBUG_YUV("Update reg 0x2854 %08x->%08x \n", + yi->reg_2854, reg_2854); + + write_reg(reg_285c, 0x0285c); + write_reg(reg_2864, 0x02864); + IVTV_DEBUG_YUV("Update reg 0x285c %08x->%08x 0x2864 %08x->%08x\n", + yi->reg_285c, reg_285c, yi->reg_2864, reg_2864); + + write_reg(reg_2874, 0x02874); + IVTV_DEBUG_YUV("Update reg 0x2874 %08x->%08x\n", + yi->reg_2874, reg_2874); + + write_reg(reg_2870, 0x02870); + IVTV_DEBUG_YUV("Update reg 0x2870 %08x->%08x\n", + yi->reg_2870, reg_2870); + + write_reg(reg_2890, 0x02890); + IVTV_DEBUG_YUV("Update reg 0x2890 %08x->%08x\n", + yi->reg_2890, reg_2890); + + /* Only update the filter if we really need to */ + if (h_filter != yi->h_filter) { + ivtv_yuv_filter(itv, h_filter, -1, -1); + yi->h_filter = h_filter; + } +} + +static void ivtv_yuv_handle_vertical(struct ivtv *itv, struct yuv_frame_info *f) +{ + struct yuv_playback_info *yi = &itv->yuv_info; + u32 master_height; + u32 reg_2918, reg_291c, reg_2920, reg_2928; + u32 reg_2930, reg_2934, reg_293c; + u32 reg_2940, reg_2944, reg_294c; + u32 reg_2950, reg_2954, reg_2958, reg_295c; + u32 reg_2960, reg_2964, reg_2968, reg_296c; + u32 reg_289c; + u32 src_major_y, src_minor_y; + u32 src_major_uv, src_minor_uv; + u32 reg_2964_base, reg_2968_base; + int v_filter_1, v_filter_2; + + IVTV_DEBUG_WARN + ("Adjust to height %d src_h %d dst_h %d src_y %d dst_y %d\n", + f->tru_h, f->src_h, f->dst_h, f->src_y, f->dst_y); + + /* What scaling mode is being used... */ + IVTV_DEBUG_YUV("Scaling mode Y: %s\n", + f->interlaced_y ? "Interlaced" : "Progressive"); + + IVTV_DEBUG_YUV("Scaling mode UV: %s\n", + f->interlaced_uv ? "Interlaced" : "Progressive"); + + /* What is the source video being treated as... */ + IVTV_DEBUG_WARN("Source video: %s\n", + f->interlaced ? "Interlaced" : "Progressive"); + + /* We offset into the image using two different index methods, so split + the y source coord into two parts. */ + if (f->src_y < 8) { + src_minor_uv = f->src_y; + src_major_uv = 0; + } else { + src_minor_uv = 8; + src_major_uv = f->src_y - 8; + } + + src_minor_y = src_minor_uv; + src_major_y = src_major_uv; + + if (f->offset_y) + src_minor_y += 16; + + if (f->interlaced_y) + reg_2918 = (f->dst_h << 16) | (f->src_h + src_minor_y); + else + reg_2918 = (f->dst_h << 16) | ((f->src_h + src_minor_y) << 1); + + if (f->interlaced_uv) + reg_291c = (f->dst_h << 16) | ((f->src_h + src_minor_uv) >> 1); + else + reg_291c = (f->dst_h << 16) | (f->src_h + src_minor_uv); + + reg_2964_base = (src_minor_y * ((f->dst_h << 16) / f->src_h)) >> 14; + reg_2968_base = (src_minor_uv * ((f->dst_h << 16) / f->src_h)) >> 14; + + if (f->dst_h / 2 >= f->src_h && !f->interlaced_y) { + master_height = (f->src_h * 0x00400000) / f->dst_h; + if ((f->src_h * 0x00400000) - (master_height * f->dst_h) >= f->dst_h / 2) + master_height++; + reg_2920 = master_height >> 2; + reg_2928 = master_height >> 3; + reg_2930 = master_height; + reg_2940 = master_height >> 1; + reg_2964_base >>= 3; + reg_2968_base >>= 3; + reg_296c = 0x00000000; + } else if (f->dst_h >= f->src_h) { + master_height = (f->src_h * 0x00400000) / f->dst_h; + master_height = (master_height >> 1) + (master_height & 1); + reg_2920 = master_height >> 2; + reg_2928 = master_height >> 2; + reg_2930 = master_height; + reg_2940 = master_height >> 1; + reg_296c = 0x00000000; + if (f->interlaced_y) { + reg_2964_base >>= 3; + } else { + reg_296c++; + reg_2964_base >>= 2; + } + if (f->interlaced_uv) + reg_2928 >>= 1; + reg_2968_base >>= 3; + } else if (f->dst_h >= f->src_h / 2) { + master_height = (f->src_h * 0x00200000) / f->dst_h; + master_height = (master_height >> 1) + (master_height & 1); + reg_2920 = master_height >> 2; + reg_2928 = master_height >> 2; + reg_2930 = master_height; + reg_2940 = master_height; + reg_296c = 0x00000101; + if (f->interlaced_y) { + reg_2964_base >>= 2; + } else { + reg_296c++; + reg_2964_base >>= 1; + } + if (f->interlaced_uv) + reg_2928 >>= 1; + reg_2968_base >>= 2; + } else { + master_height = (f->src_h * 0x00100000) / f->dst_h; + master_height = (master_height >> 1) + (master_height & 1); + reg_2920 = master_height >> 2; + reg_2928 = master_height >> 2; + reg_2930 = master_height; + reg_2940 = master_height; + reg_2964_base >>= 1; + reg_2968_base >>= 2; + reg_296c = 0x00000102; + } + + /* FIXME These registers change depending on scaled / unscaled output + We really need to work out what they should be */ + if (f->src_h == f->dst_h) { + reg_2934 = 0x00020000; + reg_293c = 0x00100000; + reg_2944 = 0x00040000; + reg_294c = 0x000b0000; + } else { + reg_2934 = 0x00000FF0; + reg_293c = 0x00000FF0; + reg_2944 = 0x00000FF0; + reg_294c = 0x00000FF0; + } + + /* The first line to be displayed */ + reg_2950 = 0x00010000 + src_major_y; + if (f->interlaced_y) + reg_2950 += 0x00010000; + reg_2954 = reg_2950 + 1; + + reg_2958 = 0x00010000 + (src_major_y >> 1); + if (f->interlaced_uv) + reg_2958 += 0x00010000; + reg_295c = reg_2958 + 1; + + if (yi->decode_height == 480) + reg_289c = 0x011e0017; + else + reg_289c = 0x01500017; + + if (f->dst_y < 0) + reg_289c = (reg_289c - ((f->dst_y & ~1)<<15))-(f->dst_y >>1); + else + reg_289c = (reg_289c + ((f->dst_y & ~1)<<15))+(f->dst_y >>1); + + /* How much of the source to decode. + Take into account the source offset */ + reg_2960 = ((src_minor_y + f->src_h + src_major_y) - 1) | + (((src_minor_uv + f->src_h + src_major_uv - 1) & ~1) << 15); + + /* Calculate correct value for register 2964 */ + if (f->src_h == f->dst_h) { + reg_2964 = 1; + } else { + reg_2964 = 2 + ((f->dst_h << 1) / f->src_h); + reg_2964 = (reg_2964 >> 1) + (reg_2964 & 1); + } + reg_2968 = (reg_2964 << 16) + reg_2964 + (reg_2964 >> 1); + reg_2964 = (reg_2964 << 16) + reg_2964 + (reg_2964 * 46 / 94); + + /* Okay, we've wasted time working out the correct value, + but if we use it, it fouls the window alignment. + Fudge it to what we want... */ + reg_2964 = 0x00010001 + ((reg_2964 & 0x0000FFFF) - (reg_2964 >> 16)); + reg_2968 = 0x00010001 + ((reg_2968 & 0x0000FFFF) - (reg_2968 >> 16)); + + /* Deviate further from what it should be. I find the flicker headache + inducing so try to reduce it slightly. Leave 2968 as-is otherwise + colours foul. */ + if ((reg_2964 != 0x00010001) && (f->dst_h / 2 <= f->src_h)) + reg_2964 = (reg_2964 & 0xFFFF0000) + ((reg_2964 & 0x0000FFFF) / 2); + + if (!f->interlaced_y) + reg_2964 -= 0x00010001; + if (!f->interlaced_uv) + reg_2968 -= 0x00010001; + + reg_2964 += ((reg_2964_base << 16) | reg_2964_base); + reg_2968 += ((reg_2968_base << 16) | reg_2968_base); + + /* Select the vertical filter */ + if (f->src_h == f->dst_h) { + /* An exact size match uses filter 0/1 */ + v_filter_1 = 0; + v_filter_2 = 1; + } else { + /* Figure out which filter to use */ + v_filter_1 = ((f->src_h << 16) / f->dst_h) >> 15; + v_filter_1 = (v_filter_1 >> 1) + (v_filter_1 & 1); + /* Only an exact size match can use filter 0 */ + v_filter_1 += !v_filter_1; + v_filter_2 = v_filter_1; + } + + write_reg(reg_2934, 0x02934); + write_reg(reg_293c, 0x0293c); + IVTV_DEBUG_YUV("Update reg 0x2934 %08x->%08x 0x293c %08x->%08x\n", + yi->reg_2934, reg_2934, yi->reg_293c, reg_293c); + write_reg(reg_2944, 0x02944); + write_reg(reg_294c, 0x0294c); + IVTV_DEBUG_YUV("Update reg 0x2944 %08x->%08x 0x294c %08x->%08x\n", + yi->reg_2944, reg_2944, yi->reg_294c, reg_294c); + + /* Ensure 2970 is 0 (does it ever change ?) */ +/* write_reg(0,0x02970); */ +/* IVTV_DEBUG_YUV("Update reg 0x2970 %08x->%08x\n", yi->reg_2970, 0); */ + + write_reg(reg_2930, 0x02938); + write_reg(reg_2930, 0x02930); + IVTV_DEBUG_YUV("Update reg 0x2930 %08x->%08x 0x2938 %08x->%08x\n", + yi->reg_2930, reg_2930, yi->reg_2938, reg_2930); + + write_reg(reg_2928, 0x02928); + write_reg(reg_2928 + 0x514, 0x0292C); + IVTV_DEBUG_YUV("Update reg 0x2928 %08x->%08x 0x292c %08x->%08x\n", + yi->reg_2928, reg_2928, yi->reg_292c, reg_2928 + 0x514); + + write_reg(reg_2920, 0x02920); + write_reg(reg_2920 + 0x514, 0x02924); + IVTV_DEBUG_YUV("Update reg 0x2920 %08x->%08x 0x2924 %08x->%08x\n", + yi->reg_2920, reg_2920, yi->reg_2924, reg_2920 + 0x514); + + write_reg(reg_2918, 0x02918); + write_reg(reg_291c, 0x0291C); + IVTV_DEBUG_YUV("Update reg 0x2918 %08x->%08x 0x291C %08x->%08x\n", + yi->reg_2918, reg_2918, yi->reg_291c, reg_291c); + + write_reg(reg_296c, 0x0296c); + IVTV_DEBUG_YUV("Update reg 0x296c %08x->%08x\n", + yi->reg_296c, reg_296c); + + write_reg(reg_2940, 0x02948); + write_reg(reg_2940, 0x02940); + IVTV_DEBUG_YUV("Update reg 0x2940 %08x->%08x 0x2948 %08x->%08x\n", + yi->reg_2940, reg_2940, yi->reg_2948, reg_2940); + + write_reg(reg_2950, 0x02950); + write_reg(reg_2954, 0x02954); + IVTV_DEBUG_YUV("Update reg 0x2950 %08x->%08x 0x2954 %08x->%08x\n", + yi->reg_2950, reg_2950, yi->reg_2954, reg_2954); + + write_reg(reg_2958, 0x02958); + write_reg(reg_295c, 0x0295C); + IVTV_DEBUG_YUV("Update reg 0x2958 %08x->%08x 0x295C %08x->%08x\n", + yi->reg_2958, reg_2958, yi->reg_295c, reg_295c); + + write_reg(reg_2960, 0x02960); + IVTV_DEBUG_YUV("Update reg 0x2960 %08x->%08x \n", + yi->reg_2960, reg_2960); + + write_reg(reg_2964, 0x02964); + write_reg(reg_2968, 0x02968); + IVTV_DEBUG_YUV("Update reg 0x2964 %08x->%08x 0x2968 %08x->%08x\n", + yi->reg_2964, reg_2964, yi->reg_2968, reg_2968); + + write_reg(reg_289c, 0x0289c); + IVTV_DEBUG_YUV("Update reg 0x289c %08x->%08x\n", + yi->reg_289c, reg_289c); + + /* Only update filter 1 if we really need to */ + if (v_filter_1 != yi->v_filter_1) { + ivtv_yuv_filter(itv, -1, v_filter_1, -1); + yi->v_filter_1 = v_filter_1; + } + + /* Only update filter 2 if we really need to */ + if (v_filter_2 != yi->v_filter_2) { + ivtv_yuv_filter(itv, -1, -1, v_filter_2); + yi->v_filter_2 = v_filter_2; + } +} + +/* Modify the supplied coordinate information to fit the visible osd area */ +static u32 ivtv_yuv_window_setup(struct ivtv *itv, struct yuv_frame_info *f) +{ + struct yuv_frame_info *of = &itv->yuv_info.old_frame_info; + int osd_crop; + u32 osd_scale; + u32 yuv_update = 0; + + /* Sorry, but no negative coords for src */ + if (f->src_x < 0) + f->src_x = 0; + if (f->src_y < 0) + f->src_y = 0; + + /* Can only reduce width down to 1/4 original size */ + if ((osd_crop = f->src_w - 4 * f->dst_w) > 0) { + f->src_x += osd_crop / 2; + f->src_w = (f->src_w - osd_crop) & ~3; + f->dst_w = f->src_w / 4; + f->dst_w += f->dst_w & 1; + } + + /* Can only reduce height down to 1/4 original size */ + if (f->src_h / f->dst_h >= 2) { + /* Overflow may be because we're running progressive, + so force mode switch */ + f->interlaced_y = 1; + /* Make sure we're still within limits for interlace */ + if ((osd_crop = f->src_h - 4 * f->dst_h) > 0) { + /* If we reach here we'll have to force the height. */ + f->src_y += osd_crop / 2; + f->src_h = (f->src_h - osd_crop) & ~3; + f->dst_h = f->src_h / 4; + f->dst_h += f->dst_h & 1; + } + } + + /* If there's nothing to safe to display, we may as well stop now */ + if ((int)f->dst_w <= 2 || (int)f->dst_h <= 2 || + (int)f->src_w <= 2 || (int)f->src_h <= 2) { + return IVTV_YUV_UPDATE_INVALID; + } + + /* Ensure video remains inside OSD area */ + osd_scale = (f->src_h << 16) / f->dst_h; + + if ((osd_crop = f->pan_y - f->dst_y) > 0) { + /* Falls off the upper edge - crop */ + f->src_y += (osd_scale * osd_crop) >> 16; + f->src_h -= (osd_scale * osd_crop) >> 16; + f->dst_h -= osd_crop; + f->dst_y = 0; + } else { + f->dst_y -= f->pan_y; + } + + if ((osd_crop = f->dst_h + f->dst_y - f->vis_h) > 0) { + /* Falls off the lower edge - crop */ + f->dst_h -= osd_crop; + f->src_h -= (osd_scale * osd_crop) >> 16; + } + + osd_scale = (f->src_w << 16) / f->dst_w; + + if ((osd_crop = f->pan_x - f->dst_x) > 0) { + /* Fall off the left edge - crop */ + f->src_x += (osd_scale * osd_crop) >> 16; + f->src_w -= (osd_scale * osd_crop) >> 16; + f->dst_w -= osd_crop; + f->dst_x = 0; + } else { + f->dst_x -= f->pan_x; + } + + if ((osd_crop = f->dst_w + f->dst_x - f->vis_w) > 0) { + /* Falls off the right edge - crop */ + f->dst_w -= osd_crop; + f->src_w -= (osd_scale * osd_crop) >> 16; + } + + if (itv->yuv_info.track_osd) { + /* The OSD can be moved. Track to it */ + f->dst_x += itv->yuv_info.osd_x_offset; + f->dst_y += itv->yuv_info.osd_y_offset; + } + + /* Width & height for both src & dst must be even. + Same for coordinates. */ + f->dst_w &= ~1; + f->dst_x &= ~1; + + f->src_w += f->src_x & 1; + f->src_x &= ~1; + + f->src_w &= ~1; + f->dst_w &= ~1; + + f->dst_h &= ~1; + f->dst_y &= ~1; + + f->src_h += f->src_y & 1; + f->src_y &= ~1; + + f->src_h &= ~1; + f->dst_h &= ~1; + + /* Due to rounding, we may have reduced the output size to <1/4 of + the source. Check again, but this time just resize. Don't change + source coordinates */ + if (f->dst_w < f->src_w / 4) { + f->src_w &= ~3; + f->dst_w = f->src_w / 4; + f->dst_w += f->dst_w & 1; + } + if (f->dst_h < f->src_h / 4) { + f->src_h &= ~3; + f->dst_h = f->src_h / 4; + f->dst_h += f->dst_h & 1; + } + + /* Check again. If there's nothing to safe to display, stop now */ + if ((int)f->dst_w <= 2 || (int)f->dst_h <= 2 || + (int)f->src_w <= 2 || (int)f->src_h <= 2) { + return IVTV_YUV_UPDATE_INVALID; + } + + /* Both x offset & width are linked, so they have to be done together */ + if ((of->dst_w != f->dst_w) || (of->src_w != f->src_w) || + (of->dst_x != f->dst_x) || (of->src_x != f->src_x) || + (of->pan_x != f->pan_x) || (of->vis_w != f->vis_w)) { + yuv_update |= IVTV_YUV_UPDATE_HORIZONTAL; + } + + if ((of->src_h != f->src_h) || (of->dst_h != f->dst_h) || + (of->dst_y != f->dst_y) || (of->src_y != f->src_y) || + (of->pan_y != f->pan_y) || (of->vis_h != f->vis_h) || + (of->lace_mode != f->lace_mode) || + (of->interlaced_y != f->interlaced_y) || + (of->interlaced_uv != f->interlaced_uv)) { + yuv_update |= IVTV_YUV_UPDATE_VERTICAL; + } + + return yuv_update; +} + +/* Update the scaling register to the requested value */ +void ivtv_yuv_work_handler(struct ivtv *itv) +{ + struct yuv_playback_info *yi = &itv->yuv_info; + struct yuv_frame_info f; + int frame = yi->update_frame; + u32 yuv_update; + + IVTV_DEBUG_YUV("Update yuv registers for frame %d\n", frame); + f = yi->new_frame_info[frame]; + + if (yi->track_osd) { + /* Snapshot the osd pan info */ + f.pan_x = yi->osd_x_pan; + f.pan_y = yi->osd_y_pan; + f.vis_w = yi->osd_vis_w; + f.vis_h = yi->osd_vis_h; + } else { + /* Not tracking the osd, so assume full screen */ + f.pan_x = 0; + f.pan_y = 0; + f.vis_w = 720; + f.vis_h = yi->decode_height; + } + + /* Calculate the display window coordinates. Exit if nothing left */ + if (!(yuv_update = ivtv_yuv_window_setup(itv, &f))) + return; + + if (yuv_update & IVTV_YUV_UPDATE_INVALID) { + write_reg(0x01008080, 0x2898); + } else if (yuv_update) { + write_reg(0x00108080, 0x2898); + + if (yuv_update & IVTV_YUV_UPDATE_HORIZONTAL) + ivtv_yuv_handle_horizontal(itv, &f); + + if (yuv_update & IVTV_YUV_UPDATE_VERTICAL) + ivtv_yuv_handle_vertical(itv, &f); + } + yi->old_frame_info = f; +} + +static void ivtv_yuv_init(struct ivtv *itv) +{ + struct yuv_playback_info *yi = &itv->yuv_info; + + IVTV_DEBUG_YUV("ivtv_yuv_init\n"); + + /* Take a snapshot of the current register settings */ + yi->reg_2834 = read_reg(0x02834); + yi->reg_2838 = read_reg(0x02838); + yi->reg_283c = read_reg(0x0283c); + yi->reg_2840 = read_reg(0x02840); + yi->reg_2844 = read_reg(0x02844); + yi->reg_2848 = read_reg(0x02848); + yi->reg_2854 = read_reg(0x02854); + yi->reg_285c = read_reg(0x0285c); + yi->reg_2864 = read_reg(0x02864); + yi->reg_2870 = read_reg(0x02870); + yi->reg_2874 = read_reg(0x02874); + yi->reg_2898 = read_reg(0x02898); + yi->reg_2890 = read_reg(0x02890); + + yi->reg_289c = read_reg(0x0289c); + yi->reg_2918 = read_reg(0x02918); + yi->reg_291c = read_reg(0x0291c); + yi->reg_2920 = read_reg(0x02920); + yi->reg_2924 = read_reg(0x02924); + yi->reg_2928 = read_reg(0x02928); + yi->reg_292c = read_reg(0x0292c); + yi->reg_2930 = read_reg(0x02930); + yi->reg_2934 = read_reg(0x02934); + yi->reg_2938 = read_reg(0x02938); + yi->reg_293c = read_reg(0x0293c); + yi->reg_2940 = read_reg(0x02940); + yi->reg_2944 = read_reg(0x02944); + yi->reg_2948 = read_reg(0x02948); + yi->reg_294c = read_reg(0x0294c); + yi->reg_2950 = read_reg(0x02950); + yi->reg_2954 = read_reg(0x02954); + yi->reg_2958 = read_reg(0x02958); + yi->reg_295c = read_reg(0x0295c); + yi->reg_2960 = read_reg(0x02960); + yi->reg_2964 = read_reg(0x02964); + yi->reg_2968 = read_reg(0x02968); + yi->reg_296c = read_reg(0x0296c); + yi->reg_2970 = read_reg(0x02970); + + yi->v_filter_1 = -1; + yi->v_filter_2 = -1; + yi->h_filter = -1; + + /* Set some valid size info */ + yi->osd_x_offset = read_reg(0x02a04) & 0x00000FFF; + yi->osd_y_offset = (read_reg(0x02a04) >> 16) & 0x00000FFF; + + /* Bit 2 of reg 2878 indicates current decoder output format + 0 : NTSC 1 : PAL */ + if (read_reg(0x2878) & 4) + yi->decode_height = 576; + else + yi->decode_height = 480; + + if (!itv->osd_info) { + yi->osd_vis_w = 720 - yi->osd_x_offset; + yi->osd_vis_h = yi->decode_height - yi->osd_y_offset; + } else { + /* If no visible size set, assume full size */ + if (!yi->osd_vis_w) + yi->osd_vis_w = 720 - yi->osd_x_offset; + + if (!yi->osd_vis_h) { + yi->osd_vis_h = yi->decode_height - yi->osd_y_offset; + } else if (yi->osd_vis_h + yi->osd_y_offset > yi->decode_height) { + /* If output video standard has changed, requested height may + not be legal */ + IVTV_DEBUG_WARN("Clipping yuv output - fb size (%d) exceeds video standard limit (%d)\n", + yi->osd_vis_h + yi->osd_y_offset, + yi->decode_height); + yi->osd_vis_h = yi->decode_height - yi->osd_y_offset; + } + } + + /* We need a buffer for blanking when Y plane is offset - non-fatal if we can't get one */ + yi->blanking_ptr = kzalloc(720 * 16, GFP_ATOMIC|__GFP_NOWARN); + if (yi->blanking_ptr) { + yi->blanking_dmaptr = dma_map_single(&itv->pdev->dev, + yi->blanking_ptr, + 720 * 16, DMA_TO_DEVICE); + if (dma_mapping_error(&itv->pdev->dev, yi->blanking_dmaptr)) { + kfree(yi->blanking_ptr); + yi->blanking_ptr = NULL; + yi->blanking_dmaptr = 0; + IVTV_DEBUG_WARN("Failed to dma_map yuv blanking buffer\n"); + } + } else { + yi->blanking_dmaptr = 0; + IVTV_DEBUG_WARN("Failed to allocate yuv blanking buffer\n"); + } + + /* Enable YUV decoder output */ + write_reg_sync(0x01, IVTV_REG_VDM); + + set_bit(IVTV_F_I_DECODING_YUV, &itv->i_flags); + atomic_set(&yi->next_dma_frame, 0); +} + +/* Get next available yuv buffer on PVR350 */ +static void ivtv_yuv_next_free(struct ivtv *itv) +{ + int draw, display; + struct yuv_playback_info *yi = &itv->yuv_info; + + if (atomic_read(&yi->next_dma_frame) == -1) + ivtv_yuv_init(itv); + + draw = atomic_read(&yi->next_fill_frame); + display = atomic_read(&yi->next_dma_frame); + + if (display > draw) + display -= IVTV_YUV_BUFFERS; + + if (draw - display >= yi->max_frames_buffered) + draw = (u8)(draw - 1) % IVTV_YUV_BUFFERS; + else + yi->new_frame_info[draw].update = 0; + + yi->draw_frame = draw; +} + +/* Set up frame according to ivtv_dma_frame parameters */ +static void ivtv_yuv_setup_frame(struct ivtv *itv, struct ivtv_dma_frame *args) +{ + struct yuv_playback_info *yi = &itv->yuv_info; + u8 frame = yi->draw_frame; + u8 last_frame = (u8)(frame - 1) % IVTV_YUV_BUFFERS; + struct yuv_frame_info *nf = &yi->new_frame_info[frame]; + struct yuv_frame_info *of = &yi->new_frame_info[last_frame]; + int lace_threshold = yi->lace_threshold; + + /* Preserve old update flag in case we're overwriting a queued frame */ + int update = nf->update; + + /* Take a snapshot of the yuv coordinate information */ + nf->src_x = args->src.left; + nf->src_y = args->src.top; + nf->src_w = args->src.width; + nf->src_h = args->src.height; + nf->dst_x = args->dst.left; + nf->dst_y = args->dst.top; + nf->dst_w = args->dst.width; + nf->dst_h = args->dst.height; + nf->tru_x = args->dst.left; + nf->tru_w = args->src_width; + nf->tru_h = args->src_height; + + /* Are we going to offset the Y plane */ + nf->offset_y = (nf->tru_h + nf->src_x < 512 - 16) ? 1 : 0; + + nf->update = 0; + nf->interlaced_y = 0; + nf->interlaced_uv = 0; + nf->delay = 0; + nf->sync_field = 0; + nf->lace_mode = yi->lace_mode & IVTV_YUV_MODE_MASK; + + if (lace_threshold < 0) + lace_threshold = yi->decode_height - 1; + + /* Work out the lace settings */ + switch (nf->lace_mode) { + case IVTV_YUV_MODE_PROGRESSIVE: /* Progressive mode */ + nf->interlaced = 0; + if (nf->tru_h < 512 || (nf->tru_h > 576 && nf->tru_h < 1021)) + nf->interlaced_y = 0; + else + nf->interlaced_y = 1; + + if (nf->tru_h < 1021 && (nf->dst_h >= nf->src_h / 2)) + nf->interlaced_uv = 0; + else + nf->interlaced_uv = 1; + break; + + case IVTV_YUV_MODE_AUTO: + if (nf->tru_h <= lace_threshold || nf->tru_h > 576 || nf->tru_w > 720) { + nf->interlaced = 0; + if ((nf->tru_h < 512) || + (nf->tru_h > 576 && nf->tru_h < 1021) || + (nf->tru_w > 720 && nf->tru_h < 1021)) + nf->interlaced_y = 0; + else + nf->interlaced_y = 1; + if (nf->tru_h < 1021 && (nf->dst_h >= nf->src_h / 2)) + nf->interlaced_uv = 0; + else + nf->interlaced_uv = 1; + } else { + nf->interlaced = 1; + nf->interlaced_y = 1; + nf->interlaced_uv = 1; + } + break; + + case IVTV_YUV_MODE_INTERLACED: /* Interlace mode */ + default: + nf->interlaced = 1; + nf->interlaced_y = 1; + nf->interlaced_uv = 1; + break; + } + + if (memcmp(&yi->old_frame_info_args, nf, sizeof(*nf))) { + yi->old_frame_info_args = *nf; + nf->update = 1; + IVTV_DEBUG_YUV("Requesting reg update for frame %d\n", frame); + } + + nf->update |= update; + nf->sync_field = yi->lace_sync_field; + nf->delay = nf->sync_field != of->sync_field; +} + +/* Frame is complete & ready for display */ +void ivtv_yuv_frame_complete(struct ivtv *itv) +{ + atomic_set(&itv->yuv_info.next_fill_frame, + (itv->yuv_info.draw_frame + 1) % IVTV_YUV_BUFFERS); +} + +static int ivtv_yuv_udma_frame(struct ivtv *itv, struct ivtv_dma_frame *args) +{ + DEFINE_WAIT(wait); + int rc = 0; + int got_sig = 0; + /* DMA the frame */ + mutex_lock(&itv->udma.lock); + + if ((rc = ivtv_yuv_prep_user_dma(itv, &itv->udma, args)) != 0) { + mutex_unlock(&itv->udma.lock); + return rc; + } + + ivtv_udma_prepare(itv); + prepare_to_wait(&itv->dma_waitq, &wait, TASK_INTERRUPTIBLE); + /* if no UDMA is pending and no UDMA is in progress, then the DMA + is finished */ + while (test_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags) || + test_bit(IVTV_F_I_UDMA, &itv->i_flags)) { + /* don't interrupt if the DMA is in progress but break off + a still pending DMA. */ + got_sig = signal_pending(current); + if (got_sig && test_and_clear_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags)) + break; + got_sig = 0; + schedule(); + } + finish_wait(&itv->dma_waitq, &wait); + + /* Unmap Last DMA Xfer */ + ivtv_udma_unmap(itv); + + if (got_sig) { + IVTV_DEBUG_INFO("User stopped YUV UDMA\n"); + mutex_unlock(&itv->udma.lock); + return -EINTR; + } + + ivtv_yuv_frame_complete(itv); + + mutex_unlock(&itv->udma.lock); + return rc; +} + +/* Setup frame according to V4L2 parameters */ +void ivtv_yuv_setup_stream_frame(struct ivtv *itv) +{ + struct yuv_playback_info *yi = &itv->yuv_info; + struct ivtv_dma_frame dma_args; + + ivtv_yuv_next_free(itv); + + /* Copy V4L2 parameters to an ivtv_dma_frame struct... */ + dma_args.y_source = NULL; + dma_args.uv_source = NULL; + dma_args.src.left = 0; + dma_args.src.top = 0; + dma_args.src.width = yi->v4l2_src_w; + dma_args.src.height = yi->v4l2_src_h; + dma_args.dst = yi->main_rect; + dma_args.src_width = yi->v4l2_src_w; + dma_args.src_height = yi->v4l2_src_h; + + /* ... and use the same setup routine as ivtv_yuv_prep_frame */ + ivtv_yuv_setup_frame(itv, &dma_args); + + if (!itv->dma_data_req_offset) + itv->dma_data_req_offset = yuv_offset[yi->draw_frame]; +} + +/* Attempt to dma a frame from a user buffer */ +int ivtv_yuv_udma_stream_frame(struct ivtv *itv, void __user *src) +{ + struct yuv_playback_info *yi = &itv->yuv_info; + struct ivtv_dma_frame dma_args; + int res; + + ivtv_yuv_setup_stream_frame(itv); + + /* We only need to supply source addresses for this */ + dma_args.y_source = src; + dma_args.uv_source = src + 720 * ((yi->v4l2_src_h + 31) & ~31); + /* Wait for frame DMA. Note that serialize_lock is locked, + so to allow other processes to access the driver while + we are waiting unlock first and later lock again. */ + mutex_unlock(&itv->serialize_lock); + res = ivtv_yuv_udma_frame(itv, &dma_args); + mutex_lock(&itv->serialize_lock); + return res; +} + +/* IVTV_IOC_DMA_FRAME ioctl handler */ +int ivtv_yuv_prep_frame(struct ivtv *itv, struct ivtv_dma_frame *args) +{ + int res; + +/* IVTV_DEBUG_INFO("yuv_prep_frame\n"); */ + ivtv_yuv_next_free(itv); + ivtv_yuv_setup_frame(itv, args); + /* Wait for frame DMA. Note that serialize_lock is locked, + so to allow other processes to access the driver while + we are waiting unlock first and later lock again. */ + mutex_unlock(&itv->serialize_lock); + res = ivtv_yuv_udma_frame(itv, args); + mutex_lock(&itv->serialize_lock); + return res; +} + +void ivtv_yuv_close(struct ivtv *itv) +{ + struct yuv_playback_info *yi = &itv->yuv_info; + int h_filter, v_filter_1, v_filter_2; + + IVTV_DEBUG_YUV("ivtv_yuv_close\n"); + mutex_unlock(&itv->serialize_lock); + ivtv_waitq(&itv->vsync_waitq); + mutex_lock(&itv->serialize_lock); + + yi->running = 0; + atomic_set(&yi->next_dma_frame, -1); + atomic_set(&yi->next_fill_frame, 0); + + /* Reset registers we have changed so mpeg playback works */ + + /* If we fully restore this register, the display may remain active. + Restore, but set one bit to blank the video. Firmware will always + clear this bit when needed, so not a problem. */ + write_reg(yi->reg_2898 | 0x01000000, 0x2898); + + write_reg(yi->reg_2834, 0x02834); + write_reg(yi->reg_2838, 0x02838); + write_reg(yi->reg_283c, 0x0283c); + write_reg(yi->reg_2840, 0x02840); + write_reg(yi->reg_2844, 0x02844); + write_reg(yi->reg_2848, 0x02848); + write_reg(yi->reg_2854, 0x02854); + write_reg(yi->reg_285c, 0x0285c); + write_reg(yi->reg_2864, 0x02864); + write_reg(yi->reg_2870, 0x02870); + write_reg(yi->reg_2874, 0x02874); + write_reg(yi->reg_2890, 0x02890); + write_reg(yi->reg_289c, 0x0289c); + + write_reg(yi->reg_2918, 0x02918); + write_reg(yi->reg_291c, 0x0291c); + write_reg(yi->reg_2920, 0x02920); + write_reg(yi->reg_2924, 0x02924); + write_reg(yi->reg_2928, 0x02928); + write_reg(yi->reg_292c, 0x0292c); + write_reg(yi->reg_2930, 0x02930); + write_reg(yi->reg_2934, 0x02934); + write_reg(yi->reg_2938, 0x02938); + write_reg(yi->reg_293c, 0x0293c); + write_reg(yi->reg_2940, 0x02940); + write_reg(yi->reg_2944, 0x02944); + write_reg(yi->reg_2948, 0x02948); + write_reg(yi->reg_294c, 0x0294c); + write_reg(yi->reg_2950, 0x02950); + write_reg(yi->reg_2954, 0x02954); + write_reg(yi->reg_2958, 0x02958); + write_reg(yi->reg_295c, 0x0295c); + write_reg(yi->reg_2960, 0x02960); + write_reg(yi->reg_2964, 0x02964); + write_reg(yi->reg_2968, 0x02968); + write_reg(yi->reg_296c, 0x0296c); + write_reg(yi->reg_2970, 0x02970); + + /* Prepare to restore filters */ + + /* First the horizontal filter */ + if ((yi->reg_2834 & 0x0000FFFF) == (yi->reg_2834 >> 16)) { + /* An exact size match uses filter 0 */ + h_filter = 0; + } else { + /* Figure out which filter to use */ + h_filter = ((yi->reg_2834 << 16) / (yi->reg_2834 >> 16)) >> 15; + h_filter = (h_filter >> 1) + (h_filter & 1); + /* Only an exact size match can use filter 0. */ + h_filter += !h_filter; + } + + /* Now the vertical filter */ + if ((yi->reg_2918 & 0x0000FFFF) == (yi->reg_2918 >> 16)) { + /* An exact size match uses filter 0/1 */ + v_filter_1 = 0; + v_filter_2 = 1; + } else { + /* Figure out which filter to use */ + v_filter_1 = ((yi->reg_2918 << 16) / (yi->reg_2918 >> 16)) >> 15; + v_filter_1 = (v_filter_1 >> 1) + (v_filter_1 & 1); + /* Only an exact size match can use filter 0 */ + v_filter_1 += !v_filter_1; + v_filter_2 = v_filter_1; + } + + /* Now restore the filters */ + ivtv_yuv_filter(itv, h_filter, v_filter_1, v_filter_2); + + /* and clear a few registers */ + write_reg(0, 0x02814); + write_reg(0, 0x0282c); + write_reg(0, 0x02904); + write_reg(0, 0x02910); + + /* Release the blanking buffer */ + if (yi->blanking_ptr) { + kfree(yi->blanking_ptr); + yi->blanking_ptr = NULL; + dma_unmap_single(&itv->pdev->dev, yi->blanking_dmaptr, + 720 * 16, DMA_TO_DEVICE); + } + + /* Invalidate the old dimension information */ + yi->old_frame_info.src_w = 0; + yi->old_frame_info.src_h = 0; + yi->old_frame_info_args.src_w = 0; + yi->old_frame_info_args.src_h = 0; + + /* All done. */ + clear_bit(IVTV_F_I_DECODING_YUV, &itv->i_flags); +} diff --git a/drivers/media/pci/ivtv/ivtv-yuv.h b/drivers/media/pci/ivtv/ivtv-yuv.h new file mode 100644 index 000000000..cc8e6ce4f --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-yuv.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + yuv support + + Copyright (C) 2007 Ian Armstrong + + */ + +#ifndef IVTV_YUV_H +#define IVTV_YUV_H + +#define IVTV_YUV_BUFFER_UV_OFFSET 0x65400 /* Offset to UV Buffer */ + +/* Offset to filter table in firmware */ +#define IVTV_YUV_HORIZONTAL_FILTER_OFFSET 0x025d8 +#define IVTV_YUV_VERTICAL_FILTER_OFFSET 0x03358 + +#define IVTV_YUV_UPDATE_HORIZONTAL 0x01 +#define IVTV_YUV_UPDATE_VERTICAL 0x02 +#define IVTV_YUV_UPDATE_INVALID 0x04 + +extern const u32 yuv_offset[IVTV_YUV_BUFFERS]; + +int ivtv_yuv_filter_check(struct ivtv *itv); +void ivtv_yuv_setup_stream_frame(struct ivtv *itv); +int ivtv_yuv_udma_stream_frame(struct ivtv *itv, void __user *src); +void ivtv_yuv_frame_complete(struct ivtv *itv); +int ivtv_yuv_prep_frame(struct ivtv *itv, struct ivtv_dma_frame *args); +void ivtv_yuv_close(struct ivtv *itv); +void ivtv_yuv_work_handler(struct ivtv *itv); + +#endif diff --git a/drivers/media/pci/ivtv/ivtvfb.c b/drivers/media/pci/ivtv/ivtvfb.c new file mode 100644 index 000000000..4f55b4473 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtvfb.c @@ -0,0 +1,1303 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + On Screen Display cx23415 Framebuffer driver + + This module presents the cx23415 OSD (onscreen display) framebuffer memory + as a standard Linux /dev/fb style framebuffer device. The framebuffer has + support for 8, 16 & 32 bpp packed pixel formats with alpha channel. In 16bpp + mode, there is a choice of a three color depths (12, 15 or 16 bits), but no + local alpha. The colorspace is selectable between rgb & yuv. + Depending on the TV standard configured in the ivtv module at load time, + the initial resolution is either 640x400 (NTSC) or 640x480 (PAL) at 8bpp. + Video timings are locked to ensure a vertical refresh rate of 50Hz (PAL) + or 59.94 (NTSC) + + Copyright (c) 2003 Matt T. Yourst + + Derived from drivers/video/vesafb.c + Portions (c) 1998 Gerd Knorr + + 2.6 kernel port: + Copyright (C) 2004 Matthias Badaire + + Copyright (C) 2004 Chris Kennedy + + Copyright (C) 2006 Ian Armstrong + + */ + +#include "ivtv-driver.h" +#include "ivtv-cards.h" +#include "ivtv-i2c.h" +#include "ivtv-udma.h" +#include "ivtv-mailbox.h" +#include "ivtv-firmware.h" + +#include +#include + +#if defined(CONFIG_X86_64) && !defined(CONFIG_UML) +#include +#endif + +/* card parameters */ +static int ivtvfb_card_id = -1; +static int ivtvfb_debug; +static bool ivtvfb_force_pat = IS_ENABLED(CONFIG_VIDEO_FB_IVTV_FORCE_PAT); +static bool osd_laced; +static int osd_depth; +static int osd_upper; +static int osd_left; +static unsigned int osd_yres; +static unsigned int osd_xres; + +module_param(ivtvfb_card_id, int, 0444); +module_param_named(debug,ivtvfb_debug, int, 0644); +module_param_named(force_pat, ivtvfb_force_pat, bool, 0644); +module_param(osd_laced, bool, 0444); +module_param(osd_depth, int, 0444); +module_param(osd_upper, int, 0444); +module_param(osd_left, int, 0444); +module_param(osd_yres, uint, 0444); +module_param(osd_xres, uint, 0444); + +MODULE_PARM_DESC(ivtvfb_card_id, + "Only use framebuffer of the specified ivtv card (0-31)\n" + "\t\t\tdefault -1: initialize all available framebuffers"); + +MODULE_PARM_DESC(debug, + "Debug level (bitmask). Default: errors only\n" + "\t\t\t(debug = 3 gives full debugging)"); + +MODULE_PARM_DESC(force_pat, + "Force initialization on x86 PAT-enabled systems (bool).\n"); + +/* Why upper, left, xres, yres, depth, laced ? To match terminology used + by fbset. + Why start at 1 for left & upper coordinate ? Because X doesn't allow 0 */ + +MODULE_PARM_DESC(osd_laced, + "Interlaced mode\n" + "\t\t\t0=off\n" + "\t\t\t1=on\n" + "\t\t\tdefault off"); + +MODULE_PARM_DESC(osd_depth, + "Bits per pixel - 8, 16, 32\n" + "\t\t\tdefault 8"); + +MODULE_PARM_DESC(osd_upper, + "Vertical start position\n" + "\t\t\tdefault 0 (Centered)"); + +MODULE_PARM_DESC(osd_left, + "Horizontal start position\n" + "\t\t\tdefault 0 (Centered)"); + +MODULE_PARM_DESC(osd_yres, + "Display height\n" + "\t\t\tdefault 480 (PAL)\n" + "\t\t\t 400 (NTSC)"); + +MODULE_PARM_DESC(osd_xres, + "Display width\n" + "\t\t\tdefault 640"); + +MODULE_AUTHOR("Kevin Thayer, Chris Kennedy, Hans Verkuil, John Harvey, Ian Armstrong"); +MODULE_DESCRIPTION("Conexant cx23415 framebuffer support"); +MODULE_LICENSE("GPL"); + +/* --------------------------------------------------------------------- */ + +#define IVTVFB_DBGFLG_WARN (1 << 0) +#define IVTVFB_DBGFLG_INFO (1 << 1) + +#define IVTVFB_DEBUG(x, type, fmt, args...) \ + do { \ + if ((x) & ivtvfb_debug) \ + printk(KERN_INFO "ivtvfb%d " type ": " fmt, itv->instance , ## args); \ + } while (0) +#define IVTVFB_DEBUG_WARN(fmt, args...) IVTVFB_DEBUG(IVTVFB_DBGFLG_WARN, "warning", fmt , ## args) +#define IVTVFB_DEBUG_INFO(fmt, args...) IVTVFB_DEBUG(IVTVFB_DBGFLG_INFO, "info", fmt , ## args) + +/* Standard kernel messages */ +#define IVTVFB_ERR(fmt, args...) printk(KERN_ERR "ivtvfb%d: " fmt, itv->instance , ## args) +#define IVTVFB_WARN(fmt, args...) printk(KERN_WARNING "ivtvfb%d: " fmt, itv->instance , ## args) +#define IVTVFB_INFO(fmt, args...) printk(KERN_INFO "ivtvfb%d: " fmt, itv->instance , ## args) + +/* --------------------------------------------------------------------- */ + +#define IVTV_OSD_MAX_WIDTH 720 +#define IVTV_OSD_MAX_HEIGHT 576 + +#define IVTV_OSD_BPP_8 0x00 +#define IVTV_OSD_BPP_16_444 0x03 +#define IVTV_OSD_BPP_16_555 0x02 +#define IVTV_OSD_BPP_16_565 0x01 +#define IVTV_OSD_BPP_32 0x04 + +struct osd_info { + /* Physical base address */ + unsigned long video_pbase; + /* Relative base address (relative to start of decoder memory) */ + u32 video_rbase; + /* Mapped base address */ + volatile char __iomem *video_vbase; + /* Buffer size */ + u32 video_buffer_size; + + /* video_base rounded down as required by hardware MTRRs */ + unsigned long fb_start_aligned_physaddr; + /* video_base rounded up as required by hardware MTRRs */ + unsigned long fb_end_aligned_physaddr; + int wc_cookie; + + /* Store the buffer offset */ + int set_osd_coords_x; + int set_osd_coords_y; + + /* Current dimensions (NOT VISIBLE SIZE!) */ + int display_width; + int display_height; + int display_byte_stride; + + /* Current bits per pixel */ + int bits_per_pixel; + int bytes_per_pixel; + + /* Frame buffer stuff */ + struct fb_info ivtvfb_info; + struct fb_var_screeninfo ivtvfb_defined; + struct fb_fix_screeninfo ivtvfb_fix; + + /* Used for a warm start */ + struct fb_var_screeninfo fbvar_cur; + int blank_cur; + u32 palette_cur[256]; + u32 pan_cur; +}; + +struct ivtv_osd_coords { + unsigned long offset; + unsigned long max_offset; + int pixel_stride; + int lines; + int x; + int y; +}; + +/* --------------------------------------------------------------------- */ + +/* ivtv API calls for framebuffer related support */ + +static int ivtvfb_get_framebuffer(struct ivtv *itv, u32 *fbbase, + u32 *fblength) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + int rc; + + ivtv_firmware_check(itv, "ivtvfb_get_framebuffer"); + rc = ivtv_vapi_result(itv, data, CX2341X_OSD_GET_FRAMEBUFFER, 0); + *fbbase = data[0]; + *fblength = data[1]; + return rc; +} + +static int ivtvfb_get_osd_coords(struct ivtv *itv, + struct ivtv_osd_coords *osd) +{ + struct osd_info *oi = itv->osd_info; + u32 data[CX2341X_MBOX_MAX_DATA]; + + ivtv_vapi_result(itv, data, CX2341X_OSD_GET_OSD_COORDS, 0); + + osd->offset = data[0] - oi->video_rbase; + osd->max_offset = oi->display_width * oi->display_height * 4; + osd->pixel_stride = data[1]; + osd->lines = data[2]; + osd->x = data[3]; + osd->y = data[4]; + return 0; +} + +static int ivtvfb_set_osd_coords(struct ivtv *itv, const struct ivtv_osd_coords *osd) +{ + struct osd_info *oi = itv->osd_info; + + oi->display_width = osd->pixel_stride; + oi->display_byte_stride = osd->pixel_stride * oi->bytes_per_pixel; + oi->set_osd_coords_x += osd->x; + oi->set_osd_coords_y = osd->y; + + return ivtv_vapi(itv, CX2341X_OSD_SET_OSD_COORDS, 5, + osd->offset + oi->video_rbase, + osd->pixel_stride, + osd->lines, osd->x, osd->y); +} + +static int ivtvfb_set_display_window(struct ivtv *itv, struct v4l2_rect *ivtv_window) +{ + int osd_height_limit = itv->is_out_50hz ? 576 : 480; + + /* Only fail if resolution too high, otherwise fudge the start coords. */ + if ((ivtv_window->height > osd_height_limit) || (ivtv_window->width > IVTV_OSD_MAX_WIDTH)) + return -EINVAL; + + /* Ensure we don't exceed display limits */ + if (ivtv_window->top + ivtv_window->height > osd_height_limit) { + IVTVFB_DEBUG_WARN("ivtv_ioctl_fb_set_display_window - Invalid height setting (%d, %d)\n", + ivtv_window->top, ivtv_window->height); + ivtv_window->top = osd_height_limit - ivtv_window->height; + } + + if (ivtv_window->left + ivtv_window->width > IVTV_OSD_MAX_WIDTH) { + IVTVFB_DEBUG_WARN("ivtv_ioctl_fb_set_display_window - Invalid width setting (%d, %d)\n", + ivtv_window->left, ivtv_window->width); + ivtv_window->left = IVTV_OSD_MAX_WIDTH - ivtv_window->width; + } + + /* Set the OSD origin */ + write_reg((ivtv_window->top << 16) | ivtv_window->left, 0x02a04); + + /* How much to display */ + write_reg(((ivtv_window->top+ivtv_window->height) << 16) | (ivtv_window->left+ivtv_window->width), 0x02a08); + + /* Pass this info back the yuv handler */ + itv->yuv_info.osd_vis_w = ivtv_window->width; + itv->yuv_info.osd_vis_h = ivtv_window->height; + itv->yuv_info.osd_x_offset = ivtv_window->left; + itv->yuv_info.osd_y_offset = ivtv_window->top; + + return 0; +} + +static int ivtvfb_prep_dec_dma_to_device(struct ivtv *itv, + unsigned long ivtv_dest_addr, void __user *userbuf, + int size_in_bytes) +{ + DEFINE_WAIT(wait); + int got_sig = 0; + + mutex_lock(&itv->udma.lock); + /* Map User DMA */ + if (ivtv_udma_setup(itv, ivtv_dest_addr, userbuf, size_in_bytes) <= 0) { + mutex_unlock(&itv->udma.lock); + IVTVFB_WARN("%s, Error in ivtv_udma_setup: %d bytes, %d pages returned\n", + __func__, size_in_bytes, itv->udma.page_count); + + /* pin_user_pages or DMA must have failed completely */ + return -EIO; + } + + IVTVFB_DEBUG_INFO("ivtvfb_prep_dec_dma_to_device, %d bytes, %d pages\n", + size_in_bytes, itv->udma.page_count); + + ivtv_udma_prepare(itv); + prepare_to_wait(&itv->dma_waitq, &wait, TASK_INTERRUPTIBLE); + /* if no UDMA is pending and no UDMA is in progress, then the DMA + is finished */ + while (test_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags) || + test_bit(IVTV_F_I_UDMA, &itv->i_flags)) { + /* don't interrupt if the DMA is in progress but break off + a still pending DMA. */ + got_sig = signal_pending(current); + if (got_sig && test_and_clear_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags)) + break; + got_sig = 0; + schedule(); + } + finish_wait(&itv->dma_waitq, &wait); + + /* Unmap Last DMA Xfer */ + ivtv_udma_unmap(itv); + mutex_unlock(&itv->udma.lock); + if (got_sig) { + IVTV_DEBUG_INFO("User stopped OSD\n"); + return -EINTR; + } + + return 0; +} + +static int ivtvfb_prep_frame(struct ivtv *itv, int cmd, void __user *source, + unsigned long dest_offset, int count) +{ + DEFINE_WAIT(wait); + struct osd_info *oi = itv->osd_info; + + /* Nothing to do */ + if (count == 0) { + IVTVFB_DEBUG_WARN("ivtvfb_prep_frame: Nothing to do. count = 0\n"); + return -EINVAL; + } + + /* Check Total FB Size */ + if ((dest_offset + count) > oi->video_buffer_size) { + IVTVFB_WARN("ivtvfb_prep_frame: Overflowing the framebuffer %ld, only %d available\n", + dest_offset + count, oi->video_buffer_size); + return -E2BIG; + } + + /* Not fatal, but will have undesirable results */ + if ((unsigned long)source & 3) + IVTVFB_WARN("ivtvfb_prep_frame: Source address not 32 bit aligned (%p)\n", + source); + + if (dest_offset & 3) + IVTVFB_WARN("ivtvfb_prep_frame: Dest offset not 32 bit aligned (%ld)\n", dest_offset); + + if (count & 3) + IVTVFB_WARN("ivtvfb_prep_frame: Count not a multiple of 4 (%d)\n", count); + + /* Check Source */ + if (!access_ok(source + dest_offset, count)) { + IVTVFB_WARN("Invalid userspace pointer %p\n", source); + + IVTVFB_DEBUG_WARN("access_ok() failed for offset 0x%08lx source %p count %d\n", + dest_offset, source, count); + return -EINVAL; + } + + /* OSD Address to send DMA to */ + dest_offset += IVTV_DECODER_OFFSET + oi->video_rbase; + + /* Fill Buffers */ + return ivtvfb_prep_dec_dma_to_device(itv, dest_offset, source, count); +} + +static ssize_t ivtvfb_write(struct fb_info *info, const char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned long p = *ppos; + void *dst; + int err = 0; + int dma_err; + unsigned long total_size; + struct ivtv *itv = (struct ivtv *) info->par; + unsigned long dma_offset = + IVTV_DECODER_OFFSET + itv->osd_info->video_rbase; + unsigned long dma_size; + u16 lead = 0, tail = 0; + + if (!info->screen_base) + return -ENODEV; + + total_size = info->screen_size; + + if (total_size == 0) + total_size = info->fix.smem_len; + + if (p > total_size) + return -EFBIG; + + if (count > total_size) { + err = -EFBIG; + count = total_size; + } + + if (count + p > total_size) { + if (!err) + err = -ENOSPC; + count = total_size - p; + } + + dst = (void __force *) (info->screen_base + p); + + if (info->fbops->fb_sync) + info->fbops->fb_sync(info); + + /* If transfer size > threshold and both src/dst + addresses are aligned, use DMA */ + if (count >= 4096 && + ((unsigned long)buf & 3) == ((unsigned long)dst & 3)) { + /* Odd address = can't DMA. Align */ + if ((unsigned long)dst & 3) { + lead = 4 - ((unsigned long)dst & 3); + if (copy_from_user(dst, buf, lead)) + return -EFAULT; + buf += lead; + dst += lead; + } + /* DMA resolution is 32 bits */ + if ((count - lead) & 3) + tail = (count - lead) & 3; + /* DMA the data */ + dma_size = count - lead - tail; + dma_err = ivtvfb_prep_dec_dma_to_device(itv, + p + lead + dma_offset, (void __user *)buf, dma_size); + if (dma_err) + return dma_err; + dst += dma_size; + buf += dma_size; + /* Copy any leftover data */ + if (tail && copy_from_user(dst, buf, tail)) + return -EFAULT; + } else if (copy_from_user(dst, buf, count)) { + return -EFAULT; + } + + if (!err) + *ppos += count; + + return (err) ? err : count; +} + +static int ivtvfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + DEFINE_WAIT(wait); + struct ivtv *itv = (struct ivtv *)info->par; + int rc = 0; + + switch (cmd) { + case FBIOGET_VBLANK: { + struct fb_vblank vblank; + u32 trace; + + memset(&vblank, 0, sizeof(struct fb_vblank)); + + vblank.flags = FB_VBLANK_HAVE_COUNT |FB_VBLANK_HAVE_VCOUNT | + FB_VBLANK_HAVE_VSYNC; + trace = read_reg(IVTV_REG_DEC_LINE_FIELD) >> 16; + if (itv->is_out_50hz && trace > 312) + trace -= 312; + else if (itv->is_out_60hz && trace > 262) + trace -= 262; + if (trace == 1) + vblank.flags |= FB_VBLANK_VSYNCING; + vblank.count = itv->last_vsync_field; + vblank.vcount = trace; + vblank.hcount = 0; + if (copy_to_user((void __user *)arg, &vblank, sizeof(vblank))) + return -EFAULT; + return 0; + } + + case FBIO_WAITFORVSYNC: + prepare_to_wait(&itv->vsync_waitq, &wait, TASK_INTERRUPTIBLE); + if (!schedule_timeout(msecs_to_jiffies(50))) + rc = -ETIMEDOUT; + finish_wait(&itv->vsync_waitq, &wait); + return rc; + + case IVTVFB_IOC_DMA_FRAME: { + struct ivtvfb_dma_frame args; + + IVTVFB_DEBUG_INFO("IVTVFB_IOC_DMA_FRAME\n"); + if (copy_from_user(&args, (void __user *)arg, sizeof(args))) + return -EFAULT; + + return ivtvfb_prep_frame(itv, cmd, args.source, args.dest_offset, args.count); + } + + default: + IVTVFB_DEBUG_INFO("Unknown ioctl %08x\n", cmd); + return -EINVAL; + } + return 0; +} + +/* Framebuffer device handling */ + +static int ivtvfb_set_var(struct ivtv *itv, struct fb_var_screeninfo *var) +{ + struct osd_info *oi = itv->osd_info; + struct ivtv_osd_coords ivtv_osd; + struct v4l2_rect ivtv_window; + int osd_mode = -1; + + IVTVFB_DEBUG_INFO("ivtvfb_set_var\n"); + + /* Select color space */ + if (var->nonstd) /* YUV */ + write_reg(read_reg(0x02a00) | 0x0002000, 0x02a00); + else /* RGB */ + write_reg(read_reg(0x02a00) & ~0x0002000, 0x02a00); + + /* Set the color mode */ + switch (var->bits_per_pixel) { + case 8: + osd_mode = IVTV_OSD_BPP_8; + break; + case 32: + osd_mode = IVTV_OSD_BPP_32; + break; + case 16: + switch (var->green.length) { + case 4: + osd_mode = IVTV_OSD_BPP_16_444; + break; + case 5: + osd_mode = IVTV_OSD_BPP_16_555; + break; + case 6: + osd_mode = IVTV_OSD_BPP_16_565; + break; + default: + IVTVFB_DEBUG_WARN("ivtvfb_set_var - Invalid bpp\n"); + } + break; + default: + IVTVFB_DEBUG_WARN("ivtvfb_set_var - Invalid bpp\n"); + } + + /* Set video mode. Although rare, the display can become scrambled even + if we don't change mode. Always 'bounce' to osd_mode via mode 0 */ + if (osd_mode != -1) { + ivtv_vapi(itv, CX2341X_OSD_SET_PIXEL_FORMAT, 1, 0); + ivtv_vapi(itv, CX2341X_OSD_SET_PIXEL_FORMAT, 1, osd_mode); + } + + oi->bits_per_pixel = var->bits_per_pixel; + oi->bytes_per_pixel = var->bits_per_pixel / 8; + + /* Set the flicker filter */ + switch (var->vmode & FB_VMODE_MASK) { + case FB_VMODE_NONINTERLACED: /* Filter on */ + ivtv_vapi(itv, CX2341X_OSD_SET_FLICKER_STATE, 1, 1); + break; + case FB_VMODE_INTERLACED: /* Filter off */ + ivtv_vapi(itv, CX2341X_OSD_SET_FLICKER_STATE, 1, 0); + break; + default: + IVTVFB_DEBUG_WARN("ivtvfb_set_var - Invalid video mode\n"); + } + + /* Read the current osd info */ + ivtvfb_get_osd_coords(itv, &ivtv_osd); + + /* Now set the OSD to the size we want */ + ivtv_osd.pixel_stride = var->xres_virtual; + ivtv_osd.lines = var->yres_virtual; + ivtv_osd.x = 0; + ivtv_osd.y = 0; + ivtvfb_set_osd_coords(itv, &ivtv_osd); + + /* Can't seem to find the right API combo for this. + Use another function which does what we need through direct register access. */ + ivtv_window.width = var->xres; + ivtv_window.height = var->yres; + + /* Minimum margin cannot be 0, as X won't allow such a mode */ + if (!var->upper_margin) + var->upper_margin++; + if (!var->left_margin) + var->left_margin++; + ivtv_window.top = var->upper_margin - 1; + ivtv_window.left = var->left_margin - 1; + + ivtvfb_set_display_window(itv, &ivtv_window); + + /* Pass screen size back to yuv handler */ + itv->yuv_info.osd_full_w = ivtv_osd.pixel_stride; + itv->yuv_info.osd_full_h = ivtv_osd.lines; + + /* Force update of yuv registers */ + itv->yuv_info.yuv_forced_update = 1; + + /* Keep a copy of these settings */ + memcpy(&oi->fbvar_cur, var, sizeof(oi->fbvar_cur)); + + IVTVFB_DEBUG_INFO("Display size: %dx%d (virtual %dx%d) @ %dbpp\n", + var->xres, var->yres, + var->xres_virtual, var->yres_virtual, + var->bits_per_pixel); + + IVTVFB_DEBUG_INFO("Display position: %d, %d\n", + var->left_margin, var->upper_margin); + + IVTVFB_DEBUG_INFO("Display filter: %s\n", + (var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED ? "on" : "off"); + IVTVFB_DEBUG_INFO("Color space: %s\n", var->nonstd ? "YUV" : "RGB"); + + return 0; +} + +static int ivtvfb_get_fix(struct ivtv *itv, struct fb_fix_screeninfo *fix) +{ + struct osd_info *oi = itv->osd_info; + + IVTVFB_DEBUG_INFO("ivtvfb_get_fix\n"); + memset(fix, 0, sizeof(struct fb_fix_screeninfo)); + strscpy(fix->id, "cx23415 TV out", sizeof(fix->id)); + fix->smem_start = oi->video_pbase; + fix->smem_len = oi->video_buffer_size; + fix->type = FB_TYPE_PACKED_PIXELS; + fix->visual = (oi->bits_per_pixel == 8) ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + fix->ywrapstep = 0; + fix->line_length = oi->display_byte_stride; + fix->accel = FB_ACCEL_NONE; + return 0; +} + +/* Check the requested display mode, returning -EINVAL if we can't + handle it. */ + +static int _ivtvfb_check_var(struct fb_var_screeninfo *var, struct ivtv *itv) +{ + struct osd_info *oi = itv->osd_info; + int osd_height_limit; + u32 pixclock, hlimit, vlimit; + + IVTVFB_DEBUG_INFO("ivtvfb_check_var\n"); + + /* Set base references for mode calcs. */ + if (itv->is_out_50hz) { + pixclock = 84316; + hlimit = 776; + vlimit = 591; + osd_height_limit = 576; + } + else { + pixclock = 83926; + hlimit = 776; + vlimit = 495; + osd_height_limit = 480; + } + + if (var->bits_per_pixel == 8 || var->bits_per_pixel == 32) { + var->transp.offset = 24; + var->transp.length = 8; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + } + else if (var->bits_per_pixel == 16) { + /* To find out the true mode, check green length */ + switch (var->green.length) { + case 4: + var->red.offset = 8; + var->red.length = 4; + var->green.offset = 4; + var->green.length = 4; + var->blue.offset = 0; + var->blue.length = 4; + var->transp.offset = 12; + var->transp.length = 1; + break; + case 5: + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 15; + var->transp.length = 1; + break; + default: + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + break; + } + } + else { + IVTVFB_DEBUG_WARN("Invalid colour mode: %d\n", var->bits_per_pixel); + return -EINVAL; + } + + /* Check the resolution */ + if (var->xres > IVTV_OSD_MAX_WIDTH || var->yres > osd_height_limit) { + IVTVFB_DEBUG_WARN("Invalid resolution: %dx%d\n", + var->xres, var->yres); + return -EINVAL; + } + + /* Max horizontal size is 1023 @ 32bpp, 2046 & 16bpp, 4092 @ 8bpp */ + if (var->xres_virtual > 4095 / (var->bits_per_pixel / 8) || + var->xres_virtual * var->yres_virtual * (var->bits_per_pixel / 8) > oi->video_buffer_size || + var->xres_virtual < var->xres || + var->yres_virtual < var->yres) { + IVTVFB_DEBUG_WARN("Invalid virtual resolution: %dx%d\n", + var->xres_virtual, var->yres_virtual); + return -EINVAL; + } + + /* Some extra checks if in 8 bit mode */ + if (var->bits_per_pixel == 8) { + /* Width must be a multiple of 4 */ + if (var->xres & 3) { + IVTVFB_DEBUG_WARN("Invalid resolution for 8bpp: %d\n", var->xres); + return -EINVAL; + } + if (var->xres_virtual & 3) { + IVTVFB_DEBUG_WARN("Invalid virtual resolution for 8bpp: %d)\n", var->xres_virtual); + return -EINVAL; + } + } + else if (var->bits_per_pixel == 16) { + /* Width must be a multiple of 2 */ + if (var->xres & 1) { + IVTVFB_DEBUG_WARN("Invalid resolution for 16bpp: %d\n", var->xres); + return -EINVAL; + } + if (var->xres_virtual & 1) { + IVTVFB_DEBUG_WARN("Invalid virtual resolution for 16bpp: %d)\n", var->xres_virtual); + return -EINVAL; + } + } + + /* Now check the offsets */ + if (var->xoffset >= var->xres_virtual || var->yoffset >= var->yres_virtual) { + IVTVFB_DEBUG_WARN("Invalid offset: %d (%d) %d (%d)\n", + var->xoffset, var->xres_virtual, var->yoffset, var->yres_virtual); + return -EINVAL; + } + + /* Check pixel format */ + if (var->nonstd > 1) { + IVTVFB_DEBUG_WARN("Invalid nonstd % d\n", var->nonstd); + return -EINVAL; + } + + /* Check video mode */ + if (((var->vmode & FB_VMODE_MASK) != FB_VMODE_NONINTERLACED) && + ((var->vmode & FB_VMODE_MASK) != FB_VMODE_INTERLACED)) { + IVTVFB_DEBUG_WARN("Invalid video mode: %d\n", var->vmode & FB_VMODE_MASK); + return -EINVAL; + } + + /* Check the left & upper margins + If the margins are too large, just center the screen + (enforcing margins causes too many problems) */ + + if (var->left_margin + var->xres > IVTV_OSD_MAX_WIDTH + 1) + var->left_margin = 1 + ((IVTV_OSD_MAX_WIDTH - var->xres) / 2); + + if (var->upper_margin + var->yres > (itv->is_out_50hz ? 577 : 481)) + var->upper_margin = 1 + (((itv->is_out_50hz ? 576 : 480) - + var->yres) / 2); + + /* Maintain overall 'size' for a constant refresh rate */ + var->right_margin = hlimit - var->left_margin - var->xres; + var->lower_margin = vlimit - var->upper_margin - var->yres; + + /* Fixed sync times */ + var->hsync_len = 24; + var->vsync_len = 2; + + /* Non-interlaced / interlaced mode is used to switch the OSD filter + on or off. Adjust the clock timings to maintain a constant + vertical refresh rate. */ + if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED) + var->pixclock = pixclock / 2; + else + var->pixclock = pixclock; + + itv->osd_rect.width = var->xres; + itv->osd_rect.height = var->yres; + + IVTVFB_DEBUG_INFO("Display size: %dx%d (virtual %dx%d) @ %dbpp\n", + var->xres, var->yres, + var->xres_virtual, var->yres_virtual, + var->bits_per_pixel); + + IVTVFB_DEBUG_INFO("Display position: %d, %d\n", + var->left_margin, var->upper_margin); + + IVTVFB_DEBUG_INFO("Display filter: %s\n", + (var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED ? "on" : "off"); + IVTVFB_DEBUG_INFO("Color space: %s\n", var->nonstd ? "YUV" : "RGB"); + return 0; +} + +static int ivtvfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct ivtv *itv = (struct ivtv *) info->par; + IVTVFB_DEBUG_INFO("ivtvfb_check_var\n"); + return _ivtvfb_check_var(var, itv); +} + +static int ivtvfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + u32 osd_pan_index; + struct ivtv *itv = (struct ivtv *) info->par; + + if (var->yoffset + info->var.yres > info->var.yres_virtual || + var->xoffset + info->var.xres > info->var.xres_virtual) + return -EINVAL; + + osd_pan_index = var->yoffset * info->fix.line_length + + var->xoffset * info->var.bits_per_pixel / 8; + write_reg(osd_pan_index, 0x02A0C); + + /* Pass this info back the yuv handler */ + itv->yuv_info.osd_x_pan = var->xoffset; + itv->yuv_info.osd_y_pan = var->yoffset; + /* Force update of yuv registers */ + itv->yuv_info.yuv_forced_update = 1; + /* Remember this value */ + itv->osd_info->pan_cur = osd_pan_index; + return 0; +} + +static int ivtvfb_set_par(struct fb_info *info) +{ + int rc = 0; + struct ivtv *itv = (struct ivtv *) info->par; + + IVTVFB_DEBUG_INFO("ivtvfb_set_par\n"); + + rc = ivtvfb_set_var(itv, &info->var); + ivtvfb_pan_display(&info->var, info); + ivtvfb_get_fix(itv, &info->fix); + ivtv_firmware_check(itv, "ivtvfb_set_par"); + return rc; +} + +static int ivtvfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + u32 color, *palette; + struct ivtv *itv = (struct ivtv *)info->par; + + if (regno >= info->cmap.len) + return -EINVAL; + + color = ((transp & 0xFF00) << 16) |((red & 0xFF00) << 8) | (green & 0xFF00) | ((blue & 0xFF00) >> 8); + if (info->var.bits_per_pixel <= 8) { + write_reg(regno, 0x02a30); + write_reg(color, 0x02a34); + itv->osd_info->palette_cur[regno] = color; + return 0; + } + if (regno >= 16) + return -EINVAL; + + palette = info->pseudo_palette; + if (info->var.bits_per_pixel == 16) { + switch (info->var.green.length) { + case 4: + color = ((red & 0xf000) >> 4) | + ((green & 0xf000) >> 8) | + ((blue & 0xf000) >> 12); + break; + case 5: + color = ((red & 0xf800) >> 1) | + ((green & 0xf800) >> 6) | + ((blue & 0xf800) >> 11); + break; + case 6: + color = (red & 0xf800 ) | + ((green & 0xfc00) >> 5) | + ((blue & 0xf800) >> 11); + break; + } + } + palette[regno] = color; + return 0; +} + +/* We don't really support blanking. All this does is enable or + disable the OSD. */ +static int ivtvfb_blank(int blank_mode, struct fb_info *info) +{ + struct ivtv *itv = (struct ivtv *)info->par; + + IVTVFB_DEBUG_INFO("Set blanking mode : %d\n", blank_mode); + switch (blank_mode) { + case FB_BLANK_UNBLANK: + ivtv_vapi(itv, CX2341X_OSD_SET_STATE, 1, 1); + ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_stream, 1); + break; + case FB_BLANK_NORMAL: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_VSYNC_SUSPEND: + ivtv_vapi(itv, CX2341X_OSD_SET_STATE, 1, 0); + ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_stream, 1); + break; + case FB_BLANK_POWERDOWN: + ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_stream, 0); + ivtv_vapi(itv, CX2341X_OSD_SET_STATE, 1, 0); + break; + } + itv->osd_info->blank_cur = blank_mode; + return 0; +} + +static const struct fb_ops ivtvfb_ops = { + .owner = THIS_MODULE, + .fb_read = fb_io_read, + .fb_write = ivtvfb_write, + .fb_check_var = ivtvfb_check_var, + .fb_set_par = ivtvfb_set_par, + .fb_setcolreg = ivtvfb_setcolreg, + __FB_DEFAULT_IOMEM_OPS_DRAW, + .fb_cursor = NULL, + .fb_ioctl = ivtvfb_ioctl, + .fb_pan_display = ivtvfb_pan_display, + .fb_blank = ivtvfb_blank, + __FB_DEFAULT_IOMEM_OPS_MMAP, +}; + +/* Restore hardware after firmware restart */ +static void ivtvfb_restore(struct ivtv *itv) +{ + struct osd_info *oi = itv->osd_info; + int i; + + ivtvfb_set_var(itv, &oi->fbvar_cur); + ivtvfb_blank(oi->blank_cur, &oi->ivtvfb_info); + for (i = 0; i < 256; i++) { + write_reg(i, 0x02a30); + write_reg(oi->palette_cur[i], 0x02a34); + } + write_reg(oi->pan_cur, 0x02a0c); +} + +/* Initialization */ + + +/* Setup our initial video mode */ +static int ivtvfb_init_vidmode(struct ivtv *itv) +{ + struct osd_info *oi = itv->osd_info; + struct v4l2_rect start_window; + int max_height; + + /* Color mode */ + + if (osd_depth != 8 && osd_depth != 16 && osd_depth != 32) + osd_depth = 8; + oi->bits_per_pixel = osd_depth; + oi->bytes_per_pixel = oi->bits_per_pixel / 8; + + /* Horizontal size & position */ + + if (osd_xres > 720) + osd_xres = 720; + + /* Must be a multiple of 4 for 8bpp & 2 for 16bpp */ + if (osd_depth == 8) + osd_xres &= ~3; + else if (osd_depth == 16) + osd_xres &= ~1; + + start_window.width = osd_xres ? osd_xres : 640; + + /* Check horizontal start (osd_left). */ + if (osd_left && osd_left + start_window.width > 721) { + IVTVFB_ERR("Invalid osd_left - assuming default\n"); + osd_left = 0; + } + + /* Hardware coords start at 0, user coords start at 1. */ + osd_left--; + + start_window.left = osd_left >= 0 ? + osd_left : ((IVTV_OSD_MAX_WIDTH - start_window.width) / 2); + + oi->display_byte_stride = + start_window.width * oi->bytes_per_pixel; + + /* Vertical size & position */ + + max_height = itv->is_out_50hz ? 576 : 480; + + if (osd_yres > max_height) + osd_yres = max_height; + + start_window.height = osd_yres ? + osd_yres : itv->is_out_50hz ? 480 : 400; + + /* Check vertical start (osd_upper). */ + if (osd_upper + start_window.height > max_height + 1) { + IVTVFB_ERR("Invalid osd_upper - assuming default\n"); + osd_upper = 0; + } + + /* Hardware coords start at 0, user coords start at 1. */ + osd_upper--; + + start_window.top = osd_upper >= 0 ? osd_upper : ((max_height - start_window.height) / 2); + + oi->display_width = start_window.width; + oi->display_height = start_window.height; + + /* Generate a valid fb_var_screeninfo */ + + oi->ivtvfb_defined.xres = oi->display_width; + oi->ivtvfb_defined.yres = oi->display_height; + oi->ivtvfb_defined.xres_virtual = oi->display_width; + oi->ivtvfb_defined.yres_virtual = oi->display_height; + oi->ivtvfb_defined.bits_per_pixel = oi->bits_per_pixel; + oi->ivtvfb_defined.vmode = (osd_laced ? FB_VMODE_INTERLACED : FB_VMODE_NONINTERLACED); + oi->ivtvfb_defined.left_margin = start_window.left + 1; + oi->ivtvfb_defined.upper_margin = start_window.top + 1; + oi->ivtvfb_defined.accel_flags = FB_ACCEL_NONE; + oi->ivtvfb_defined.nonstd = 0; + + /* We've filled in the most data, let the usual mode check + routine fill in the rest. */ + _ivtvfb_check_var(&oi->ivtvfb_defined, itv); + + /* Generate valid fb_fix_screeninfo */ + + ivtvfb_get_fix(itv, &oi->ivtvfb_fix); + + /* Generate valid fb_info */ + + oi->ivtvfb_info.node = -1; + oi->ivtvfb_info.par = itv; + oi->ivtvfb_info.var = oi->ivtvfb_defined; + oi->ivtvfb_info.fix = oi->ivtvfb_fix; + oi->ivtvfb_info.screen_base = (u8 __iomem *)oi->video_vbase; + oi->ivtvfb_info.fbops = &ivtvfb_ops; + + /* Supply some monitor specs. Bogus values will do for now */ + oi->ivtvfb_info.monspecs.hfmin = 8000; + oi->ivtvfb_info.monspecs.hfmax = 70000; + oi->ivtvfb_info.monspecs.vfmin = 10; + oi->ivtvfb_info.monspecs.vfmax = 100; + + /* Allocate color map */ + if (fb_alloc_cmap(&oi->ivtvfb_info.cmap, 256, 1)) { + IVTVFB_ERR("abort, unable to alloc cmap\n"); + return -ENOMEM; + } + + /* Allocate the pseudo palette */ + oi->ivtvfb_info.pseudo_palette = + kmalloc_array(16, sizeof(u32), GFP_KERNEL|__GFP_NOWARN); + + if (!oi->ivtvfb_info.pseudo_palette) { + IVTVFB_ERR("abort, unable to alloc pseudo palette\n"); + return -ENOMEM; + } + + return 0; +} + +/* Find OSD buffer base & size. Add to mtrr. Zero osd buffer. */ + +static int ivtvfb_init_io(struct ivtv *itv) +{ + struct osd_info *oi = itv->osd_info; + /* Find the largest power of two that maps the whole buffer */ + int size_shift = 31; + + mutex_lock(&itv->serialize_lock); + if (ivtv_init_on_first_open(itv)) { + mutex_unlock(&itv->serialize_lock); + IVTVFB_ERR("Failed to initialize ivtv\n"); + return -ENXIO; + } + mutex_unlock(&itv->serialize_lock); + + if (ivtvfb_get_framebuffer(itv, &oi->video_rbase, + &oi->video_buffer_size) < 0) { + IVTVFB_ERR("Firmware failed to respond\n"); + return -EIO; + } + + /* The osd buffer size depends on the number of video buffers allocated + on the PVR350 itself. For now we'll hardcode the smallest osd buffer + size to prevent any overlap. */ + oi->video_buffer_size = 1704960; + + oi->video_pbase = itv->base_addr + IVTV_DECODER_OFFSET + oi->video_rbase; + oi->video_vbase = itv->dec_mem + oi->video_rbase; + + if (!oi->video_vbase) { + IVTVFB_ERR("abort, video memory 0x%x @ 0x%lx isn't mapped!\n", + oi->video_buffer_size, oi->video_pbase); + return -EIO; + } + + IVTVFB_INFO("Framebuffer at 0x%lx, mapped to 0x%p, size %dk\n", + oi->video_pbase, oi->video_vbase, + oi->video_buffer_size / 1024); + + while (!(oi->video_buffer_size & (1 << size_shift))) + size_shift--; + size_shift++; + oi->fb_start_aligned_physaddr = oi->video_pbase & ~((1 << size_shift) - 1); + oi->fb_end_aligned_physaddr = oi->video_pbase + oi->video_buffer_size; + oi->fb_end_aligned_physaddr += (1 << size_shift) - 1; + oi->fb_end_aligned_physaddr &= ~((1 << size_shift) - 1); + oi->wc_cookie = arch_phys_wc_add(oi->fb_start_aligned_physaddr, + oi->fb_end_aligned_physaddr - + oi->fb_start_aligned_physaddr); + /* Blank the entire osd. */ + memset_io(oi->video_vbase, 0, oi->video_buffer_size); + + return 0; +} + +/* Release any memory we've grabbed & remove mtrr entry */ +static void ivtvfb_release_buffers (struct ivtv *itv) +{ + struct osd_info *oi = itv->osd_info; + + /* Release cmap */ + if (oi->ivtvfb_info.cmap.len) + fb_dealloc_cmap(&oi->ivtvfb_info.cmap); + + /* Release pseudo palette */ + kfree(oi->ivtvfb_info.pseudo_palette); + arch_phys_wc_del(oi->wc_cookie); + kfree(oi); + itv->osd_info = NULL; +} + +/* Initialize the specified card */ + +static int ivtvfb_init_card(struct ivtv *itv) +{ + int rc; + +#if defined(CONFIG_X86_64) && !defined(CONFIG_UML) + if (pat_enabled()) { + if (ivtvfb_force_pat) { + pr_info("PAT is enabled. Write-combined framebuffer caching will be disabled.\n"); + pr_info("To enable caching, boot with nopat kernel parameter\n"); + } else { + pr_warn("ivtvfb needs PAT disabled for write-combined framebuffer caching.\n"); + pr_warn("Boot with nopat kernel parameter to use caching, or use the\n"); + pr_warn("force_pat module parameter to run with caching disabled\n"); + return -ENODEV; + } + } +#endif + + if (itv->osd_info) { + IVTVFB_ERR("Card %d already initialised\n", ivtvfb_card_id); + return -EBUSY; + } + + itv->osd_info = kzalloc_obj(struct osd_info, GFP_KERNEL | __GFP_NOWARN); + if (itv->osd_info == NULL) { + IVTVFB_ERR("Failed to allocate memory for osd_info\n"); + return -ENOMEM; + } + + /* Find & setup the OSD buffer */ + rc = ivtvfb_init_io(itv); + if (rc) { + ivtvfb_release_buffers(itv); + return rc; + } + + /* Set the startup video mode information */ + if ((rc = ivtvfb_init_vidmode(itv))) { + ivtvfb_release_buffers(itv); + return rc; + } + + /* Register the framebuffer */ + if (register_framebuffer(&itv->osd_info->ivtvfb_info) < 0) { + ivtvfb_release_buffers(itv); + return -EINVAL; + } + + itv->osd_video_pbase = itv->osd_info->video_pbase; + + /* Set the card to the requested mode */ + ivtvfb_set_par(&itv->osd_info->ivtvfb_info); + + /* Set color 0 to black */ + write_reg(0, 0x02a30); + write_reg(0, 0x02a34); + + /* Enable the osd */ + ivtvfb_blank(FB_BLANK_UNBLANK, &itv->osd_info->ivtvfb_info); + + /* Enable restart */ + itv->ivtvfb_restore = ivtvfb_restore; + + /* Allocate DMA */ + ivtv_udma_alloc(itv); + itv->streams[IVTV_DEC_STREAM_TYPE_YUV].vdev.device_caps |= + V4L2_CAP_VIDEO_OUTPUT_OVERLAY; + itv->streams[IVTV_DEC_STREAM_TYPE_MPG].vdev.device_caps |= + V4L2_CAP_VIDEO_OUTPUT_OVERLAY; + itv->v4l2_cap |= V4L2_CAP_VIDEO_OUTPUT_OVERLAY; + return 0; + +} + +static int __init ivtvfb_callback_init(struct device *dev, void *p) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); + struct ivtv *itv = container_of(v4l2_dev, struct ivtv, v4l2_dev); + + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) { + if (ivtvfb_init_card(itv) == 0) { + IVTVFB_INFO("Framebuffer registered on %s\n", + itv->v4l2_dev.name); + (*(int *)p)++; + } + } + return 0; +} + +static int ivtvfb_callback_cleanup(struct device *dev, void *p) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); + struct ivtv *itv = container_of(v4l2_dev, struct ivtv, v4l2_dev); + struct osd_info *oi = itv->osd_info; + + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) { + itv->streams[IVTV_DEC_STREAM_TYPE_YUV].vdev.device_caps &= + ~V4L2_CAP_VIDEO_OUTPUT_OVERLAY; + itv->streams[IVTV_DEC_STREAM_TYPE_MPG].vdev.device_caps &= + ~V4L2_CAP_VIDEO_OUTPUT_OVERLAY; + itv->v4l2_cap &= ~V4L2_CAP_VIDEO_OUTPUT_OVERLAY; + unregister_framebuffer(&itv->osd_info->ivtvfb_info); + IVTVFB_INFO("Unregister framebuffer %d\n", itv->instance); + itv->ivtvfb_restore = NULL; + ivtvfb_blank(FB_BLANK_VSYNC_SUSPEND, &oi->ivtvfb_info); + ivtvfb_release_buffers(itv); + itv->osd_video_pbase = 0; + } + return 0; +} + +static int __init ivtvfb_init(void) +{ + struct device_driver *drv; + int registered = 0; + int err; + + + if (ivtvfb_card_id < -1 || ivtvfb_card_id >= IVTV_MAX_CARDS) { + pr_err("ivtvfb_card_id parameter is out of range (valid range: -1 - %d)\n", + IVTV_MAX_CARDS - 1); + return -EINVAL; + } + + drv = driver_find("ivtv", &pci_bus_type); + err = driver_for_each_device(drv, NULL, ®istered, ivtvfb_callback_init); + (void)err; /* suppress compiler warning */ + if (!registered) { + pr_err("no cards found\n"); + return -ENODEV; + } + return 0; +} + +static void ivtvfb_cleanup(void) +{ + struct device_driver *drv; + int err; + + pr_info("Unloading framebuffer module\n"); + + drv = driver_find("ivtv", &pci_bus_type); + err = driver_for_each_device(drv, NULL, NULL, ivtvfb_callback_cleanup); + (void)err; /* suppress compiler warning */ +} + +module_init(ivtvfb_init); +module_exit(ivtvfb_cleanup); diff --git a/drivers/media/pci/mantis/Kconfig b/drivers/media/pci/mantis/Kconfig new file mode 100644 index 000000000..374b8f382 --- /dev/null +++ b/drivers/media/pci/mantis/Kconfig @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-2.0-only +config MANTIS_CORE + tristate "Mantis/Hopper PCI bridge based devices" + depends on PCI && I2C && INPUT && RC_CORE && DVB_CORE + + help + Support for PCI cards based on the Mantis and Hopper PCi bridge. + + Say Y if you own such a device and want to use it. + +config DVB_MANTIS + tristate "MANTIS based cards" + depends on MANTIS_CORE && DVB_CORE && PCI && I2C + select DVB_MB86A16 if MEDIA_SUBDRV_AUTOSELECT + select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB0899 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA665x if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10021 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10023 if MEDIA_SUBDRV_AUTOSELECT + select DVB_PLL + help + Support for PCI cards based on the Mantis PCI bridge. + Say Y when you have a Mantis based DVB card and want to use it. + + If unsure say N. + +config DVB_HOPPER + tristate "HOPPER based cards" + depends on MANTIS_CORE && DVB_CORE && PCI && I2C + select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT + select DVB_PLL + help + Support for PCI cards based on the Hopper PCI bridge. + Say Y when you have a Hopper based DVB card and want to use it. + + If unsure say N diff --git a/drivers/media/pci/mantis/Makefile b/drivers/media/pci/mantis/Makefile new file mode 100644 index 000000000..49e82247b --- /dev/null +++ b/drivers/media/pci/mantis/Makefile @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0 +mantis_core-objs := mantis_ioc.o \ + mantis_uart.o \ + mantis_dma.o \ + mantis_pci.o \ + mantis_i2c.o \ + mantis_dvb.o \ + mantis_evm.o \ + mantis_hif.o \ + mantis_ca.o \ + mantis_pcmcia.o \ + mantis_input.o + +mantis-objs := mantis_cards.o \ + mantis_vp1033.o \ + mantis_vp1034.o \ + mantis_vp1041.o \ + mantis_vp2033.o \ + mantis_vp2040.o \ + mantis_vp3030.o + +hopper-objs := hopper_cards.o \ + hopper_vp3028.o + +obj-$(CONFIG_MANTIS_CORE) += mantis_core.o +obj-$(CONFIG_DVB_MANTIS) += mantis.o +obj-$(CONFIG_DVB_HOPPER) += hopper.o + +ccflags-y += -I $(srctree)/drivers/media/dvb-frontends/ diff --git a/drivers/media/pci/mantis/hopper_cards.c b/drivers/media/pci/mantis/hopper_cards.c new file mode 100644 index 000000000..050068308 --- /dev/null +++ b/drivers/media/pci/mantis/hopper_cards.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Hopper PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mantis_common.h" +#include "hopper_vp3028.h" +#include "mantis_dma.h" +#include "mantis_dvb.h" +#include "mantis_uart.h" +#include "mantis_ioc.h" +#include "mantis_pci.h" +#include "mantis_i2c.h" +#include "mantis_reg.h" + +static unsigned int verbose; +module_param(verbose, int, 0644); +MODULE_PARM_DESC(verbose, "verbose startup messages, default is 0 (no)"); + +#define DRIVER_NAME "Hopper" + +static char *label[10] = { + "DMA", + "IRQ-0", + "IRQ-1", + "OCERR", + "PABRT", + "RIPRR", + "PPERR", + "FTRGT", + "RISCI", + "RACK" +}; + +static int devs; + +static irqreturn_t hopper_irq_handler(int irq, void *dev_id) +{ + u32 stat = 0, mask = 0; + u32 rst_stat = 0, rst_mask = 0; + + struct mantis_pci *mantis; + struct mantis_ca *ca; + + mantis = (struct mantis_pci *) dev_id; + if (unlikely(!mantis)) + return IRQ_NONE; + ca = mantis->mantis_ca; + + stat = mmread(MANTIS_INT_STAT); + mask = mmread(MANTIS_INT_MASK); + if (!(stat & mask)) + return IRQ_NONE; + + rst_mask = MANTIS_GPIF_WRACK | + MANTIS_GPIF_OTHERR | + MANTIS_SBUF_WSTO | + MANTIS_GPIF_EXTIRQ; + + rst_stat = mmread(MANTIS_GPIF_STATUS); + rst_stat &= rst_mask; + mmwrite(rst_stat, MANTIS_GPIF_STATUS); + + mantis->mantis_int_stat = stat; + mantis->mantis_int_mask = mask; + dprintk(MANTIS_DEBUG, 0, "\n-- Stat=<%02x> Mask=<%02x> --", stat, mask); + if (stat & MANTIS_INT_RISCEN) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[0]); + } + if (stat & MANTIS_INT_IRQ0) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[1]); + mantis->gpif_status = rst_stat; + wake_up(&ca->hif_write_wq); + schedule_work(&ca->hif_evm_work); + } + if (stat & MANTIS_INT_IRQ1) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[2]); + spin_lock(&mantis->intmask_lock); + mmwrite(mmread(MANTIS_INT_MASK) & ~MANTIS_INT_IRQ1, + MANTIS_INT_MASK); + spin_unlock(&mantis->intmask_lock); + schedule_work(&mantis->uart_work); + } + if (stat & MANTIS_INT_OCERR) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[3]); + } + if (stat & MANTIS_INT_PABORT) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[4]); + } + if (stat & MANTIS_INT_RIPERR) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[5]); + } + if (stat & MANTIS_INT_PPERR) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[6]); + } + if (stat & MANTIS_INT_FTRGT) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[7]); + } + if (stat & MANTIS_INT_RISCI) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[8]); + mantis->busy_block = (stat & MANTIS_INT_RISCSTAT) >> 28; + queue_work(system_bh_wq, &mantis->bh_work); + } + if (stat & MANTIS_INT_I2CDONE) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[9]); + wake_up(&mantis->i2c_wq); + } + mmwrite(stat, MANTIS_INT_STAT); + stat &= ~(MANTIS_INT_RISCEN | MANTIS_INT_I2CDONE | + MANTIS_INT_I2CRACK | MANTIS_INT_PCMCIA7 | + MANTIS_INT_PCMCIA6 | MANTIS_INT_PCMCIA5 | + MANTIS_INT_PCMCIA4 | MANTIS_INT_PCMCIA3 | + MANTIS_INT_PCMCIA2 | MANTIS_INT_PCMCIA1 | + MANTIS_INT_PCMCIA0 | MANTIS_INT_IRQ1 | + MANTIS_INT_IRQ0 | MANTIS_INT_OCERR | + MANTIS_INT_PABORT | MANTIS_INT_RIPERR | + MANTIS_INT_PPERR | MANTIS_INT_FTRGT | + MANTIS_INT_RISCI); + + if (stat) + dprintk(MANTIS_DEBUG, 0, " Stat=<%02x> Mask=<%02x>", stat, mask); + + dprintk(MANTIS_DEBUG, 0, "\n"); + return IRQ_HANDLED; +} + +static int hopper_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *pci_id) +{ + struct mantis_pci_drvdata *drvdata; + struct mantis_pci *mantis; + struct mantis_hwconfig *config; + int err; + + mantis = kzalloc_obj(*mantis); + if (!mantis) { + err = -ENOMEM; + goto fail0; + } + + drvdata = (void *)pci_id->driver_data; + mantis->num = devs; + mantis->verbose = verbose; + mantis->pdev = pdev; + config = drvdata->hwconfig; + config->irq_handler = &hopper_irq_handler; + mantis->hwconfig = config; + mantis->rc_map_name = drvdata->rc_map_name; + + spin_lock_init(&mantis->intmask_lock); + + err = mantis_pci_init(mantis); + if (err) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis PCI initialization failed <%d>", err); + goto fail1; + } + + err = mantis_stream_control(mantis, STREAM_TO_HIF); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis stream control failed <%d>", err); + goto fail1; + } + + err = mantis_i2c_init(mantis); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis I2C initialization failed <%d>", err); + goto fail2; + } + + err = mantis_get_mac(mantis); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis MAC address read failed <%d>", err); + goto fail2; + } + + err = mantis_dma_init(mantis); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis DMA initialization failed <%d>", err); + goto fail3; + } + + err = mantis_dvb_init(mantis); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis DVB initialization failed <%d>", err); + goto fail4; + } + devs++; + + return err; + +fail4: + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis DMA exit! <%d>", err); + mantis_dma_exit(mantis); + +fail3: + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis I2C exit! <%d>", err); + mantis_i2c_exit(mantis); + +fail2: + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis PCI exit! <%d>", err); + mantis_pci_exit(mantis); + +fail1: + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis free! <%d>", err); + kfree(mantis); + +fail0: + return err; +} + +static void hopper_pci_remove(struct pci_dev *pdev) +{ + struct mantis_pci *mantis = pci_get_drvdata(pdev); + + if (mantis) { + mantis_dvb_exit(mantis); + mantis_dma_exit(mantis); + mantis_i2c_exit(mantis); + mantis_pci_exit(mantis); + kfree(mantis); + } + return; + +} + +static const struct pci_device_id hopper_pci_table[] = { + MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_3028_DVB_T, &vp3028_config, + NULL), + { } +}; + +MODULE_DEVICE_TABLE(pci, hopper_pci_table); + +static struct pci_driver hopper_pci_driver = { + .name = DRIVER_NAME, + .id_table = hopper_pci_table, + .probe = hopper_pci_probe, + .remove = hopper_pci_remove, +}; + +module_pci_driver(hopper_pci_driver); + +MODULE_DESCRIPTION("HOPPER driver"); +MODULE_AUTHOR("Manu Abraham"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/mantis/hopper_vp3028.c b/drivers/media/pci/mantis/hopper_vp3028.c new file mode 100644 index 000000000..ce1e8737b --- /dev/null +++ b/drivers/media/pci/mantis/hopper_vp3028.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Hopper VP-3028 driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "zl10353.h" +#include "mantis_common.h" +#include "mantis_ioc.h" +#include "mantis_dvb.h" +#include "hopper_vp3028.h" + +static struct zl10353_config hopper_vp3028_config = { + .demod_address = 0x0f, +}; + +#define MANTIS_MODEL_NAME "VP-3028" +#define MANTIS_DEV_TYPE "DVB-T" + +static int vp3028_frontend_init(struct mantis_pci *mantis, struct dvb_frontend *fe) +{ + struct i2c_adapter *adapter = &mantis->adapter; + struct mantis_hwconfig *config = mantis->hwconfig; + int err; + + mantis_gpio_set_bits(mantis, config->reset, 0); + msleep(100); + err = mantis_frontend_power(mantis, POWER_ON); + msleep(100); + mantis_gpio_set_bits(mantis, config->reset, 1); + + err = mantis_frontend_power(mantis, POWER_ON); + if (err == 0) { + msleep(250); + dprintk(MANTIS_ERROR, 1, "Probing for 10353 (DVB-T)"); + fe = dvb_attach(zl10353_attach, &hopper_vp3028_config, adapter); + + if (!fe) + return -1; + } else { + dprintk(MANTIS_ERROR, 1, "Frontend on <%s> POWER ON failed! <%d>", + adapter->name, + err); + + return -EIO; + } + dprintk(MANTIS_ERROR, 1, "Done!"); + + return 0; +} + +struct mantis_hwconfig vp3028_config = { + .model_name = MANTIS_MODEL_NAME, + .dev_type = MANTIS_DEV_TYPE, + .ts_size = MANTIS_TS_188, + + .baud_rate = MANTIS_BAUD_9600, + .parity = MANTIS_PARITY_NONE, + .bytes = 0, + + .frontend_init = vp3028_frontend_init, + .power = GPIF_A00, + .reset = GPIF_A03, +}; diff --git a/drivers/media/pci/mantis/hopper_vp3028.h b/drivers/media/pci/mantis/hopper_vp3028.h new file mode 100644 index 000000000..e8906b7d8 --- /dev/null +++ b/drivers/media/pci/mantis/hopper_vp3028.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Hopper VP-3028 driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_VP3028_H +#define __MANTIS_VP3028_H + +#include "mantis_common.h" + +#define MANTIS_VP_3028_DVB_T 0x0028 + +extern struct mantis_hwconfig vp3028_config; + +#endif /* __MANTIS_VP3028_H */ diff --git a/drivers/media/pci/mantis/mantis_ca.c b/drivers/media/pci/mantis/mantis_ca.c new file mode 100644 index 000000000..96fee641d --- /dev/null +++ b/drivers/media/pci/mantis/mantis_ca.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mantis_common.h" +#include "mantis_link.h" +#include "mantis_hif.h" +#include "mantis_reg.h" + +#include "mantis_ca.h" + +static int mantis_ca_read_attr_mem(struct dvb_ca_en50221 *en50221, int slot, int addr) +{ + struct mantis_ca *ca = en50221->data; + struct mantis_pci *mantis = ca->ca_priv; + + dprintk(MANTIS_DEBUG, 1, "Slot(%d): Request Attribute Mem Read", slot); + + if (slot != 0) + return -EINVAL; + + return mantis_hif_read_mem(ca, addr); +} + +static int mantis_ca_write_attr_mem(struct dvb_ca_en50221 *en50221, int slot, int addr, u8 data) +{ + struct mantis_ca *ca = en50221->data; + struct mantis_pci *mantis = ca->ca_priv; + + dprintk(MANTIS_DEBUG, 1, "Slot(%d): Request Attribute Mem Write", slot); + + if (slot != 0) + return -EINVAL; + + return mantis_hif_write_mem(ca, addr, data); +} + +static int mantis_ca_read_cam_ctl(struct dvb_ca_en50221 *en50221, int slot, u8 addr) +{ + struct mantis_ca *ca = en50221->data; + struct mantis_pci *mantis = ca->ca_priv; + + dprintk(MANTIS_DEBUG, 1, "Slot(%d): Request CAM control Read", slot); + + if (slot != 0) + return -EINVAL; + + return mantis_hif_read_iom(ca, addr); +} + +static int mantis_ca_write_cam_ctl(struct dvb_ca_en50221 *en50221, int slot, u8 addr, u8 data) +{ + struct mantis_ca *ca = en50221->data; + struct mantis_pci *mantis = ca->ca_priv; + + dprintk(MANTIS_DEBUG, 1, "Slot(%d): Request CAM control Write", slot); + + if (slot != 0) + return -EINVAL; + + return mantis_hif_write_iom(ca, addr, data); +} + +static int mantis_ca_slot_reset(struct dvb_ca_en50221 *en50221, int slot) +{ + struct mantis_ca *ca = en50221->data; + struct mantis_pci *mantis = ca->ca_priv; + + dprintk(MANTIS_DEBUG, 1, "Slot(%d): Slot RESET", slot); + udelay(500); /* Wait.. */ + mmwrite(0xda, MANTIS_PCMCIA_RESET); /* Leading edge assert */ + udelay(500); + mmwrite(0x00, MANTIS_PCMCIA_RESET); /* Trailing edge deassert */ + msleep(1000); + dvb_ca_en50221_camready_irq(&ca->en50221, 0); + + return 0; +} + +static int mantis_ca_slot_shutdown(struct dvb_ca_en50221 *en50221, int slot) +{ + struct mantis_ca *ca = en50221->data; + struct mantis_pci *mantis = ca->ca_priv; + + dprintk(MANTIS_DEBUG, 1, "Slot(%d): Slot shutdown", slot); + + return 0; +} + +static int mantis_ts_control(struct dvb_ca_en50221 *en50221, int slot) +{ + struct mantis_ca *ca = en50221->data; + struct mantis_pci *mantis = ca->ca_priv; + + dprintk(MANTIS_DEBUG, 1, "Slot(%d): TS control", slot); + + return 0; +} + +static int mantis_slot_status(struct dvb_ca_en50221 *en50221, int slot, int open) +{ + struct mantis_ca *ca = en50221->data; + struct mantis_pci *mantis = ca->ca_priv; + + dprintk(MANTIS_DEBUG, 1, "Slot(%d): Poll Slot status", slot); + + if (ca->slot_state == MODULE_INSERTED) { + dprintk(MANTIS_DEBUG, 1, "CA Module present and ready"); + return DVB_CA_EN50221_POLL_CAM_PRESENT | DVB_CA_EN50221_POLL_CAM_READY; + } else { + dprintk(MANTIS_DEBUG, 1, "CA Module not present or not ready"); + } + + return 0; +} + +int mantis_ca_init(struct mantis_pci *mantis) +{ + struct dvb_adapter *dvb_adapter = &mantis->dvb_adapter; + struct mantis_ca *ca; + int ca_flags = 0, result; + + dprintk(MANTIS_DEBUG, 1, "Initializing Mantis CA"); + ca = kzalloc_obj(struct mantis_ca); + if (!ca) { + dprintk(MANTIS_ERROR, 1, "Out of memory!, exiting .."); + result = -ENOMEM; + goto err; + } + + ca->ca_priv = mantis; + mantis->mantis_ca = ca; + ca_flags = DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE; + /* register CA interface */ + ca->en50221.owner = THIS_MODULE; + ca->en50221.read_attribute_mem = mantis_ca_read_attr_mem; + ca->en50221.write_attribute_mem = mantis_ca_write_attr_mem; + ca->en50221.read_cam_control = mantis_ca_read_cam_ctl; + ca->en50221.write_cam_control = mantis_ca_write_cam_ctl; + ca->en50221.slot_reset = mantis_ca_slot_reset; + ca->en50221.slot_shutdown = mantis_ca_slot_shutdown; + ca->en50221.slot_ts_enable = mantis_ts_control; + ca->en50221.poll_slot_status = mantis_slot_status; + ca->en50221.data = ca; + + mutex_init(&ca->ca_lock); + + init_waitqueue_head(&ca->hif_data_wq); + init_waitqueue_head(&ca->hif_opdone_wq); + init_waitqueue_head(&ca->hif_write_wq); + + dprintk(MANTIS_ERROR, 1, "Registering EN50221 device"); + result = dvb_ca_en50221_init(dvb_adapter, &ca->en50221, ca_flags, 1); + if (result != 0) { + dprintk(MANTIS_ERROR, 1, "EN50221: Initialization failed <%d>", result); + goto err; + } + dprintk(MANTIS_ERROR, 1, "Registered EN50221 device"); + mantis_evmgr_init(ca); + return 0; +err: + kfree(ca); + return result; +} +EXPORT_SYMBOL_GPL(mantis_ca_init); + +void mantis_ca_exit(struct mantis_pci *mantis) +{ + struct mantis_ca *ca = mantis->mantis_ca; + + dprintk(MANTIS_DEBUG, 1, "Mantis CA exit"); + if (!ca) + return; + + mantis_evmgr_exit(ca); + dprintk(MANTIS_ERROR, 1, "Unregistering EN50221 device"); + dvb_ca_en50221_release(&ca->en50221); + + kfree(ca); +} +EXPORT_SYMBOL_GPL(mantis_ca_exit); diff --git a/drivers/media/pci/mantis/mantis_ca.h b/drivers/media/pci/mantis/mantis_ca.h new file mode 100644 index 000000000..4a6927551 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_ca.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_CA_H +#define __MANTIS_CA_H + +extern int mantis_ca_init(struct mantis_pci *mantis); +extern void mantis_ca_exit(struct mantis_pci *mantis); + +#endif /* __MANTIS_CA_H */ diff --git a/drivers/media/pci/mantis/mantis_cards.c b/drivers/media/pci/mantis/mantis_cards.c new file mode 100644 index 000000000..5625260b6 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_cards.c @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mantis_common.h" + +#include "mantis_vp1033.h" +#include "mantis_vp1034.h" +#include "mantis_vp1041.h" +#include "mantis_vp2033.h" +#include "mantis_vp2040.h" +#include "mantis_vp3030.h" + +#include "mantis_dma.h" +#include "mantis_ca.h" +#include "mantis_dvb.h" +#include "mantis_uart.h" +#include "mantis_ioc.h" +#include "mantis_pci.h" +#include "mantis_i2c.h" +#include "mantis_reg.h" +#include "mantis_input.h" + +static unsigned int verbose; +module_param(verbose, int, 0644); +MODULE_PARM_DESC(verbose, "verbose startup messages, default is 0 (no)"); + +static int devs; + +#define DRIVER_NAME "Mantis" + +static char *label[10] = { + "DMA", + "IRQ-0", + "IRQ-1", + "OCERR", + "PABRT", + "RIPRR", + "PPERR", + "FTRGT", + "RISCI", + "RACK" +}; + +static irqreturn_t mantis_irq_handler(int irq, void *dev_id) +{ + u32 stat = 0, mask = 0; + u32 rst_stat = 0, rst_mask = 0; + + struct mantis_pci *mantis; + struct mantis_ca *ca; + + mantis = (struct mantis_pci *) dev_id; + if (unlikely(!mantis)) + return IRQ_NONE; + ca = mantis->mantis_ca; + + stat = mmread(MANTIS_INT_STAT); + mask = mmread(MANTIS_INT_MASK); + if (!(stat & mask)) + return IRQ_NONE; + + rst_mask = MANTIS_GPIF_WRACK | + MANTIS_GPIF_OTHERR | + MANTIS_SBUF_WSTO | + MANTIS_GPIF_EXTIRQ; + + rst_stat = mmread(MANTIS_GPIF_STATUS); + rst_stat &= rst_mask; + mmwrite(rst_stat, MANTIS_GPIF_STATUS); + + mantis->mantis_int_stat = stat; + mantis->mantis_int_mask = mask; + dprintk(MANTIS_DEBUG, 0, "\n-- Stat=<%02x> Mask=<%02x> --", stat, mask); + if (stat & MANTIS_INT_RISCEN) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[0]); + } + if (stat & MANTIS_INT_IRQ0) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[1]); + mantis->gpif_status = rst_stat; + wake_up(&ca->hif_write_wq); + schedule_work(&ca->hif_evm_work); + } + if (stat & MANTIS_INT_IRQ1) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[2]); + spin_lock(&mantis->intmask_lock); + mmwrite(mmread(MANTIS_INT_MASK) & ~MANTIS_INT_IRQ1, + MANTIS_INT_MASK); + spin_unlock(&mantis->intmask_lock); + schedule_work(&mantis->uart_work); + } + if (stat & MANTIS_INT_OCERR) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[3]); + } + if (stat & MANTIS_INT_PABORT) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[4]); + } + if (stat & MANTIS_INT_RIPERR) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[5]); + } + if (stat & MANTIS_INT_PPERR) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[6]); + } + if (stat & MANTIS_INT_FTRGT) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[7]); + } + if (stat & MANTIS_INT_RISCI) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[8]); + mantis->busy_block = (stat & MANTIS_INT_RISCSTAT) >> 28; + queue_work(system_bh_wq, &mantis->bh_work); + } + if (stat & MANTIS_INT_I2CDONE) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[9]); + wake_up(&mantis->i2c_wq); + } + mmwrite(stat, MANTIS_INT_STAT); + stat &= ~(MANTIS_INT_RISCEN | MANTIS_INT_I2CDONE | + MANTIS_INT_I2CRACK | MANTIS_INT_PCMCIA7 | + MANTIS_INT_PCMCIA6 | MANTIS_INT_PCMCIA5 | + MANTIS_INT_PCMCIA4 | MANTIS_INT_PCMCIA3 | + MANTIS_INT_PCMCIA2 | MANTIS_INT_PCMCIA1 | + MANTIS_INT_PCMCIA0 | MANTIS_INT_IRQ1 | + MANTIS_INT_IRQ0 | MANTIS_INT_OCERR | + MANTIS_INT_PABORT | MANTIS_INT_RIPERR | + MANTIS_INT_PPERR | MANTIS_INT_FTRGT | + MANTIS_INT_RISCI); + + if (stat) + dprintk(MANTIS_DEBUG, 0, " Stat=<%02x> Mask=<%02x>", stat, mask); + + dprintk(MANTIS_DEBUG, 0, "\n"); + return IRQ_HANDLED; +} + +static int mantis_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *pci_id) +{ + struct mantis_pci_drvdata *drvdata; + struct mantis_pci *mantis; + struct mantis_hwconfig *config; + int err; + + mantis = kzalloc_obj(*mantis); + if (!mantis) + return -ENOMEM; + + drvdata = (void *)pci_id->driver_data; + mantis->num = devs; + mantis->verbose = verbose; + mantis->pdev = pdev; + config = drvdata->hwconfig; + config->irq_handler = &mantis_irq_handler; + mantis->hwconfig = config; + mantis->rc_map_name = drvdata->rc_map_name; + + spin_lock_init(&mantis->intmask_lock); + + err = mantis_pci_init(mantis); + if (err) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis PCI initialization failed <%d>", err); + goto err_free_mantis; + } + + err = mantis_stream_control(mantis, STREAM_TO_HIF); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis stream control failed <%d>", err); + goto err_pci_exit; + } + + err = mantis_i2c_init(mantis); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis I2C initialization failed <%d>", err); + goto err_pci_exit; + } + + err = mantis_get_mac(mantis); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis MAC address read failed <%d>", err); + goto err_i2c_exit; + } + + err = mantis_dma_init(mantis); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis DMA initialization failed <%d>", err); + goto err_i2c_exit; + } + + err = mantis_dvb_init(mantis); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis DVB initialization failed <%d>", err); + goto err_dma_exit; + } + + err = mantis_input_init(mantis); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, + "ERROR: Mantis DVB initialization failed <%d>", err); + goto err_dvb_exit; + } + + err = mantis_uart_init(mantis); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis UART initialization failed <%d>", err); + goto err_input_exit; + } + + devs++; + + return 0; + +err_input_exit: + mantis_input_exit(mantis); + +err_dvb_exit: + mantis_dvb_exit(mantis); + +err_dma_exit: + mantis_dma_exit(mantis); + +err_i2c_exit: + mantis_i2c_exit(mantis); + +err_pci_exit: + mantis_pci_exit(mantis); + +err_free_mantis: + kfree(mantis); + + return err; +} + +static void mantis_pci_remove(struct pci_dev *pdev) +{ + struct mantis_pci *mantis = pci_get_drvdata(pdev); + + if (mantis) { + + mantis_uart_exit(mantis); + mantis_input_exit(mantis); + mantis_dvb_exit(mantis); + mantis_dma_exit(mantis); + mantis_i2c_exit(mantis); + mantis_pci_exit(mantis); + kfree(mantis); + } + return; +} + +static const struct pci_device_id mantis_pci_table[] = { + MAKE_ENTRY(TECHNISAT, CABLESTAR_HD2, &vp2040_config, + RC_MAP_TECHNISAT_TS35), + MAKE_ENTRY(TECHNISAT, SKYSTAR_HD2_10, &vp1041_config, + NULL), + MAKE_ENTRY(TECHNISAT, SKYSTAR_HD2_20, &vp1041_config, + NULL), + MAKE_ENTRY(TERRATEC, CINERGY_C, &vp2040_config, + RC_MAP_TERRATEC_CINERGY_C_PCI), + MAKE_ENTRY(TERRATEC, CINERGY_S2_PCI_HD, &vp1041_config, + RC_MAP_TERRATEC_CINERGY_S2_HD), + MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_1033_DVB_S, &vp1033_config, + NULL), + MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_1034_DVB_S, &vp1034_config, + NULL), + MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_1041_DVB_S2, &vp1041_config, + RC_MAP_TWINHAN_DTV_CAB_CI), + MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_2033_DVB_C, &vp2033_config, + RC_MAP_TWINHAN_DTV_CAB_CI), + MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_2040_DVB_C, &vp2040_config, + NULL), + MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_3030_DVB_T, &vp3030_config, + NULL), + { } +}; + +MODULE_DEVICE_TABLE(pci, mantis_pci_table); + +static struct pci_driver mantis_pci_driver = { + .name = DRIVER_NAME, + .id_table = mantis_pci_table, + .probe = mantis_pci_probe, + .remove = mantis_pci_remove, +}; + +module_pci_driver(mantis_pci_driver); + +MODULE_DESCRIPTION("MANTIS driver"); +MODULE_AUTHOR("Manu Abraham"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/mantis/mantis_common.h b/drivers/media/pci/mantis/mantis_common.h new file mode 100644 index 000000000..f357ca268 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_common.h @@ -0,0 +1,190 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_COMMON_H +#define __MANTIS_COMMON_H + +#include +#include +#include + +#include "mantis_reg.h" +#include "mantis_uart.h" + +#include "mantis_link.h" + +#define MANTIS_ERROR 0 +#define MANTIS_NOTICE 1 +#define MANTIS_INFO 2 +#define MANTIS_DEBUG 3 +#define MANTIS_TMG 9 + +#define dprintk(y, z, format, arg...) do { \ + if (z) { \ + if ((mantis->verbose > MANTIS_ERROR) && (mantis->verbose > y)) \ + printk(KERN_ERR "%s (%d): " format "\n" , __func__ , mantis->num , ##arg); \ + else if ((mantis->verbose > MANTIS_NOTICE) && (mantis->verbose > y)) \ + printk(KERN_NOTICE "%s (%d): " format "\n" , __func__ , mantis->num , ##arg); \ + else if ((mantis->verbose > MANTIS_INFO) && (mantis->verbose > y)) \ + printk(KERN_INFO "%s (%d): " format "\n" , __func__ , mantis->num , ##arg); \ + else if ((mantis->verbose > MANTIS_DEBUG) && (mantis->verbose > y)) \ + printk(KERN_DEBUG "%s (%d): " format "\n" , __func__ , mantis->num , ##arg); \ + else if ((mantis->verbose > MANTIS_TMG) && (mantis->verbose > y)) \ + printk(KERN_DEBUG "%s (%d): " format "\n" , __func__ , mantis->num , ##arg); \ + } else { \ + if (mantis->verbose > y) \ + printk(format , ##arg); \ + } \ +} while(0) + +#define mwrite(dat, addr) writel((dat), addr) +#define mread(addr) readl(addr) + +#define mmwrite(dat, addr) mwrite((dat), (mantis->mmio + (addr))) +#define mmread(addr) mread(mantis->mmio + (addr)) + +#define MANTIS_TS_188 0 +#define MANTIS_TS_204 1 + +#define TWINHAN_TECHNOLOGIES 0x1822 +#define MANTIS 0x4e35 + +#define TECHNISAT 0x1ae4 +#define TERRATEC 0x153b + +#define MAKE_ENTRY(__subven, __subdev, __configptr, __rc) { \ + PCI_DEVICE_SUB(TWINHAN_TECHNOLOGIES, MANTIS, \ + (__subven), (__subdev)), \ + .driver_data = (unsigned long) \ + &(struct mantis_pci_drvdata){__configptr, __rc} \ +} + +enum mantis_i2c_mode { + MANTIS_PAGE_MODE = 0, + MANTIS_BYTE_MODE, +}; + +struct mantis_pci; + +struct mantis_hwconfig { + char *model_name; + char *dev_type; + u32 ts_size; + + enum mantis_baud baud_rate; + enum mantis_parity parity; + u32 bytes; + + irqreturn_t (*irq_handler)(int irq, void *dev_id); + int (*frontend_init)(struct mantis_pci *mantis, struct dvb_frontend *fe); + + u8 power; + u8 reset; + + enum mantis_i2c_mode i2c_mode; +}; + +struct mantis_pci_drvdata { + struct mantis_hwconfig *hwconfig; + char *rc_map_name; +}; + +struct mantis_pci { + unsigned int verbose; + + /* PCI stuff */ + u16 vendor_id; + u16 device_id; + u16 subsystem_vendor; + u16 subsystem_device; + + u8 latency; + + struct pci_dev *pdev; + + unsigned long mantis_addr; + void __iomem *mmio; + + u8 irq; + u8 revision; + + unsigned int num; + + /* RISC Core */ + u32 busy_block; + u32 last_block; + u8 *buf_cpu; + dma_addr_t buf_dma; + __le32 *risc_cpu; + dma_addr_t risc_dma; + + struct work_struct bh_work; + spinlock_t intmask_lock; + + struct i2c_adapter adapter; + int i2c_rc; + wait_queue_head_t i2c_wq; + struct mutex i2c_lock; + + /* DVB stuff */ + struct dvb_adapter dvb_adapter; + struct dvb_frontend *fe; + struct dvb_demux demux; + struct dmxdev dmxdev; + struct dmx_frontend fe_hw; + struct dmx_frontend fe_mem; + struct dvb_net dvbnet; + + u8 feeds; + + struct mantis_hwconfig *hwconfig; + + u32 mantis_int_stat; + u32 mantis_int_mask; + + /* board specific */ + u8 mac_address[8]; + u32 sub_vendor_id; + u32 sub_device_id; + + /* A12 A13 A14 */ + u32 gpio_status; + + u32 gpif_status; + + struct mantis_ca *mantis_ca; + + struct work_struct uart_work; + + struct rc_dev *rc; + char device_name[80]; + char input_phys[80]; + char *rc_map_name; +}; + +#define MANTIS_HIF_STATUS (mantis->gpio_status) + +static inline void mantis_mask_ints(struct mantis_pci *mantis, u32 mask) +{ + unsigned long flags; + + spin_lock_irqsave(&mantis->intmask_lock, flags); + mmwrite(mmread(MANTIS_INT_MASK) & ~mask, MANTIS_INT_MASK); + spin_unlock_irqrestore(&mantis->intmask_lock, flags); +} + +static inline void mantis_unmask_ints(struct mantis_pci *mantis, u32 mask) +{ + unsigned long flags; + + spin_lock_irqsave(&mantis->intmask_lock, flags); + mmwrite(mmread(MANTIS_INT_MASK) | mask, MANTIS_INT_MASK); + spin_unlock_irqrestore(&mantis->intmask_lock, flags); +} + +#endif /* __MANTIS_COMMON_H */ diff --git a/drivers/media/pci/mantis/mantis_dma.c b/drivers/media/pci/mantis/mantis_dma.c new file mode 100644 index 000000000..eeb210a28 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_dma.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mantis_common.h" +#include "mantis_reg.h" +#include "mantis_dma.h" + +#define RISC_WRITE (0x01 << 28) +#define RISC_JUMP (0x07 << 28) +#define RISC_IRQ (0x01 << 24) + +#define RISC_STATUS(status) ((((~status) & 0x0f) << 20) | ((status & 0x0f) << 16)) +#define RISC_FLUSH(risc_pos) (risc_pos = 0) +#define RISC_INSTR(risc_pos, opcode) (mantis->risc_cpu[risc_pos++] = cpu_to_le32(opcode)) + +#define MANTIS_BUF_SIZE (64 * 1024) +#define MANTIS_BLOCK_BYTES (MANTIS_BUF_SIZE / 4) +#define MANTIS_DMA_TR_BYTES (2 * 1024) /* upper limit: 4095 bytes. */ +#define MANTIS_BLOCK_COUNT (MANTIS_BUF_SIZE / MANTIS_BLOCK_BYTES) + +#define MANTIS_DMA_TR_UNITS (MANTIS_BLOCK_BYTES / MANTIS_DMA_TR_BYTES) +/* MANTIS_BUF_SIZE / MANTIS_DMA_TR_UNITS must not exceed MANTIS_RISC_SIZE (4k RISC cmd buffer) */ +#define MANTIS_RISC_SIZE PAGE_SIZE /* RISC program must fit here. */ + +int mantis_dma_exit(struct mantis_pci *mantis) +{ + if (mantis->buf_cpu) { + dprintk(MANTIS_ERROR, 1, + "DMA=0x%lx cpu=0x%p size=%d", + (unsigned long) mantis->buf_dma, + mantis->buf_cpu, + MANTIS_BUF_SIZE); + + dma_free_coherent(&mantis->pdev->dev, MANTIS_BUF_SIZE, + mantis->buf_cpu, mantis->buf_dma); + + mantis->buf_cpu = NULL; + } + if (mantis->risc_cpu) { + dprintk(MANTIS_ERROR, 1, + "RISC=0x%lx cpu=0x%p size=%lx", + (unsigned long) mantis->risc_dma, + mantis->risc_cpu, + MANTIS_RISC_SIZE); + + dma_free_coherent(&mantis->pdev->dev, MANTIS_RISC_SIZE, + mantis->risc_cpu, mantis->risc_dma); + + mantis->risc_cpu = NULL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(mantis_dma_exit); + +static inline int mantis_alloc_buffers(struct mantis_pci *mantis) +{ + if (!mantis->buf_cpu) { + mantis->buf_cpu = dma_alloc_coherent(&mantis->pdev->dev, + MANTIS_BUF_SIZE, + &mantis->buf_dma, GFP_KERNEL); + if (!mantis->buf_cpu) { + dprintk(MANTIS_ERROR, 1, + "DMA buffer allocation failed"); + + goto err; + } + dprintk(MANTIS_ERROR, 1, + "DMA=0x%lx cpu=0x%p size=%d", + (unsigned long) mantis->buf_dma, + mantis->buf_cpu, MANTIS_BUF_SIZE); + } + if (!mantis->risc_cpu) { + mantis->risc_cpu = dma_alloc_coherent(&mantis->pdev->dev, + MANTIS_RISC_SIZE, + &mantis->risc_dma, GFP_KERNEL); + + if (!mantis->risc_cpu) { + dprintk(MANTIS_ERROR, 1, + "RISC program allocation failed"); + + mantis_dma_exit(mantis); + + goto err; + } + dprintk(MANTIS_ERROR, 1, + "RISC=0x%lx cpu=0x%p size=%lx", + (unsigned long) mantis->risc_dma, + mantis->risc_cpu, MANTIS_RISC_SIZE); + } + + return 0; +err: + dprintk(MANTIS_ERROR, 1, "Out of memory (?) ....."); + return -ENOMEM; +} + +int mantis_dma_init(struct mantis_pci *mantis) +{ + int err; + + dprintk(MANTIS_DEBUG, 1, "Mantis DMA init"); + err = mantis_alloc_buffers(mantis); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "Error allocating DMA buffer"); + + /* Stop RISC Engine */ + mmwrite(0, MANTIS_DMA_CTL); + + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(mantis_dma_init); + +static inline void mantis_risc_program(struct mantis_pci *mantis) +{ + u32 buf_pos = 0; + u32 line, step; + u32 risc_pos; + + dprintk(MANTIS_DEBUG, 1, "Mantis create RISC program"); + RISC_FLUSH(risc_pos); + + dprintk(MANTIS_DEBUG, 1, "risc len lines %u, bytes per line %u, bytes per DMA tr %u", + MANTIS_BLOCK_COUNT, MANTIS_BLOCK_BYTES, MANTIS_DMA_TR_BYTES); + + for (line = 0; line < MANTIS_BLOCK_COUNT; line++) { + for (step = 0; step < MANTIS_DMA_TR_UNITS; step++) { + dprintk(MANTIS_DEBUG, 1, "RISC PROG line=[%d], step=[%d]", line, step); + if (step == 0) { + RISC_INSTR(risc_pos, RISC_WRITE | + RISC_IRQ | + RISC_STATUS(line) | + MANTIS_DMA_TR_BYTES); + } else { + RISC_INSTR(risc_pos, RISC_WRITE | MANTIS_DMA_TR_BYTES); + } + RISC_INSTR(risc_pos, mantis->buf_dma + buf_pos); + buf_pos += MANTIS_DMA_TR_BYTES; + } + } + RISC_INSTR(risc_pos, RISC_JUMP); + RISC_INSTR(risc_pos, mantis->risc_dma); +} + +void mantis_dma_start(struct mantis_pci *mantis) +{ + dprintk(MANTIS_DEBUG, 1, "Mantis Start DMA engine"); + + mantis_risc_program(mantis); + mmwrite(mantis->risc_dma, MANTIS_RISC_START); + mmwrite(mmread(MANTIS_GPIF_ADDR) | MANTIS_GPIF_HIFRDWRN, MANTIS_GPIF_ADDR); + + mmwrite(0, MANTIS_DMA_CTL); + mantis->last_block = mantis->busy_block = 0; + + mantis_unmask_ints(mantis, MANTIS_INT_RISCI); + + mmwrite(MANTIS_FIFO_EN | MANTIS_DCAP_EN + | MANTIS_RISC_EN, MANTIS_DMA_CTL); + +} + +void mantis_dma_stop(struct mantis_pci *mantis) +{ + dprintk(MANTIS_DEBUG, 1, "Mantis Stop DMA engine"); + + mmwrite((mmread(MANTIS_GPIF_ADDR) & (~(MANTIS_GPIF_HIFRDWRN))), MANTIS_GPIF_ADDR); + + mmwrite((mmread(MANTIS_DMA_CTL) & ~(MANTIS_FIFO_EN | + MANTIS_DCAP_EN | + MANTIS_RISC_EN)), MANTIS_DMA_CTL); + + mmwrite(mmread(MANTIS_INT_STAT), MANTIS_INT_STAT); + + mantis_mask_ints(mantis, MANTIS_INT_RISCI | MANTIS_INT_RISCEN); +} + + +void mantis_dma_xfer(struct work_struct *t) +{ + struct mantis_pci *mantis = from_work(mantis, t, bh_work); + struct mantis_hwconfig *config = mantis->hwconfig; + + while (mantis->last_block != mantis->busy_block) { + dprintk(MANTIS_DEBUG, 1, "last block=[%d] finished block=[%d]", + mantis->last_block, mantis->busy_block); + + (config->ts_size ? dvb_dmx_swfilter_204 : dvb_dmx_swfilter) + (&mantis->demux, &mantis->buf_cpu[mantis->last_block * MANTIS_BLOCK_BYTES], MANTIS_BLOCK_BYTES); + mantis->last_block = (mantis->last_block + 1) % MANTIS_BLOCK_COUNT; + } +} diff --git a/drivers/media/pci/mantis/mantis_dma.h b/drivers/media/pci/mantis/mantis_dma.h new file mode 100644 index 000000000..5db0d3728 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_dma.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_DMA_H +#define __MANTIS_DMA_H + +extern int mantis_dma_init(struct mantis_pci *mantis); +extern int mantis_dma_exit(struct mantis_pci *mantis); +extern void mantis_dma_start(struct mantis_pci *mantis); +extern void mantis_dma_stop(struct mantis_pci *mantis); +extern void mantis_dma_xfer(struct work_struct *t); + +#endif /* __MANTIS_DMA_H */ diff --git a/drivers/media/pci/mantis/mantis_dvb.c b/drivers/media/pci/mantis/mantis_dvb.c new file mode 100644 index 000000000..398e32f44 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_dvb.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis PCI bridge driver + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mantis_common.h" +#include "mantis_dma.h" +#include "mantis_ca.h" +#include "mantis_ioc.h" +#include "mantis_dvb.h" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +int mantis_frontend_power(struct mantis_pci *mantis, enum mantis_power power) +{ + struct mantis_hwconfig *config = mantis->hwconfig; + + switch (power) { + case POWER_ON: + dprintk(MANTIS_DEBUG, 1, "Power ON"); + mantis_gpio_set_bits(mantis, config->power, POWER_ON); + msleep(100); + mantis_gpio_set_bits(mantis, config->power, POWER_ON); + msleep(100); + break; + + case POWER_OFF: + dprintk(MANTIS_DEBUG, 1, "Power OFF"); + mantis_gpio_set_bits(mantis, config->power, POWER_OFF); + msleep(100); + break; + + default: + dprintk(MANTIS_DEBUG, 1, "Unknown state <%02x>", power); + return -1; + } + + return 0; +} +EXPORT_SYMBOL_GPL(mantis_frontend_power); + +void mantis_frontend_soft_reset(struct mantis_pci *mantis) +{ + struct mantis_hwconfig *config = mantis->hwconfig; + + dprintk(MANTIS_DEBUG, 1, "Frontend RESET"); + mantis_gpio_set_bits(mantis, config->reset, 0); + msleep(100); + mantis_gpio_set_bits(mantis, config->reset, 0); + msleep(100); + mantis_gpio_set_bits(mantis, config->reset, 1); + msleep(100); + mantis_gpio_set_bits(mantis, config->reset, 1); + msleep(100); + + return; +} +EXPORT_SYMBOL_GPL(mantis_frontend_soft_reset); + +static int mantis_frontend_shutdown(struct mantis_pci *mantis) +{ + int err; + + mantis_frontend_soft_reset(mantis); + err = mantis_frontend_power(mantis, POWER_OFF); + if (err != 0) { + dprintk(MANTIS_ERROR, 1, "Frontend POWER OFF failed! <%d>", err); + return 1; + } + + return 0; +} + +static int mantis_dvb_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct mantis_pci *mantis = dvbdmx->priv; + + dprintk(MANTIS_DEBUG, 1, "Mantis DVB Start feed"); + if (!dvbdmx->dmx.frontend) { + dprintk(MANTIS_DEBUG, 1, "no frontend ?"); + return -EINVAL; + } + + mantis->feeds++; + dprintk(MANTIS_DEBUG, 1, "mantis start feed, feeds=%d", mantis->feeds); + + if (mantis->feeds == 1) { + dprintk(MANTIS_DEBUG, 1, "mantis start feed & dma"); + mantis_dma_start(mantis); + enable_and_queue_work(system_bh_wq, &mantis->bh_work); + } + + return mantis->feeds; +} + +static int mantis_dvb_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct mantis_pci *mantis = dvbdmx->priv; + + dprintk(MANTIS_DEBUG, 1, "Mantis DVB Stop feed"); + if (!dvbdmx->dmx.frontend) { + dprintk(MANTIS_DEBUG, 1, "no frontend ?"); + return -EINVAL; + } + + mantis->feeds--; + if (mantis->feeds == 0) { + dprintk(MANTIS_DEBUG, 1, "mantis stop feed and dma"); + disable_work_sync(&mantis->bh_work); + mantis_dma_stop(mantis); + } + + return 0; +} + +int mantis_dvb_init(struct mantis_pci *mantis) +{ + struct mantis_hwconfig *config = mantis->hwconfig; + int result; + + dprintk(MANTIS_DEBUG, 1, "dvb_register_adapter"); + + result = dvb_register_adapter(&mantis->dvb_adapter, + "Mantis DVB adapter", + THIS_MODULE, + &mantis->pdev->dev, + adapter_nr); + + if (result < 0) { + + dprintk(MANTIS_ERROR, 1, "Error registering adapter"); + return -ENODEV; + } + + mantis->dvb_adapter.priv = mantis; + mantis->demux.dmx.capabilities = DMX_TS_FILTERING | + DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING; + + mantis->demux.priv = mantis; + mantis->demux.filternum = 256; + mantis->demux.feednum = 256; + mantis->demux.start_feed = mantis_dvb_start_feed; + mantis->demux.stop_feed = mantis_dvb_stop_feed; + mantis->demux.write_to_decoder = NULL; + + dprintk(MANTIS_DEBUG, 1, "dvb_dmx_init"); + result = dvb_dmx_init(&mantis->demux); + if (result < 0) { + dprintk(MANTIS_ERROR, 1, "dvb_dmx_init failed, ERROR=%d", result); + + goto err0; + } + + mantis->dmxdev.filternum = 256; + mantis->dmxdev.demux = &mantis->demux.dmx; + mantis->dmxdev.capabilities = 0; + dprintk(MANTIS_DEBUG, 1, "dvb_dmxdev_init"); + + result = dvb_dmxdev_init(&mantis->dmxdev, &mantis->dvb_adapter); + if (result < 0) { + + dprintk(MANTIS_ERROR, 1, "dvb_dmxdev_init failed, ERROR=%d", result); + goto err1; + } + + mantis->fe_hw.source = DMX_FRONTEND_0; + result = mantis->demux.dmx.add_frontend(&mantis->demux.dmx, &mantis->fe_hw); + if (result < 0) { + + dprintk(MANTIS_ERROR, 1, "dvb_dmx_init failed, ERROR=%d", result); + goto err2; + } + + mantis->fe_mem.source = DMX_MEMORY_FE; + result = mantis->demux.dmx.add_frontend(&mantis->demux.dmx, &mantis->fe_mem); + if (result < 0) { + dprintk(MANTIS_ERROR, 1, "dvb_dmx_init failed, ERROR=%d", result); + goto err3; + } + + result = mantis->demux.dmx.connect_frontend(&mantis->demux.dmx, &mantis->fe_hw); + if (result < 0) { + dprintk(MANTIS_ERROR, 1, "dvb_dmx_init failed, ERROR=%d", result); + goto err4; + } + + dvb_net_init(&mantis->dvb_adapter, &mantis->dvbnet, &mantis->demux.dmx); + INIT_WORK(&mantis->bh_work, mantis_dma_xfer); + disable_work_sync(&mantis->bh_work); + if (mantis->hwconfig) { + result = config->frontend_init(mantis, mantis->fe); + if (result < 0) { + dprintk(MANTIS_ERROR, 1, "!!! NO Frontends found !!!"); + goto err5; + } else { + if (mantis->fe == NULL) { + result = -ENOMEM; + dprintk(MANTIS_ERROR, 1, "FE "); + goto err5; + } + result = dvb_register_frontend(&mantis->dvb_adapter, mantis->fe); + if (result) { + dprintk(MANTIS_ERROR, 1, "ERROR: Frontend registration failed"); + + if (mantis->fe->ops.release) + mantis->fe->ops.release(mantis->fe); + + mantis->fe = NULL; + goto err5; + } + } + } + + return 0; + + /* Error conditions .. */ +err5: + cancel_work_sync(&mantis->bh_work); + dvb_net_release(&mantis->dvbnet); + if (mantis->fe) { + dvb_unregister_frontend(mantis->fe); + dvb_frontend_detach(mantis->fe); + } +err4: + mantis->demux.dmx.remove_frontend(&mantis->demux.dmx, &mantis->fe_mem); + +err3: + mantis->demux.dmx.remove_frontend(&mantis->demux.dmx, &mantis->fe_hw); + +err2: + dvb_dmxdev_release(&mantis->dmxdev); + +err1: + dvb_dmx_release(&mantis->demux); + +err0: + dvb_unregister_adapter(&mantis->dvb_adapter); + + return result; +} +EXPORT_SYMBOL_GPL(mantis_dvb_init); + +int mantis_dvb_exit(struct mantis_pci *mantis) +{ + int err; + + if (mantis->fe) { + /* mantis_ca_exit(mantis); */ + err = mantis_frontend_shutdown(mantis); + if (err != 0) + dprintk(MANTIS_ERROR, 1, "Frontend exit while POWER ON! <%d>", err); + dvb_unregister_frontend(mantis->fe); + dvb_frontend_detach(mantis->fe); + } + + cancel_work_sync(&mantis->bh_work); + dvb_net_release(&mantis->dvbnet); + + mantis->demux.dmx.remove_frontend(&mantis->demux.dmx, &mantis->fe_mem); + mantis->demux.dmx.remove_frontend(&mantis->demux.dmx, &mantis->fe_hw); + + dvb_dmxdev_release(&mantis->dmxdev); + dvb_dmx_release(&mantis->demux); + + dprintk(MANTIS_DEBUG, 1, "dvb_unregister_adapter"); + dvb_unregister_adapter(&mantis->dvb_adapter); + + return 0; +} +EXPORT_SYMBOL_GPL(mantis_dvb_exit); diff --git a/drivers/media/pci/mantis/mantis_dvb.h b/drivers/media/pci/mantis/mantis_dvb.h new file mode 100644 index 000000000..fd4182951 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_dvb.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_DVB_H +#define __MANTIS_DVB_H + +enum mantis_power { + POWER_OFF = 0, + POWER_ON = 1 +}; + +extern int mantis_frontend_power(struct mantis_pci *mantis, enum mantis_power power); +extern void mantis_frontend_soft_reset(struct mantis_pci *mantis); + +extern int mantis_dvb_init(struct mantis_pci *mantis); +extern int mantis_dvb_exit(struct mantis_pci *mantis); + +#endif /* __MANTIS_DVB_H */ diff --git a/drivers/media/pci/mantis/mantis_evm.c b/drivers/media/pci/mantis/mantis_evm.c new file mode 100644 index 000000000..2fb98ecd2 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_evm.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mantis_common.h" +#include "mantis_link.h" +#include "mantis_hif.h" +#include "mantis_reg.h" + +static void mantis_hifevm_work(struct work_struct *work) +{ + struct mantis_ca *ca = container_of(work, struct mantis_ca, hif_evm_work); + struct mantis_pci *mantis = ca->ca_priv; + + u32 gpif_stat; + + gpif_stat = mmread(MANTIS_GPIF_STATUS); + + if (gpif_stat & MANTIS_GPIF_DETSTAT) { + if (gpif_stat & MANTIS_CARD_PLUGIN) { + dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): CAM Plugin", mantis->num); + mmwrite(0xdada0000, MANTIS_CARD_RESET); + mantis_event_cam_plugin(ca); + dvb_ca_en50221_camchange_irq(&ca->en50221, + 0, + DVB_CA_EN50221_CAMCHANGE_INSERTED); + } + } else { + if (gpif_stat & MANTIS_CARD_PLUGOUT) { + dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): CAM Unplug", mantis->num); + mmwrite(0xdada0000, MANTIS_CARD_RESET); + mantis_event_cam_unplug(ca); + dvb_ca_en50221_camchange_irq(&ca->en50221, + 0, + DVB_CA_EN50221_CAMCHANGE_REMOVED); + } + } + + if (mantis->gpif_status & MANTIS_GPIF_EXTIRQ) + dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): Ext IRQ", mantis->num); + + if (mantis->gpif_status & MANTIS_SBUF_WSTO) + dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): Smart Buffer Timeout", mantis->num); + + if (mantis->gpif_status & MANTIS_GPIF_OTHERR) + dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): Alignment Error", mantis->num); + + if (gpif_stat & MANTIS_SBUF_OVFLW) + dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): Smart Buffer Overflow", mantis->num); + + if (gpif_stat & MANTIS_GPIF_BRRDY) + dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): Smart Buffer Read Ready", mantis->num); + + if (gpif_stat & MANTIS_GPIF_INTSTAT) + dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): GPIF IRQ", mantis->num); + + if (gpif_stat & MANTIS_SBUF_EMPTY) + dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): Smart Buffer Empty", mantis->num); + + if (gpif_stat & MANTIS_SBUF_OPDONE) { + dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): Smart Buffer operation complete", mantis->num); + ca->sbuf_status = MANTIS_SBUF_DATA_AVAIL; + ca->hif_event = MANTIS_SBUF_OPDONE; + wake_up(&ca->hif_opdone_wq); + } +} + +int mantis_evmgr_init(struct mantis_ca *ca) +{ + struct mantis_pci *mantis = ca->ca_priv; + + dprintk(MANTIS_DEBUG, 1, "Initializing Mantis Host I/F Event manager"); + INIT_WORK(&ca->hif_evm_work, mantis_hifevm_work); + mantis_pcmcia_init(ca); + schedule_work(&ca->hif_evm_work); + mantis_hif_init(ca); + return 0; +} + +void mantis_evmgr_exit(struct mantis_ca *ca) +{ + struct mantis_pci *mantis = ca->ca_priv; + + dprintk(MANTIS_DEBUG, 1, "Mantis Host I/F Event manager exiting"); + flush_work(&ca->hif_evm_work); + mantis_hif_exit(ca); + mantis_pcmcia_exit(ca); +} diff --git a/drivers/media/pci/mantis/mantis_hif.c b/drivers/media/pci/mantis/mantis_hif.c new file mode 100644 index 000000000..683ea7f58 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_hif.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "mantis_common.h" + +#include "mantis_hif.h" +#include "mantis_link.h" /* temporary due to physical layer stuff */ + +#include "mantis_reg.h" + + +static int mantis_hif_sbuf_opdone_wait(struct mantis_ca *ca) +{ + struct mantis_pci *mantis = ca->ca_priv; + int rc = 0; + + if (wait_event_timeout(ca->hif_opdone_wq, + ca->hif_event & MANTIS_SBUF_OPDONE, + msecs_to_jiffies(500)) == -ERESTARTSYS) { + + dprintk(MANTIS_ERROR, 1, "Adapter(%d) Slot(0): Smart buffer operation timeout !", mantis->num); + rc = -EREMOTEIO; + } + dprintk(MANTIS_DEBUG, 1, "Smart Buffer Operation complete"); + ca->hif_event &= ~MANTIS_SBUF_OPDONE; + return rc; +} + +static int mantis_hif_write_wait(struct mantis_ca *ca) +{ + struct mantis_pci *mantis = ca->ca_priv; + u32 opdone = 0, timeout = 0; + int rc = 0; + + if (wait_event_timeout(ca->hif_write_wq, + mantis->gpif_status & MANTIS_GPIF_WRACK, + msecs_to_jiffies(500)) == -ERESTARTSYS) { + + dprintk(MANTIS_ERROR, 1, "Adapter(%d) Slot(0): Write ACK timed out !", mantis->num); + rc = -EREMOTEIO; + } + dprintk(MANTIS_DEBUG, 1, "Write Acknowledged"); + mantis->gpif_status &= ~MANTIS_GPIF_WRACK; + while (!opdone) { + opdone = (mmread(MANTIS_GPIF_STATUS) & MANTIS_SBUF_OPDONE); + udelay(500); + timeout++; + if (timeout > 100) { + dprintk(MANTIS_ERROR, 1, "Adapter(%d) Slot(0): Write operation timed out!", mantis->num); + rc = -ETIMEDOUT; + break; + } + } + dprintk(MANTIS_DEBUG, 1, "HIF Write success"); + return rc; +} + + +int mantis_hif_read_mem(struct mantis_ca *ca, u32 addr) +{ + struct mantis_pci *mantis = ca->ca_priv; + u32 hif_addr = 0, data, count = 4; + + dprintk(MANTIS_DEBUG, 1, "Adapter(%d) Slot(0): Request HIF Mem Read", mantis->num); + mutex_lock(&ca->ca_lock); + hif_addr &= ~MANTIS_GPIF_PCMCIAREG; + hif_addr &= ~MANTIS_GPIF_PCMCIAIOM; + hif_addr |= MANTIS_HIF_STATUS; + hif_addr |= addr; + + mmwrite(hif_addr, MANTIS_GPIF_BRADDR); + mmwrite(count, MANTIS_GPIF_BRBYTES); + udelay(20); + mmwrite(hif_addr | MANTIS_GPIF_HIFRDWRN, MANTIS_GPIF_ADDR); + + if (mantis_hif_sbuf_opdone_wait(ca) != 0) { + dprintk(MANTIS_ERROR, 1, "Adapter(%d) Slot(0): GPIF Smart Buffer operation failed", mantis->num); + mutex_unlock(&ca->ca_lock); + return -EREMOTEIO; + } + data = mmread(MANTIS_GPIF_DIN); + mutex_unlock(&ca->ca_lock); + dprintk(MANTIS_DEBUG, 1, "Mem Read: 0x%02x", data); + return (data >> 24) & 0xff; +} + +int mantis_hif_write_mem(struct mantis_ca *ca, u32 addr, u8 data) +{ + struct mantis_slot *slot = ca->slot; + struct mantis_pci *mantis = ca->ca_priv; + u32 hif_addr = 0; + + dprintk(MANTIS_DEBUG, 1, "Adapter(%d) Slot(0): Request HIF Mem Write", mantis->num); + mutex_lock(&ca->ca_lock); + hif_addr &= ~MANTIS_GPIF_HIFRDWRN; + hif_addr &= ~MANTIS_GPIF_PCMCIAREG; + hif_addr &= ~MANTIS_GPIF_PCMCIAIOM; + hif_addr |= MANTIS_HIF_STATUS; + hif_addr |= addr; + + mmwrite(slot->slave_cfg, MANTIS_GPIF_CFGSLA); /* Slot0 alone for now */ + mmwrite(hif_addr, MANTIS_GPIF_ADDR); + mmwrite(data, MANTIS_GPIF_DOUT); + + if (mantis_hif_write_wait(ca) != 0) { + dprintk(MANTIS_ERROR, 1, "Adapter(%d) Slot(0): HIF Smart Buffer operation failed", mantis->num); + mutex_unlock(&ca->ca_lock); + return -EREMOTEIO; + } + dprintk(MANTIS_DEBUG, 1, "Mem Write: (0x%02x to 0x%02x)", data, addr); + mutex_unlock(&ca->ca_lock); + + return 0; +} + +int mantis_hif_read_iom(struct mantis_ca *ca, u32 addr) +{ + struct mantis_pci *mantis = ca->ca_priv; + u32 data, hif_addr = 0; + + dprintk(MANTIS_DEBUG, 1, "Adapter(%d) Slot(0): Request HIF I/O Read", mantis->num); + mutex_lock(&ca->ca_lock); + hif_addr &= ~MANTIS_GPIF_PCMCIAREG; + hif_addr |= MANTIS_GPIF_PCMCIAIOM; + hif_addr |= MANTIS_HIF_STATUS; + hif_addr |= addr; + + mmwrite(hif_addr, MANTIS_GPIF_BRADDR); + mmwrite(1, MANTIS_GPIF_BRBYTES); + udelay(20); + mmwrite(hif_addr | MANTIS_GPIF_HIFRDWRN, MANTIS_GPIF_ADDR); + + if (mantis_hif_sbuf_opdone_wait(ca) != 0) { + dprintk(MANTIS_ERROR, 1, "Adapter(%d) Slot(0): HIF Smart Buffer operation failed", mantis->num); + mutex_unlock(&ca->ca_lock); + return -EREMOTEIO; + } + data = mmread(MANTIS_GPIF_DIN); + dprintk(MANTIS_DEBUG, 1, "I/O Read: 0x%02x", data); + udelay(50); + mutex_unlock(&ca->ca_lock); + + return (u8) data; +} + +int mantis_hif_write_iom(struct mantis_ca *ca, u32 addr, u8 data) +{ + struct mantis_pci *mantis = ca->ca_priv; + u32 hif_addr = 0; + + dprintk(MANTIS_DEBUG, 1, "Adapter(%d) Slot(0): Request HIF I/O Write", mantis->num); + mutex_lock(&ca->ca_lock); + hif_addr &= ~MANTIS_GPIF_PCMCIAREG; + hif_addr &= ~MANTIS_GPIF_HIFRDWRN; + hif_addr |= MANTIS_GPIF_PCMCIAIOM; + hif_addr |= MANTIS_HIF_STATUS; + hif_addr |= addr; + + mmwrite(hif_addr, MANTIS_GPIF_ADDR); + mmwrite(data, MANTIS_GPIF_DOUT); + + if (mantis_hif_write_wait(ca) != 0) { + dprintk(MANTIS_ERROR, 1, "Adapter(%d) Slot(0): HIF Smart Buffer operation failed", mantis->num); + mutex_unlock(&ca->ca_lock); + return -EREMOTEIO; + } + dprintk(MANTIS_DEBUG, 1, "I/O Write: (0x%02x to 0x%02x)", data, addr); + mutex_unlock(&ca->ca_lock); + udelay(50); + + return 0; +} + +int mantis_hif_init(struct mantis_ca *ca) +{ + struct mantis_slot *slot = ca->slot; + struct mantis_pci *mantis = ca->ca_priv; + u32 irqcfg; + + slot[0].slave_cfg = 0x70773028; + dprintk(MANTIS_ERROR, 1, "Adapter(%d) Initializing Mantis Host Interface", mantis->num); + + mutex_lock(&ca->ca_lock); + irqcfg = mmread(MANTIS_GPIF_IRQCFG); + irqcfg = MANTIS_MASK_BRRDY | + MANTIS_MASK_WRACK | + MANTIS_MASK_EXTIRQ | + MANTIS_MASK_WSTO | + MANTIS_MASK_OTHERR | + MANTIS_MASK_OVFLW; + + mmwrite(irqcfg, MANTIS_GPIF_IRQCFG); + mutex_unlock(&ca->ca_lock); + + return 0; +} + +void mantis_hif_exit(struct mantis_ca *ca) +{ + struct mantis_pci *mantis = ca->ca_priv; + u32 irqcfg; + + dprintk(MANTIS_ERROR, 1, "Adapter(%d) Exiting Mantis Host Interface", mantis->num); + mutex_lock(&ca->ca_lock); + irqcfg = mmread(MANTIS_GPIF_IRQCFG); + irqcfg &= ~MANTIS_MASK_BRRDY; + mmwrite(irqcfg, MANTIS_GPIF_IRQCFG); + mutex_unlock(&ca->ca_lock); +} diff --git a/drivers/media/pci/mantis/mantis_hif.h b/drivers/media/pci/mantis/mantis_hif.h new file mode 100644 index 000000000..392fac284 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_hif.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_HIF_H +#define __MANTIS_HIF_H + +#define MANTIS_HIF_MEMRD 1 +#define MANTIS_HIF_MEMWR 2 +#define MANTIS_HIF_IOMRD 3 +#define MANTIS_HIF_IOMWR 4 + +#endif /* __MANTIS_HIF_H */ diff --git a/drivers/media/pci/mantis/mantis_i2c.c b/drivers/media/pci/mantis/mantis_i2c.c new file mode 100644 index 000000000..4ff6022ad --- /dev/null +++ b/drivers/media/pci/mantis/mantis_i2c.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mantis_common.h" +#include "mantis_reg.h" +#include "mantis_i2c.h" + +#define TRIALS 10000 + +static int mantis_i2c_read(struct mantis_pci *mantis, const struct i2c_msg *msg) +{ + u32 rxd, i, stat, trials; + + dprintk(MANTIS_INFO, 0, " %s: Address=[0x%02x] [ ", + __func__, msg->addr); + + for (i = 0; i < msg->len; i++) { + rxd = (msg->addr << 25) | (1 << 24) + | MANTIS_I2C_RATE_3 + | MANTIS_I2C_STOP + | MANTIS_I2C_PGMODE; + + if (i == (msg->len - 1)) + rxd &= ~MANTIS_I2C_STOP; + + mmwrite(MANTIS_INT_I2CDONE, MANTIS_INT_STAT); + mmwrite(rxd, MANTIS_I2CDATA_CTL); + + /* wait for xfer completion */ + for (trials = 0; trials < TRIALS; trials++) { + stat = mmread(MANTIS_INT_STAT); + if (stat & MANTIS_INT_I2CDONE) + break; + } + + dprintk(MANTIS_TMG, 0, "I2CDONE: trials=%d\n", trials); + + /* wait for xfer completion */ + for (trials = 0; trials < TRIALS; trials++) { + stat = mmread(MANTIS_INT_STAT); + if (stat & MANTIS_INT_I2CRACK) + break; + } + + dprintk(MANTIS_TMG, 0, "I2CRACK: trials=%d\n", trials); + + rxd = mmread(MANTIS_I2CDATA_CTL); + msg->buf[i] = (u8)((rxd >> 8) & 0xFF); + dprintk(MANTIS_INFO, 0, "%02x ", msg->buf[i]); + } + dprintk(MANTIS_INFO, 0, "]\n"); + + return 0; +} + +static int mantis_i2c_write(struct mantis_pci *mantis, const struct i2c_msg *msg) +{ + int i; + u32 txd = 0, stat, trials; + + dprintk(MANTIS_INFO, 0, " %s: Address=[0x%02x] [ ", + __func__, msg->addr); + + for (i = 0; i < msg->len; i++) { + dprintk(MANTIS_INFO, 0, "%02x ", msg->buf[i]); + txd = (msg->addr << 25) | (msg->buf[i] << 8) + | MANTIS_I2C_RATE_3 + | MANTIS_I2C_STOP + | MANTIS_I2C_PGMODE; + + if (i == (msg->len - 1)) + txd &= ~MANTIS_I2C_STOP; + + mmwrite(MANTIS_INT_I2CDONE, MANTIS_INT_STAT); + mmwrite(txd, MANTIS_I2CDATA_CTL); + + /* wait for xfer completion */ + for (trials = 0; trials < TRIALS; trials++) { + stat = mmread(MANTIS_INT_STAT); + if (stat & MANTIS_INT_I2CDONE) + break; + } + + dprintk(MANTIS_TMG, 0, "I2CDONE: trials=%d\n", trials); + + /* wait for xfer completion */ + for (trials = 0; trials < TRIALS; trials++) { + stat = mmread(MANTIS_INT_STAT); + if (stat & MANTIS_INT_I2CRACK) + break; + } + + dprintk(MANTIS_TMG, 0, "I2CRACK: trials=%d\n", trials); + } + dprintk(MANTIS_INFO, 0, "]\n"); + + return 0; +} + +static int mantis_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num) +{ + int ret = 0, i = 0, trials; + u32 stat, data, txd; + struct mantis_pci *mantis; + struct mantis_hwconfig *config; + + mantis = i2c_get_adapdata(adapter); + BUG_ON(!mantis); + config = mantis->hwconfig; + BUG_ON(!config); + + dprintk(MANTIS_DEBUG, 1, "Messages:%d", num); + mutex_lock(&mantis->i2c_lock); + + while (i < num) { + /* Byte MODE */ + if ((config->i2c_mode & MANTIS_BYTE_MODE) && + ((i + 1) < num) && + (msgs[i].len < 2) && + (msgs[i + 1].len < 2) && + (msgs[i + 1].flags & I2C_M_RD)) { + + dprintk(MANTIS_DEBUG, 0, " Byte MODE:\n"); + + /* Read operation */ + txd = msgs[i].addr << 25 | (0x1 << 24) + | (msgs[i].buf[0] << 16) + | MANTIS_I2C_RATE_3; + + mmwrite(txd, MANTIS_I2CDATA_CTL); + /* wait for xfer completion */ + for (trials = 0; trials < TRIALS; trials++) { + stat = mmread(MANTIS_INT_STAT); + if (stat & MANTIS_INT_I2CDONE) + break; + } + + /* check for xfer completion */ + if (stat & MANTIS_INT_I2CDONE) { + /* check xfer was acknowledged */ + if (stat & MANTIS_INT_I2CRACK) { + data = mmread(MANTIS_I2CDATA_CTL); + msgs[i + 1].buf[0] = (data >> 8) & 0xff; + dprintk(MANTIS_DEBUG, 0, " Byte <%d> RXD=0x%02x [%02x]\n", 0x0, data, msgs[i + 1].buf[0]); + } else { + /* I/O error */ + dprintk(MANTIS_ERROR, 1, " I/O error, LINE:%d", __LINE__); + ret = -EIO; + break; + } + } else { + /* I/O error */ + dprintk(MANTIS_ERROR, 1, " I/O error, LINE:%d", __LINE__); + ret = -EIO; + break; + } + i += 2; /* Write/Read operation in one go */ + } + + if (i < num) { + if (msgs[i].flags & I2C_M_RD) + ret = mantis_i2c_read(mantis, &msgs[i]); + else + ret = mantis_i2c_write(mantis, &msgs[i]); + + i++; + if (ret < 0) + goto bail_out; + } + + } + + mutex_unlock(&mantis->i2c_lock); + + return num; + +bail_out: + mutex_unlock(&mantis->i2c_lock); + return ret; +} + +static u32 mantis_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm mantis_algo = { + .master_xfer = mantis_i2c_xfer, + .functionality = mantis_i2c_func, +}; + +int mantis_i2c_init(struct mantis_pci *mantis) +{ + u32 intstat; + struct i2c_adapter *i2c_adapter = &mantis->adapter; + struct pci_dev *pdev = mantis->pdev; + + init_waitqueue_head(&mantis->i2c_wq); + mutex_init(&mantis->i2c_lock); + strscpy(i2c_adapter->name, "Mantis I2C", sizeof(i2c_adapter->name)); + i2c_set_adapdata(i2c_adapter, mantis); + + i2c_adapter->owner = THIS_MODULE; + i2c_adapter->algo = &mantis_algo; + i2c_adapter->algo_data = NULL; + i2c_adapter->timeout = 500; + i2c_adapter->retries = 3; + i2c_adapter->dev.parent = &pdev->dev; + + mantis->i2c_rc = i2c_add_adapter(i2c_adapter); + if (mantis->i2c_rc < 0) + return mantis->i2c_rc; + + dprintk(MANTIS_DEBUG, 1, "Initializing I2C .."); + + intstat = mmread(MANTIS_INT_STAT); + mmread(MANTIS_INT_MASK); + mmwrite(intstat, MANTIS_INT_STAT); + dprintk(MANTIS_DEBUG, 1, "Disabling I2C interrupt"); + mantis_mask_ints(mantis, MANTIS_INT_I2CDONE); + + return 0; +} +EXPORT_SYMBOL_GPL(mantis_i2c_init); + +int mantis_i2c_exit(struct mantis_pci *mantis) +{ + dprintk(MANTIS_DEBUG, 1, "Disabling I2C interrupt"); + mantis_mask_ints(mantis, MANTIS_INT_I2CDONE); + + dprintk(MANTIS_DEBUG, 1, "Removing I2C adapter"); + i2c_del_adapter(&mantis->adapter); + + return 0; +} +EXPORT_SYMBOL_GPL(mantis_i2c_exit); diff --git a/drivers/media/pci/mantis/mantis_i2c.h b/drivers/media/pci/mantis/mantis_i2c.h new file mode 100644 index 000000000..aaa53d8e1 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_i2c.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_I2C_H +#define __MANTIS_I2C_H + +#define I2C_STOP (1 << 0) +#define I2C_READ (1 << 1) + +extern int mantis_i2c_init(struct mantis_pci *mantis); +extern int mantis_i2c_exit(struct mantis_pci *mantis); + +#endif /* __MANTIS_I2C_H */ diff --git a/drivers/media/pci/mantis/mantis_input.c b/drivers/media/pci/mantis/mantis_input.c new file mode 100644 index 000000000..edb4cacf5 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_input.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include + +#include +#include +#include +#include +#include + +#include "mantis_common.h" +#include "mantis_input.h" + +#define MODULE_NAME "mantis_core" + +void mantis_input_process(struct mantis_pci *mantis, int scancode) +{ + if (mantis->rc) + rc_keydown(mantis->rc, RC_PROTO_UNKNOWN, scancode, 0); +} + +int mantis_input_init(struct mantis_pci *mantis) +{ + struct rc_dev *dev; + int err; + + dev = rc_allocate_device(RC_DRIVER_SCANCODE); + if (!dev) { + dprintk(MANTIS_ERROR, 1, "Remote device allocation failed"); + err = -ENOMEM; + goto out; + } + + snprintf(mantis->device_name, sizeof(mantis->device_name), + "Mantis %s IR receiver", mantis->hwconfig->model_name); + snprintf(mantis->input_phys, sizeof(mantis->input_phys), + "pci-%s/ir0", pci_name(mantis->pdev)); + + dev->device_name = mantis->device_name; + dev->input_phys = mantis->input_phys; + dev->input_id.bustype = BUS_PCI; + dev->input_id.vendor = mantis->vendor_id; + dev->input_id.product = mantis->device_id; + dev->input_id.version = 1; + dev->driver_name = MODULE_NAME; + dev->map_name = mantis->rc_map_name ? : RC_MAP_EMPTY; + dev->dev.parent = &mantis->pdev->dev; + + err = rc_register_device(dev); + if (err) { + dprintk(MANTIS_ERROR, 1, "IR device registration failed, ret = %d", err); + goto out_dev; + } + + mantis->rc = dev; + return 0; + +out_dev: + rc_free_device(dev); +out: + return err; +} +EXPORT_SYMBOL_GPL(mantis_input_init); + +void mantis_input_exit(struct mantis_pci *mantis) +{ + rc_unregister_device(mantis->rc); + rc_free_device(mantis->rc); +} +EXPORT_SYMBOL_GPL(mantis_input_exit); diff --git a/drivers/media/pci/mantis/mantis_input.h b/drivers/media/pci/mantis/mantis_input.h new file mode 100644 index 000000000..23e1780e2 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_input.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_INPUT_H +#define __MANTIS_INPUT_H + +int mantis_input_init(struct mantis_pci *mantis); +void mantis_input_exit(struct mantis_pci *mantis); +void mantis_input_process(struct mantis_pci *mantis, int scancode); + +#endif /* __MANTIS_UART_H */ diff --git a/drivers/media/pci/mantis/mantis_ioc.c b/drivers/media/pci/mantis/mantis_ioc.c new file mode 100644 index 000000000..e8ef17896 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_ioc.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mantis_common.h" +#include "mantis_reg.h" +#include "mantis_ioc.h" + +static int read_eeprom_bytes(struct mantis_pci *mantis, u8 reg, u8 *data, u8 length) +{ + struct i2c_adapter *adapter = &mantis->adapter; + int err; + u8 buf = reg; + + struct i2c_msg msg[] = { + { .addr = 0x50, .flags = 0, .buf = &buf, .len = 1 }, + { .addr = 0x50, .flags = I2C_M_RD, .buf = data, .len = length }, + }; + + err = i2c_transfer(adapter, msg, 2); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: i2c read: < err=%i d0=0x%02x d1=0x%02x >", + err, data[0], data[1]); + + return err; + } + + return 0; +} +int mantis_get_mac(struct mantis_pci *mantis) +{ + int err; + u8 mac_addr[6] = {0}; + + err = read_eeprom_bytes(mantis, 0x08, mac_addr, 6); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis EEPROM read error <%d>", err); + + return err; + } + + dprintk(MANTIS_ERROR, 0, " MAC Address=[%pM]\n", mac_addr); + + return 0; +} +EXPORT_SYMBOL_GPL(mantis_get_mac); + +/* Turn the given bit on or off. */ +void mantis_gpio_set_bits(struct mantis_pci *mantis, u32 bitpos, u8 value) +{ + u32 cur; + + dprintk(MANTIS_DEBUG, 1, "Set Bit <%d> to <%d>", bitpos, value); + cur = mmread(MANTIS_GPIF_ADDR); + if (value) + mantis->gpio_status = cur | (1 << bitpos); + else + mantis->gpio_status = cur & (~(1 << bitpos)); + + dprintk(MANTIS_DEBUG, 1, "GPIO Value <%02x>", mantis->gpio_status); + mmwrite(mantis->gpio_status, MANTIS_GPIF_ADDR); + mmwrite(0x00, MANTIS_GPIF_DOUT); +} +EXPORT_SYMBOL_GPL(mantis_gpio_set_bits); + +int mantis_stream_control(struct mantis_pci *mantis, enum mantis_stream_control stream_ctl) +{ + u32 reg; + + reg = mmread(MANTIS_CONTROL); + switch (stream_ctl) { + case STREAM_TO_HIF: + dprintk(MANTIS_DEBUG, 1, "Set stream to HIF"); + reg &= 0xff - MANTIS_BYPASS; + mmwrite(reg, MANTIS_CONTROL); + reg |= MANTIS_BYPASS; + mmwrite(reg, MANTIS_CONTROL); + break; + + case STREAM_TO_CAM: + dprintk(MANTIS_DEBUG, 1, "Set stream to CAM"); + reg |= MANTIS_BYPASS; + mmwrite(reg, MANTIS_CONTROL); + reg &= 0xff - MANTIS_BYPASS; + mmwrite(reg, MANTIS_CONTROL); + break; + default: + dprintk(MANTIS_ERROR, 1, "Unknown MODE <%02x>", stream_ctl); + return -1; + } + + return 0; +} +EXPORT_SYMBOL_GPL(mantis_stream_control); diff --git a/drivers/media/pci/mantis/mantis_ioc.h b/drivers/media/pci/mantis/mantis_ioc.h new file mode 100644 index 000000000..baaacaf09 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_ioc.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_IOC_H +#define __MANTIS_IOC_H + +#define GPIF_A00 0x00 +#define GPIF_A01 0x01 +#define GPIF_A02 0x02 +#define GPIF_A03 0x03 +#define GPIF_A04 0x04 +#define GPIF_A05 0x05 +#define GPIF_A06 0x06 +#define GPIF_A07 0x07 +#define GPIF_A08 0x08 +#define GPIF_A09 0x09 +#define GPIF_A10 0x0a +#define GPIF_A11 0x0b + +#define GPIF_A12 0x0c +#define GPIF_A13 0x0d +#define GPIF_A14 0x0e + +enum mantis_stream_control { + STREAM_TO_HIF = 0, + STREAM_TO_CAM +}; + +extern int mantis_get_mac(struct mantis_pci *mantis); +extern void mantis_gpio_set_bits(struct mantis_pci *mantis, u32 bitpos, u8 value); + +extern int mantis_stream_control(struct mantis_pci *mantis, enum mantis_stream_control stream_ctl); + +#endif /* __MANTIS_IOC_H */ diff --git a/drivers/media/pci/mantis/mantis_link.h b/drivers/media/pci/mantis/mantis_link.h new file mode 100644 index 000000000..ebef99557 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_link.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_LINK_H +#define __MANTIS_LINK_H + +#include +#include +#include + +enum mantis_sbuf_status { + MANTIS_SBUF_DATA_AVAIL = 1, + MANTIS_SBUF_DATA_EMPTY = 2, + MANTIS_SBUF_DATA_OVFLW = 3 +}; + +struct mantis_slot { + u32 timeout; + u32 slave_cfg; + u32 bar; +}; + +/* Physical layer */ +enum mantis_slot_state { + MODULE_INSERTED = 3, + MODULE_XTRACTED = 4 +}; + +struct mantis_ca { + struct mantis_slot slot[4]; + + struct work_struct hif_evm_work; + + u32 hif_event; + wait_queue_head_t hif_opdone_wq; + wait_queue_head_t hif_brrdyw_wq; + wait_queue_head_t hif_data_wq; + wait_queue_head_t hif_write_wq; /* HIF Write op */ + + enum mantis_sbuf_status sbuf_status; + + enum mantis_slot_state slot_state; + + void *ca_priv; + + struct dvb_ca_en50221 en50221; + struct mutex ca_lock; +}; + +/* CA */ +extern void mantis_event_cam_plugin(struct mantis_ca *ca); +extern void mantis_event_cam_unplug(struct mantis_ca *ca); +extern int mantis_pcmcia_init(struct mantis_ca *ca); +extern void mantis_pcmcia_exit(struct mantis_ca *ca); +extern int mantis_evmgr_init(struct mantis_ca *ca); +extern void mantis_evmgr_exit(struct mantis_ca *ca); + +/* HIF */ +extern int mantis_hif_init(struct mantis_ca *ca); +extern void mantis_hif_exit(struct mantis_ca *ca); +extern int mantis_hif_read_mem(struct mantis_ca *ca, u32 addr); +extern int mantis_hif_write_mem(struct mantis_ca *ca, u32 addr, u8 data); +extern int mantis_hif_read_iom(struct mantis_ca *ca, u32 addr); +extern int mantis_hif_write_iom(struct mantis_ca *ca, u32 addr, u8 data); + +#endif /* __MANTIS_LINK_H */ diff --git a/drivers/media/pci/mantis/mantis_pci.c b/drivers/media/pci/mantis/mantis_pci.c new file mode 100644 index 000000000..9fbce74b0 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_pci.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mantis_common.h" +#include "mantis_reg.h" +#include "mantis_pci.h" + +#define DRIVER_NAME "Mantis Core" + +int mantis_pci_init(struct mantis_pci *mantis) +{ + u8 latency; + struct mantis_hwconfig *config = mantis->hwconfig; + struct pci_dev *pdev = mantis->pdev; + int err, ret = 0; + + dprintk(MANTIS_ERROR, 0, "found a %s PCI %s device on (%02x:%02x.%x),\n", + config->model_name, + config->dev_type, + mantis->pdev->bus->number, + PCI_SLOT(mantis->pdev->devfn), + PCI_FUNC(mantis->pdev->devfn)); + + err = pci_enable_device(pdev); + if (err != 0) { + ret = -ENODEV; + dprintk(MANTIS_ERROR, 1, "ERROR: PCI enable failed <%i>", err); + goto fail0; + } + + err = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (err != 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Unable to obtain 32 bit DMA <%i>", err); + ret = -ENOMEM; + goto fail1; + } + + pci_set_master(pdev); + + if (!request_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0), + DRIVER_NAME)) { + + dprintk(MANTIS_ERROR, 1, "ERROR: BAR0 Request failed !"); + ret = -ENODEV; + goto fail1; + } + + mantis->mmio = ioremap(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + + if (!mantis->mmio) { + dprintk(MANTIS_ERROR, 1, "ERROR: BAR0 remap failed !"); + ret = -ENODEV; + goto fail2; + } + + pci_read_config_byte(pdev, PCI_LATENCY_TIMER, &latency); + mantis->latency = latency; + mantis->revision = pdev->revision; + + dprintk(MANTIS_ERROR, 0, " Mantis Rev %d [%04x:%04x], ", + mantis->revision, + mantis->pdev->subsystem_vendor, + mantis->pdev->subsystem_device); + + dprintk(MANTIS_ERROR, 0, + "irq: %d, latency: %d\n memory: 0x%lx, mmio: 0x%p\n", + mantis->pdev->irq, + mantis->latency, + mantis->mantis_addr, + mantis->mmio); + + err = request_irq(pdev->irq, + config->irq_handler, + IRQF_SHARED, + DRIVER_NAME, + mantis); + + if (err != 0) { + + dprintk(MANTIS_ERROR, 1, "ERROR: IRQ registration failed ! <%d>", err); + ret = -ENODEV; + goto fail3; + } + + pci_set_drvdata(pdev, mantis); + return ret; + + /* Error conditions */ +fail3: + dprintk(MANTIS_ERROR, 1, "ERROR: <%d> I/O unmap", ret); + if (mantis->mmio) + iounmap(mantis->mmio); + +fail2: + dprintk(MANTIS_ERROR, 1, "ERROR: <%d> releasing regions", ret); + release_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + +fail1: + dprintk(MANTIS_ERROR, 1, "ERROR: <%d> disabling device", ret); + pci_disable_device(pdev); + +fail0: + dprintk(MANTIS_ERROR, 1, "ERROR: <%d> exiting", ret); + return ret; +} +EXPORT_SYMBOL_GPL(mantis_pci_init); + +void mantis_pci_exit(struct mantis_pci *mantis) +{ + struct pci_dev *pdev = mantis->pdev; + + dprintk(MANTIS_NOTICE, 1, " mem: 0x%p", mantis->mmio); + free_irq(pdev->irq, mantis); + if (mantis->mmio) { + iounmap(mantis->mmio); + release_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + } + + pci_disable_device(pdev); +} +EXPORT_SYMBOL_GPL(mantis_pci_exit); + +MODULE_DESCRIPTION("Mantis PCI DTV bridge driver"); +MODULE_AUTHOR("Manu Abraham"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/mantis/mantis_pci.h b/drivers/media/pci/mantis/mantis_pci.h new file mode 100644 index 000000000..67557f218 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_pci.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_PCI_H +#define __MANTIS_PCI_H + +extern int mantis_pci_init(struct mantis_pci *mantis); +extern void mantis_pci_exit(struct mantis_pci *mantis); + +#endif /* __MANTIS_PCI_H */ diff --git a/drivers/media/pci/mantis/mantis_pcmcia.c b/drivers/media/pci/mantis/mantis_pcmcia.c new file mode 100644 index 000000000..e4eac069e --- /dev/null +++ b/drivers/media/pci/mantis/mantis_pcmcia.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mantis_common.h" +#include "mantis_link.h" /* temporary due to physical layer stuff */ +#include "mantis_reg.h" + +/* + * If Slot state is already PLUG_IN event and we are called + * again, definitely it is jitter alone + */ +void mantis_event_cam_plugin(struct mantis_ca *ca) +{ + struct mantis_pci *mantis = ca->ca_priv; + + u32 gpif_irqcfg; + + if (ca->slot_state == MODULE_XTRACTED) { + dprintk(MANTIS_DEBUG, 1, "Event: CAM Plugged IN: Adapter(%d) Slot(0)", mantis->num); + udelay(50); + mmwrite(0xda000000, MANTIS_CARD_RESET); + gpif_irqcfg = mmread(MANTIS_GPIF_IRQCFG); + gpif_irqcfg |= MANTIS_MASK_PLUGOUT; + gpif_irqcfg &= ~MANTIS_MASK_PLUGIN; + mmwrite(gpif_irqcfg, MANTIS_GPIF_IRQCFG); + udelay(500); + ca->slot_state = MODULE_INSERTED; + } + udelay(100); +} + +/* + * If Slot state is already UN_PLUG event and we are called + * again, definitely it is jitter alone + */ +void mantis_event_cam_unplug(struct mantis_ca *ca) +{ + struct mantis_pci *mantis = ca->ca_priv; + + u32 gpif_irqcfg; + + if (ca->slot_state == MODULE_INSERTED) { + dprintk(MANTIS_DEBUG, 1, "Event: CAM Unplugged: Adapter(%d) Slot(0)", mantis->num); + udelay(50); + mmwrite(0x00da0000, MANTIS_CARD_RESET); + gpif_irqcfg = mmread(MANTIS_GPIF_IRQCFG); + gpif_irqcfg |= MANTIS_MASK_PLUGIN; + gpif_irqcfg &= ~MANTIS_MASK_PLUGOUT; + mmwrite(gpif_irqcfg, MANTIS_GPIF_IRQCFG); + udelay(500); + ca->slot_state = MODULE_XTRACTED; + } + udelay(100); +} + +int mantis_pcmcia_init(struct mantis_ca *ca) +{ + struct mantis_pci *mantis = ca->ca_priv; + + u32 gpif_stat, card_stat; + + mantis_unmask_ints(mantis, MANTIS_INT_IRQ0); + gpif_stat = mmread(MANTIS_GPIF_STATUS); + card_stat = mmread(MANTIS_GPIF_IRQCFG); + + if (gpif_stat & MANTIS_GPIF_DETSTAT) { + dprintk(MANTIS_DEBUG, 1, "CAM found on Adapter(%d) Slot(0)", mantis->num); + mmwrite(card_stat | MANTIS_MASK_PLUGOUT, MANTIS_GPIF_IRQCFG); + ca->slot_state = MODULE_INSERTED; + dvb_ca_en50221_camchange_irq(&ca->en50221, + 0, + DVB_CA_EN50221_CAMCHANGE_INSERTED); + } else { + dprintk(MANTIS_DEBUG, 1, "Empty Slot on Adapter(%d) Slot(0)", mantis->num); + mmwrite(card_stat | MANTIS_MASK_PLUGIN, MANTIS_GPIF_IRQCFG); + ca->slot_state = MODULE_XTRACTED; + dvb_ca_en50221_camchange_irq(&ca->en50221, + 0, + DVB_CA_EN50221_CAMCHANGE_REMOVED); + } + + return 0; +} + +void mantis_pcmcia_exit(struct mantis_ca *ca) +{ + struct mantis_pci *mantis = ca->ca_priv; + + mmwrite(mmread(MANTIS_GPIF_STATUS) & (~MANTIS_CARD_PLUGOUT | ~MANTIS_CARD_PLUGIN), MANTIS_GPIF_STATUS); + mantis_mask_ints(mantis, MANTIS_INT_IRQ0); +} diff --git a/drivers/media/pci/mantis/mantis_reg.h b/drivers/media/pci/mantis/mantis_reg.h new file mode 100644 index 000000000..a1e66ef6a --- /dev/null +++ b/drivers/media/pci/mantis/mantis_reg.h @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_REG_H +#define __MANTIS_REG_H + +/* Interrupts */ +#define MANTIS_INT_STAT 0x00 +#define MANTIS_INT_MASK 0x04 + +#define MANTIS_INT_RISCSTAT (0x0f << 28) +#define MANTIS_INT_RISCEN BIT(27) +#define MANTIS_INT_I2CRACK BIT(26) + +/* #define MANTIS_INT_GPIF (0xff << 12) */ + +#define MANTIS_INT_PCMCIA7 BIT(19) +#define MANTIS_INT_PCMCIA6 BIT(18) +#define MANTIS_INT_PCMCIA5 BIT(17) +#define MANTIS_INT_PCMCIA4 BIT(16) +#define MANTIS_INT_PCMCIA3 BIT(15) +#define MANTIS_INT_PCMCIA2 BIT(14) +#define MANTIS_INT_PCMCIA1 BIT(13) +#define MANTIS_INT_PCMCIA0 BIT(12) +#define MANTIS_INT_IRQ1 BIT(11) +#define MANTIS_INT_IRQ0 BIT(10) +#define MANTIS_INT_OCERR BIT(8) +#define MANTIS_INT_PABORT BIT(7) +#define MANTIS_INT_RIPERR BIT(6) +#define MANTIS_INT_PPERR BIT(5) +#define MANTIS_INT_FTRGT BIT(3) +#define MANTIS_INT_RISCI BIT(1) +#define MANTIS_INT_I2CDONE BIT(0) + +/* DMA */ +#define MANTIS_DMA_CTL 0x08 +#define MANTIS_GPIF_RD (0xff << 24) +#define MANTIS_GPIF_WR (0xff << 16) +#define MANTIS_CPU_DO BIT(10) +#define MANTIS_DRV_DO BIT(9) +#define MANTIS_I2C_RD BIT(7) +#define MANTIS_I2C_WR BIT(6) +#define MANTIS_DCAP_MODE BIT(5) +#define MANTIS_FIFO_TP_4 (0x00 << 3) +#define MANTIS_FIFO_TP_8 (0x01 << 3) +#define MANTIS_FIFO_TP_16 (0x02 << 3) +#define MANTIS_FIFO_EN BIT(2) +#define MANTIS_DCAP_EN BIT(1) +#define MANTIS_RISC_EN BIT(0) + +/* DEBUG */ +#define MANTIS_DEBUGREG 0x0c +#define MANTIS_DATINV (0x0e << 7) +#define MANTIS_TOP_DEBUGSEL (0x07 << 4) +#define MANTIS_PCMCIA_DEBUGSEL (0x0f << 0) + +#define MANTIS_RISC_START 0x10 +#define MANTIS_RISC_PC 0x14 + +/* I2C */ +#define MANTIS_I2CDATA_CTL 0x18 +#define MANTIS_I2C_RATE_1 (0x00 << 6) +#define MANTIS_I2C_RATE_2 (0x01 << 6) +#define MANTIS_I2C_RATE_3 (0x02 << 6) +#define MANTIS_I2C_RATE_4 (0x03 << 6) +#define MANTIS_I2C_STOP BIT(5) +#define MANTIS_I2C_PGMODE BIT(3) + +/* DATA */ +#define MANTIS_CMD_DATA_R1 0x20 +#define MANTIS_CMD_DATA_3 (0xff << 24) +#define MANTIS_CMD_DATA_2 (0xff << 16) +#define MANTIS_CMD_DATA_1 (0xff << 8) +#define MANTIS_CMD_DATA_0 (0xff << 0) + +#define MANTIS_CMD_DATA_R2 0x24 +#define MANTIS_CMD_DATA_7 (0xff << 24) +#define MANTIS_CMD_DATA_6 (0xff << 16) +#define MANTIS_CMD_DATA_5 (0xff << 8) +#define MANTIS_CMD_DATA_4 (0xff << 0) + +#define MANTIS_CONTROL 0x28 +#define MANTIS_DET BIT(7) +#define MANTIS_DAT_CF_EN BIT(6) +#define MANTIS_ACS (0x03 << 4) +#define MANTIS_VCCEN BIT(3) +#define MANTIS_BYPASS BIT(2) +#define MANTIS_MRST BIT(1) +#define MANTIS_CRST_INT BIT(0) + +#define MANTIS_GPIF_CFGSLA 0x84 +#define MANTIS_GPIF_WAITSMPL (0x07 << 28) +#define MANTIS_GPIF_BYTEADDRSUB BIT(25) +#define MANTIS_GPIF_WAITPOL BIT(24) +#define MANTIS_GPIF_NCDELAY (0x07 << 20) +#define MANTIS_GPIF_RW2CSDELAY (0x07 << 16) +#define MANTIS_GPIF_SLFTIMEDMODE BIT(15) +#define MANTIS_GPIF_SLFTIMEDDELY (0x7f << 8) +#define MANTIS_GPIF_DEVTYPE (0x07 << 4) +#define MANTIS_GPIF_BIGENDIAN BIT(3) +#define MANTIS_GPIF_FETCHCMD (0x03 << 1) +#define MANTIS_GPIF_HWORDDEV BIT(0) + +#define MANTIS_GPIF_WSTOPER 0x90 +#define MANTIS_GPIF_WSTOPERWREN3 BIT(31) +#define MANTIS_GPIF_PARBOOTN BIT(29) +#define MANTIS_GPIF_WSTOPERSLID3 (0x1f << 24) +#define MANTIS_GPIF_WSTOPERWREN2 BIT(23) +#define MANTIS_GPIF_WSTOPERSLID2 (0x1f << 16) +#define MANTIS_GPIF_WSTOPERWREN1 BIT(15) +#define MANTIS_GPIF_WSTOPERSLID1 (0x1f << 8) +#define MANTIS_GPIF_WSTOPERWREN0 BIT(7) +#define MANTIS_GPIF_WSTOPERSLID0 (0x1f << 0) + +#define MANTIS_GPIF_CS2RW 0x94 +#define MANTIS_GPIF_CS2RWWREN3 BIT(31) +#define MANTIS_GPIF_CS2RWDELY3 (0x3f << 24) +#define MANTIS_GPIF_CS2RWWREN2 BIT(23) +#define MANTIS_GPIF_CS2RWDELY2 (0x3f << 16) +#define MANTIS_GPIF_CS2RWWREN1 BIT(15) +#define MANTIS_GPIF_CS2RWDELY1 (0x3f << 8) +#define MANTIS_GPIF_CS2RWWREN0 BIT(7) +#define MANTIS_GPIF_CS2RWDELY0 (0x3f << 0) + +#define MANTIS_GPIF_IRQCFG 0x98 +#define MANTIS_GPIF_IRQPOL BIT(8) +#define MANTIS_MASK_WRACK BIT(7) +#define MANTIS_MASK_BRRDY BIT(6) +#define MANTIS_MASK_OVFLW BIT(5) +#define MANTIS_MASK_OTHERR BIT(4) +#define MANTIS_MASK_WSTO BIT(3) +#define MANTIS_MASK_EXTIRQ BIT(2) +#define MANTIS_MASK_PLUGIN BIT(1) +#define MANTIS_MASK_PLUGOUT BIT(0) + +#define MANTIS_GPIF_STATUS 0x9c +#define MANTIS_SBUF_KILLOP BIT(15) +#define MANTIS_SBUF_OPDONE BIT(14) +#define MANTIS_SBUF_EMPTY BIT(13) +#define MANTIS_GPIF_DETSTAT BIT(9) +#define MANTIS_GPIF_INTSTAT BIT(8) +#define MANTIS_GPIF_WRACK BIT(7) +#define MANTIS_GPIF_BRRDY BIT(6) +#define MANTIS_SBUF_OVFLW BIT(5) +#define MANTIS_GPIF_OTHERR BIT(4) +#define MANTIS_SBUF_WSTO BIT(3) +#define MANTIS_GPIF_EXTIRQ BIT(2) +#define MANTIS_CARD_PLUGIN BIT(1) +#define MANTIS_CARD_PLUGOUT BIT(0) + +#define MANTIS_GPIF_BRADDR 0xa0 +#define MANTIS_GPIF_PCMCIAREG BIT(27) +#define MANTIS_GPIF_PCMCIAIOM BIT(26) +#define MANTIS_GPIF_BR_ADDR (0xfffffff << 0) + +#define MANTIS_GPIF_BRBYTES 0xa4 +#define MANTIS_GPIF_BRCNT (0xfff << 0) + +#define MANTIS_PCMCIA_RESET 0xa8 +#define MANTIS_PCMCIA_RSTVAL (0xff << 0) + +#define MANTIS_CARD_RESET 0xac + +#define MANTIS_GPIF_ADDR 0xb0 +#define MANTIS_GPIF_HIFRDWRN BIT(31) +#define MANTIS_GPIF_PCMCIAREG BIT(27) +#define MANTIS_GPIF_PCMCIAIOM BIT(26) +#define MANTIS_GPIF_HIFADDR (0xfffffff << 0) + +#define MANTIS_GPIF_DOUT 0xb4 +#define MANTIS_GPIF_HIFDOUT (0xfffffff << 0) + +#define MANTIS_GPIF_DIN 0xb8 +#define MANTIS_GPIF_HIFDIN (0xfffffff << 0) + +#define MANTIS_GPIF_SPARE 0xbc +#define MANTIS_GPIF_LOGICRD (0xffff << 16) +#define MANTIS_GPIF_LOGICRW (0xffff << 0) + +#endif /* __MANTIS_REG_H */ diff --git a/drivers/media/pci/mantis/mantis_uart.c b/drivers/media/pci/mantis/mantis_uart.c new file mode 100644 index 000000000..42983a89e --- /dev/null +++ b/drivers/media/pci/mantis/mantis_uart.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mantis_common.h" +#include "mantis_reg.h" +#include "mantis_uart.h" +#include "mantis_input.h" + +struct mantis_uart_params { + enum mantis_baud baud_rate; + enum mantis_parity parity; +}; + +static struct { + char string[7]; +} rates[5] = { + { "9600" }, + { "19200" }, + { "38400" }, + { "57600" }, + { "115200" } +}; + +static struct { + char string[5]; +} parity[3] = { + { "NONE" }, + { "ODD" }, + { "EVEN" } +}; + +static void mantis_uart_read(struct mantis_pci *mantis) +{ + struct mantis_hwconfig *config = mantis->hwconfig; + int i, scancode = 0, err = 0; + + /* get data */ + dprintk(MANTIS_DEBUG, 1, "UART Reading ..."); + for (i = 0; i < (config->bytes + 1); i++) { + int data = mmread(MANTIS_UART_RXD); + + dprintk(MANTIS_DEBUG, 0, " <%02x>", data); + + scancode = (scancode << 8) | (data & 0x3f); + err |= data; + + if (data & (1 << 7)) + dprintk(MANTIS_ERROR, 1, "UART framing error"); + + if (data & (1 << 6)) + dprintk(MANTIS_ERROR, 1, "UART parity error"); + } + dprintk(MANTIS_DEBUG, 0, "\n"); + + if ((err & 0xC0) == 0) + mantis_input_process(mantis, scancode); +} + +static void mantis_uart_work(struct work_struct *work) +{ + struct mantis_pci *mantis = container_of(work, struct mantis_pci, uart_work); + u32 stat; + unsigned long timeout; + + stat = mmread(MANTIS_UART_STAT); + + if (stat & MANTIS_UART_RXFIFO_FULL) + dprintk(MANTIS_ERROR, 1, "RX Fifo FULL"); + + /* + * MANTIS_UART_RXFIFO_DATA is only set if at least + * config->bytes + 1 bytes are in the FIFO. + */ + + /* FIXME: is 10ms good enough ? */ + timeout = jiffies + msecs_to_jiffies(10); + while (stat & MANTIS_UART_RXFIFO_DATA) { + mantis_uart_read(mantis); + stat = mmread(MANTIS_UART_STAT); + + if (!time_is_after_jiffies(timeout)) + break; + } + + /* re-enable UART (RX) interrupt */ + mantis_unmask_ints(mantis, MANTIS_INT_IRQ1); +} + +static int mantis_uart_setup(struct mantis_pci *mantis, + struct mantis_uart_params *params) +{ + u32 reg; + + mmwrite((mmread(MANTIS_UART_CTL) | (params->parity & 0x3)), MANTIS_UART_CTL); + + reg = mmread(MANTIS_UART_BAUD); + + switch (params->baud_rate) { + case MANTIS_BAUD_9600: + reg |= 0xd8; + break; + case MANTIS_BAUD_19200: + reg |= 0x6c; + break; + case MANTIS_BAUD_38400: + reg |= 0x36; + break; + case MANTIS_BAUD_57600: + reg |= 0x23; + break; + case MANTIS_BAUD_115200: + reg |= 0x11; + break; + default: + return -EINVAL; + } + + mmwrite(reg, MANTIS_UART_BAUD); + + return 0; +} + +int mantis_uart_init(struct mantis_pci *mantis) +{ + struct mantis_hwconfig *config = mantis->hwconfig; + struct mantis_uart_params params; + + /* default parity: */ + params.baud_rate = config->baud_rate; + params.parity = config->parity; + dprintk(MANTIS_INFO, 1, "Initializing UART @ %sbps parity:%s", + rates[params.baud_rate].string, + parity[params.parity].string); + + INIT_WORK(&mantis->uart_work, mantis_uart_work); + + /* disable interrupt */ + mmwrite(mmread(MANTIS_UART_CTL) & 0xffef, MANTIS_UART_CTL); + + mantis_uart_setup(mantis, ¶ms); + + /* default 1 byte */ + mmwrite((mmread(MANTIS_UART_BAUD) | (config->bytes << 8)), MANTIS_UART_BAUD); + + /* flush buffer */ + mmwrite((mmread(MANTIS_UART_CTL) | MANTIS_UART_RXFLUSH), MANTIS_UART_CTL); + + /* enable interrupt */ + mmwrite(mmread(MANTIS_UART_CTL) | MANTIS_UART_RXINT, MANTIS_UART_CTL); + mantis_unmask_ints(mantis, MANTIS_INT_IRQ1); + + schedule_work(&mantis->uart_work); + dprintk(MANTIS_DEBUG, 1, "UART successfully initialized"); + + return 0; +} +EXPORT_SYMBOL_GPL(mantis_uart_init); + +void mantis_uart_exit(struct mantis_pci *mantis) +{ + /* disable interrupt */ + mantis_mask_ints(mantis, MANTIS_INT_IRQ1); + mmwrite(mmread(MANTIS_UART_CTL) & 0xffef, MANTIS_UART_CTL); + flush_work(&mantis->uart_work); +} +EXPORT_SYMBOL_GPL(mantis_uart_exit); diff --git a/drivers/media/pci/mantis/mantis_uart.h b/drivers/media/pci/mantis/mantis_uart.h new file mode 100644 index 000000000..0cc04fc3e --- /dev/null +++ b/drivers/media/pci/mantis/mantis_uart.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_UART_H +#define __MANTIS_UART_H + +#define MANTIS_UART_CTL 0xe0 +#define MANTIS_UART_RXINT (1 << 4) +#define MANTIS_UART_RXFLUSH (1 << 2) + +#define MANTIS_UART_RXD 0xe8 +#define MANTIS_UART_BAUD 0xec + +#define MANTIS_UART_STAT 0xf0 +#define MANTIS_UART_RXFIFO_DATA (1 << 7) +#define MANTIS_UART_RXFIFO_EMPTY (1 << 6) +#define MANTIS_UART_RXFIFO_FULL (1 << 3) +#define MANTIS_UART_FRAME_ERR (1 << 2) +#define MANTIS_UART_PARITY_ERR (1 << 1) +#define MANTIS_UART_RXTHRESH_INT (1 << 0) + +enum mantis_baud { + MANTIS_BAUD_9600 = 0, + MANTIS_BAUD_19200, + MANTIS_BAUD_38400, + MANTIS_BAUD_57600, + MANTIS_BAUD_115200 +}; + +enum mantis_parity { + MANTIS_PARITY_NONE = 0, + MANTIS_PARITY_EVEN, + MANTIS_PARITY_ODD, +}; + +struct mantis_pci; + +extern int mantis_uart_init(struct mantis_pci *mantis); +extern void mantis_uart_exit(struct mantis_pci *mantis); + +#endif /* __MANTIS_UART_H */ diff --git a/drivers/media/pci/mantis/mantis_vp1033.c b/drivers/media/pci/mantis/mantis_vp1033.c new file mode 100644 index 000000000..ed594e50f --- /dev/null +++ b/drivers/media/pci/mantis/mantis_vp1033.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis VP-1033 driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "stv0299.h" +#include "mantis_common.h" +#include "mantis_ioc.h" +#include "mantis_dvb.h" +#include "mantis_vp1033.h" +#include "mantis_reg.h" + +static u8 lgtdqcs001f_inittab[] = { + 0x01, 0x15, + 0x02, 0x30, + 0x03, 0x00, + 0x04, 0x2a, + 0x05, 0x85, + 0x06, 0x02, + 0x07, 0x00, + 0x08, 0x00, + 0x0c, 0x01, + 0x0d, 0x81, + 0x0e, 0x44, + 0x0f, 0x94, + 0x10, 0x3c, + 0x11, 0x84, + 0x12, 0xb9, + 0x13, 0xb5, + 0x14, 0x4f, + 0x15, 0xc9, + 0x16, 0x80, + 0x17, 0x36, + 0x18, 0xfb, + 0x19, 0xcf, + 0x1a, 0xbc, + 0x1c, 0x2b, + 0x1d, 0x27, + 0x1e, 0x00, + 0x1f, 0x0b, + 0x20, 0xa1, + 0x21, 0x60, + 0x22, 0x00, + 0x23, 0x00, + 0x28, 0x00, + 0x29, 0x28, + 0x2a, 0x14, + 0x2b, 0x0f, + 0x2c, 0x09, + 0x2d, 0x05, + 0x31, 0x1f, + 0x32, 0x19, + 0x33, 0xfc, + 0x34, 0x13, + 0xff, 0xff, +}; + +#define MANTIS_MODEL_NAME "VP-1033" +#define MANTIS_DEV_TYPE "DVB-S/DSS" + +static int lgtdqcs001f_tuner_set(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct mantis_pci *mantis = fe->dvb->priv; + struct i2c_adapter *adapter = &mantis->adapter; + + u8 buf[4]; + u32 div; + + + struct i2c_msg msg = {.addr = 0x61, .flags = 0, .buf = buf, .len = sizeof(buf)}; + + div = p->frequency / 250; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0x83; + buf[3] = 0xc0; + + if (p->frequency < 1531000) + buf[3] |= 0x04; + else + buf[3] &= ~0x04; + if (i2c_transfer(adapter, &msg, 1) < 0) { + dprintk(MANTIS_ERROR, 1, "Write: I2C Transfer failed"); + return -EIO; + } + msleep_interruptible(100); + + return 0; +} + +static int lgtdqcs001f_set_symbol_rate(struct dvb_frontend *fe, + u32 srate, u32 ratio) +{ + u8 aclk = 0; + u8 bclk = 0; + + if (srate < 1500000) { + aclk = 0xb7; + bclk = 0x47; + } else if (srate < 3000000) { + aclk = 0xb7; + bclk = 0x4b; + } else if (srate < 7000000) { + aclk = 0xb7; + bclk = 0x4f; + } else if (srate < 14000000) { + aclk = 0xb7; + bclk = 0x53; + } else if (srate < 30000000) { + aclk = 0xb6; + bclk = 0x53; + } else if (srate < 45000000) { + aclk = 0xb4; + bclk = 0x51; + } + stv0299_writereg(fe, 0x13, aclk); + stv0299_writereg(fe, 0x14, bclk); + + stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg(fe, 0x21, ratio & 0xf0); + + return 0; +} + +static struct stv0299_config lgtdqcs001f_config = { + .demod_address = 0x68, + .inittab = lgtdqcs001f_inittab, + .mclk = 88000000UL, + .invert = 0, + .skip_reinit = 0, + .volt13_op0_op1 = STV0299_VOLT13_OP0, + .min_delay_ms = 100, + .set_symbol_rate = lgtdqcs001f_set_symbol_rate, +}; + +static int vp1033_frontend_init(struct mantis_pci *mantis, struct dvb_frontend *fe) +{ + struct i2c_adapter *adapter = &mantis->adapter; + + int err = 0; + + err = mantis_frontend_power(mantis, POWER_ON); + if (err == 0) { + mantis_frontend_soft_reset(mantis); + msleep(250); + + dprintk(MANTIS_ERROR, 1, "Probing for STV0299 (DVB-S)"); + fe = dvb_attach(stv0299_attach, &lgtdqcs001f_config, adapter); + + if (fe) { + fe->ops.tuner_ops.set_params = lgtdqcs001f_tuner_set; + dprintk(MANTIS_ERROR, 1, "found STV0299 DVB-S frontend @ 0x%02x", + lgtdqcs001f_config.demod_address); + + dprintk(MANTIS_ERROR, 1, "Mantis DVB-S STV0299 frontend attach success"); + } else { + return -1; + } + } else { + dprintk(MANTIS_ERROR, 1, "Frontend on <%s> POWER ON failed! <%d>", + adapter->name, + err); + + return -EIO; + } + mantis->fe = fe; + dprintk(MANTIS_ERROR, 1, "Done!"); + + return 0; +} + +struct mantis_hwconfig vp1033_config = { + .model_name = MANTIS_MODEL_NAME, + .dev_type = MANTIS_DEV_TYPE, + .ts_size = MANTIS_TS_204, + + .baud_rate = MANTIS_BAUD_9600, + .parity = MANTIS_PARITY_NONE, + .bytes = 0, + + .frontend_init = vp1033_frontend_init, + .power = GPIF_A12, + .reset = GPIF_A13, +}; diff --git a/drivers/media/pci/mantis/mantis_vp1033.h b/drivers/media/pci/mantis/mantis_vp1033.h new file mode 100644 index 000000000..3daa989bd --- /dev/null +++ b/drivers/media/pci/mantis/mantis_vp1033.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis VP-1033 driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_VP1033_H +#define __MANTIS_VP1033_H + +#include "mantis_common.h" + +#define MANTIS_VP_1033_DVB_S 0x0016 + +extern struct mantis_hwconfig vp1033_config; + +#endif /* __MANTIS_VP1033_H */ diff --git a/drivers/media/pci/mantis/mantis_vp1034.c b/drivers/media/pci/mantis/mantis_vp1034.c new file mode 100644 index 000000000..29ae1a351 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_vp1034.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis VP-1034 driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mb86a16.h" +#include "mantis_common.h" +#include "mantis_ioc.h" +#include "mantis_dvb.h" +#include "mantis_vp1034.h" +#include "mantis_reg.h" + +static const struct mb86a16_config vp1034_mb86a16_config = { + .demod_address = 0x08, + .set_voltage = vp1034_set_voltage, +}; + +#define MANTIS_MODEL_NAME "VP-1034" +#define MANTIS_DEV_TYPE "DVB-S/DSS" + +int vp1034_set_voltage(struct dvb_frontend *fe, enum fe_sec_voltage voltage) +{ + struct mantis_pci *mantis = fe->dvb->priv; + + switch (voltage) { + case SEC_VOLTAGE_13: + dprintk(MANTIS_ERROR, 1, "Polarization=[13V]"); + mantis_gpio_set_bits(mantis, 13, 1); + mantis_gpio_set_bits(mantis, 14, 0); + break; + case SEC_VOLTAGE_18: + dprintk(MANTIS_ERROR, 1, "Polarization=[18V]"); + mantis_gpio_set_bits(mantis, 13, 1); + mantis_gpio_set_bits(mantis, 14, 1); + break; + case SEC_VOLTAGE_OFF: + dprintk(MANTIS_ERROR, 1, "Frontend (dummy) POWERDOWN"); + break; + default: + dprintk(MANTIS_ERROR, 1, "Invalid = (%d)", (u32) voltage); + return -EINVAL; + } + mmwrite(0x00, MANTIS_GPIF_DOUT); + + return 0; +} + +static int vp1034_frontend_init(struct mantis_pci *mantis, struct dvb_frontend *fe) +{ + struct i2c_adapter *adapter = &mantis->adapter; + + int err = 0; + + err = mantis_frontend_power(mantis, POWER_ON); + if (err == 0) { + mantis_frontend_soft_reset(mantis); + msleep(250); + + dprintk(MANTIS_ERROR, 1, "Probing for MB86A16 (DVB-S/DSS)"); + fe = dvb_attach(mb86a16_attach, &vp1034_mb86a16_config, adapter); + if (fe) { + dprintk(MANTIS_ERROR, 1, + "found MB86A16 DVB-S/DSS frontend @0x%02x", + vp1034_mb86a16_config.demod_address); + + } else { + return -1; + } + } else { + dprintk(MANTIS_ERROR, 1, "Frontend on <%s> POWER ON failed! <%d>", + adapter->name, + err); + + return -EIO; + } + mantis->fe = fe; + dprintk(MANTIS_ERROR, 1, "Done!"); + + return 0; +} + +struct mantis_hwconfig vp1034_config = { + .model_name = MANTIS_MODEL_NAME, + .dev_type = MANTIS_DEV_TYPE, + .ts_size = MANTIS_TS_204, + + .baud_rate = MANTIS_BAUD_9600, + .parity = MANTIS_PARITY_NONE, + .bytes = 0, + + .frontend_init = vp1034_frontend_init, + .power = GPIF_A12, + .reset = GPIF_A13, +}; diff --git a/drivers/media/pci/mantis/mantis_vp1034.h b/drivers/media/pci/mantis/mantis_vp1034.h new file mode 100644 index 000000000..d51ccdc53 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_vp1034.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis VP-1034 driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_VP1034_H +#define __MANTIS_VP1034_H + +#include +#include "mantis_common.h" + + +#define MANTIS_VP_1034_DVB_S 0x0014 + +extern struct mantis_hwconfig vp1034_config; +extern int vp1034_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage voltage); + +#endif /* __MANTIS_VP1034_H */ diff --git a/drivers/media/pci/mantis/mantis_vp1041.c b/drivers/media/pci/mantis/mantis_vp1041.c new file mode 100644 index 000000000..3f1390d80 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_vp1041.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis VP-1041 driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mantis_common.h" +#include "mantis_ioc.h" +#include "mantis_dvb.h" +#include "mantis_vp1041.h" +#include "stb0899_reg.h" +#include "stb0899_drv.h" +#include "stb0899_cfg.h" +#include "stb6100_cfg.h" +#include "stb6100.h" +#include "lnbp21.h" + +#define MANTIS_MODEL_NAME "VP-1041" +#define MANTIS_DEV_TYPE "DSS/DVB-S/DVB-S2" + +static const struct stb0899_s1_reg vp1041_stb0899_s1_init_1[] = { + + /* 0x0000000b, *//* SYSREG */ + { STB0899_DEV_ID , 0x30 }, + { STB0899_DISCNTRL1 , 0x32 }, + { STB0899_DISCNTRL2 , 0x80 }, + { STB0899_DISRX_ST0 , 0x04 }, + { STB0899_DISRX_ST1 , 0x00 }, + { STB0899_DISPARITY , 0x00 }, + { STB0899_DISSTATUS , 0x20 }, + { STB0899_DISF22 , 0x99 }, + { STB0899_DISF22RX , 0xa8 }, + /* SYSREG ? */ + { STB0899_ACRPRESC , 0x11 }, + { STB0899_ACRDIV1 , 0x0a }, + { STB0899_ACRDIV2 , 0x05 }, + { STB0899_DACR1 , 0x00 }, + { STB0899_DACR2 , 0x00 }, + { STB0899_OUTCFG , 0x00 }, + { STB0899_MODECFG , 0x00 }, + { STB0899_IRQSTATUS_3 , 0xfe }, + { STB0899_IRQSTATUS_2 , 0x03 }, + { STB0899_IRQSTATUS_1 , 0x7c }, + { STB0899_IRQSTATUS_0 , 0xf4 }, + { STB0899_IRQMSK_3 , 0xf3 }, + { STB0899_IRQMSK_2 , 0xfc }, + { STB0899_IRQMSK_1 , 0xff }, + { STB0899_IRQMSK_0 , 0xff }, + { STB0899_IRQCFG , 0x00 }, + { STB0899_I2CCFG , 0x88 }, + { STB0899_I2CRPT , 0x58 }, + { STB0899_IOPVALUE5 , 0x00 }, + { STB0899_IOPVALUE4 , 0x33 }, + { STB0899_IOPVALUE3 , 0x6d }, + { STB0899_IOPVALUE2 , 0x90 }, + { STB0899_IOPVALUE1 , 0x60 }, + { STB0899_IOPVALUE0 , 0x00 }, + { STB0899_GPIO00CFG , 0x82 }, + { STB0899_GPIO01CFG , 0x82 }, + { STB0899_GPIO02CFG , 0x82 }, + { STB0899_GPIO03CFG , 0x82 }, + { STB0899_GPIO04CFG , 0x82 }, + { STB0899_GPIO05CFG , 0x82 }, + { STB0899_GPIO06CFG , 0x82 }, + { STB0899_GPIO07CFG , 0x82 }, + { STB0899_GPIO08CFG , 0x82 }, + { STB0899_GPIO09CFG , 0x82 }, + { STB0899_GPIO10CFG , 0x82 }, + { STB0899_GPIO11CFG , 0x82 }, + { STB0899_GPIO12CFG , 0x82 }, + { STB0899_GPIO13CFG , 0x82 }, + { STB0899_GPIO14CFG , 0x82 }, + { STB0899_GPIO15CFG , 0x82 }, + { STB0899_GPIO16CFG , 0x82 }, + { STB0899_GPIO17CFG , 0x82 }, + { STB0899_GPIO18CFG , 0x82 }, + { STB0899_GPIO19CFG , 0x82 }, + { STB0899_GPIO20CFG , 0x82 }, + { STB0899_SDATCFG , 0xb8 }, + { STB0899_SCLTCFG , 0xba }, + { STB0899_AGCRFCFG , 0x1c }, /* 0x11 */ + { STB0899_GPIO22 , 0x82 }, /* AGCBB2CFG */ + { STB0899_GPIO21 , 0x91 }, /* AGCBB1CFG */ + { STB0899_DIRCLKCFG , 0x82 }, + { STB0899_CLKOUT27CFG , 0x7e }, + { STB0899_STDBYCFG , 0x82 }, + { STB0899_CS0CFG , 0x82 }, + { STB0899_CS1CFG , 0x82 }, + { STB0899_DISEQCOCFG , 0x20 }, + { STB0899_GPIO32CFG , 0x82 }, + { STB0899_GPIO33CFG , 0x82 }, + { STB0899_GPIO34CFG , 0x82 }, + { STB0899_GPIO35CFG , 0x82 }, + { STB0899_GPIO36CFG , 0x82 }, + { STB0899_GPIO37CFG , 0x82 }, + { STB0899_GPIO38CFG , 0x82 }, + { STB0899_GPIO39CFG , 0x82 }, + { STB0899_NCOARSE , 0x17 }, /* 0x15 = 27 Mhz Clock, F/3 = 198MHz, F/6 = 99MHz */ + { STB0899_SYNTCTRL , 0x02 }, /* 0x00 = CLK from CLKI, 0x02 = CLK from XTALI */ + { STB0899_FILTCTRL , 0x00 }, + { STB0899_SYSCTRL , 0x01 }, + { STB0899_STOPCLK1 , 0x20 }, + { STB0899_STOPCLK2 , 0x00 }, + { STB0899_INTBUFSTATUS , 0x00 }, + { STB0899_INTBUFCTRL , 0x0a }, + { 0xffff , 0xff }, +}; + +static const struct stb0899_s1_reg vp1041_stb0899_s1_init_3[] = { + { STB0899_DEMOD , 0x00 }, + { STB0899_RCOMPC , 0xc9 }, + { STB0899_AGC1CN , 0x01 }, + { STB0899_AGC1REF , 0x10 }, + { STB0899_RTC , 0x23 }, + { STB0899_TMGCFG , 0x4e }, + { STB0899_AGC2REF , 0x34 }, + { STB0899_TLSR , 0x84 }, + { STB0899_CFD , 0xf7 }, + { STB0899_ACLC , 0x87 }, + { STB0899_BCLC , 0x94 }, + { STB0899_EQON , 0x41 }, + { STB0899_LDT , 0xf1 }, + { STB0899_LDT2 , 0xe3 }, + { STB0899_EQUALREF , 0xb4 }, + { STB0899_TMGRAMP , 0x10 }, + { STB0899_TMGTHD , 0x30 }, + { STB0899_IDCCOMP , 0xfd }, + { STB0899_QDCCOMP , 0xff }, + { STB0899_POWERI , 0x0c }, + { STB0899_POWERQ , 0x0f }, + { STB0899_RCOMP , 0x6c }, + { STB0899_AGCIQIN , 0x80 }, + { STB0899_AGC2I1 , 0x06 }, + { STB0899_AGC2I2 , 0x00 }, + { STB0899_TLIR , 0x30 }, + { STB0899_RTF , 0x7f }, + { STB0899_DSTATUS , 0x00 }, + { STB0899_LDI , 0xbc }, + { STB0899_CFRM , 0xea }, + { STB0899_CFRL , 0x31 }, + { STB0899_NIRM , 0x2b }, + { STB0899_NIRL , 0x80 }, + { STB0899_ISYMB , 0x1d }, + { STB0899_QSYMB , 0xa6 }, + { STB0899_SFRH , 0x2f }, + { STB0899_SFRM , 0x68 }, + { STB0899_SFRL , 0x40 }, + { STB0899_SFRUPH , 0x2f }, + { STB0899_SFRUPM , 0x68 }, + { STB0899_SFRUPL , 0x40 }, + { STB0899_EQUAI1 , 0x02 }, + { STB0899_EQUAQ1 , 0xff }, + { STB0899_EQUAI2 , 0x04 }, + { STB0899_EQUAQ2 , 0x05 }, + { STB0899_EQUAI3 , 0x02 }, + { STB0899_EQUAQ3 , 0xfd }, + { STB0899_EQUAI4 , 0x03 }, + { STB0899_EQUAQ4 , 0x07 }, + { STB0899_EQUAI5 , 0x08 }, + { STB0899_EQUAQ5 , 0xf5 }, + { STB0899_DSTATUS2 , 0x00 }, + { STB0899_VSTATUS , 0x00 }, + { STB0899_VERROR , 0x86 }, + { STB0899_IQSWAP , 0x2a }, + { STB0899_ECNT1M , 0x00 }, + { STB0899_ECNT1L , 0x00 }, + { STB0899_ECNT2M , 0x00 }, + { STB0899_ECNT2L , 0x00 }, + { STB0899_ECNT3M , 0x0a }, + { STB0899_ECNT3L , 0xad }, + { STB0899_FECAUTO1 , 0x06 }, + { STB0899_FECM , 0x01 }, + { STB0899_VTH12 , 0xb0 }, + { STB0899_VTH23 , 0x7a }, + { STB0899_VTH34 , 0x58 }, + { STB0899_VTH56 , 0x38 }, + { STB0899_VTH67 , 0x34 }, + { STB0899_VTH78 , 0x24 }, + { STB0899_PRVIT , 0xff }, + { STB0899_VITSYNC , 0x19 }, + { STB0899_RSULC , 0xb1 }, /* DVB = 0xb1, DSS = 0xa1 */ + { STB0899_TSULC , 0x42 }, + { STB0899_RSLLC , 0x41 }, + { STB0899_TSLPL , 0x12 }, + { STB0899_TSCFGH , 0x0c }, + { STB0899_TSCFGM , 0x00 }, + { STB0899_TSCFGL , 0x00 }, + { STB0899_TSOUT , 0x69 }, /* 0x0d for CAM */ + { STB0899_RSSYNCDEL , 0x00 }, + { STB0899_TSINHDELH , 0x02 }, + { STB0899_TSINHDELM , 0x00 }, + { STB0899_TSINHDELL , 0x00 }, + { STB0899_TSLLSTKM , 0x1b }, + { STB0899_TSLLSTKL , 0xb3 }, + { STB0899_TSULSTKM , 0x00 }, + { STB0899_TSULSTKL , 0x00 }, + { STB0899_PCKLENUL , 0xbc }, + { STB0899_PCKLENLL , 0xcc }, + { STB0899_RSPCKLEN , 0xbd }, + { STB0899_TSSTATUS , 0x90 }, + { STB0899_ERRCTRL1 , 0xb6 }, + { STB0899_ERRCTRL2 , 0x95 }, + { STB0899_ERRCTRL3 , 0x8d }, + { STB0899_DMONMSK1 , 0x27 }, + { STB0899_DMONMSK0 , 0x03 }, + { STB0899_DEMAPVIT , 0x5c }, + { STB0899_PLPARM , 0x19 }, + { STB0899_PDELCTRL , 0x48 }, + { STB0899_PDELCTRL2 , 0x00 }, + { STB0899_BBHCTRL1 , 0x00 }, + { STB0899_BBHCTRL2 , 0x00 }, + { STB0899_HYSTTHRESH , 0x77 }, + { STB0899_MATCSTM , 0x00 }, + { STB0899_MATCSTL , 0x00 }, + { STB0899_UPLCSTM , 0x00 }, + { STB0899_UPLCSTL , 0x00 }, + { STB0899_DFLCSTM , 0x00 }, + { STB0899_DFLCSTL , 0x00 }, + { STB0899_SYNCCST , 0x00 }, + { STB0899_SYNCDCSTM , 0x00 }, + { STB0899_SYNCDCSTL , 0x00 }, + { STB0899_ISI_ENTRY , 0x00 }, + { STB0899_ISI_BIT_EN , 0x00 }, + { STB0899_MATSTRM , 0xf0 }, + { STB0899_MATSTRL , 0x02 }, + { STB0899_UPLSTRM , 0x45 }, + { STB0899_UPLSTRL , 0x60 }, + { STB0899_DFLSTRM , 0xe3 }, + { STB0899_DFLSTRL , 0x00 }, + { STB0899_SYNCSTR , 0x47 }, + { STB0899_SYNCDSTRM , 0x05 }, + { STB0899_SYNCDSTRL , 0x18 }, + { STB0899_CFGPDELSTATUS1 , 0x19 }, + { STB0899_CFGPDELSTATUS2 , 0x2b }, + { STB0899_BBFERRORM , 0x00 }, + { STB0899_BBFERRORL , 0x01 }, + { STB0899_UPKTERRORM , 0x00 }, + { STB0899_UPKTERRORL , 0x00 }, + { 0xffff , 0xff }, +}; + +static struct stb0899_config vp1041_stb0899_config = { + .init_dev = vp1041_stb0899_s1_init_1, + .init_s2_demod = stb0899_s2_init_2, + .init_s1_demod = vp1041_stb0899_s1_init_3, + .init_s2_fec = stb0899_s2_init_4, + .init_tst = stb0899_s1_init_5, + + .demod_address = 0x68, /* 0xd0 >> 1 */ + + .xtal_freq = 27000000, + .inversion = IQ_SWAP_ON, + + .lo_clk = 76500000, + .hi_clk = 99000000, + + .esno_ave = STB0899_DVBS2_ESNO_AVE, + .esno_quant = STB0899_DVBS2_ESNO_QUANT, + .avframes_coarse = STB0899_DVBS2_AVFRAMES_COARSE, + .avframes_fine = STB0899_DVBS2_AVFRAMES_FINE, + .miss_threshold = STB0899_DVBS2_MISS_THRESHOLD, + .uwp_threshold_acq = STB0899_DVBS2_UWP_THRESHOLD_ACQ, + .uwp_threshold_track = STB0899_DVBS2_UWP_THRESHOLD_TRACK, + .uwp_threshold_sof = STB0899_DVBS2_UWP_THRESHOLD_SOF, + .sof_search_timeout = STB0899_DVBS2_SOF_SEARCH_TIMEOUT, + + .btr_nco_bits = STB0899_DVBS2_BTR_NCO_BITS, + .btr_gain_shift_offset = STB0899_DVBS2_BTR_GAIN_SHIFT_OFFSET, + .crl_nco_bits = STB0899_DVBS2_CRL_NCO_BITS, + .ldpc_max_iter = STB0899_DVBS2_LDPC_MAX_ITER, + + .tuner_get_frequency = stb6100_get_frequency, + .tuner_set_frequency = stb6100_set_frequency, + .tuner_set_bandwidth = stb6100_set_bandwidth, + .tuner_get_bandwidth = stb6100_get_bandwidth, + .tuner_set_rfsiggain = NULL, +}; + +static struct stb6100_config vp1041_stb6100_config = { + .tuner_address = 0x60, + .refclock = 27000000, +}; + +static int vp1041_frontend_init(struct mantis_pci *mantis, struct dvb_frontend *fe) +{ + struct i2c_adapter *adapter = &mantis->adapter; + + int err = 0; + + err = mantis_frontend_power(mantis, POWER_ON); + if (err == 0) { + mantis_frontend_soft_reset(mantis); + msleep(250); + mantis->fe = dvb_attach(stb0899_attach, &vp1041_stb0899_config, adapter); + if (mantis->fe) { + dprintk(MANTIS_ERROR, 1, + "found STB0899 DVB-S/DVB-S2 frontend @0x%02x", + vp1041_stb0899_config.demod_address); + + if (dvb_attach(stb6100_attach, mantis->fe, &vp1041_stb6100_config, adapter)) { + if (!dvb_attach(lnbp21_attach, mantis->fe, adapter, 0, 0)) + dprintk(MANTIS_ERROR, 1, "No LNBP21 found!"); + } + } else { + return -EREMOTEIO; + } + } else { + dprintk(MANTIS_ERROR, 1, "Frontend on <%s> POWER ON failed! <%d>", + adapter->name, + err); + + return -EIO; + } + + + dprintk(MANTIS_ERROR, 1, "Done!"); + + return 0; +} + +struct mantis_hwconfig vp1041_config = { + .model_name = MANTIS_MODEL_NAME, + .dev_type = MANTIS_DEV_TYPE, + .ts_size = MANTIS_TS_188, + + .baud_rate = MANTIS_BAUD_9600, + .parity = MANTIS_PARITY_NONE, + .bytes = 0, + + .frontend_init = vp1041_frontend_init, + .power = GPIF_A12, + .reset = GPIF_A13, +}; diff --git a/drivers/media/pci/mantis/mantis_vp1041.h b/drivers/media/pci/mantis/mantis_vp1041.h new file mode 100644 index 000000000..5a43aeb3c --- /dev/null +++ b/drivers/media/pci/mantis/mantis_vp1041.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis VP-1041 driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_VP1041_H +#define __MANTIS_VP1041_H + +#include "mantis_common.h" + +#define MANTIS_VP_1041_DVB_S2 0x0031 +#define SKYSTAR_HD2_10 0x0001 +#define SKYSTAR_HD2_20 0x0003 +#define CINERGY_S2_PCI_HD 0x1179 + +extern struct mantis_hwconfig vp1041_config; + +#endif /* __MANTIS_VP1041_H */ diff --git a/drivers/media/pci/mantis/mantis_vp2033.c b/drivers/media/pci/mantis/mantis_vp2033.c new file mode 100644 index 000000000..861c1e493 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_vp2033.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis VP-2033 driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tda1002x.h" +#include "mantis_common.h" +#include "mantis_ioc.h" +#include "mantis_dvb.h" +#include "mantis_vp2033.h" + +#define MANTIS_MODEL_NAME "VP-2033" +#define MANTIS_DEV_TYPE "DVB-C" + +static struct tda1002x_config vp2033_tda1002x_cu1216_config = { + .demod_address = 0x18 >> 1, + .invert = 1, +}; + +static struct tda10023_config vp2033_tda10023_cu1216_config = { + .demod_address = 0x18 >> 1, + .invert = 1, +}; + +static u8 read_pwm(struct mantis_pci *mantis) +{ + struct i2c_adapter *adapter = &mantis->adapter; + + u8 b = 0xff; + u8 pwm; + struct i2c_msg msg[] = { + {.addr = 0x50, .flags = 0, .buf = &b, .len = 1}, + {.addr = 0x50, .flags = I2C_M_RD, .buf = &pwm, .len = 1} + }; + + if ((i2c_transfer(adapter, msg, 2) != 2) + || (pwm == 0xff)) + pwm = 0x48; + + return pwm; +} + +static int tda1002x_cu1216_tuner_set(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct mantis_pci *mantis = fe->dvb->priv; + struct i2c_adapter *adapter = &mantis->adapter; + + u8 buf[6]; + struct i2c_msg msg = {.addr = 0x60, .flags = 0, .buf = buf, .len = sizeof(buf)}; + int i; + +#define CU1216_IF 36125000 +#define TUNER_MUL 62500 + + u32 div = (p->frequency + CU1216_IF + TUNER_MUL / 2) / TUNER_MUL; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0xce; + buf[3] = (p->frequency < 150000000 ? 0x01 : + p->frequency < 445000000 ? 0x02 : 0x04); + buf[4] = 0xde; + buf[5] = 0x20; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + if (i2c_transfer(adapter, &msg, 1) != 1) + return -EIO; + + /* wait for the pll lock */ + msg.flags = I2C_M_RD; + msg.len = 1; + for (i = 0; i < 20; i++) { + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + if (i2c_transfer(adapter, &msg, 1) == 1 && (buf[0] & 0x40)) + break; + + msleep(10); + } + + /* switch the charge pump to the lower current */ + msg.flags = 0; + msg.len = 2; + msg.buf = &buf[2]; + buf[2] &= ~0x40; + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + if (i2c_transfer(adapter, &msg, 1) != 1) + return -EIO; + + return 0; +} + +static int vp2033_frontend_init(struct mantis_pci *mantis, struct dvb_frontend *fe) +{ + struct i2c_adapter *adapter = &mantis->adapter; + + int err = 0; + + err = mantis_frontend_power(mantis, POWER_ON); + if (err == 0) { + mantis_frontend_soft_reset(mantis); + msleep(250); + + dprintk(MANTIS_ERROR, 1, "Probing for CU1216 (DVB-C)"); + fe = dvb_attach(tda10021_attach, &vp2033_tda1002x_cu1216_config, + adapter, + read_pwm(mantis)); + + if (fe) { + dprintk(MANTIS_ERROR, 1, + "found Philips CU1216 DVB-C frontend (TDA10021) @ 0x%02x", + vp2033_tda1002x_cu1216_config.demod_address); + } else { + fe = dvb_attach(tda10023_attach, &vp2033_tda10023_cu1216_config, + adapter, + read_pwm(mantis)); + + if (fe) { + dprintk(MANTIS_ERROR, 1, + "found Philips CU1216 DVB-C frontend (TDA10023) @ 0x%02x", + vp2033_tda1002x_cu1216_config.demod_address); + } + } + + if (fe) { + fe->ops.tuner_ops.set_params = tda1002x_cu1216_tuner_set; + dprintk(MANTIS_ERROR, 1, "Mantis DVB-C Philips CU1216 frontend attach success"); + } else { + return -1; + } + } else { + dprintk(MANTIS_ERROR, 1, "Frontend on <%s> POWER ON failed! <%d>", + adapter->name, + err); + + return -EIO; + } + + mantis->fe = fe; + dprintk(MANTIS_DEBUG, 1, "Done!"); + + return 0; +} + +struct mantis_hwconfig vp2033_config = { + .model_name = MANTIS_MODEL_NAME, + .dev_type = MANTIS_DEV_TYPE, + .ts_size = MANTIS_TS_204, + + .baud_rate = MANTIS_BAUD_9600, + .parity = MANTIS_PARITY_NONE, + .bytes = 0, + + .frontend_init = vp2033_frontend_init, + .power = GPIF_A12, + .reset = GPIF_A13, +}; diff --git a/drivers/media/pci/mantis/mantis_vp2033.h b/drivers/media/pci/mantis/mantis_vp2033.h new file mode 100644 index 000000000..e2483d1c3 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_vp2033.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis VP-2033 driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_VP2033_H +#define __MANTIS_VP2033_H + +#include "mantis_common.h" + +#define MANTIS_VP_2033_DVB_C 0x0008 + +extern struct mantis_hwconfig vp2033_config; + +#endif /* __MANTIS_VP2033_H */ diff --git a/drivers/media/pci/mantis/mantis_vp2040.c b/drivers/media/pci/mantis/mantis_vp2040.c new file mode 100644 index 000000000..67795adb8 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_vp2040.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis VP-2040 driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tda1002x.h" +#include "mantis_common.h" +#include "mantis_ioc.h" +#include "mantis_dvb.h" +#include "mantis_vp2040.h" + +#define MANTIS_MODEL_NAME "VP-2040" +#define MANTIS_DEV_TYPE "DVB-C" + +static struct tda1002x_config vp2040_tda1002x_cu1216_config = { + .demod_address = 0x18 >> 1, + .invert = 1, +}; + +static struct tda10023_config vp2040_tda10023_cu1216_config = { + .demod_address = 0x18 >> 1, + .invert = 1, +}; + +static int tda1002x_cu1216_tuner_set(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct mantis_pci *mantis = fe->dvb->priv; + struct i2c_adapter *adapter = &mantis->adapter; + + u8 buf[6]; + struct i2c_msg msg = {.addr = 0x60, .flags = 0, .buf = buf, .len = sizeof(buf)}; + int i; + +#define CU1216_IF 36125000 +#define TUNER_MUL 62500 + + u32 div = (p->frequency + CU1216_IF + TUNER_MUL / 2) / TUNER_MUL; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0xce; + buf[3] = (p->frequency < 150000000 ? 0x01 : + p->frequency < 445000000 ? 0x02 : 0x04); + buf[4] = 0xde; + buf[5] = 0x20; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + if (i2c_transfer(adapter, &msg, 1) != 1) + return -EIO; + + /* wait for the pll lock */ + msg.flags = I2C_M_RD; + msg.len = 1; + for (i = 0; i < 20; i++) { + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + if (i2c_transfer(adapter, &msg, 1) == 1 && (buf[0] & 0x40)) + break; + + msleep(10); + } + + /* switch the charge pump to the lower current */ + msg.flags = 0; + msg.len = 2; + msg.buf = &buf[2]; + buf[2] &= ~0x40; + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + if (i2c_transfer(adapter, &msg, 1) != 1) + return -EIO; + + return 0; +} + +static u8 read_pwm(struct mantis_pci *mantis) +{ + struct i2c_adapter *adapter = &mantis->adapter; + + u8 b = 0xff; + u8 pwm; + struct i2c_msg msg[] = { + {.addr = 0x50, .flags = 0, .buf = &b, .len = 1}, + {.addr = 0x50, .flags = I2C_M_RD, .buf = &pwm, .len = 1} + }; + + if ((i2c_transfer(adapter, msg, 2) != 2) + || (pwm == 0xff)) + pwm = 0x48; + + return pwm; +} + +static int vp2040_frontend_init(struct mantis_pci *mantis, struct dvb_frontend *fe) +{ + struct i2c_adapter *adapter = &mantis->adapter; + + int err = 0; + + err = mantis_frontend_power(mantis, POWER_ON); + if (err == 0) { + mantis_frontend_soft_reset(mantis); + msleep(250); + + dprintk(MANTIS_ERROR, 1, "Probing for CU1216 (DVB-C)"); + fe = dvb_attach(tda10021_attach, &vp2040_tda1002x_cu1216_config, + adapter, + read_pwm(mantis)); + + if (fe) { + dprintk(MANTIS_ERROR, 1, + "found Philips CU1216 DVB-C frontend (TDA10021) @ 0x%02x", + vp2040_tda1002x_cu1216_config.demod_address); + } else { + fe = dvb_attach(tda10023_attach, &vp2040_tda10023_cu1216_config, + adapter, + read_pwm(mantis)); + + if (fe) { + dprintk(MANTIS_ERROR, 1, + "found Philips CU1216 DVB-C frontend (TDA10023) @ 0x%02x", + vp2040_tda1002x_cu1216_config.demod_address); + } + } + + if (fe) { + fe->ops.tuner_ops.set_params = tda1002x_cu1216_tuner_set; + dprintk(MANTIS_ERROR, 1, "Mantis DVB-C Philips CU1216 frontend attach success"); + } else { + return -1; + } + } else { + dprintk(MANTIS_ERROR, 1, "Frontend on <%s> POWER ON failed! <%d>", + adapter->name, + err); + + return -EIO; + } + mantis->fe = fe; + dprintk(MANTIS_DEBUG, 1, "Done!"); + + return 0; +} + +struct mantis_hwconfig vp2040_config = { + .model_name = MANTIS_MODEL_NAME, + .dev_type = MANTIS_DEV_TYPE, + .ts_size = MANTIS_TS_204, + + .baud_rate = MANTIS_BAUD_9600, + .parity = MANTIS_PARITY_NONE, + .bytes = 0, + + .frontend_init = vp2040_frontend_init, + .power = GPIF_A12, + .reset = GPIF_A13, +}; diff --git a/drivers/media/pci/mantis/mantis_vp2040.h b/drivers/media/pci/mantis/mantis_vp2040.h new file mode 100644 index 000000000..e50a02f70 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_vp2040.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis VP-2040 driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_VP2040_H +#define __MANTIS_VP2040_H + +#include "mantis_common.h" + +#define MANTIS_VP_2040_DVB_C 0x0043 +#define CINERGY_C 0x1178 +#define CABLESTAR_HD2 0x0002 + +extern struct mantis_hwconfig vp2040_config; + +#endif /* __MANTIS_VP2040_H */ diff --git a/drivers/media/pci/mantis/mantis_vp3030.c b/drivers/media/pci/mantis/mantis_vp3030.c new file mode 100644 index 000000000..0f6b02591 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_vp3030.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Mantis VP-3030 driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "zl10353.h" +#include "tda665x.h" +#include "mantis_common.h" +#include "mantis_ioc.h" +#include "mantis_dvb.h" +#include "mantis_vp3030.h" + +static struct zl10353_config mantis_vp3030_config = { + .demod_address = 0x0f, +}; + +static struct tda665x_config env57h12d5_config = { + .name = "ENV57H12D5 (ET-50DT)", + .addr = 0x60, + .frequency_min = 47 * MHz, + .frequency_max = 862 * MHz, + .frequency_offst = 3616667, + .ref_multiplier = 6, /* 1/6 MHz */ + .ref_divider = 100000, /* 1/6 MHz */ +}; + +#define MANTIS_MODEL_NAME "VP-3030" +#define MANTIS_DEV_TYPE "DVB-T" + + +static int vp3030_frontend_init(struct mantis_pci *mantis, struct dvb_frontend *fe) +{ + struct i2c_adapter *adapter = &mantis->adapter; + struct mantis_hwconfig *config = mantis->hwconfig; + int err = 0; + + mantis_gpio_set_bits(mantis, config->reset, 0); + msleep(100); + err = mantis_frontend_power(mantis, POWER_ON); + msleep(100); + mantis_gpio_set_bits(mantis, config->reset, 1); + + if (err == 0) { + msleep(250); + dprintk(MANTIS_ERROR, 1, "Probing for 10353 (DVB-T)"); + fe = dvb_attach(zl10353_attach, &mantis_vp3030_config, adapter); + + if (!fe) + return -1; + + dvb_attach(tda665x_attach, fe, &env57h12d5_config, adapter); + } else { + dprintk(MANTIS_ERROR, 1, "Frontend on <%s> POWER ON failed! <%d>", + adapter->name, + err); + + return -EIO; + + } + mantis->fe = fe; + dprintk(MANTIS_ERROR, 1, "Done!"); + + return 0; +} + +struct mantis_hwconfig vp3030_config = { + .model_name = MANTIS_MODEL_NAME, + .dev_type = MANTIS_DEV_TYPE, + .ts_size = MANTIS_TS_188, + + .baud_rate = MANTIS_BAUD_9600, + .parity = MANTIS_PARITY_NONE, + .bytes = 0, + + .frontend_init = vp3030_frontend_init, + .power = GPIF_A12, + .reset = GPIF_A13, + + .i2c_mode = MANTIS_BYTE_MODE +}; diff --git a/drivers/media/pci/mantis/mantis_vp3030.h b/drivers/media/pci/mantis/mantis_vp3030.h new file mode 100644 index 000000000..f4787079a --- /dev/null +++ b/drivers/media/pci/mantis/mantis_vp3030.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Mantis VP-3030 driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + +*/ + +#ifndef __MANTIS_VP3030_H +#define __MANTIS_VP3030_H + +#include "mantis_common.h" + +#define MANTIS_VP_3030_DVB_T 0x0024 + +extern struct mantis_hwconfig vp3030_config; + +#endif /* __MANTIS_VP3030_H */ diff --git a/drivers/media/pci/mgb4/Kconfig b/drivers/media/pci/mgb4/Kconfig new file mode 100644 index 000000000..f2a05a1c8 --- /dev/null +++ b/drivers/media/pci/mgb4/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_MGB4 + tristate "Digiteq Automotive MGB4 support" + depends on VIDEO_DEV && PCI && I2C && DMADEVICES && SPI && MTD && IIO + depends on COMMON_CLK + select VIDEOBUF2_DMA_SG + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select I2C_XILINX + select SPI_XILINX + select MTD_SPI_NOR + select XILINX_XDMA + help + This is a video4linux driver for Digiteq Automotive MGB4 grabber + cards. + + To compile this driver as a module, choose M here: the + module will be called mgb4. diff --git a/drivers/media/pci/mgb4/Makefile b/drivers/media/pci/mgb4/Makefile new file mode 100644 index 000000000..e92ead18b --- /dev/null +++ b/drivers/media/pci/mgb4/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +mgb4-objs := mgb4_regs.o mgb4_core.o mgb4_vin.o mgb4_vout.o \ + mgb4_sysfs_pci.o mgb4_sysfs_in.o mgb4_sysfs_out.o \ + mgb4_i2c.o mgb4_cmt.o mgb4_trigger.o mgb4_dma.o + +obj-$(CONFIG_VIDEO_MGB4) += mgb4.o diff --git a/drivers/media/pci/mgb4/mgb4_cmt.c b/drivers/media/pci/mgb4/mgb4_cmt.c new file mode 100644 index 000000000..c22ef5143 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_cmt.c @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + * + * The CMT module configures the FPGA Clock Management Tile (CMT) registers. For + * different video signal frequencies (FPGA input signal frequencies), the FPGA + * CMT registers need to be adjusted for the FPGA to work properly. The values + * are precomputed based on formulas given by Xilinx in their FPGA documentation + * (which are in turn full of some magic values/tables...). + */ + +#include +#include +#include "mgb4_core.h" +#include "mgb4_cmt.h" + +static const u16 cmt_vals_out[][15] = { + {0x1208, 0x0000, 0x171C, 0x0000, 0x1E38, 0x0000, 0x11C7, 0x0000, 0x1041, 0x01BC, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, }, + {0x11C7, 0x0000, 0x1619, 0x0080, 0x1C71, 0x0000, 0x130D, 0x0080, 0x0041, 0x0090, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x9000, }, + {0x11C7, 0x0000, 0x1619, 0x0080, 0x1C71, 0x0000, 0x165A, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x11C7, 0x0000, 0x1619, 0x0080, 0x1C71, 0x0000, 0x1187, 0x0080, 0x1041, 0x01EE, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, }, + {0x1186, 0x0000, 0x1555, 0x0000, 0x1AAA, 0x0000, 0x1451, 0x0000, 0x0042, 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x11C7, 0x0000, 0x1619, 0x0080, 0x1C71, 0x0000, 0x134E, 0x0080, 0x0041, 0x005E, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x1619, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x179E, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x179F, 0x0080, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x17DF, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x8800, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x128B, 0x0080, 0x0041, 0x00DB, 0x7C01, 0x7DE9, 0xFFFF, 0x9000, 0x0100, }, + {0x1186, 0x0000, 0x1555, 0x0000, 0x1AAA, 0x0000, 0x1820, 0x0000, 0x0083, 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, }, + {0x1186, 0x0000, 0x1555, 0x0000, 0x1AAA, 0x0000, 0x1187, 0x0080, 0x1041, 0x01EE, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x169B, 0x0080, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x171C, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x1186, 0x0000, 0x1555, 0x0000, 0x1AAA, 0x0000, 0x1515, 0x0080, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x1493, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x15D8, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x124A, 0x0080, 0x0041, 0x010D, 0x7C01, 0x7DE9, 0xFFFF, 0x9000, 0x0100, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x175D, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x1619, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x17DF, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x8800, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x17E0, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x9000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x1820, 0x0000, 0x0083, 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x13D0, 0x0080, 0x0042, 0x002C, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x128B, 0x0080, 0x0041, 0x00DB, 0x7C01, 0x7DE9, 0xFFFF, 0x9000, 0x0100, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x1820, 0x0000, 0x00C3, 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x134E, 0x0080, 0x0041, 0x005E, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x1515, 0x0080, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x175D, 0x0000, 0x00C4, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x11C7, 0x0000, 0x1041, 0x01BC, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1209, 0x0080, 0x0041, 0x013F, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x1100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1556, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x8000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x179F, 0x0080, 0x00C4, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x15D8, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1105, 0x0080, 0x1041, 0x01E8, 0x6401, 0x65E9, 0xFFFF, 0x9800, 0x1100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1820, 0x0000, 0x00C4, 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x1493, 0x0080, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x138E, 0x0000, 0x0042, 0x005E, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x17E0, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x9000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x165A, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x175D, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x1187, 0x0080, 0x1041, 0x01EE, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x175E, 0x0080, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x179E, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x134E, 0x0080, 0x0041, 0x005E, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x165A, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x16DC, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x169A, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x11C7, 0x0000, 0x1041, 0x01BC, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x169B, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, }, + {0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x171D, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x16DB, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1146, 0x0080, 0x1041, 0x0184, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x171C, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1451, 0x0000, 0x0042, 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x171D, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x175D, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1452, 0x0080, 0x0042, 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x15D8, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1104, 0x0000, 0x1041, 0x01E8, 0x5801, 0x59E9, 0xFFFF, 0x9900, 0x0900, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x179F, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1515, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x17DF, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x8800, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1659, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1555, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x8000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x14D3, 0x0000, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1820, 0x0000, 0x0083, 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1556, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x8000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1187, 0x0080, 0x1041, 0x01EE, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1452, 0x0080, 0x0082, 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x169B, 0x0080, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1514, 0x0000, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x17E0, 0x0080, 0x00C4, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x9000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1515, 0x0080, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x16DC, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1493, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x15D8, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x171D, 0x0080, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1618, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x175D, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x14D4, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1619, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x179E, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x179F, 0x0080, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1515, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x13D0, 0x0080, 0x0042, 0x002C, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x169A, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x128B, 0x0080, 0x0041, 0x00DB, 0x7C01, 0x7DE9, 0xFFFF, 0x9000, 0x0100, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x169B, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1820, 0x0000, 0x00C3, 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1556, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x8000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x16DB, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1411, 0x0080, 0x0042, 0x002C, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1597, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x8000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1451, 0x0000, 0x0042, 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x171D, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x12CC, 0x0080, 0x0041, 0x00A9, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x175D, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1452, 0x0080, 0x0042, 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x15D8, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x175E, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1492, 0x0000, 0x0042, 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x179F, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1619, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1493, 0x0080, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x17DF, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x8800, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x130D, 0x0080, 0x0041, 0x0090, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x17E0, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x14D3, 0x0000, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x165A, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1820, 0x0000, 0x0083, 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x14D4, 0x0080, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, }, + {0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x169B, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, }, +}; + +static const u16 cmt_vals_in[][13] = { + {0x1104, 0x0000, 0x9208, 0x0000, 0x138E, 0x0000, 0x1041, 0x015E, 0x7C01, 0xFFE9, 0x0100, 0x0908, 0x1000}, + {0x1082, 0x0000, 0x5104, 0x0000, 0x11C7, 0x0000, 0x1041, 0x02BC, 0x7C01, 0xFFE9, 0x9900, 0x9908, 0x8100}, +}; + +static const u32 cmt_addrs_out[][15] = { + {0x420, 0x424, 0x428, 0x42C, 0x430, 0x434, 0x450, 0x454, 0x458, 0x460, 0x464, 0x468, 0x4A0, 0x538, 0x53C}, + {0x620, 0x624, 0x628, 0x62C, 0x630, 0x634, 0x650, 0x654, 0x658, 0x660, 0x664, 0x668, 0x6A0, 0x738, 0x73C}, +}; + +static const u32 cmt_addrs_in[][13] = { + {0x020, 0x024, 0x028, 0x02C, 0x050, 0x054, 0x058, 0x060, 0x064, 0x068, 0x0A0, 0x138, 0x13C}, + {0x220, 0x224, 0x228, 0x22C, 0x250, 0x254, 0x258, 0x260, 0x264, 0x268, 0x2A0, 0x338, 0x33C}, +}; + +static const u32 cmt_freq[] = { + 25000, 25510, 26020, 26530, 26983, 27551, 28000, 28570, + 29046, 29522, 30000, 30476, 30952, 31546, 32000, 32539, + 33035, 33571, 33928, 34522, 35000, 35428, 36000, 36571, + 36904, 37500, 38093, 38571, 39047, 39453, 40000, 40476, + 40952, 41494, 41964, 42857, 43535, 44047, 44444, 45000, + 45535, 46029, 46428, 46823, 47617, 48214, 48571, 49107, + 49523, 50000, 50476, 50892, 51428, 52380, 53333, 53967, + 54285, 55238, 55555, 55952, 57142, 58095, 58571, 59047, + 59521, 60000, 60316, 60952, 61428, 61904, 62500, 63092, + 63491, 64282, 65078, 65476, 66071, 66664, 67142, 67854, + 68571, 69044, 69642, 70000, 71425, 72616, 73214, 73808, + 74285, 75000, 75714, 76187, 76785, 77142, 78570, 80000, + 80357, 80951, 81428, 82142, 82857, 83332, 83928, 84285, + 85713, 87142, 87500, 88094, 88571, 89285, 90000, 90475, + 91071, 91428, 92856, 94642, +}; + +static size_t freq_srch(u32 key, const u32 *array, size_t size) +{ + int l = 0; + int r = size - 1; + int m = 0; + + while (l <= r) { + m = (l + r) / 2; + if (array[m] < key) + l = m + 1; + else if (array[m] > key) + r = m - 1; + else + return m; + } + + if (r < 0 || l > size - 1) + return m; + else + return (abs(key - array[l]) < abs(key - array[r])) ? l : r; +} + +u32 mgb4_cmt_set_vout_freq(struct mgb4_vout_dev *voutdev, unsigned int freq) +{ + struct mgb4_regs *video = &voutdev->mgbdev->video; + const struct mgb4_vout_regs *regs = &voutdev->config->regs; + const u16 *reg_set; + const u32 *addr; + u32 config; + size_t i, index; + + index = freq_srch(freq, cmt_freq, ARRAY_SIZE(cmt_freq)); + addr = cmt_addrs_out[voutdev->config->id]; + reg_set = cmt_vals_out[index]; + + config = mgb4_read_reg(video, regs->config); + + mgb4_write_reg(video, regs->config, 0x1 | (config & ~0x3)); + + mgb4_mask_reg(video, regs->config, 0x100, 0x100); + + for (i = 0; i < ARRAY_SIZE(cmt_addrs_out[0]); i++) + mgb4_write_reg(&voutdev->mgbdev->cmt, addr[i], reg_set[i]); + + mgb4_mask_reg(video, regs->config, 0x100, 0x0); + + mgb4_write_reg(video, regs->config, config & ~0x1); + + return cmt_freq[index]; +} + +void mgb4_cmt_set_vin_freq_range(struct mgb4_vin_dev *vindev, + unsigned int freq_range) +{ + struct mgb4_regs *video = &vindev->mgbdev->video; + const struct mgb4_vin_regs *regs = &vindev->config->regs; + const u16 *reg_set; + const u32 *addr; + u32 config; + size_t i; + + freq_range = array_index_nospec(freq_range, ARRAY_SIZE(cmt_vals_in)); + + addr = cmt_addrs_in[vindev->config->id]; + reg_set = cmt_vals_in[freq_range]; + + config = mgb4_read_reg(video, regs->config); + + mgb4_write_reg(video, regs->config, 0x1 | (config & ~0x3)); + + mgb4_mask_reg(video, regs->config, 0x1000, 0x1000); + + for (i = 0; i < ARRAY_SIZE(cmt_addrs_in[0]); i++) + mgb4_write_reg(&vindev->mgbdev->cmt, addr[i], reg_set[i]); + + mgb4_mask_reg(video, regs->config, 0x1000, 0x0); + + mgb4_write_reg(video, regs->config, config & ~0x1); +} diff --git a/drivers/media/pci/mgb4/mgb4_cmt.h b/drivers/media/pci/mgb4/mgb4_cmt.h new file mode 100644 index 000000000..b15df56ca --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_cmt.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_CMT_H__ +#define __MGB4_CMT_H__ + +#include "mgb4_vout.h" +#include "mgb4_vin.h" + +u32 mgb4_cmt_set_vout_freq(struct mgb4_vout_dev *voutdev, unsigned int freq); +void mgb4_cmt_set_vin_freq_range(struct mgb4_vin_dev *vindev, + unsigned int freq_range); + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_core.c b/drivers/media/pci/mgb4/mgb4_core.c new file mode 100644 index 000000000..cbd193e06 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_core.c @@ -0,0 +1,715 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This is the driver for the MGB4 video grabber card by Digiteq Automotive. + * + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + * + * This is the main driver module. The DMA, I2C and SPI sub-drivers are + * initialized here and the input/output v4l2 devices are created. + * + * The mgb4 card uses different expansion modules for different video sources + * (GMSL and FPDL3 for now) so in probe() we detect the module type based on + * what we see on the I2C bus and check if it matches the FPGA bitstream (there + * are different bitstreams for different expansion modules). When no expansion + * module is present, we still let the driver initialize to allow flashing of + * the FPGA firmware using the SPI FLASH device. No v4l2 video devices are + * created in this case. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mgb4_dma.h" +#include "mgb4_i2c.h" +#include "mgb4_sysfs.h" +#include "mgb4_vout.h" +#include "mgb4_vin.h" +#include "mgb4_trigger.h" +#include "mgb4_core.h" + +#define MGB4_USER_IRQS 16 +#define MGB4_MGB4_BAR_ID 0 +#define MGB4_XDMA_BAR_ID 1 + +#define DIGITEQ_VID 0x1ed8 +#define T100_DID 0x0101 +#define T200_DID 0x0201 + +ATTRIBUTE_GROUPS(mgb4_pci); + +static int flashid; + +static struct xdma_chan_info h2c_chan_info = { + .dir = DMA_MEM_TO_DEV, +}; + +static struct xdma_chan_info c2h_chan_info = { + .dir = DMA_DEV_TO_MEM, +}; + +static struct xspi_platform_data spi_platform_data = { + .num_chipselect = 1, + .bits_per_word = 8 +}; + +static const struct i2c_board_info extender_info = { + I2C_BOARD_INFO("extender", 0x21) +}; + +#if IS_REACHABLE(CONFIG_HWMON) +static umode_t temp_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type == hwmon_temp && + (attr == hwmon_temp_input || attr == hwmon_temp_label)) + return 0444; + else + return 0; +} + +static int temp_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + struct mgb4_dev *mgbdev = dev_get_drvdata(dev); + u32 raw; + int val10; + + if (type != hwmon_temp || attr != hwmon_temp_input) + return -EOPNOTSUPP; + + raw = mgb4_read_reg(&mgbdev->video, 0xD0); + /* register value -> Celsius degrees formula given by Xilinx */ + val10 = ((((raw >> 20) & 0xFFF) * 503975) - 1118822400) / 409600; + *val = val10 * 100; + + return 0; +} + +static int temp_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + if (type != hwmon_temp || attr != hwmon_temp_label) + return -EOPNOTSUPP; + + *str = "FPGA Temperature"; + + return 0; +} + +static const struct hwmon_ops temp_ops = { + .is_visible = temp_is_visible, + .read = temp_read, + .read_string = temp_read_string +}; + +static const struct hwmon_channel_info *temp_channel_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), + NULL +}; + +static const struct hwmon_chip_info temp_chip_info = { + .ops = &temp_ops, + .info = temp_channel_info, +}; +#endif + +static int match_i2c_adap(struct device *dev, const void *data) +{ + return i2c_verify_adapter(dev) ? 1 : 0; +} + +static struct i2c_adapter *get_i2c_adap(struct platform_device *pdev) +{ + struct device *dev; + + mutex_lock(&pdev->dev.mutex); + dev = device_find_child(&pdev->dev, NULL, match_i2c_adap); + mutex_unlock(&pdev->dev.mutex); + + return dev ? to_i2c_adapter(dev) : NULL; +} + +static int match_spi_adap(struct device *dev, const void *data) +{ + return to_spi_device(dev) ? 1 : 0; +} + +static struct spi_controller *get_spi_adap(struct platform_device *pdev) +{ + struct device *dev; + + mutex_lock(&pdev->dev.mutex); + dev = device_find_child(&pdev->dev, NULL, match_spi_adap); + mutex_unlock(&pdev->dev.mutex); + + return dev ? container_of(dev, struct spi_controller, dev) : NULL; +} + +static int init_spi(struct mgb4_dev *mgbdev, u32 devid) +{ + struct resource spi_resources[] = { + { + .start = 0x400, + .end = 0x47f, + .flags = IORESOURCE_MEM, + .name = "io-memory", + }, + { + .start = 14, + .end = 14, + .flags = IORESOURCE_IRQ, + .name = "irq", + }, + }; + struct spi_board_info spi_info = { + .max_speed_hz = 10000000, + .modalias = "m25p80", + .chip_select = 0, + .mode = SPI_MODE_3, + }; + struct pci_dev *pdev = mgbdev->pdev; + struct device *dev = &pdev->dev; + struct spi_controller *ctlr; + struct spi_device *spi_dev; + u32 irq; + int rv, id; + resource_size_t mapbase = pci_resource_start(pdev, MGB4_MGB4_BAR_ID); + + request_module("platform:xilinx_spi"); + + irq = xdma_get_user_irq(mgbdev->xdev, 14); + xdma_enable_user_irq(mgbdev->xdev, irq); + + spi_resources[0].parent = &pdev->resource[MGB4_MGB4_BAR_ID]; + spi_resources[0].start += mapbase; + spi_resources[0].end += mapbase; + spi_resources[1].start = irq; + spi_resources[1].end = irq; + + id = pci_dev_id(pdev); + mgbdev->spi_pdev = platform_device_register_resndata(dev, "xilinx_spi", + id, spi_resources, + ARRAY_SIZE(spi_resources), + &spi_platform_data, + sizeof(spi_platform_data)); + if (IS_ERR(mgbdev->spi_pdev)) { + dev_err(dev, "failed to register SPI device\n"); + return PTR_ERR(mgbdev->spi_pdev); + } + + ctlr = get_spi_adap(mgbdev->spi_pdev); + if (!ctlr) { + dev_err(dev, "failed to get SPI adapter\n"); + rv = -EINVAL; + goto err_pdev; + } + + snprintf(mgbdev->fw_part_name, sizeof(mgbdev->fw_part_name), + "mgb4-fw.%d", flashid); + mgbdev->partitions[0].name = mgbdev->fw_part_name; + if (devid == T200_DID) { + mgbdev->partitions[0].size = 0x950000; + mgbdev->partitions[0].offset = 0x1000000; + } else { + mgbdev->partitions[0].size = 0x400000; + mgbdev->partitions[0].offset = 0x400000; + } + mgbdev->partitions[0].mask_flags = 0; + + snprintf(mgbdev->data_part_name, sizeof(mgbdev->data_part_name), + "mgb4-data.%d", flashid); + mgbdev->partitions[1].name = mgbdev->data_part_name; + mgbdev->partitions[1].size = 0x10000; + mgbdev->partitions[1].offset = 0xFF0000; + mgbdev->partitions[1].mask_flags = MTD_CAP_NORFLASH; + + snprintf(mgbdev->flash_name, sizeof(mgbdev->flash_name), + "mgb4-flash.%d", flashid); + mgbdev->flash_data.name = mgbdev->flash_name; + mgbdev->flash_data.parts = mgbdev->partitions; + mgbdev->flash_data.nr_parts = ARRAY_SIZE(mgbdev->partitions); + mgbdev->flash_data.type = "spi-nor"; + + spi_info.platform_data = &mgbdev->flash_data; + + spi_dev = spi_new_device(ctlr, &spi_info); + put_device(&ctlr->dev); + if (!spi_dev) { + dev_err(dev, "failed to create MTD device\n"); + rv = -EINVAL; + goto err_pdev; + } + + return 0; + +err_pdev: + platform_device_unregister(mgbdev->spi_pdev); + + return rv; +} + +static void free_spi(struct mgb4_dev *mgbdev) +{ + platform_device_unregister(mgbdev->spi_pdev); +} + +static int init_i2c(struct mgb4_dev *mgbdev) +{ + struct resource i2c_resources[] = { + { + .start = 0x200, + .end = 0x3ff, + .flags = IORESOURCE_MEM, + .name = "io-memory", + }, + { + .start = 15, + .end = 15, + .flags = IORESOURCE_IRQ, + .name = "irq", + }, + }; + struct pci_dev *pdev = mgbdev->pdev; + struct device *dev = &pdev->dev; + char clk_name[16]; + u32 irq; + int rv, id; + resource_size_t mapbase = pci_resource_start(pdev, MGB4_MGB4_BAR_ID); + + request_module("platform:xiic-i2c"); + + irq = xdma_get_user_irq(mgbdev->xdev, 15); + xdma_enable_user_irq(mgbdev->xdev, irq); + + i2c_resources[0].parent = &pdev->resource[MGB4_MGB4_BAR_ID]; + i2c_resources[0].start += mapbase; + i2c_resources[0].end += mapbase; + i2c_resources[1].start = irq; + i2c_resources[1].end = irq; + + id = pci_dev_id(pdev); + + /* create dummy clock required by the xiic-i2c adapter */ + snprintf(clk_name, sizeof(clk_name), "xiic-i2c.%d", id); + mgbdev->i2c_clk = clk_hw_register_fixed_rate(NULL, clk_name, NULL, + 0, MGB4_HW_FREQ); + if (IS_ERR(mgbdev->i2c_clk)) { + dev_err(dev, "failed to register I2C clock\n"); + return PTR_ERR(mgbdev->i2c_clk); + } + mgbdev->i2c_cl = clkdev_hw_create(mgbdev->i2c_clk, NULL, "xiic-i2c.%d", + id); + if (!mgbdev->i2c_cl) { + dev_err(dev, "failed to register I2C clockdev\n"); + rv = -ENOMEM; + goto err_clk; + } + + mgbdev->i2c_pdev = platform_device_register_resndata(dev, "xiic-i2c", + id, i2c_resources, + ARRAY_SIZE(i2c_resources), + NULL, 0); + if (IS_ERR(mgbdev->i2c_pdev)) { + dev_err(dev, "failed to register I2C device\n"); + rv = PTR_ERR(mgbdev->i2c_pdev); + goto err_clkdev; + } + + mgbdev->i2c_adap = get_i2c_adap(mgbdev->i2c_pdev); + if (!mgbdev->i2c_adap) { + dev_err(dev, "failed to get I2C adapter\n"); + rv = -EINVAL; + goto err_pdev; + } + + mutex_init(&mgbdev->i2c_lock); + + return 0; + +err_pdev: + platform_device_unregister(mgbdev->i2c_pdev); +err_clkdev: + clkdev_drop(mgbdev->i2c_cl); +err_clk: + clk_hw_unregister(mgbdev->i2c_clk); + + return rv; +} + +static void free_i2c(struct mgb4_dev *mgbdev) +{ + put_device(&mgbdev->i2c_adap->dev); + platform_device_unregister(mgbdev->i2c_pdev); + clkdev_drop(mgbdev->i2c_cl); + clk_hw_unregister(mgbdev->i2c_clk); +} + +static int get_serial_number(struct mgb4_dev *mgbdev) +{ + struct device *dev = &mgbdev->pdev->dev; + struct mtd_info *mtd; + size_t rs; + int rv; + + mgbdev->serial_number = 0; + + mtd = get_mtd_device_nm(mgbdev->data_part_name); + if (IS_ERR(mtd)) { + dev_warn(dev, "failed to get data MTD device\n"); + return -ENOENT; + } + rv = mtd_read(mtd, 0, sizeof(mgbdev->serial_number), &rs, + (u_char *)&mgbdev->serial_number); + put_mtd_device(mtd); + if (rv < 0 || rs != sizeof(mgbdev->serial_number)) { + dev_warn(dev, "error reading MTD device\n"); + return -EIO; + } + + return 0; +} + +static const char *module_type_str(struct mgb4_dev *mgbdev) +{ + if (MGB4_IS_FPDL3(mgbdev)) + return "FPDL3"; + else if (MGB4_IS_GMSL3(mgbdev)) + return "GMSL3"; + else if (MGB4_IS_GMSL1(mgbdev)) + return "GMSL1"; + else + return "UNKNOWN"; +} + +static int get_module_version(struct mgb4_dev *mgbdev) +{ + struct device *dev = &mgbdev->pdev->dev; + struct mgb4_i2c_client extender; + s32 version; + u32 fw_version; + int rv; + + rv = mgb4_i2c_init(&extender, mgbdev->i2c_adap, &extender_info, 8); + if (rv < 0) { + dev_err(dev, "failed to create extender I2C device\n"); + return rv; + } + version = mgb4_i2c_read_byte(&extender, 0x00); + mgb4_i2c_free(&extender); + if (version < 0) { + dev_err(dev, "error reading module version\n"); + return -EIO; + } + + mgbdev->module_version = ~((u32)version) & 0xff; + if (!(MGB4_IS_FPDL3(mgbdev) || + MGB4_IS_GMSL3(mgbdev) || + MGB4_IS_GMSL1(mgbdev))) { + dev_err(dev, "unknown module type\n"); + return -EINVAL; + } + fw_version = mgb4_read_reg(&mgbdev->video, 0xC4) >> 24; + if ((MGB4_IS_FPDL3(mgbdev) && fw_version != 1) || + (MGB4_IS_GMSL3(mgbdev) && fw_version != 2) || + (MGB4_IS_GMSL1(mgbdev) && fw_version != 3)) { + dev_err(dev, "module/firmware type mismatch\n"); + return -EINVAL; + } + + dev_info(dev, "%s module detected\n", module_type_str(mgbdev)); + + return 0; +} + +static int map_regs(struct pci_dev *pdev, struct resource *res, + struct mgb4_regs *regs) +{ + int rv; + resource_size_t mapbase = pci_resource_start(pdev, MGB4_MGB4_BAR_ID); + + res->start += mapbase; + res->end += mapbase; + + rv = mgb4_regs_map(res, regs); + if (rv < 0) { + dev_err(&pdev->dev, "failed to map %s registers\n", res->name); + return rv; + } + + return 0; +} + +static int init_xdma(struct mgb4_dev *mgbdev) +{ + struct xdma_platdata data; + struct resource res[2] = { 0 }; + struct dma_slave_map *map; + struct pci_dev *pdev = mgbdev->pdev; + struct device *dev = &pdev->dev; + int i; + + res[0].start = pci_resource_start(pdev, MGB4_XDMA_BAR_ID); + res[0].end = pci_resource_end(pdev, MGB4_XDMA_BAR_ID); + res[0].flags = IORESOURCE_MEM; + res[0].parent = &pdev->resource[MGB4_XDMA_BAR_ID]; + res[1].start = pci_irq_vector(pdev, 0); + res[1].end = res[1].start + MGB4_VIN_DEVICES + MGB4_VOUT_DEVICES + + MGB4_USER_IRQS - 1; + res[1].flags = IORESOURCE_IRQ; + + data.max_dma_channels = MGB4_VIN_DEVICES + MGB4_VOUT_DEVICES; + data.device_map = mgbdev->slave_map; + data.device_map_cnt = MGB4_VIN_DEVICES + MGB4_VOUT_DEVICES; + + for (i = 0; i < MGB4_VIN_DEVICES; i++) { + sprintf(mgbdev->channel_names[i], "c2h%d", i); + map = &data.device_map[i]; + map->slave = mgbdev->channel_names[i]; + map->devname = dev_name(dev); + map->param = XDMA_FILTER_PARAM(&c2h_chan_info); + } + for (i = 0; i < MGB4_VOUT_DEVICES; i++) { + sprintf(mgbdev->channel_names[i + MGB4_VIN_DEVICES], "h2c%d", i); + map = &data.device_map[i + MGB4_VIN_DEVICES]; + map->slave = mgbdev->channel_names[i + MGB4_VIN_DEVICES]; + map->devname = dev_name(dev); + map->param = XDMA_FILTER_PARAM(&h2c_chan_info); + } + + mgbdev->xdev = platform_device_register_resndata(dev, "xdma", + PLATFORM_DEVID_AUTO, res, + 2, &data, sizeof(data)); + if (IS_ERR(mgbdev->xdev)) { + dev_err(dev, "failed to register XDMA device\n"); + return PTR_ERR(mgbdev->xdev); + } + + return 0; +} + +static void free_xdma(struct mgb4_dev *mgbdev) +{ + platform_device_unregister(mgbdev->xdev); +} + +static int mgb4_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + int i, rv; + struct mgb4_dev *mgbdev; + struct resource video = { + .start = 0x0, + .end = 0xff, + .flags = IORESOURCE_MEM, + .name = "mgb4-video", + }; + struct resource cmt = { + .start = 0x1000, + .end = 0x17ff, + .flags = IORESOURCE_MEM, + .name = "mgb4-cmt", + }; + int irqs = pci_msix_vec_count(pdev); + + mgbdev = kzalloc_obj(*mgbdev); + if (!mgbdev) + return -ENOMEM; + + mgbdev->pdev = pdev; + pci_set_drvdata(pdev, mgbdev); + + /* PCIe related stuff */ + rv = pci_enable_device(pdev); + if (rv) { + dev_err(&pdev->dev, "error enabling PCI device\n"); + goto err_mgbdev; + } + + rv = pcie_capability_set_word(pdev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_RELAX_EN); + if (rv) + dev_warn(&pdev->dev, "error enabling PCIe relaxed ordering\n"); + rv = pcie_capability_set_word(pdev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_EXT_TAG); + if (rv) + dev_warn(&pdev->dev, "error enabling PCIe extended tag field\n"); + rv = pcie_set_readrq(pdev, 512); + if (rv) + dev_warn(&pdev->dev, "error setting PCIe max. memory read size\n"); + pci_set_master(pdev); + + rv = pci_alloc_irq_vectors(pdev, irqs, irqs, PCI_IRQ_MSIX); + if (rv < 0) { + dev_err(&pdev->dev, "error allocating MSI-X IRQs\n"); + goto err_enable_pci; + } + + rv = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (rv) { + dev_err(&pdev->dev, "error setting DMA mask\n"); + goto err_enable_pci; + } + + /* DMA + IRQ engine */ + rv = init_xdma(mgbdev); + if (rv) + goto err_alloc_irq; + rv = mgb4_dma_channel_init(mgbdev); + if (rv) + goto err_dma_chan; + + /* mgb4 video registers */ + rv = map_regs(pdev, &video, &mgbdev->video); + if (rv < 0) + goto err_dma_chan; + /* mgb4 cmt registers */ + rv = map_regs(pdev, &cmt, &mgbdev->cmt); + if (rv < 0) + goto err_video_regs; + + /* SPI FLASH */ + rv = init_spi(mgbdev, id->device); + if (rv < 0) + goto err_cmt_regs; + + /* I2C controller */ + rv = init_i2c(mgbdev); + if (rv < 0) + goto err_spi; + + /* PCI card related sysfs attributes */ + rv = device_add_groups(&pdev->dev, mgb4_pci_groups); + if (rv < 0) + goto err_i2c; + +#if IS_REACHABLE(CONFIG_HWMON) + /* HWmon (card temperature) */ + mgbdev->hwmon_dev = hwmon_device_register_with_info(&pdev->dev, "mgb4", + mgbdev, + &temp_chip_info, + NULL); +#endif + + mgbdev->debugfs = debugfs_create_dir(dev_name(&pdev->dev), NULL); + + /* Get card serial number. On systems without MTD flash support we may + * get an error thus ignore the return value. An invalid serial number + * should not break anything... + */ + if (get_serial_number(mgbdev) < 0) + dev_warn(&pdev->dev, "error reading card serial number\n"); + + /* Get module type. If no valid module is found, skip the video device + * creation part but do not exit with error to allow flashing the card. + */ + rv = get_module_version(mgbdev); + if (rv < 0) + goto exit; + /* Propagate the module type(version) to the FPGA */ + mgb4_write_reg(&mgbdev->video, 0xD4, mgbdev->module_version); + + /* Video input v4l2 devices */ + for (i = 0; i < MGB4_VIN_DEVICES; i++) + mgbdev->vin[i] = mgb4_vin_create(mgbdev, i); + + /* Video output v4l2 devices */ + if (MGB4_HAS_VOUT(mgbdev)) { + for (i = 0; i < MGB4_VOUT_DEVICES; i++) + mgbdev->vout[i] = mgb4_vout_create(mgbdev, i); + } + + /* Triggers */ + mgbdev->indio_dev = mgb4_trigger_create(mgbdev); + +exit: + flashid++; + + return 0; + +err_i2c: + free_i2c(mgbdev); +err_spi: + free_spi(mgbdev); +err_cmt_regs: + mgb4_regs_free(&mgbdev->cmt); +err_video_regs: + mgb4_regs_free(&mgbdev->video); +err_dma_chan: + mgb4_dma_channel_free(mgbdev); + free_xdma(mgbdev); +err_alloc_irq: + pci_disable_msix(pdev); +err_enable_pci: + pci_disable_device(pdev); +err_mgbdev: + kfree(mgbdev); + + return rv; +} + +static void mgb4_remove(struct pci_dev *pdev) +{ + struct mgb4_dev *mgbdev = pci_get_drvdata(pdev); + int i; + +#if IS_REACHABLE(CONFIG_HWMON) + hwmon_device_unregister(mgbdev->hwmon_dev); +#endif + + debugfs_remove_recursive(mgbdev->debugfs); + + if (mgbdev->indio_dev) + mgb4_trigger_free(mgbdev->indio_dev); + + for (i = 0; i < MGB4_VOUT_DEVICES; i++) + if (mgbdev->vout[i]) + mgb4_vout_free(mgbdev->vout[i]); + for (i = 0; i < MGB4_VIN_DEVICES; i++) + if (mgbdev->vin[i]) + mgb4_vin_free(mgbdev->vin[i]); + + device_remove_groups(&mgbdev->pdev->dev, mgb4_pci_groups); + free_spi(mgbdev); + free_i2c(mgbdev); + mgb4_regs_free(&mgbdev->video); + mgb4_regs_free(&mgbdev->cmt); + + mgb4_dma_channel_free(mgbdev); + free_xdma(mgbdev); + + pci_disable_msix(mgbdev->pdev); + pci_disable_device(mgbdev->pdev); + + kfree(mgbdev); +} + +static const struct pci_device_id mgb4_pci_ids[] = { + { PCI_DEVICE(DIGITEQ_VID, T100_DID) }, + { PCI_DEVICE(DIGITEQ_VID, T200_DID) }, + { } +}; +MODULE_DEVICE_TABLE(pci, mgb4_pci_ids); + +static struct pci_driver mgb4_pci_driver = { + .name = KBUILD_MODNAME, + .id_table = mgb4_pci_ids, + .probe = mgb4_probe, + .remove = mgb4_remove, +}; + +module_pci_driver(mgb4_pci_driver); + +MODULE_AUTHOR("Digiteq Automotive s.r.o."); +MODULE_DESCRIPTION("Digiteq Automotive MGB4 Driver"); +MODULE_LICENSE("GPL"); +MODULE_SOFTDEP("pre: platform:xiic-i2c platform:xilinx_spi spi-nor"); diff --git a/drivers/media/pci/mgb4/mgb4_core.h b/drivers/media/pci/mgb4/mgb4_core.h new file mode 100644 index 000000000..bfae75f30 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_core.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_CORE_H__ +#define __MGB4_CORE_H__ + +#include +#include +#include +#include +#include "mgb4_regs.h" + +#define MGB4_HW_FREQ 125000000 + +#define MGB4_VIN_DEVICES 2 +#define MGB4_VOUT_DEVICES 2 + +#define MGB4_IS_GMSL1(mgbdev) \ + (((mgbdev)->module_version >> 4) == 6) +#define MGB4_IS_GMSL3(mgbdev) \ + (((((mgbdev)->module_version >> 4) >= 2) && \ + (((mgbdev)->module_version >> 4) <= 4)) || \ + (((mgbdev)->module_version >> 4) == 8)) +#define MGB4_IS_GMSL3C(mgbdev) \ + (((mgbdev)->module_version >> 4) == 8) +#define MGB4_IS_FPDL3(mgbdev) \ + (((mgbdev)->module_version >> 4) == 1) +#define MGB4_HAS_VOUT(mgbdev) \ + (((((mgbdev)->module_version >> 4) >= 1) && \ + (((mgbdev)->module_version >> 4) <= 3)) || \ + ((((mgbdev)->module_version >> 4) == 6))) + +struct mgb4_dma_channel { + struct dma_chan *chan; + struct completion req_compl; +}; + +struct mgb4_dev { + struct pci_dev *pdev; + struct platform_device *xdev; + struct mgb4_vin_dev *vin[MGB4_VIN_DEVICES]; + struct mgb4_vout_dev *vout[MGB4_VOUT_DEVICES]; + + struct mgb4_dma_channel c2h_chan[MGB4_VIN_DEVICES]; + struct mgb4_dma_channel h2c_chan[MGB4_VOUT_DEVICES]; + struct dma_slave_map slave_map[MGB4_VIN_DEVICES + MGB4_VOUT_DEVICES]; + + struct mgb4_regs video; + struct mgb4_regs cmt; + + struct clk_hw *i2c_clk; + struct clk_lookup *i2c_cl; + struct platform_device *i2c_pdev; + struct i2c_adapter *i2c_adap; + struct mutex i2c_lock; /* I2C bus access lock */ + + struct platform_device *spi_pdev; + struct flash_platform_data flash_data; + struct mtd_partition partitions[2]; + char flash_name[16]; + char fw_part_name[16]; + char data_part_name[16]; + char channel_names[MGB4_VIN_DEVICES + MGB4_VOUT_DEVICES][16]; + + struct iio_dev *indio_dev; +#if IS_REACHABLE(CONFIG_HWMON) + struct device *hwmon_dev; +#endif + + unsigned long io_reconfig; + + u8 module_version; + u32 serial_number; + + struct dentry *debugfs; +}; + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_dma.c b/drivers/media/pci/mgb4/mgb4_dma.c new file mode 100644 index 000000000..cae888e65 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_dma.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + * + * This module handles the DMA transfers. A standard dmaengine API as provided + * by the XDMA module is used. + */ + +#include +#include +#include "mgb4_core.h" +#include "mgb4_dma.h" + +static void chan_irq(void *param) +{ + struct mgb4_dma_channel *chan = param; + + complete(&chan->req_compl); +} + +int mgb4_dma_transfer(struct mgb4_dev *mgbdev, u32 channel, bool write, + u64 paddr, struct sg_table *sgt) +{ + struct dma_slave_config cfg; + struct mgb4_dma_channel *chan; + struct dma_async_tx_descriptor *tx; + struct pci_dev *pdev = mgbdev->pdev; + int ret; + + memset(&cfg, 0, sizeof(cfg)); + + if (write) { + cfg.direction = DMA_MEM_TO_DEV; + cfg.dst_addr = paddr; + cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + chan = &mgbdev->h2c_chan[channel]; + } else { + cfg.direction = DMA_DEV_TO_MEM; + cfg.src_addr = paddr; + cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + chan = &mgbdev->c2h_chan[channel]; + } + + ret = dmaengine_slave_config(chan->chan, &cfg); + if (ret) { + dev_err(&pdev->dev, "failed to config dma: %d\n", ret); + return ret; + } + + tx = dmaengine_prep_slave_sg(chan->chan, sgt->sgl, sgt->nents, + cfg.direction, 0); + if (!tx) { + dev_err(&pdev->dev, "failed to prep slave sg\n"); + return -EIO; + } + + tx->callback = chan_irq; + tx->callback_param = chan; + + ret = dma_submit_error(dmaengine_submit(tx)); + if (ret) { + dev_err(&pdev->dev, "failed to submit sg\n"); + return -EIO; + } + + dma_async_issue_pending(chan->chan); + + if (!wait_for_completion_timeout(&chan->req_compl, + msecs_to_jiffies(10000))) { + dev_err(&pdev->dev, "dma timeout\n"); + dmaengine_terminate_sync(chan->chan); + return -EIO; + } + + return 0; +} + +int mgb4_dma_channel_init(struct mgb4_dev *mgbdev) +{ + int i, ret; + char name[16]; + struct pci_dev *pdev = mgbdev->pdev; + + for (i = 0; i < MGB4_VIN_DEVICES; i++) { + sprintf(name, "c2h%d", i); + mgbdev->c2h_chan[i].chan = dma_request_chan(&pdev->dev, name); + if (IS_ERR(mgbdev->c2h_chan[i].chan)) { + dev_err(&pdev->dev, "failed to initialize %s", name); + ret = PTR_ERR(mgbdev->c2h_chan[i].chan); + mgbdev->c2h_chan[i].chan = NULL; + return ret; + } + init_completion(&mgbdev->c2h_chan[i].req_compl); + } + for (i = 0; i < MGB4_VOUT_DEVICES; i++) { + sprintf(name, "h2c%d", i); + mgbdev->h2c_chan[i].chan = dma_request_chan(&pdev->dev, name); + if (IS_ERR(mgbdev->h2c_chan[i].chan)) { + dev_err(&pdev->dev, "failed to initialize %s", name); + ret = PTR_ERR(mgbdev->h2c_chan[i].chan); + mgbdev->h2c_chan[i].chan = NULL; + return ret; + } + init_completion(&mgbdev->h2c_chan[i].req_compl); + } + + return 0; +} + +void mgb4_dma_channel_free(struct mgb4_dev *mgbdev) +{ + int i; + + for (i = 0; i < MGB4_VIN_DEVICES; i++) { + if (mgbdev->c2h_chan[i].chan) + dma_release_channel(mgbdev->c2h_chan[i].chan); + } + for (i = 0; i < MGB4_VOUT_DEVICES; i++) { + if (mgbdev->h2c_chan[i].chan) + dma_release_channel(mgbdev->h2c_chan[i].chan); + } +} diff --git a/drivers/media/pci/mgb4/mgb4_dma.h b/drivers/media/pci/mgb4/mgb4_dma.h new file mode 100644 index 000000000..4ebc2b1ce --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_dma.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_DMA_H__ +#define __MGB4_DMA_H__ + +#include "mgb4_core.h" + +int mgb4_dma_channel_init(struct mgb4_dev *mgbdev); +void mgb4_dma_channel_free(struct mgb4_dev *mgbdev); + +int mgb4_dma_transfer(struct mgb4_dev *mgbdev, u32 channel, bool write, + u64 paddr, struct sg_table *sgt); + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_i2c.c b/drivers/media/pci/mgb4/mgb4_i2c.c new file mode 100644 index 000000000..2697b67e2 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_i2c.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + * + * The i2c module unifies the I2C access to the serializes/deserializes. The I2C + * chips on the GMSL module use 16b addressing, the FPDL3 chips use standard + * 8b addressing. + */ + +#include "mgb4_i2c.h" + +static int read_r16(struct i2c_client *client, u16 reg, u8 *val, int len) +{ + int ret; + u8 buf[2]; + struct i2c_msg msg[2] = { + { + .addr = client->addr, + .flags = 0, + .len = 2, + .buf = buf, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = val, + } + }; + + buf[0] = (reg >> 8) & 0xff; + buf[1] = (reg >> 0) & 0xff; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) + return ret; + else if (ret != 2) + return -EREMOTEIO; + else + return 0; +} + +static int write_r16(struct i2c_client *client, u16 reg, const u8 *val, int len) +{ + int ret; + u8 buf[4]; + struct i2c_msg msg[1] = { + { + .addr = client->addr, + .flags = 0, + .len = 2 + len, + .buf = buf, + } + }; + + if (2 + len > sizeof(buf)) + return -EINVAL; + + buf[0] = (reg >> 8) & 0xff; + buf[1] = (reg >> 0) & 0xff; + memcpy(&buf[2], val, len); + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret < 0) + return ret; + else if (ret != 1) + return -EREMOTEIO; + else + return 0; +} + +int mgb4_i2c_init(struct mgb4_i2c_client *client, struct i2c_adapter *adap, + struct i2c_board_info const *info, int addr_size) +{ + client->client = i2c_new_client_device(adap, info); + if (IS_ERR(client->client)) + return PTR_ERR(client->client); + + client->addr_size = addr_size; + + return 0; +} + +void mgb4_i2c_free(struct mgb4_i2c_client *client) +{ + i2c_unregister_device(client->client); +} + +s32 mgb4_i2c_read_byte(struct mgb4_i2c_client *client, u16 reg) +{ + int ret; + u8 b; + + if (client->addr_size == 8) + return i2c_smbus_read_byte_data(client->client, reg); + + ret = read_r16(client->client, reg, &b, 1); + if (ret < 0) + return ret; + + return (s32)b; +} + +s32 mgb4_i2c_write_byte(struct mgb4_i2c_client *client, u16 reg, u8 val) +{ + if (client->addr_size == 8) + return i2c_smbus_write_byte_data(client->client, reg, val); + else + return write_r16(client->client, reg, &val, 1); +} + +s32 mgb4_i2c_mask_byte(struct mgb4_i2c_client *client, u16 reg, u8 mask, u8 val) +{ + s32 ret; + + if (mask != 0xFF) { + ret = mgb4_i2c_read_byte(client, reg); + if (ret < 0) + return ret; + val |= (u8)ret & ~mask; + } + + return mgb4_i2c_write_byte(client, reg, val); +} + +int mgb4_i2c_configure(struct mgb4_i2c_client *client, + const struct mgb4_i2c_kv *values, size_t count) +{ + size_t i; + s32 res; + + for (i = 0; i < count; i++) { + res = mgb4_i2c_mask_byte(client, values[i].reg, values[i].mask, + values[i].val); + if (res < 0) + return res; + } + + return 0; +} diff --git a/drivers/media/pci/mgb4/mgb4_i2c.h b/drivers/media/pci/mgb4/mgb4_i2c.h new file mode 100644 index 000000000..fac6a1634 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_i2c.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_I2C_H__ +#define __MGB4_I2C_H__ + +#include + +struct mgb4_i2c_client { + struct i2c_client *client; + int addr_size; +}; + +struct mgb4_i2c_kv { + u16 reg; + u8 mask; + u8 val; +}; + +int mgb4_i2c_init(struct mgb4_i2c_client *client, struct i2c_adapter *adap, + struct i2c_board_info const *info, int addr_size); +void mgb4_i2c_free(struct mgb4_i2c_client *client); + +s32 mgb4_i2c_read_byte(struct mgb4_i2c_client *client, u16 reg); +s32 mgb4_i2c_write_byte(struct mgb4_i2c_client *client, u16 reg, u8 val); +s32 mgb4_i2c_mask_byte(struct mgb4_i2c_client *client, u16 reg, u8 mask, + u8 val); + +int mgb4_i2c_configure(struct mgb4_i2c_client *client, + const struct mgb4_i2c_kv *values, size_t count); + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_io.h b/drivers/media/pci/mgb4/mgb4_io.h new file mode 100644 index 000000000..dd8696d7d --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_io.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_IO_H__ +#define __MGB4_IO_H__ + +#include +#include +#include "mgb4_core.h" + +/* Register access error indication */ +#define MGB4_ERR_NO_REG 0xFFFFFFFE +/* Frame buffer addresses greater than 0xFFFFFFFA indicate HW errors */ +#define MGB4_ERR_QUEUE_TIMEOUT 0xFFFFFFFD +#define MGB4_ERR_QUEUE_EMPTY 0xFFFFFFFC +#define MGB4_ERR_QUEUE_FULL 0xFFFFFFFB + +#define MGB4_PERIOD(numerator, denominator) \ + ((u32)div_u64((MGB4_HW_FREQ * (u64)(numerator)), (denominator))) + +struct mgb4_frame_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +static inline struct mgb4_frame_buffer *to_frame_buffer(struct vb2_v4l2_buffer *vbuf) +{ + return container_of(vbuf, struct mgb4_frame_buffer, vb); +} + +static inline bool has_yuv_and_timeperframe(struct mgb4_regs *video) +{ + u32 status = mgb4_read_reg(video, 0xD0); + + return (status & (1U << 8)); +} + +#define has_yuv(video) has_yuv_and_timeperframe(video) +#define has_timeperframe(video) has_yuv_and_timeperframe(video) + +static inline u32 pixel_size(struct v4l2_dv_timings *timings) +{ + struct v4l2_bt_timings *bt = &timings->bt; + + u32 height = bt->height + bt->vfrontporch + bt->vsync + bt->vbackporch; + u32 width = bt->width + bt->hfrontporch + bt->hsync + bt->hbackporch; + + return width * height; +} + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_regs.c b/drivers/media/pci/mgb4/mgb4_regs.c new file mode 100644 index 000000000..b45537dbf --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_regs.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#include +#include +#include "mgb4_regs.h" + +int mgb4_regs_map(struct resource *res, struct mgb4_regs *regs) +{ + regs->mapbase = res->start; + regs->mapsize = resource_size(res); + + if (!request_mem_region(regs->mapbase, regs->mapsize, res->name)) + return -EINVAL; + regs->membase = ioremap(regs->mapbase, regs->mapsize); + if (!regs->membase) { + release_mem_region(regs->mapbase, regs->mapsize); + return -EINVAL; + } + + return 0; +} + +void mgb4_regs_free(struct mgb4_regs *regs) +{ + iounmap(regs->membase); + release_mem_region(regs->mapbase, regs->mapsize); +} diff --git a/drivers/media/pci/mgb4/mgb4_regs.h b/drivers/media/pci/mgb4/mgb4_regs.h new file mode 100644 index 000000000..c45180890 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_regs.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_REGS_H__ +#define __MGB4_REGS_H__ + +#include + +struct mgb4_regs { + resource_size_t mapbase; + resource_size_t mapsize; + void __iomem *membase; +}; + +#define mgb4_write_reg(regs, offset, val) \ + iowrite32(val, (regs)->membase + (offset)) +#define mgb4_read_reg(regs, offset) \ + ioread32((regs)->membase + (offset)) + +static inline void mgb4_mask_reg(struct mgb4_regs *regs, u32 reg, u32 mask, + u32 val) +{ + u32 ret = mgb4_read_reg(regs, reg); + + val |= ret & ~mask; + mgb4_write_reg(regs, reg, val); +} + +int mgb4_regs_map(struct resource *res, struct mgb4_regs *regs); +void mgb4_regs_free(struct mgb4_regs *regs); + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_sysfs.h b/drivers/media/pci/mgb4/mgb4_sysfs.h new file mode 100644 index 000000000..fa7fd232c --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_sysfs.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_SYSFS_H__ +#define __MGB4_SYSFS_H__ + +#include + +extern struct attribute *mgb4_pci_attrs[]; +extern struct attribute *mgb4_fpdl3_in_attrs[]; +extern struct attribute *mgb4_gmsl3_in_attrs[]; +extern struct attribute *mgb4_gmsl1_in_attrs[]; +extern struct attribute *mgb4_fpdl3_out_attrs[]; +extern struct attribute *mgb4_gmsl3_out_attrs[]; +extern struct attribute *mgb4_gmsl1_out_attrs[]; + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_in.c b/drivers/media/pci/mgb4/mgb4_sysfs_in.c new file mode 100644 index 000000000..80ff05450 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_sysfs_in.c @@ -0,0 +1,813 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + * + * This module handles all the sysfs info/configuration that is related to the + * v4l2 input devices. + */ + +#include +#include "mgb4_core.h" +#include "mgb4_i2c.h" +#include "mgb4_vin.h" +#include "mgb4_cmt.h" +#include "mgb4_sysfs.h" + +/* Common for both FPDL3 and GMSL */ + +static ssize_t input_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + + return sprintf(buf, "%d\n", vindev->config->id); +} + +static ssize_t oldi_lane_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + struct mgb4_dev *mgbdev = vindev->mgbdev; + u16 i2c_reg; + u8 i2c_mask, i2c_single_val, i2c_dual_val; + u32 config; + int ret; + + if (MGB4_IS_GMSL1(mgbdev)) + return sprintf(buf, "0\n"); + + i2c_reg = MGB4_IS_GMSL3(mgbdev) ? 0x1CE : 0x49; + i2c_mask = MGB4_IS_GMSL3(mgbdev) ? 0x0E : 0x03; + i2c_single_val = MGB4_IS_GMSL3(mgbdev) ? 0x00 : 0x02; + i2c_dual_val = MGB4_IS_GMSL3(mgbdev) ? 0x0E : 0x00; + + mutex_lock(&mgbdev->i2c_lock); + ret = mgb4_i2c_read_byte(&vindev->deser, i2c_reg); + mutex_unlock(&mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + config = mgb4_read_reg(&mgbdev->video, vindev->config->regs.config); + + if (((config & (1U << 9)) && ((ret & i2c_mask) != i2c_dual_val)) || + (!(config & (1U << 9)) && ((ret & i2c_mask) != i2c_single_val))) { + dev_err(dev, "I2C/FPGA register value mismatch\n"); + return -EINVAL; + } + + return sprintf(buf, "%s\n", config & (1U << 9) ? "1" : "0"); +} + +/* + * OLDI lane width change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t oldi_lane_width_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + struct mgb4_dev *mgbdev = vindev->mgbdev; + u32 fpga_data; + u16 i2c_reg; + u8 i2c_mask, i2c_data; + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + if (MGB4_IS_GMSL1(mgbdev)) + return val ? -EINVAL : count; + + switch (val) { + case 0: /* single */ + fpga_data = 0; + i2c_data = MGB4_IS_GMSL3(mgbdev) ? 0x00 : 0x02; + break; + case 1: /* dual */ + fpga_data = 1U << 9; + i2c_data = MGB4_IS_GMSL3(mgbdev) ? 0x0E : 0x00; + break; + default: + return -EINVAL; + } + + i2c_reg = MGB4_IS_GMSL3(mgbdev) ? 0x1CE : 0x49; + i2c_mask = MGB4_IS_GMSL3(mgbdev) ? 0x0E : 0x03; + + mutex_lock(&mgbdev->i2c_lock); + ret = mgb4_i2c_mask_byte(&vindev->deser, i2c_reg, i2c_mask, i2c_data); + mutex_unlock(&mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + mgb4_mask_reg(&mgbdev->video, vindev->config->regs.config, 1U << 9, + fpga_data); + if (MGB4_IS_GMSL3(mgbdev)) { + /* reset input link */ + mutex_lock(&mgbdev->i2c_lock); + ret = mgb4_i2c_mask_byte(&vindev->deser, 0x10, 1U << 5, 1U << 5); + mutex_unlock(&mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + } + + return count; +} + +static ssize_t color_mapping_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.config); + + switch ((config >> 7) & 3) { + case 0: /* SPWG/VESA */ + return sprintf(buf, "1\n"); + case 1: /* ZDML */ + return sprintf(buf, "2\n"); + case 2: /* OLDI/JEIDA */ + return sprintf(buf, "0\n"); + default: + return -EIO; + } +} + +/* + * Color mapping change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t color_mapping_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 fpga_data; + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* OLDI/JEIDA */ + fpga_data = 2; + break; + case 1: /* SPWG/VESA */ + fpga_data = 0; + break; + case 2: /* ZDML */ + fpga_data = 1; + break; + default: + return -EINVAL; + } + + mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.config, + 3U << 7, fpga_data << 7); + + return count; +} + +static ssize_t link_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 status = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.status); + + return sprintf(buf, "%s\n", status & (1U << 2) ? "1" : "0"); +} + +static ssize_t stream_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 status = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.status); + + return sprintf(buf, "%s\n", ((status & (1 << 14)) && + (status & (1 << 2)) && (status & (3 << 9))) ? "1" : "0"); +} + +static ssize_t video_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.resolution); + + return sprintf(buf, "%u\n", config >> 16); +} + +static ssize_t video_height_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.resolution); + + return sprintf(buf, "%u\n", config & 0xFFFF); +} + +static ssize_t hsync_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 status = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.status); + u32 res; + + if (!(status & (1U << 11))) + res = 0x02; // not available + else if (status & (1U << 12)) + res = 0x01; // active high + else + res = 0x00; // active low + + return sprintf(buf, "%u\n", res); +} + +static ssize_t vsync_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 status = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.status); + u32 res; + + if (!(status & (1U << 11))) + res = 0x02; // not available + else if (status & (1U << 13)) + res = 0x01; // active high + else + res = 0x00; // active low + + return sprintf(buf, "%u\n", res); +} + +static ssize_t hsync_gap_length_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 sync = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.sync); + + return sprintf(buf, "%u\n", sync >> 16); +} + +/* + * HSYNC gap length change is expected to be called on live streams. Video + * device locking/queue check is not needed. + */ +static ssize_t hsync_gap_length_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFFFF) + return -EINVAL; + + mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.sync, + 0xFFFF0000, val << 16); + + return count; +} + +static ssize_t vsync_gap_length_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 sync = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.sync); + + return sprintf(buf, "%u\n", sync & 0xFFFF); +} + +/* + * VSYNC gap length change is expected to be called on live streams. Video + * device locking/queue check is not needed. + */ +static ssize_t vsync_gap_length_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFFFF) + return -EINVAL; + + mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.sync, 0xFFFF, + val); + + return count; +} + +static ssize_t pclk_frequency_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 freq = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.pclk); + + return sprintf(buf, "%u\n", freq); +} + +static ssize_t hsync_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.hsync); + + return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16); +} + +static ssize_t vsync_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.vsync); + + return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16); +} + +static ssize_t hback_porch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.hsync); + + return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8); +} + +static ssize_t hfront_porch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.hsync); + + return sprintf(buf, "%u\n", (sig & 0x000000FF)); +} + +static ssize_t vback_porch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.vsync); + + return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8); +} + +static ssize_t vfront_porch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&vindev->mgbdev->video, + vindev->config->regs.vsync); + + return sprintf(buf, "%u\n", (sig & 0x000000FF)); +} + +static ssize_t frequency_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + + return sprintf(buf, "%d\n", vindev->freq_range); +} + +static ssize_t frequency_range_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 1) + return -EINVAL; + + mutex_lock(vindev->vdev.lock); + if (vb2_is_busy(vindev->vdev.queue)) { + mutex_unlock(vindev->vdev.lock); + return -EBUSY; + } + + mgb4_cmt_set_vin_freq_range(vindev, val); + vindev->freq_range = val; + + mutex_unlock(vindev->vdev.lock); + + return count; +} + +/* FPDL3 only */ + +static ssize_t fpdl3_input_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + s32 ret; + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret = mgb4_i2c_read_byte(&vindev->deser, 0x34); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + switch ((u8)ret & 0x18) { + case 0: + return sprintf(buf, "0\n"); + case 0x10: + return sprintf(buf, "1\n"); + case 0x08: + return sprintf(buf, "2\n"); + default: + return -EINVAL; + } +} + +/* + * FPD-Link width change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t fpdl3_input_width_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + u8 i2c_data; + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* auto */ + i2c_data = 0x00; + break; + case 1: /* single */ + i2c_data = 0x10; + break; + case 2: /* dual */ + i2c_data = 0x08; + break; + default: + return -EINVAL; + } + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret = mgb4_i2c_mask_byte(&vindev->deser, 0x34, 0x18, i2c_data); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + return count; +} + +/* GMSL only */ + +static ssize_t gmsl_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + s32 r1, r300, r3; + + mutex_lock(&vindev->mgbdev->i2c_lock); + r1 = mgb4_i2c_read_byte(&vindev->deser, 0x01); + r300 = mgb4_i2c_read_byte(&vindev->deser, 0x300); + r3 = mgb4_i2c_read_byte(&vindev->deser, 0x03); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (r1 < 0 || r300 < 0 || r3 < 0) + return -EIO; + + if ((r1 & 0x03) == 0x03 && (r300 & 0x0C) == 0x0C && (r3 & 0xC0) == 0xC0) + return sprintf(buf, "0\n"); + else if ((r1 & 0x03) == 0x02 && (r300 & 0x0C) == 0x08 && (r3 & 0xC0) == 0x00) + return sprintf(buf, "1\n"); + else if ((r1 & 0x03) == 0x01 && (r300 & 0x0C) == 0x04 && (r3 & 0xC0) == 0x00) + return sprintf(buf, "2\n"); + else if ((r1 & 0x03) == 0x00 && (r300 & 0x0C) == 0x00 && (r3 & 0xC0) == 0x00) + return sprintf(buf, "3\n"); + else + return -EINVAL; +} + +/* + * GMSL mode change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t gmsl_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + static const struct mgb4_i2c_kv G12[] = { + {0x01, 0x03, 0x03}, {0x300, 0x0C, 0x0C}, {0x03, 0xC0, 0xC0}}; + static const struct mgb4_i2c_kv G6[] = { + {0x01, 0x03, 0x02}, {0x300, 0x0C, 0x08}, {0x03, 0xC0, 0x00}}; + static const struct mgb4_i2c_kv G3[] = { + {0x01, 0x03, 0x01}, {0x300, 0x0C, 0x04}, {0x03, 0xC0, 0x00}}; + static const struct mgb4_i2c_kv G1[] = { + {0x01, 0x03, 0x00}, {0x300, 0x0C, 0x00}, {0x03, 0xC0, 0x00}}; + static const struct mgb4_i2c_kv reset[] = { + {0x10, 1U << 5, 1U << 5}, {0x300, 1U << 6, 1U << 6}}; + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + const struct mgb4_i2c_kv *values; + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* 12Gb/s */ + values = G12; + break; + case 1: /* 6Gb/s */ + values = G6; + break; + case 2: /* 3Gb/s */ + values = G3; + break; + case 3: /* 1.5Gb/s */ + values = G1; + break; + default: + return -EINVAL; + } + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret = mgb4_i2c_configure(&vindev->deser, values, 3); + ret |= mgb4_i2c_configure(&vindev->deser, reset, 2); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + return count; +} + +static ssize_t gmsl_stream_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + s32 ret; + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret = mgb4_i2c_read_byte(&vindev->deser, 0xA0); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + return sprintf(buf, "%d\n", ret & 0x03); +} + +static ssize_t gmsl_stream_id_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 3) + return -EINVAL; + + mutex_lock(vindev->vdev.lock); + if (vb2_is_busy(vindev->vdev.queue)) { + mutex_unlock(vindev->vdev.lock); + return -EBUSY; + } + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret = mgb4_i2c_mask_byte(&vindev->deser, 0xA0, 0x03, (u8)val); + mutex_unlock(&vindev->mgbdev->i2c_lock); + + mutex_unlock(vindev->vdev.lock); + + return (ret < 0) ? -EIO : count; +} + +static ssize_t gmsl_fec_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + s32 r3e0, r308; + + mutex_lock(&vindev->mgbdev->i2c_lock); + r3e0 = mgb4_i2c_read_byte(&vindev->deser, 0x3E0); + r308 = mgb4_i2c_read_byte(&vindev->deser, 0x308); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (r3e0 < 0 || r308 < 0) + return -EIO; + + if ((r3e0 & 0x07) == 0x00 && (r308 & 0x01) == 0x00) + return sprintf(buf, "0\n"); + else if ((r3e0 & 0x07) == 0x07 && (r308 & 0x01) == 0x01) + return sprintf(buf, "1\n"); + else + return -EINVAL; +} + +/* + * GMSL FEC change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t gmsl_fec_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vin_dev *vindev = video_get_drvdata(vdev); + static const struct mgb4_i2c_kv enable[] = { + {0x3E0, 0x07, 0x07}, {0x308, 0x01, 0x01}}; + static const struct mgb4_i2c_kv disable[] = { + {0x3E0, 0x07, 0x00}, {0x308, 0x01, 0x00}}; + static const struct mgb4_i2c_kv reset[] = { + {0x10, 1U << 5, 1U << 5}, {0x300, 1U << 6, 1U << 6}}; + const struct mgb4_i2c_kv *values; + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* disabled */ + values = disable; + break; + case 1: /* enabled */ + values = enable; + break; + default: + return -EINVAL; + } + + mutex_lock(&vindev->mgbdev->i2c_lock); + ret = mgb4_i2c_configure(&vindev->deser, values, 2); + ret |= mgb4_i2c_configure(&vindev->deser, reset, 2); + mutex_unlock(&vindev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + return count; +} + +static DEVICE_ATTR_RO(input_id); +static DEVICE_ATTR_RW(oldi_lane_width); +static DEVICE_ATTR_RW(color_mapping); +static DEVICE_ATTR_RO(link_status); +static DEVICE_ATTR_RO(stream_status); +static DEVICE_ATTR_RO(video_width); +static DEVICE_ATTR_RO(video_height); +static DEVICE_ATTR_RO(hsync_status); +static DEVICE_ATTR_RO(vsync_status); +static DEVICE_ATTR_RW(hsync_gap_length); +static DEVICE_ATTR_RW(vsync_gap_length); +static DEVICE_ATTR_RO(pclk_frequency); +static DEVICE_ATTR_RO(hsync_width); +static DEVICE_ATTR_RO(vsync_width); +static DEVICE_ATTR_RO(hback_porch); +static DEVICE_ATTR_RO(hfront_porch); +static DEVICE_ATTR_RO(vback_porch); +static DEVICE_ATTR_RO(vfront_porch); +static DEVICE_ATTR_RW(frequency_range); + +static DEVICE_ATTR_RW(fpdl3_input_width); + +static DEVICE_ATTR_RW(gmsl_mode); +static DEVICE_ATTR_RW(gmsl_stream_id); +static DEVICE_ATTR_RW(gmsl_fec); + +struct attribute *mgb4_fpdl3_in_attrs[] = { + &dev_attr_input_id.attr, + &dev_attr_link_status.attr, + &dev_attr_stream_status.attr, + &dev_attr_video_width.attr, + &dev_attr_video_height.attr, + &dev_attr_hsync_status.attr, + &dev_attr_vsync_status.attr, + &dev_attr_oldi_lane_width.attr, + &dev_attr_color_mapping.attr, + &dev_attr_hsync_gap_length.attr, + &dev_attr_vsync_gap_length.attr, + &dev_attr_pclk_frequency.attr, + &dev_attr_hsync_width.attr, + &dev_attr_vsync_width.attr, + &dev_attr_hback_porch.attr, + &dev_attr_hfront_porch.attr, + &dev_attr_vback_porch.attr, + &dev_attr_vfront_porch.attr, + &dev_attr_frequency_range.attr, + &dev_attr_fpdl3_input_width.attr, + NULL +}; + +struct attribute *mgb4_gmsl3_in_attrs[] = { + &dev_attr_input_id.attr, + &dev_attr_link_status.attr, + &dev_attr_stream_status.attr, + &dev_attr_video_width.attr, + &dev_attr_video_height.attr, + &dev_attr_hsync_status.attr, + &dev_attr_vsync_status.attr, + &dev_attr_oldi_lane_width.attr, + &dev_attr_color_mapping.attr, + &dev_attr_hsync_gap_length.attr, + &dev_attr_vsync_gap_length.attr, + &dev_attr_pclk_frequency.attr, + &dev_attr_hsync_width.attr, + &dev_attr_vsync_width.attr, + &dev_attr_hback_porch.attr, + &dev_attr_hfront_porch.attr, + &dev_attr_vback_porch.attr, + &dev_attr_vfront_porch.attr, + &dev_attr_frequency_range.attr, + &dev_attr_gmsl_mode.attr, + &dev_attr_gmsl_stream_id.attr, + &dev_attr_gmsl_fec.attr, + NULL +}; + +struct attribute *mgb4_gmsl1_in_attrs[] = { + &dev_attr_input_id.attr, + &dev_attr_link_status.attr, + &dev_attr_stream_status.attr, + &dev_attr_video_width.attr, + &dev_attr_video_height.attr, + &dev_attr_hsync_status.attr, + &dev_attr_vsync_status.attr, + &dev_attr_oldi_lane_width.attr, + &dev_attr_color_mapping.attr, + &dev_attr_hsync_gap_length.attr, + &dev_attr_vsync_gap_length.attr, + &dev_attr_pclk_frequency.attr, + &dev_attr_hsync_width.attr, + &dev_attr_vsync_width.attr, + &dev_attr_hback_porch.attr, + &dev_attr_hfront_porch.attr, + &dev_attr_vback_porch.attr, + &dev_attr_vfront_porch.attr, + &dev_attr_frequency_range.attr, + NULL +}; diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_out.c b/drivers/media/pci/mgb4/mgb4_sysfs_out.c new file mode 100644 index 000000000..d807218e2 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_sysfs_out.c @@ -0,0 +1,825 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + * + * This module handles all the sysfs info/configuration that is related to the + * v4l2 output devices. + */ + +#include +#include +#include "mgb4_core.h" +#include "mgb4_i2c.h" +#include "mgb4_vout.h" +#include "mgb4_vin.h" +#include "mgb4_cmt.h" +#include "mgb4_sysfs.h" + +static int loopin_cnt(struct mgb4_vin_dev *vindev) +{ + struct mgb4_vout_dev *voutdev; + u32 config; + int i, cnt = 0; + + for (i = 0; i < MGB4_VOUT_DEVICES; i++) { + voutdev = vindev->mgbdev->vout[i]; + if (!voutdev) + continue; + + config = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.config); + if ((config & 0xc) >> 2 == vindev->config->id) + cnt++; + } + + return cnt; +} + +static bool is_busy(struct video_device *dev) +{ + bool ret; + + mutex_lock(dev->lock); + ret = vb2_is_busy(dev->queue); + mutex_unlock(dev->lock); + + return ret; +} + +/* Common for both FPDL3 and GMSL */ + +static ssize_t output_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + + return sprintf(buf, "%d\n", voutdev->config->id); +} + +static ssize_t video_source_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.config); + + return sprintf(buf, "%u\n", (config & 0xc) >> 2); +} + +/* + * Video source change may affect the buffer queue of ANY video input/output on + * the card thus if any of the inputs/outputs is in use, we do not allow + * the change. + * + * As we do not want to lock all the video devices at the same time, a two-stage + * locking strategy is used. In addition to the video device locking there is + * a global (PCI device) variable "io_reconfig" atomically checked/set when + * the reconfiguration is running. All the video devices check the variable in + * their queue_setup() functions and do not allow to start the queue when + * the reconfiguration has started. + */ +static ssize_t video_source_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + struct mgb4_dev *mgbdev = voutdev->mgbdev; + struct mgb4_vin_dev *loopin_new = NULL, *loopin_old = NULL; + unsigned long val; + ssize_t ret; + u32 config; + int i; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 3) + return -EINVAL; + + if (test_and_set_bit(0, &mgbdev->io_reconfig)) + return -EBUSY; + + ret = -EBUSY; + for (i = 0; i < MGB4_VIN_DEVICES; i++) + if (mgbdev->vin[i] && is_busy(&mgbdev->vin[i]->vdev)) + goto end; + for (i = 0; i < MGB4_VOUT_DEVICES; i++) + if (mgbdev->vout[i] && is_busy(&mgbdev->vout[i]->vdev)) + goto end; + + config = mgb4_read_reg(&mgbdev->video, voutdev->config->regs.config); + + if (((config & 0xc) >> 2) < MGB4_VIN_DEVICES) + loopin_old = mgbdev->vin[(config & 0xc) >> 2]; + if (val < MGB4_VIN_DEVICES) { + val = array_index_nospec(val, MGB4_VIN_DEVICES); + loopin_new = mgbdev->vin[val]; + } + if (loopin_old && loopin_cnt(loopin_old) == 1) + mgb4_mask_reg(&mgbdev->video, loopin_old->config->regs.config, + 0x2, 0x0); + if (loopin_new) + mgb4_mask_reg(&mgbdev->video, loopin_new->config->regs.config, + 0x2, 0x2); + + if (val == voutdev->config->id + MGB4_VIN_DEVICES) + mgb4_write_reg(&mgbdev->video, voutdev->config->regs.config, + config & ~(1 << 1)); + else + mgb4_write_reg(&mgbdev->video, voutdev->config->regs.config, + config | (1U << 1)); + + mgb4_mask_reg(&mgbdev->video, voutdev->config->regs.config, 0xc, + val << 2); + + ret = count; +end: + clear_bit(0, &mgbdev->io_reconfig); + + return ret; +} + +static ssize_t color_mapping_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.config); + + switch ((config >> 6) & 3) { + case 0: /* SPWG/VESA */ + return sprintf(buf, "1\n"); + case 1: /* ZDML */ + return sprintf(buf, "2\n"); + case 2: /* OLDI/JEIDA */ + return sprintf(buf, "0\n"); + default: + return -EIO; + } +} + +/* + * Color mapping change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t color_mapping_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 fpga_data; + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* OLDI/JEIDA */ + fpga_data = 2; + break; + case 1: /* SPWG/VESA */ + fpga_data = 0; + break; + case 2: /* ZDML */ + fpga_data = 1; + break; + default: + return -EINVAL; + } + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.config, + 3U << 6, fpga_data << 6); + + return count; +} + +static ssize_t display_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.resolution); + + return sprintf(buf, "%u\n", config >> 16); +} + +static ssize_t display_width_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFFFF) + return -EINVAL; + + mutex_lock(voutdev->vdev.lock); + if (vb2_is_busy(voutdev->vdev.queue)) { + mutex_unlock(voutdev->vdev.lock); + return -EBUSY; + } + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.resolution, + 0xFFFF0000, val << 16); + + mutex_unlock(voutdev->vdev.lock); + + return count; +} + +static ssize_t display_height_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.resolution); + + return sprintf(buf, "%u\n", config & 0xFFFF); +} + +static ssize_t display_height_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFFFF) + return -EINVAL; + + mutex_lock(voutdev->vdev.lock); + if (vb2_is_busy(voutdev->vdev.queue)) { + mutex_unlock(voutdev->vdev.lock); + return -EBUSY; + } + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.resolution, + 0xFFFF, val); + + mutex_unlock(voutdev->vdev.lock); + + return count; +} + +static ssize_t frame_rate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 period = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.frame_limit); + + return sprintf(buf, "%u\n", period ? MGB4_HW_FREQ / period : 0); +} + +/* + * Frame rate change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t frame_rate_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int limit, ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + limit = val ? MGB4_HW_FREQ / val : 0; + mgb4_write_reg(&voutdev->mgbdev->video, + voutdev->config->regs.frame_limit, limit); + + return count; +} + +static ssize_t hsync_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.hsync); + + return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16); +} + +/* + * HSYNC width change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t hsync_width_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFF) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.hsync, + 0x00FF0000, val << 16); + + return count; +} + +static ssize_t vsync_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.vsync); + + return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16); +} + +/* + * VSYNC vidth change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t vsync_width_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFF) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync, + 0x00FF0000, val << 16); + + return count; +} + +static ssize_t hback_porch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.hsync); + + return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8); +} + +/* + * hback porch change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t hback_porch_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFF) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.hsync, + 0x0000FF00, val << 8); + + return count; +} + +static ssize_t vback_porch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.vsync); + + return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8); +} + +/* + * vback porch change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t vback_porch_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFF) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync, + 0x0000FF00, val << 8); + + return count; +} + +static ssize_t hfront_porch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.hsync); + + return sprintf(buf, "%u\n", (sig & 0x000000FF)); +} + +/* + * hfront porch change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t hfront_porch_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFF) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.hsync, + 0x000000FF, val); + + return count; +} + +static ssize_t vfront_porch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.vsync); + + return sprintf(buf, "%u\n", (sig & 0x000000FF)); +} + +/* + * vfront porch change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t vfront_porch_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 0xFF) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync, + 0x000000FF, val); + + return count; +} + +/* FPDL3 only */ + +static ssize_t hsync_polarity_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.hsync); + + return sprintf(buf, "%u\n", (config & (1U << 31)) >> 31); +} + +/* + * HSYNC polarity change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t hsync_polarity_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 1) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.hsync, + (1U << 31), val << 31); + + return count; +} + +static ssize_t vsync_polarity_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.vsync); + + return sprintf(buf, "%u\n", (config & (1U << 31)) >> 31); +} + +/* + * VSYNC polarity change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t vsync_polarity_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 1) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync, + (1U << 31), val << 31); + + return count; +} + +static ssize_t de_polarity_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u32 config = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.vsync); + + return sprintf(buf, "%u\n", (config & (1U << 30)) >> 30); +} + +/* + * DE polarity change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t de_polarity_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val > 1) + return -EINVAL; + + mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync, + (1U << 30), val << 30); + + return count; +} + +static ssize_t fpdl3_output_width_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + s32 ret; + + mutex_lock(&voutdev->mgbdev->i2c_lock); + ret = mgb4_i2c_read_byte(&voutdev->ser, 0x5B); + mutex_unlock(&voutdev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + switch ((u8)ret & 0x03) { + case 0: + return sprintf(buf, "0\n"); + case 1: + return sprintf(buf, "1\n"); + case 3: + return sprintf(buf, "2\n"); + default: + return -EINVAL; + } +} + +/* + * FPD-Link width change is expected to be called on live streams. Video device + * locking/queue check is not needed. + */ +static ssize_t fpdl3_output_width_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + u8 i2c_data; + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + switch (val) { + case 0: /* auto */ + i2c_data = 0x00; + break; + case 1: /* single */ + i2c_data = 0x01; + break; + case 2: /* dual */ + i2c_data = 0x03; + break; + default: + return -EINVAL; + } + + mutex_lock(&voutdev->mgbdev->i2c_lock); + ret = mgb4_i2c_mask_byte(&voutdev->ser, 0x5B, 0x03, i2c_data); + mutex_unlock(&voutdev->mgbdev->i2c_lock); + if (ret < 0) + return -EIO; + + return count; +} + +static ssize_t pclk_frequency_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + + return sprintf(buf, "%u\n", voutdev->freq); +} + +static ssize_t pclk_frequency_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct video_device *vdev = to_video_device(dev); + struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + struct mgb4_dev *mgbdev = voutdev->mgbdev; + unsigned long val; + int ret; + unsigned int dp; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(voutdev->vdev.lock); + if (vb2_is_busy(voutdev->vdev.queue)) { + mutex_unlock(voutdev->vdev.lock); + return -EBUSY; + } + + dp = (MGB4_IS_FPDL3(mgbdev) && val > 50000) ? 1 : 0; + voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, val >> dp) << dp; + mgb4_mask_reg(&mgbdev->video, voutdev->config->regs.config, 0x10, + dp << 4); + if (MGB4_IS_FPDL3(mgbdev)) { + mutex_lock(&mgbdev->i2c_lock); + ret = mgb4_i2c_mask_byte(&voutdev->ser, 0x4F, 1 << 6, + ((~dp) & 1) << 6); + mutex_unlock(&mgbdev->i2c_lock); + } + + mutex_unlock(voutdev->vdev.lock); + + return (ret < 0) ? -EIO : count; +} + +static DEVICE_ATTR_RO(output_id); +static DEVICE_ATTR_RW(video_source); +static DEVICE_ATTR_RW(display_width); +static DEVICE_ATTR_RW(display_height); +static DEVICE_ATTR_RW(frame_rate); +static DEVICE_ATTR_RW(hsync_polarity); +static DEVICE_ATTR_RW(vsync_polarity); +static DEVICE_ATTR_RW(de_polarity); +static DEVICE_ATTR_RW(pclk_frequency); +static DEVICE_ATTR_RW(hsync_width); +static DEVICE_ATTR_RW(vsync_width); +static DEVICE_ATTR_RW(hback_porch); +static DEVICE_ATTR_RW(hfront_porch); +static DEVICE_ATTR_RW(vback_porch); +static DEVICE_ATTR_RW(vfront_porch); +static DEVICE_ATTR_RW(color_mapping); + +static DEVICE_ATTR_RW(fpdl3_output_width); + +struct attribute *mgb4_fpdl3_out_attrs[] = { + &dev_attr_output_id.attr, + &dev_attr_video_source.attr, + &dev_attr_display_width.attr, + &dev_attr_display_height.attr, + &dev_attr_frame_rate.attr, + &dev_attr_hsync_polarity.attr, + &dev_attr_vsync_polarity.attr, + &dev_attr_de_polarity.attr, + &dev_attr_pclk_frequency.attr, + &dev_attr_hsync_width.attr, + &dev_attr_vsync_width.attr, + &dev_attr_hback_porch.attr, + &dev_attr_hfront_porch.attr, + &dev_attr_vback_porch.attr, + &dev_attr_vfront_porch.attr, + &dev_attr_fpdl3_output_width.attr, + &dev_attr_color_mapping.attr, + NULL +}; + +struct attribute *mgb4_gmsl3_out_attrs[] = { + &dev_attr_output_id.attr, + &dev_attr_video_source.attr, + &dev_attr_display_width.attr, + &dev_attr_display_height.attr, + &dev_attr_frame_rate.attr, + &dev_attr_color_mapping.attr, + NULL +}; + +struct attribute *mgb4_gmsl1_out_attrs[] = { + &dev_attr_output_id.attr, + &dev_attr_video_source.attr, + &dev_attr_display_width.attr, + &dev_attr_display_height.attr, + &dev_attr_frame_rate.attr, + &dev_attr_hsync_polarity.attr, + &dev_attr_vsync_polarity.attr, + &dev_attr_de_polarity.attr, + &dev_attr_pclk_frequency.attr, + &dev_attr_hsync_width.attr, + &dev_attr_vsync_width.attr, + &dev_attr_hback_porch.attr, + &dev_attr_hfront_porch.attr, + &dev_attr_vback_porch.attr, + &dev_attr_vfront_porch.attr, + &dev_attr_color_mapping.attr, + NULL +}; diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_pci.c b/drivers/media/pci/mgb4/mgb4_sysfs_pci.c new file mode 100644 index 000000000..d26935ff9 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_sysfs_pci.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + * + * This module handles all the sysfs info/configuration that is related to the + * PCI card device. + */ + +#include +#include "mgb4_core.h" +#include "mgb4_sysfs.h" + +static ssize_t module_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mgb4_dev *mgbdev = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", mgbdev->module_version & 0x0F); +} + +static ssize_t module_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mgb4_dev *mgbdev = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", mgbdev->module_version >> 4); +} + +static ssize_t fw_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mgb4_dev *mgbdev = dev_get_drvdata(dev); + u32 config = mgb4_read_reg(&mgbdev->video, 0xC4); + + return sprintf(buf, "%u\n", config & 0xFFFF); +} + +static ssize_t fw_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mgb4_dev *mgbdev = dev_get_drvdata(dev); + u32 config = mgb4_read_reg(&mgbdev->video, 0xC4); + + return sprintf(buf, "%u\n", config >> 24); +} + +static ssize_t serial_number_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mgb4_dev *mgbdev = dev_get_drvdata(dev); + u32 sn = mgbdev->serial_number; + + return sprintf(buf, "%03d-%03d-%03d-%03d\n", sn >> 24, (sn >> 16) & 0xFF, + (sn >> 8) & 0xFF, sn & 0xFF); +} + +static DEVICE_ATTR_RO(module_version); +static DEVICE_ATTR_RO(module_type); +static DEVICE_ATTR_RO(fw_version); +static DEVICE_ATTR_RO(fw_type); +static DEVICE_ATTR_RO(serial_number); + +struct attribute *mgb4_pci_attrs[] = { + &dev_attr_module_type.attr, + &dev_attr_module_version.attr, + &dev_attr_fw_type.attr, + &dev_attr_fw_version.attr, + &dev_attr_serial_number.attr, + NULL +}; diff --git a/drivers/media/pci/mgb4/mgb4_trigger.c b/drivers/media/pci/mgb4/mgb4_trigger.c new file mode 100644 index 000000000..70cad324d --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_trigger.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + * + * This module handles the IIO trigger device. The card has two signal inputs + * for event triggers that can be used to record events related to the video + * stream. A standard linux IIO device with triggered buffer capability is + * created and configured that can be used to fetch the events with the same + * clock source as the video frames. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "mgb4_core.h" +#include "mgb4_trigger.h" + +struct trigger_data { + struct mgb4_dev *mgbdev; + struct iio_trigger *trig; +}; + +static int trigger_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct trigger_data *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + *val = mgb4_read_reg(&st->mgbdev->video, 0xA0); + + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int trigger_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct trigger_data *st = iio_priv(indio_dev); + int irq = xdma_get_user_irq(st->mgbdev->xdev, 11); + + if (state) + xdma_enable_user_irq(st->mgbdev->xdev, irq); + else + xdma_disable_user_irq(st->mgbdev->xdev, irq); + + return 0; +} + +static const struct iio_trigger_ops trigger_ops = { + .set_trigger_state = &trigger_set_state, +}; + +static const struct iio_info trigger_info = { + .read_raw = trigger_read_raw, +}; + +#define TRIGGER_CHANNEL(_si) { \ + .type = IIO_ACTIVITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 32, \ + .storagebits = 32, \ + .shift = 0, \ + .endianness = IIO_CPU \ + }, \ +} + +static const struct iio_chan_spec trigger_channels[] = { + TRIGGER_CHANNEL(0), + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static irqreturn_t trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct trigger_data *st = iio_priv(indio_dev); + struct { + u32 data; + aligned_s64 ts; + } scan = { }; + + scan.data = mgb4_read_reg(&st->mgbdev->video, 0xA0); + mgb4_write_reg(&st->mgbdev->video, 0xA0, scan.data); + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp); + iio_trigger_notify_done(indio_dev->trig); + + mgb4_write_reg(&st->mgbdev->video, 0xB4, 1U << 11); + + return IRQ_HANDLED; +} + +static int probe_trigger(struct iio_dev *indio_dev, int irq) +{ + int ret; + struct trigger_data *st = iio_priv(indio_dev); + + st->trig = iio_trigger_alloc(&st->mgbdev->pdev->dev, "%s-dev%d", + indio_dev->name, iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + ret = request_irq(irq, &iio_trigger_generic_data_rdy_poll, IRQF_NO_THREAD, + "mgb4-trigger", st->trig); + if (ret) + goto error_free_trig; + + st->trig->ops = &trigger_ops; + iio_trigger_set_drvdata(st->trig, indio_dev); + ret = iio_trigger_register(st->trig); + if (ret) + goto error_free_irq; + + indio_dev->trig = iio_trigger_get(st->trig); + + return 0; + +error_free_irq: + free_irq(irq, st->trig); +error_free_trig: + iio_trigger_free(st->trig); + + return ret; +} + +static void remove_trigger(struct iio_dev *indio_dev, int irq) +{ + struct trigger_data *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + free_irq(irq, st->trig); + iio_trigger_free(st->trig); +} + +struct iio_dev *mgb4_trigger_create(struct mgb4_dev *mgbdev) +{ + struct iio_dev *indio_dev; + struct trigger_data *data; + struct pci_dev *pdev = mgbdev->pdev; + struct device *dev = &pdev->dev; + int rv, irq; + + indio_dev = iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return NULL; + + indio_dev->info = &trigger_info; + indio_dev->name = "mgb4"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = trigger_channels; + indio_dev->num_channels = ARRAY_SIZE(trigger_channels); + + data = iio_priv(indio_dev); + data->mgbdev = mgbdev; + + irq = xdma_get_user_irq(mgbdev->xdev, 11); + rv = probe_trigger(indio_dev, irq); + if (rv < 0) { + dev_err(dev, "iio triggered setup failed\n"); + goto error_alloc; + } + rv = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + trigger_handler, NULL); + if (rv < 0) { + dev_err(dev, "iio triggered buffer setup failed\n"); + goto error_trigger; + } + rv = iio_device_register(indio_dev); + if (rv < 0) { + dev_err(dev, "iio device register failed\n"); + goto error_buffer; + } + + return indio_dev; + +error_buffer: + iio_triggered_buffer_cleanup(indio_dev); +error_trigger: + remove_trigger(indio_dev, irq); +error_alloc: + iio_device_free(indio_dev); + + return NULL; +} + +void mgb4_trigger_free(struct iio_dev *indio_dev) +{ + struct trigger_data *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + remove_trigger(indio_dev, xdma_get_user_irq(st->mgbdev->xdev, 11)); + iio_device_free(indio_dev); +} diff --git a/drivers/media/pci/mgb4/mgb4_trigger.h b/drivers/media/pci/mgb4/mgb4_trigger.h new file mode 100644 index 000000000..6c25bc457 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_trigger.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2022 Digiteq Automotive + * author: Martin Tuma + */ + +struct iio_dev *mgb4_trigger_create(struct mgb4_dev *mgbdev); +void mgb4_trigger_free(struct iio_dev *indio_dev); diff --git a/drivers/media/pci/mgb4/mgb4_vin.c b/drivers/media/pci/mgb4/mgb4_vin.c new file mode 100644 index 000000000..b24785455 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_vin.c @@ -0,0 +1,1078 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + * + * This is the v4l2 input device module. It initializes the signal deserializers + * and creates the v4l2 video devices. The input signal can change at any time + * which is handled by the "timings" callbacks and an IRQ based watcher, that + * emits the V4L2_EVENT_SOURCE_CHANGE event in case of a signal source change. + * + * When the device is in loopback mode (a direct, in HW, in->out frame passing + * mode) the card's frame queue must be running regardless of whether a v4l2 + * stream is running and the output parameters like frame buffers padding must + * be in sync with the input parameters. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mgb4_core.h" +#include "mgb4_dma.h" +#include "mgb4_sysfs.h" +#include "mgb4_io.h" +#include "mgb4_vout.h" +#include "mgb4_vin.h" + +ATTRIBUTE_GROUPS(mgb4_fpdl3_in); +ATTRIBUTE_GROUPS(mgb4_gmsl3_in); +ATTRIBUTE_GROUPS(mgb4_gmsl1_in); + +static const struct mgb4_vin_config vin_cfg[] = { + {0, 0, 0, 6, {0x10, 0x00, 0x04, 0x08, 0x1C, 0x14, 0x18, 0x20, 0x24, 0x28, 0xE8}}, + {1, 1, 1, 7, {0x40, 0x30, 0x34, 0x38, 0x4C, 0x44, 0x48, 0x50, 0x54, 0x58, 0xEC}} +}; + +static const struct i2c_board_info fpdl3_deser_info[] = { + {I2C_BOARD_INFO("deserializer1", 0x38)}, + {I2C_BOARD_INFO("deserializer2", 0x36)}, +}; + +static const struct i2c_board_info gmsl3_deser_info[] = { + {I2C_BOARD_INFO("deserializer1", 0x4C)}, + {I2C_BOARD_INFO("deserializer2", 0x2A)}, +}; + +static const struct i2c_board_info gmsl3c_deser_info[] = { + {I2C_BOARD_INFO("deserializer1", 0x6A)}, + {I2C_BOARD_INFO("deserializer2", 0x6C)}, +}; + +static const struct i2c_board_info gmsl1_deser_info[] = { + {I2C_BOARD_INFO("deserializer1", 0x2C)}, + {I2C_BOARD_INFO("deserializer2", 0x6C)}, +}; + +static const struct mgb4_i2c_kv fpdl3_i2c[] = { + {0x06, 0xFF, 0x04}, {0x07, 0xFF, 0x01}, {0x45, 0xFF, 0xE8}, + {0x49, 0xFF, 0x00}, {0x34, 0xFF, 0x00}, {0x23, 0xFF, 0x00} +}; + +static const struct mgb4_i2c_kv gmsl3_i2c[] = { + {0x01, 0x03, 0x03}, {0x300, 0x0C, 0x0C}, {0x03, 0xC0, 0xC0}, + {0x1CE, 0x0E, 0x0E}, {0x11, 0x05, 0x00}, {0x05, 0xC0, 0x40}, + {0x307, 0x0F, 0x00}, {0xA0, 0x03, 0x00}, {0x3E0, 0x07, 0x07}, + {0x308, 0x01, 0x01}, {0x10, 0x20, 0x20}, {0x300, 0x40, 0x40} +}; + +static const struct mgb4_i2c_kv gmsl3c_i2c[] = { + {0x01, 0x03, 0x02}, {0x300, 0x0C, 0x08}, {0x03, 0xC0, 0x00}, + {0x1CE, 0x0E, 0x0E}, {0x11, 0x05, 0x05}, {0x05, 0xC0, 0x40}, + {0x307, 0x0F, 0x00}, {0xA0, 0x03, 0x00}, {0x3E0, 0x07, 0x00}, + {0x308, 0x01, 0x00}, {0x10, 0x20, 0x20}, {0x300, 0x40, 0x40} +}; + +static const struct mgb4_i2c_kv gmsl1_i2c[] = { +}; + +static const struct v4l2_dv_timings_cap video_timings_cap = { + .type = V4L2_DV_BT_656_1120, + .bt = { + .min_width = 64, + .max_width = 4096, + .min_height = 64, + .max_height = 4096, + .min_pixelclock = 20000000, + .max_pixelclock = 200000000, + .standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF, + .capabilities = V4L2_DV_BT_CAP_PROGRESSIVE | + V4L2_DV_BT_CAP_CUSTOM, + }, +}; + +/* Dummy timings when no signal present */ +static const struct v4l2_dv_timings cea1080p60 = V4L2_DV_BT_CEA_1920X1080P60; + +/* + * Returns the video output connected with the given video input if the input + * is in loopback mode. + */ +static struct mgb4_vout_dev *loopback_dev(struct mgb4_vin_dev *vindev, int i) +{ + struct mgb4_vout_dev *voutdev; + u32 config; + + voutdev = vindev->mgbdev->vout[i]; + if (!voutdev) + return NULL; + + config = mgb4_read_reg(&voutdev->mgbdev->video, + voutdev->config->regs.config); + if ((config & 0xc) >> 2 == vindev->config->id) + return voutdev; + + return NULL; +} + +/* + * Check, whether the loopback mode - a HW INPUT->OUTPUT transmission - is + * enabled on the given input. + */ +static int loopback_active(struct mgb4_vin_dev *vindev) +{ + int i; + + for (i = 0; i < MGB4_VOUT_DEVICES; i++) + if (loopback_dev(vindev, i)) + return 1; + + return 0; +} + +/* + * Set the output frame buffer padding of all outputs connected with the given + * input when the video input is set to loopback mode. The paddings must be + * the same for the loopback to work properly. + */ +static void set_loopback_padding(struct mgb4_vin_dev *vindev, u32 padding) +{ + struct mgb4_regs *video = &vindev->mgbdev->video; + struct mgb4_vout_dev *voutdev; + int i; + + for (i = 0; i < MGB4_VOUT_DEVICES; i++) { + voutdev = loopback_dev(vindev, i); + if (voutdev) + mgb4_write_reg(video, voutdev->config->regs.padding, + padding); + } +} + +static int get_timings(struct mgb4_vin_dev *vindev, + struct v4l2_dv_timings *timings) +{ + struct mgb4_regs *video = &vindev->mgbdev->video; + const struct mgb4_vin_regs *regs = &vindev->config->regs; + + u32 status = mgb4_read_reg(video, regs->status); + u32 pclk = mgb4_read_reg(video, regs->pclk); + u32 hsync = mgb4_read_reg(video, regs->hsync); + u32 vsync = mgb4_read_reg(video, regs->vsync); + u32 resolution = mgb4_read_reg(video, regs->resolution); + + if (!(status & (1U << 2))) + return -ENOLCK; + if (!(status & (3 << 9))) + return -ENOLINK; + + memset(timings, 0, sizeof(*timings)); + timings->type = V4L2_DV_BT_656_1120; + timings->bt.width = resolution >> 16; + timings->bt.height = resolution & 0xFFFF; + if (status & (1U << 12)) + timings->bt.polarities |= V4L2_DV_HSYNC_POS_POL; + if (status & (1U << 13)) + timings->bt.polarities |= V4L2_DV_VSYNC_POS_POL; + timings->bt.pixelclock = pclk * 1000; + timings->bt.hsync = (hsync & 0x00FF0000) >> 16; + timings->bt.vsync = (vsync & 0x00FF0000) >> 16; + timings->bt.hbackporch = (hsync & 0x0000FF00) >> 8; + timings->bt.hfrontporch = hsync & 0x000000FF; + timings->bt.vbackporch = (vsync & 0x0000FF00) >> 8; + timings->bt.vfrontporch = vsync & 0x000000FF; + + return 0; +} + +static void return_all_buffers(struct mgb4_vin_dev *vindev, + enum vb2_buffer_state state) +{ + struct mgb4_frame_buffer *buf, *node; + unsigned long flags; + + spin_lock_irqsave(&vindev->qlock, flags); + list_for_each_entry_safe(buf, node, &vindev->buf_list, list) { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->list); + } + spin_unlock_irqrestore(&vindev->qlock, flags); +} + +static int queue_setup(struct vb2_queue *q, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct mgb4_vin_dev *vindev = vb2_get_drv_priv(q); + struct mgb4_regs *video = &vindev->mgbdev->video; + u32 config = mgb4_read_reg(video, vindev->config->regs.config); + u32 pixelsize = (config & (1U << 16)) ? 2 : 4; + unsigned int size = (vindev->timings.bt.width + vindev->padding) + * vindev->timings.bt.height * pixelsize; + + /* + * If I/O reconfiguration is in process, do not allow to start + * the queue. See video_source_store() in mgb4_sysfs_out.c for + * details. + */ + if (test_bit(0, &vindev->mgbdev->io_reconfig)) + return -EBUSY; + + if (!size) + return -EINVAL; + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + *nplanes = 1; + sizes[0] = size; + + return 0; +} + +static int buffer_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mgb4_frame_buffer *buf = to_frame_buffer(vbuf); + + INIT_LIST_HEAD(&buf->list); + + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct mgb4_vin_dev *vindev = vb2_get_drv_priv(vb->vb2_queue); + struct mgb4_regs *video = &vindev->mgbdev->video; + struct device *dev = &vindev->mgbdev->pdev->dev; + u32 config = mgb4_read_reg(video, vindev->config->regs.config); + u32 pixelsize = (config & (1U << 16)) ? 2 : 4; + unsigned int size = (vindev->timings.bt.width + vindev->padding) + * vindev->timings.bt.height * pixelsize; + + if (vb2_plane_size(vb, 0) < size) { + dev_err(dev, "buffer too small (%lu < %u)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + + return 0; +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + struct mgb4_vin_dev *vindev = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mgb4_frame_buffer *buf = to_frame_buffer(vbuf); + unsigned long flags; + + spin_lock_irqsave(&vindev->qlock, flags); + list_add_tail(&buf->list, &vindev->buf_list); + spin_unlock_irqrestore(&vindev->qlock, flags); +} + +static void stop_streaming(struct vb2_queue *vq) +{ + struct mgb4_vin_dev *vindev = vb2_get_drv_priv(vq); + struct mgb4_regs *video = &vindev->mgbdev->video; + const struct mgb4_vin_config *config = vindev->config; + int irq = xdma_get_user_irq(vindev->mgbdev->xdev, config->vin_irq); + + xdma_disable_user_irq(vindev->mgbdev->xdev, irq); + + /* + * In loopback mode, the HW frame queue must be left running for + * the IN->OUT transmission to work! + */ + if (!loopback_active(vindev)) + mgb4_mask_reg(&vindev->mgbdev->video, config->regs.config, 0x2, + 0x0); + + mgb4_write_reg(video, vindev->config->regs.padding, 0); + set_loopback_padding(vindev, 0); + + cancel_work_sync(&vindev->dma_work); + return_all_buffers(vindev, VB2_BUF_STATE_ERROR); +} + +static int start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct mgb4_vin_dev *vindev = vb2_get_drv_priv(vq); + struct mgb4_regs *video = &vindev->mgbdev->video; + const struct mgb4_vin_config *config = vindev->config; + int irq = xdma_get_user_irq(vindev->mgbdev->xdev, config->vin_irq); + + vindev->sequence = 0; + + /* + * In loopback mode, the HW frame queue is already running. + */ + if (!loopback_active(vindev)) + mgb4_mask_reg(&vindev->mgbdev->video, config->regs.config, 0x2, + 0x2); + + mgb4_write_reg(video, vindev->config->regs.padding, vindev->padding); + set_loopback_padding(vindev, vindev->padding); + + xdma_enable_user_irq(vindev->mgbdev->xdev, irq); + + return 0; +} + +static const struct vb2_ops queue_ops = { + .queue_setup = queue_setup, + .buf_init = buffer_init, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, +}; + +static int fh_open(struct file *file) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + int rv; + + mutex_lock(&vindev->lock); + + rv = v4l2_fh_open(file); + if (rv) + goto out; + + if (!v4l2_fh_is_singular_file(file)) + goto out; + + if (get_timings(vindev, &vindev->timings) < 0) + vindev->timings = cea1080p60; + +out: + mutex_unlock(&vindev->lock); + return rv; +} + +static const struct v4l2_file_operations video_fops = { + .owner = THIS_MODULE, + .open = fh_open, + .release = vb2_fop_release, + .unlocked_ioctl = video_ioctl2, + .read = vb2_fop_read, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strscpy(cap->card, "MGB4 PCIe Card", sizeof(cap->card)); + + return 0; +} + +static int vidioc_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + struct mgb4_regs *video = &vindev->mgbdev->video; + + if (f->index == 0) { + f->pixelformat = V4L2_PIX_FMT_ABGR32; + return 0; + } else if (f->index == 1 && has_yuv(video)) { + f->pixelformat = V4L2_PIX_FMT_YUYV; + return 0; + } else { + return -EINVAL; + } +} + +static int vidioc_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *ival) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + struct mgb4_regs *video = &vindev->mgbdev->video; + + if (ival->index != 0) + return -EINVAL; + if (!(ival->pixel_format == V4L2_PIX_FMT_ABGR32 || + ((has_yuv(video) && ival->pixel_format == V4L2_PIX_FMT_YUYV)))) + return -EINVAL; + if (ival->width != vindev->timings.bt.width || + ival->height != vindev->timings.bt.height) + return -EINVAL; + + ival->type = V4L2_FRMIVAL_TYPE_STEPWISE; + ival->stepwise.max.denominator = MGB4_HW_FREQ; + ival->stepwise.max.numerator = 0xFFFFFFFF; + ival->stepwise.min.denominator = vindev->timings.bt.pixelclock; + ival->stepwise.min.numerator = pixel_size(&vindev->timings); + ival->stepwise.step.denominator = MGB4_HW_FREQ; + ival->stepwise.step.numerator = 1; + + return 0; +} + +static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + struct mgb4_regs *video = &vindev->mgbdev->video; + u32 config = mgb4_read_reg(video, vindev->config->regs.config); + + f->fmt.pix.width = vindev->timings.bt.width; + f->fmt.pix.height = vindev->timings.bt.height; + f->fmt.pix.field = V4L2_FIELD_NONE; + + if (config & (1U << 16)) { + f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; + if (config & (1U << 20)) { + f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; + } else { + if (config & (1U << 19)) + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + else + f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + } + f->fmt.pix.bytesperline = (f->fmt.pix.width + vindev->padding) * 2; + } else { + f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32; + f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW; + f->fmt.pix.bytesperline = (f->fmt.pix.width + vindev->padding) * 4; + } + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; + + return 0; +} + +static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + struct mgb4_regs *video = &vindev->mgbdev->video; + u32 pixelsize; + + f->fmt.pix.width = vindev->timings.bt.width; + f->fmt.pix.height = vindev->timings.bt.height; + f->fmt.pix.field = V4L2_FIELD_NONE; + + if (has_yuv(video) && f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) { + pixelsize = 2; + if (!(f->fmt.pix.colorspace == V4L2_COLORSPACE_REC709 || + f->fmt.pix.colorspace == V4L2_COLORSPACE_SMPTE170M)) + f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + } else { + pixelsize = 4; + f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32; + f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW; + } + + if (f->fmt.pix.bytesperline > f->fmt.pix.width * pixelsize && + f->fmt.pix.bytesperline < f->fmt.pix.width * pixelsize * 2) + f->fmt.pix.bytesperline = ALIGN(f->fmt.pix.bytesperline, + pixelsize); + else + f->fmt.pix.bytesperline = f->fmt.pix.width * pixelsize; + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; + + return 0; +} + +static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + struct mgb4_regs *video = &vindev->mgbdev->video; + u32 config, pixelsize; + + if (vb2_is_busy(&vindev->queue)) + return -EBUSY; + + vidioc_try_fmt(file, priv, f); + + config = mgb4_read_reg(video, vindev->config->regs.config); + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) { + pixelsize = 2; + config |= 1U << 16; + + if (f->fmt.pix.colorspace == V4L2_COLORSPACE_REC709) { + config |= 1U << 20; + config |= 1U << 19; + } else if (f->fmt.pix.colorspace == V4L2_COLORSPACE_SMPTE170M) { + config &= ~(1U << 20); + config |= 1U << 19; + } else { + config &= ~(1U << 20); + config &= ~(1U << 19); + } + } else { + pixelsize = 4; + config &= ~(1U << 16); + } + mgb4_write_reg(video, vindev->config->regs.config, config); + + vindev->padding = (f->fmt.pix.bytesperline - (f->fmt.pix.width + * pixelsize)) / pixelsize; + + return 0; +} + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + struct mgb4_regs *video = &vindev->mgbdev->video; + u32 status; + + if (i->index != 0) + return -EINVAL; + + strscpy(i->name, "MGB4", sizeof(i->name)); + i->type = V4L2_INPUT_TYPE_CAMERA; + i->capabilities = V4L2_IN_CAP_DV_TIMINGS; + i->status = 0; + + status = mgb4_read_reg(video, vindev->config->regs.status); + if (!(status & (1U << 2))) + i->status |= V4L2_IN_ST_NO_SYNC; + if (!(status & (3 << 9))) + i->status |= V4L2_IN_ST_NO_SIGNAL; + + return 0; +} + +static int vidioc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + + if (fsize->index != 0 || !(fsize->pixel_format == V4L2_PIX_FMT_ABGR32 || + fsize->pixel_format == V4L2_PIX_FMT_YUYV)) + return -EINVAL; + + fsize->discrete.width = vindev->timings.bt.width; + fsize->discrete.height = vindev->timings.bt.height; + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + return (i == 0) ? 0 : -EINVAL; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int vidioc_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *parm) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + struct mgb4_regs *video = &vindev->mgbdev->video; + struct v4l2_fract *tpf = &parm->parm.output.timeperframe; + u32 timer; + + parm->parm.capture.readbuffers = 2; + + if (has_timeperframe(video)) { + timer = mgb4_read_reg(video, vindev->config->regs.timer); + if (timer < 0xFFFF) { + tpf->numerator = pixel_size(&vindev->timings); + tpf->denominator = vindev->timings.bt.pixelclock; + } else { + tpf->numerator = timer; + tpf->denominator = MGB4_HW_FREQ; + } + + parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + } + + return 0; +} + +static int vidioc_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *parm) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + struct mgb4_regs *video = &vindev->mgbdev->video; + struct v4l2_fract *tpf = &parm->parm.output.timeperframe; + u32 period, timer; + + if (has_timeperframe(video)) { + timer = tpf->denominator ? + MGB4_PERIOD(tpf->numerator, tpf->denominator) : 0; + if (timer) { + period = MGB4_PERIOD(pixel_size(&vindev->timings), + vindev->timings.bt.pixelclock); + if (timer < period) + timer = 0; + } + + mgb4_write_reg(video, vindev->config->regs.timer, timer); + } + + return vidioc_g_parm(file, priv, parm); +} + +static int vidioc_s_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + + if (timings->bt.width < video_timings_cap.bt.min_width || + timings->bt.width > video_timings_cap.bt.max_width || + timings->bt.height < video_timings_cap.bt.min_height || + timings->bt.height > video_timings_cap.bt.max_height) + return -EINVAL; + if (v4l2_match_dv_timings(timings, &vindev->timings, 0, false)) + return 0; + if (vb2_is_busy(&vindev->queue)) + return -EBUSY; + + vindev->timings = *timings; + + return 0; +} + +static int vidioc_g_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + *timings = vindev->timings; + + return 0; +} + +static int vidioc_query_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + + return get_timings(vindev, timings); +} + +static int vidioc_enum_dv_timings(struct file *file, void *fh, + struct v4l2_enum_dv_timings *timings) +{ + struct mgb4_vin_dev *vindev = video_drvdata(file); + + if (timings->index != 0) + return -EINVAL; + if (get_timings(vindev, &timings->timings) < 0) + return -ENODATA; + + return 0; +} + +static int vidioc_dv_timings_cap(struct file *file, void *fh, + struct v4l2_dv_timings_cap *cap) +{ + *cap = video_timings_cap; + + return 0; +} + +static int vidioc_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subscribe(fh, sub); + } + + return v4l2_ctrl_subscribe_event(fh, sub); +} + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt, + .vidioc_enum_framesizes = vidioc_enum_framesizes, + .vidioc_enum_frameintervals = vidioc_enum_frameintervals, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_g_parm = vidioc_g_parm, + .vidioc_s_parm = vidioc_s_parm, + .vidioc_dv_timings_cap = vidioc_dv_timings_cap, + .vidioc_enum_dv_timings = vidioc_enum_dv_timings, + .vidioc_g_dv_timings = vidioc_g_dv_timings, + .vidioc_s_dv_timings = vidioc_s_dv_timings, + .vidioc_query_dv_timings = vidioc_query_dv_timings, + .vidioc_subscribe_event = vidioc_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static void dma_transfer(struct work_struct *work) +{ + struct mgb4_vin_dev *vindev = container_of(work, struct mgb4_vin_dev, + dma_work); + struct mgb4_regs *video = &vindev->mgbdev->video; + struct device *dev = &vindev->mgbdev->pdev->dev; + struct mgb4_frame_buffer *buf = NULL; + unsigned long flags; + u32 addr; + int rv; + + spin_lock_irqsave(&vindev->qlock, flags); + if (!list_empty(&vindev->buf_list)) { + buf = list_first_entry(&vindev->buf_list, + struct mgb4_frame_buffer, list); + list_del_init(vindev->buf_list.next); + } + spin_unlock_irqrestore(&vindev->qlock, flags); + + if (!buf) + return; + + addr = mgb4_read_reg(video, vindev->config->regs.address); + if (addr >= MGB4_ERR_QUEUE_FULL) { + dev_dbg(dev, "frame queue error (%d)\n", (int)addr); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + return; + } + + rv = mgb4_dma_transfer(vindev->mgbdev, vindev->config->dma_channel, + false, addr, + vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0)); + if (rv < 0) { + dev_warn(dev, "DMA transfer error\n"); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } else { + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + buf->vb.sequence = vindev->sequence++; + buf->vb.field = V4L2_FIELD_NONE; + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + } +} + +static void signal_change(struct work_struct *work) +{ + struct mgb4_vin_dev *vindev = container_of(work, struct mgb4_vin_dev, + err_work); + struct mgb4_regs *video = &vindev->mgbdev->video; + struct v4l2_bt_timings *timings = &vindev->timings.bt; + struct device *dev = &vindev->mgbdev->pdev->dev; + + u32 resolution = mgb4_read_reg(video, vindev->config->regs.resolution); + u32 width = resolution >> 16; + u32 height = resolution & 0xFFFF; + + static const struct v4l2_event ev = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, + }; + + v4l2_event_queue(&vindev->vdev, &ev); + + if (timings->width != width || timings->height != height) { + if (vb2_is_streaming(&vindev->queue)) + vb2_queue_error(&vindev->queue); + } + + dev_dbg(dev, "stream changed to %ux%u\n", width, height); +} + +static irqreturn_t vin_handler(int irq, void *ctx) +{ + struct mgb4_vin_dev *vindev = (struct mgb4_vin_dev *)ctx; + struct mgb4_regs *video = &vindev->mgbdev->video; + + schedule_work(&vindev->dma_work); + + mgb4_write_reg(video, 0xB4, 1U << vindev->config->vin_irq); + + return IRQ_HANDLED; +} + +static irqreturn_t err_handler(int irq, void *ctx) +{ + struct mgb4_vin_dev *vindev = (struct mgb4_vin_dev *)ctx; + struct mgb4_regs *video = &vindev->mgbdev->video; + + schedule_work(&vindev->err_work); + + mgb4_write_reg(video, 0xB4, 1U << vindev->config->err_irq); + + return IRQ_HANDLED; +} + +static int deser_init(struct mgb4_vin_dev *vindev, int id) +{ + int rv, addr_size = 0; + size_t count = 0; + const struct mgb4_i2c_kv *values = NULL; + const struct i2c_board_info *info = NULL; + struct device *dev = &vindev->mgbdev->pdev->dev; + + if (MGB4_IS_GMSL3(vindev->mgbdev)) { + if (MGB4_IS_GMSL3C(vindev->mgbdev)) { + info = &gmsl3c_deser_info[id]; + addr_size = 16; + values = gmsl3c_i2c; + count = ARRAY_SIZE(gmsl3c_i2c); + } else { + info = &gmsl3_deser_info[id]; + addr_size = 16; + values = gmsl3_i2c; + count = ARRAY_SIZE(gmsl3_i2c); + } + } else if (MGB4_IS_FPDL3(vindev->mgbdev)) { + info = &fpdl3_deser_info[id]; + addr_size = 8; + values = fpdl3_i2c; + count = ARRAY_SIZE(fpdl3_i2c); + } else if (MGB4_IS_GMSL1(vindev->mgbdev)) { + info = &gmsl1_deser_info[id]; + addr_size = 8; + values = gmsl1_i2c; + count = ARRAY_SIZE(gmsl1_i2c); + } else { + return -EINVAL; + } + + rv = mgb4_i2c_init(&vindev->deser, vindev->mgbdev->i2c_adap, info, + addr_size); + if (rv < 0) { + dev_err(dev, "failed to create deserializer\n"); + return rv; + } + rv = mgb4_i2c_configure(&vindev->deser, values, count); + if (rv < 0) { + dev_err(dev, "failed to configure deserializer\n"); + goto err_i2c_dev; + } + + return 0; + +err_i2c_dev: + mgb4_i2c_free(&vindev->deser); + + return rv; +} + +static void fpga_init(struct mgb4_vin_dev *vindev) +{ + struct mgb4_regs *video = &vindev->mgbdev->video; + const struct mgb4_vin_regs *regs = &vindev->config->regs; + int dp = MGB4_IS_GMSL1(vindev->mgbdev) ? 0 : 1; + + mgb4_write_reg(video, regs->config, 0x00000001); + mgb4_write_reg(video, regs->sync, 0x03E80002); + mgb4_write_reg(video, regs->padding, 0x00000000); + mgb4_write_reg(video, regs->config, dp << 9); +} + +static void create_debugfs(struct mgb4_vin_dev *vindev) +{ +#ifdef CONFIG_DEBUG_FS + struct mgb4_regs *video = &vindev->mgbdev->video; + struct dentry *entry; + + if (IS_ERR_OR_NULL(vindev->mgbdev->debugfs)) + return; + entry = debugfs_create_dir(vindev->vdev.name, vindev->mgbdev->debugfs); + if (IS_ERR(entry)) + return; + + vindev->regs[0].name = "CONFIG"; + vindev->regs[0].offset = vindev->config->regs.config; + vindev->regs[1].name = "STATUS"; + vindev->regs[1].offset = vindev->config->regs.status; + vindev->regs[2].name = "RESOLUTION"; + vindev->regs[2].offset = vindev->config->regs.resolution; + vindev->regs[3].name = "FRAME_PERIOD"; + vindev->regs[3].offset = vindev->config->regs.frame_period; + vindev->regs[4].name = "HS_VS_GENER_SETTINGS"; + vindev->regs[4].offset = vindev->config->regs.sync; + vindev->regs[5].name = "PCLK_FREQUENCY"; + vindev->regs[5].offset = vindev->config->regs.pclk; + vindev->regs[6].name = "VIDEO_PARAMS_1"; + vindev->regs[6].offset = vindev->config->regs.hsync; + vindev->regs[7].name = "VIDEO_PARAMS_2"; + vindev->regs[7].offset = vindev->config->regs.vsync; + vindev->regs[8].name = "PADDING_PIXELS"; + vindev->regs[8].offset = vindev->config->regs.padding; + if (has_timeperframe(video)) { + vindev->regs[9].name = "TIMER"; + vindev->regs[9].offset = vindev->config->regs.timer; + vindev->regset.nregs = 10; + } else { + vindev->regset.nregs = 9; + } + + vindev->regset.base = video->membase; + vindev->regset.regs = vindev->regs; + + debugfs_create_regset32("registers", 0444, entry, &vindev->regset); +#endif +} + +static const struct attribute_group **module_groups(struct mgb4_dev *mgbdev) +{ + if (MGB4_IS_FPDL3(mgbdev)) + return mgb4_fpdl3_in_groups; + else if (MGB4_IS_GMSL3(mgbdev)) + return mgb4_gmsl3_in_groups; + else if (MGB4_IS_GMSL1(mgbdev)) + return mgb4_gmsl1_in_groups; + else + return NULL; +} + +struct mgb4_vin_dev *mgb4_vin_create(struct mgb4_dev *mgbdev, int id) +{ + int rv; + struct mgb4_vin_dev *vindev; + struct pci_dev *pdev = mgbdev->pdev; + struct device *dev = &pdev->dev; + int vin_irq, err_irq; + + vindev = kzalloc_obj(*vindev); + if (!vindev) + return NULL; + + vindev->mgbdev = mgbdev; + vindev->config = &vin_cfg[id]; + + /* Frame queue*/ + INIT_LIST_HEAD(&vindev->buf_list); + spin_lock_init(&vindev->qlock); + + /* Work queues */ + INIT_WORK(&vindev->dma_work, dma_transfer); + INIT_WORK(&vindev->err_work, signal_change); + + /* IRQ callbacks */ + vin_irq = xdma_get_user_irq(mgbdev->xdev, vindev->config->vin_irq); + rv = request_irq(vin_irq, vin_handler, 0, "mgb4-vin", vindev); + if (rv) { + dev_err(dev, "failed to register vin irq handler\n"); + goto err_alloc; + } + err_irq = xdma_get_user_irq(mgbdev->xdev, vindev->config->err_irq); + rv = request_irq(err_irq, err_handler, 0, "mgb4-err", vindev); + if (rv) { + dev_err(dev, "failed to register err irq handler\n"); + goto err_vin_irq; + } + + /* Set the FPGA registers default values */ + fpga_init(vindev); + + /* Set the deserializer default values */ + rv = deser_init(vindev, id); + if (rv) + goto err_err_irq; + + /* V4L2 stuff init */ + rv = v4l2_device_register(dev, &vindev->v4l2dev); + if (rv) { + dev_err(dev, "failed to register v4l2 device\n"); + goto err_err_irq; + } + + mutex_init(&vindev->lock); + + vindev->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vindev->queue.io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ; + vindev->queue.buf_struct_size = sizeof(struct mgb4_frame_buffer); + vindev->queue.ops = &queue_ops; + vindev->queue.mem_ops = &vb2_dma_sg_memops; + vindev->queue.gfp_flags = GFP_DMA32; + vindev->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vindev->queue.min_queued_buffers = 2; + vindev->queue.drv_priv = vindev; + vindev->queue.lock = &vindev->lock; + vindev->queue.dev = dev; + rv = vb2_queue_init(&vindev->queue); + if (rv) { + dev_err(dev, "failed to initialize vb2 queue\n"); + goto err_v4l2_dev; + } + + snprintf(vindev->vdev.name, sizeof(vindev->vdev.name), "mgb4-in%d", + id + 1); + vindev->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE + | V4L2_CAP_STREAMING; + vindev->vdev.fops = &video_fops; + vindev->vdev.ioctl_ops = &video_ioctl_ops; + vindev->vdev.release = video_device_release_empty; + vindev->vdev.v4l2_dev = &vindev->v4l2dev; + vindev->vdev.lock = &vindev->lock; + vindev->vdev.queue = &vindev->queue; + video_set_drvdata(&vindev->vdev, vindev); + + /* Enable the video signal change watcher */ + xdma_enable_user_irq(vindev->mgbdev->xdev, err_irq); + + /* Register the video device */ + rv = video_register_device(&vindev->vdev, VFL_TYPE_VIDEO, -1); + if (rv) { + dev_err(dev, "failed to register video device\n"); + goto err_v4l2_dev; + } + + /* Module sysfs attributes */ + rv = device_add_groups(&vindev->vdev.dev, module_groups(mgbdev)); + if (rv) { + dev_err(dev, "failed to create sysfs attributes\n"); + goto err_video_dev; + } + + create_debugfs(vindev); + + return vindev; + +err_video_dev: + video_unregister_device(&vindev->vdev); +err_v4l2_dev: + v4l2_device_unregister(&vindev->v4l2dev); +err_err_irq: + free_irq(err_irq, vindev); +err_vin_irq: + free_irq(vin_irq, vindev); +err_alloc: + kfree(vindev); + + return NULL; +} + +void mgb4_vin_free(struct mgb4_vin_dev *vindev) +{ + int vin_irq = xdma_get_user_irq(vindev->mgbdev->xdev, + vindev->config->vin_irq); + int err_irq = xdma_get_user_irq(vindev->mgbdev->xdev, + vindev->config->err_irq); + + xdma_disable_user_irq(vindev->mgbdev->xdev, err_irq); + + free_irq(vin_irq, vindev); + free_irq(err_irq, vindev); + + device_remove_groups(&vindev->vdev.dev, module_groups(vindev->mgbdev)); + + mgb4_i2c_free(&vindev->deser); + video_unregister_device(&vindev->vdev); + v4l2_device_unregister(&vindev->v4l2dev); + + kfree(vindev); +} diff --git a/drivers/media/pci/mgb4/mgb4_vin.h b/drivers/media/pci/mgb4/mgb4_vin.h new file mode 100644 index 000000000..2a2c82991 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_vin.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_VIN_H__ +#define __MGB4_VIN_H__ + +#include +#include +#include +#include +#include +#include "mgb4_i2c.h" + +struct mgb4_vin_regs { + u32 address; + u32 config; + u32 status; + u32 resolution; + u32 frame_period; + u32 sync; + u32 pclk; + u32 hsync; + u32 vsync; + u32 padding; + u32 timer; +}; + +struct mgb4_vin_config { + int id; + int dma_channel; + int vin_irq; + int err_irq; + struct mgb4_vin_regs regs; +}; + +struct mgb4_vin_dev { + struct mgb4_dev *mgbdev; + struct v4l2_device v4l2dev; + struct video_device vdev; + struct vb2_queue queue; + struct mutex lock; /* vdev lock */ + + spinlock_t qlock; /* video buffer queue lock */ + struct list_head buf_list; + struct work_struct dma_work, err_work; + + unsigned int sequence; + + struct v4l2_dv_timings timings; + u32 freq_range; + u32 padding; + + struct mgb4_i2c_client deser; + + const struct mgb4_vin_config *config; + +#ifdef CONFIG_DEBUG_FS + struct debugfs_regset32 regset; + struct debugfs_reg32 regs[sizeof(struct mgb4_vin_regs) / 4]; +#endif +}; + +struct mgb4_vin_dev *mgb4_vin_create(struct mgb4_dev *mgbdev, int id); +void mgb4_vin_free(struct mgb4_vin_dev *vindev); + +#endif diff --git a/drivers/media/pci/mgb4/mgb4_vout.c b/drivers/media/pci/mgb4/mgb4_vout.c new file mode 100644 index 000000000..22fbc9e53 --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_vout.c @@ -0,0 +1,873 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + * + * This is the v4l2 output device module. It initializes the signal serializers + * and creates the v4l2 video devices. + * + * When the device is in loopback mode (a direct, in HW, in->out frame passing + * mode) we disable the v4l2 output by returning EBUSY in the open() syscall. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "mgb4_core.h" +#include "mgb4_dma.h" +#include "mgb4_sysfs.h" +#include "mgb4_io.h" +#include "mgb4_cmt.h" +#include "mgb4_vout.h" + +ATTRIBUTE_GROUPS(mgb4_fpdl3_out); +ATTRIBUTE_GROUPS(mgb4_gmsl3_out); +ATTRIBUTE_GROUPS(mgb4_gmsl1_out); + +static const struct mgb4_vout_config vout_cfg[] = { + {0, 0, 8, {0x78, 0x60, 0x64, 0x68, 0x74, 0x6C, 0x70, 0x7C, 0xE0}}, + {1, 1, 9, {0x98, 0x80, 0x84, 0x88, 0x94, 0x8C, 0x90, 0x9C, 0xE4}} +}; + +static const struct i2c_board_info fpdl3_ser_info[] = { + {I2C_BOARD_INFO("serializer1", 0x14)}, + {I2C_BOARD_INFO("serializer2", 0x16)}, +}; + +static const struct i2c_board_info gmsl1_ser_info[] = { + {I2C_BOARD_INFO("serializer1", 0x24)}, + {I2C_BOARD_INFO("serializer2", 0x22)}, +}; + +static const struct mgb4_i2c_kv fpdl3_i2c[] = { + {0x05, 0xFF, 0x04}, {0x06, 0xFF, 0x01}, {0xC2, 0xFF, 0x80} +}; + +static const struct mgb4_i2c_kv gmsl1_i2c[] = { +}; + +static const struct v4l2_dv_timings_cap video_timings_cap = { + .type = V4L2_DV_BT_656_1120, + .bt = { + .min_width = 64, + .max_width = 4096, + .min_height = 64, + .max_height = 4096, + .min_pixelclock = 25000000, + .max_pixelclock = 189284000, + .standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF, + .capabilities = V4L2_DV_BT_CAP_PROGRESSIVE | + V4L2_DV_BT_CAP_CUSTOM, + }, +}; + +static void get_timings(struct mgb4_vout_dev *voutdev, + struct v4l2_dv_timings *timings) +{ + struct mgb4_regs *video = &voutdev->mgbdev->video; + const struct mgb4_vout_regs *regs = &voutdev->config->regs; + + u32 hsync = mgb4_read_reg(video, regs->hsync); + u32 vsync = mgb4_read_reg(video, regs->vsync); + u32 resolution = mgb4_read_reg(video, regs->resolution); + + memset(timings, 0, sizeof(*timings)); + timings->type = V4L2_DV_BT_656_1120; + timings->bt.width = resolution >> 16; + timings->bt.height = resolution & 0xFFFF; + if (hsync & (1U << 31)) + timings->bt.polarities |= V4L2_DV_HSYNC_POS_POL; + if (vsync & (1U << 31)) + timings->bt.polarities |= V4L2_DV_VSYNC_POS_POL; + timings->bt.pixelclock = voutdev->freq * 1000; + timings->bt.hsync = (hsync & 0x00FF0000) >> 16; + timings->bt.vsync = (vsync & 0x00FF0000) >> 16; + timings->bt.hbackporch = (hsync & 0x0000FF00) >> 8; + timings->bt.hfrontporch = hsync & 0x000000FF; + timings->bt.vbackporch = (vsync & 0x0000FF00) >> 8; + timings->bt.vfrontporch = vsync & 0x000000FF; +} + +static void return_all_buffers(struct mgb4_vout_dev *voutdev, + enum vb2_buffer_state state) +{ + struct mgb4_frame_buffer *buf, *node; + unsigned long flags; + + spin_lock_irqsave(&voutdev->qlock, flags); + list_for_each_entry_safe(buf, node, &voutdev->buf_list, list) { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->list); + } + spin_unlock_irqrestore(&voutdev->qlock, flags); +} + +static int queue_setup(struct vb2_queue *q, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct mgb4_vout_dev *voutdev = vb2_get_drv_priv(q); + struct mgb4_regs *video = &voutdev->mgbdev->video; + u32 config = mgb4_read_reg(video, voutdev->config->regs.config); + u32 pixelsize = (config & (1U << 16)) ? 2 : 4; + unsigned int size = (voutdev->width + voutdev->padding) * voutdev->height + * pixelsize; + + /* + * If I/O reconfiguration is in process, do not allow to start + * the queue. See video_source_store() in mgb4_sysfs_out.c for + * details. + */ + if (test_bit(0, &voutdev->mgbdev->io_reconfig)) + return -EBUSY; + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + *nplanes = 1; + sizes[0] = size; + + return 0; +} + +static int buffer_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mgb4_frame_buffer *buf = to_frame_buffer(vbuf); + + INIT_LIST_HEAD(&buf->list); + + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct mgb4_vout_dev *voutdev = vb2_get_drv_priv(vb->vb2_queue); + struct device *dev = &voutdev->mgbdev->pdev->dev; + struct mgb4_regs *video = &voutdev->mgbdev->video; + u32 config = mgb4_read_reg(video, voutdev->config->regs.config); + u32 pixelsize = (config & (1U << 16)) ? 2 : 4; + unsigned int size = (voutdev->width + voutdev->padding) * voutdev->height + * pixelsize; + + if (vb2_plane_size(vb, 0) < size) { + dev_err(dev, "buffer too small (%lu < %u)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + + return 0; +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + struct mgb4_vout_dev *vindev = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mgb4_frame_buffer *buf = to_frame_buffer(vbuf); + unsigned long flags; + + spin_lock_irqsave(&vindev->qlock, flags); + list_add_tail(&buf->list, &vindev->buf_list); + spin_unlock_irqrestore(&vindev->qlock, flags); +} + +static void stop_streaming(struct vb2_queue *vq) +{ + struct mgb4_vout_dev *voutdev = vb2_get_drv_priv(vq); + struct mgb4_dev *mgbdev = voutdev->mgbdev; + int irq = xdma_get_user_irq(mgbdev->xdev, voutdev->config->irq); + + xdma_disable_user_irq(mgbdev->xdev, irq); + cancel_work_sync(&voutdev->dma_work); + + mgb4_mask_reg(&mgbdev->video, voutdev->config->regs.config, 0x2, 0x0); + mgb4_write_reg(&mgbdev->video, voutdev->config->regs.padding, 0); + + return_all_buffers(voutdev, VB2_BUF_STATE_ERROR); +} + +static int start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct mgb4_vout_dev *voutdev = vb2_get_drv_priv(vq); + struct mgb4_dev *mgbdev = voutdev->mgbdev; + struct device *dev = &mgbdev->pdev->dev; + struct mgb4_frame_buffer *buf; + struct mgb4_regs *video = &mgbdev->video; + const struct mgb4_vout_config *config = voutdev->config; + int irq = xdma_get_user_irq(mgbdev->xdev, config->irq); + int rv; + u32 addr; + + mgb4_write_reg(video, config->regs.padding, voutdev->padding); + mgb4_mask_reg(video, config->regs.config, 0x2, 0x2); + + addr = mgb4_read_reg(video, config->regs.address); + if (addr >= MGB4_ERR_QUEUE_FULL) { + dev_dbg(dev, "frame queue error (%d)\n", (int)addr); + return_all_buffers(voutdev, VB2_BUF_STATE_QUEUED); + return -EBUSY; + } + + buf = list_first_entry(&voutdev->buf_list, struct mgb4_frame_buffer, + list); + list_del_init(voutdev->buf_list.next); + + rv = mgb4_dma_transfer(mgbdev, config->dma_channel, true, addr, + vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0)); + if (rv < 0) { + dev_warn(dev, "DMA transfer error\n"); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } else { + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + } + + xdma_enable_user_irq(mgbdev->xdev, irq); + + return 0; +} + +static const struct vb2_ops queue_ops = { + .queue_setup = queue_setup, + .buf_init = buffer_init, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, +}; + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strscpy(cap->card, "MGB4 PCIe Card", sizeof(cap->card)); + + return 0; +} + +static int vidioc_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct mgb4_vin_dev *voutdev = video_drvdata(file); + struct mgb4_regs *video = &voutdev->mgbdev->video; + + if (f->index == 0) { + f->pixelformat = V4L2_PIX_FMT_ABGR32; + return 0; + } else if (f->index == 1 && has_yuv(video)) { + f->pixelformat = V4L2_PIX_FMT_YUYV; + return 0; + } else { + return -EINVAL; + } +} + +static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct mgb4_vout_dev *voutdev = video_drvdata(file); + struct mgb4_regs *video = &voutdev->mgbdev->video; + u32 config = mgb4_read_reg(video, voutdev->config->regs.config); + + f->fmt.pix.width = voutdev->width; + f->fmt.pix.height = voutdev->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + + if (config & (1U << 16)) { + f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; + if (config & (1U << 20)) { + f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; + } else { + if (config & (1U << 19)) + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + else + f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + } + f->fmt.pix.bytesperline = (f->fmt.pix.width + voutdev->padding) * 2; + } else { + f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32; + f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW; + f->fmt.pix.bytesperline = (f->fmt.pix.width + voutdev->padding) * 4; + } + + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; + + return 0; +} + +static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct mgb4_vout_dev *voutdev = video_drvdata(file); + struct mgb4_regs *video = &voutdev->mgbdev->video; + u32 pixelsize; + + f->fmt.pix.width = voutdev->width; + f->fmt.pix.height = voutdev->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + + if (has_yuv(video) && f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) { + pixelsize = 2; + if (!(f->fmt.pix.colorspace == V4L2_COLORSPACE_REC709 || + f->fmt.pix.colorspace == V4L2_COLORSPACE_SMPTE170M)) + f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + } else { + pixelsize = 4; + f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32; + f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW; + } + + if (f->fmt.pix.bytesperline > f->fmt.pix.width * pixelsize && + f->fmt.pix.bytesperline < f->fmt.pix.width * pixelsize * 2) + f->fmt.pix.bytesperline = ALIGN(f->fmt.pix.bytesperline, + pixelsize); + else + f->fmt.pix.bytesperline = f->fmt.pix.width * pixelsize; + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; + + return 0; +} + +static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct mgb4_vout_dev *voutdev = video_drvdata(file); + struct mgb4_regs *video = &voutdev->mgbdev->video; + u32 config, pixelsize; + int ret; + + if (vb2_is_busy(&voutdev->queue)) + return -EBUSY; + + ret = vidioc_try_fmt(file, priv, f); + if (ret < 0) + return ret; + + config = mgb4_read_reg(video, voutdev->config->regs.config); + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) { + pixelsize = 2; + config |= 1U << 16; + + if (f->fmt.pix.colorspace == V4L2_COLORSPACE_REC709) { + config |= 1U << 20; + config |= 1U << 19; + } else if (f->fmt.pix.colorspace == V4L2_COLORSPACE_SMPTE170M) { + config &= ~(1U << 20); + config |= 1U << 19; + } else { + config &= ~(1U << 20); + config &= ~(1U << 19); + } + } else { + pixelsize = 4; + config &= ~(1U << 16); + } + mgb4_write_reg(video, voutdev->config->regs.config, config); + + voutdev->padding = (f->fmt.pix.bytesperline - (f->fmt.pix.width + * pixelsize)) / pixelsize; + + return 0; +} + +static int vidioc_g_output(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int vidioc_s_output(struct file *file, void *priv, unsigned int i) +{ + return i ? -EINVAL : 0; +} + +static int vidioc_enum_output(struct file *file, void *priv, + struct v4l2_output *out) +{ + if (out->index != 0) + return -EINVAL; + + out->type = V4L2_OUTPUT_TYPE_ANALOG; + out->capabilities = V4L2_OUT_CAP_DV_TIMINGS; + strscpy(out->name, "MGB4", sizeof(out->name)); + + return 0; +} + +static int vidioc_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *ival) +{ + struct mgb4_vout_dev *voutdev = video_drvdata(file); + struct mgb4_regs *video = &voutdev->mgbdev->video; + struct v4l2_dv_timings timings; + + if (ival->index != 0) + return -EINVAL; + if (!(ival->pixel_format == V4L2_PIX_FMT_ABGR32 || + ((has_yuv(video) && ival->pixel_format == V4L2_PIX_FMT_YUYV)))) + return -EINVAL; + if (ival->width != voutdev->width || ival->height != voutdev->height) + return -EINVAL; + + get_timings(voutdev, &timings); + + ival->type = V4L2_FRMIVAL_TYPE_STEPWISE; + ival->stepwise.max.denominator = MGB4_HW_FREQ; + ival->stepwise.max.numerator = 0xFFFFFFFF; + ival->stepwise.min.denominator = timings.bt.pixelclock; + ival->stepwise.min.numerator = pixel_size(&timings); + ival->stepwise.step.denominator = MGB4_HW_FREQ; + ival->stepwise.step.numerator = 1; + + return 0; +} + +static int vidioc_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *parm) +{ + struct mgb4_vout_dev *voutdev = video_drvdata(file); + struct mgb4_regs *video = &voutdev->mgbdev->video; + struct v4l2_fract *tpf = &parm->parm.output.timeperframe; + struct v4l2_dv_timings timings; + u32 timer; + + parm->parm.output.writebuffers = 2; + + if (has_timeperframe(video)) { + timer = mgb4_read_reg(video, voutdev->config->regs.timer); + if (timer < 0xFFFF) { + get_timings(voutdev, &timings); + tpf->numerator = pixel_size(&timings); + tpf->denominator = timings.bt.pixelclock; + } else { + tpf->numerator = timer; + tpf->denominator = MGB4_HW_FREQ; + } + + parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + } + + return 0; +} + +static int vidioc_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *parm) +{ + struct mgb4_vout_dev *voutdev = video_drvdata(file); + struct mgb4_regs *video = &voutdev->mgbdev->video; + struct v4l2_fract *tpf = &parm->parm.output.timeperframe; + struct v4l2_dv_timings timings; + u32 timer, period; + + if (has_timeperframe(video)) { + timer = tpf->denominator ? + MGB4_PERIOD(tpf->numerator, tpf->denominator) : 0; + if (timer) { + get_timings(voutdev, &timings); + period = MGB4_PERIOD(pixel_size(&timings), + timings.bt.pixelclock); + if (timer < period) + timer = 0; + } + + mgb4_write_reg(video, voutdev->config->regs.timer, timer); + } + + return vidioc_g_parm(file, priv, parm); +} + +static int vidioc_g_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct mgb4_vout_dev *voutdev = video_drvdata(file); + + get_timings(voutdev, timings); + + return 0; +} + +static int vidioc_s_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct mgb4_vout_dev *voutdev = video_drvdata(file); + + get_timings(voutdev, timings); + + return 0; +} + +static int vidioc_enum_dv_timings(struct file *file, void *fh, + struct v4l2_enum_dv_timings *timings) +{ + struct mgb4_vout_dev *voutdev = video_drvdata(file); + + if (timings->index != 0) + return -EINVAL; + + get_timings(voutdev, &timings->timings); + + return 0; +} + +static int vidioc_dv_timings_cap(struct file *file, void *fh, + struct v4l2_dv_timings_cap *cap) +{ + *cap = video_timings_cap; + + return 0; +} + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt, + .vidioc_try_fmt_vid_out = vidioc_try_fmt, + .vidioc_s_fmt_vid_out = vidioc_s_fmt, + .vidioc_g_fmt_vid_out = vidioc_g_fmt, + .vidioc_enum_output = vidioc_enum_output, + .vidioc_enum_frameintervals = vidioc_enum_frameintervals, + .vidioc_g_output = vidioc_g_output, + .vidioc_s_output = vidioc_s_output, + .vidioc_g_parm = vidioc_g_parm, + .vidioc_s_parm = vidioc_s_parm, + .vidioc_dv_timings_cap = vidioc_dv_timings_cap, + .vidioc_enum_dv_timings = vidioc_enum_dv_timings, + .vidioc_g_dv_timings = vidioc_g_dv_timings, + .vidioc_s_dv_timings = vidioc_s_dv_timings, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +static int fh_open(struct file *file) +{ + struct mgb4_vout_dev *voutdev = video_drvdata(file); + struct mgb4_regs *video = &voutdev->mgbdev->video; + struct device *dev = &voutdev->mgbdev->pdev->dev; + u32 config, resolution; + int rv; + + /* Return EBUSY when the device is in loopback mode */ + config = mgb4_read_reg(video, voutdev->config->regs.config); + if ((config & 0xc) >> 2 != voutdev->config->id + MGB4_VIN_DEVICES) { + dev_dbg(dev, "can not open - device in loopback mode"); + return -EBUSY; + } + + mutex_lock(&voutdev->lock); + + rv = v4l2_fh_open(file); + if (rv) + goto out; + + if (!v4l2_fh_is_singular_file(file)) + goto out; + + resolution = mgb4_read_reg(video, voutdev->config->regs.resolution); + voutdev->width = resolution >> 16; + voutdev->height = resolution & 0xFFFF; + +out: + mutex_unlock(&voutdev->lock); + return rv; +} + +static const struct v4l2_file_operations video_fops = { + .owner = THIS_MODULE, + .open = fh_open, + .release = vb2_fop_release, + .unlocked_ioctl = video_ioctl2, + .write = vb2_fop_write, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + +static void dma_transfer(struct work_struct *work) +{ + struct mgb4_vout_dev *voutdev = container_of(work, struct mgb4_vout_dev, + dma_work); + struct device *dev = &voutdev->mgbdev->pdev->dev; + struct mgb4_regs *video = &voutdev->mgbdev->video; + struct mgb4_frame_buffer *buf = NULL; + unsigned long flags; + u32 addr; + int rv; + + spin_lock_irqsave(&voutdev->qlock, flags); + if (!list_empty(&voutdev->buf_list)) { + buf = list_first_entry(&voutdev->buf_list, + struct mgb4_frame_buffer, list); + list_del_init(voutdev->buf_list.next); + } + spin_unlock_irqrestore(&voutdev->qlock, flags); + + if (!buf) + return; + + addr = mgb4_read_reg(video, voutdev->config->regs.address); + if (addr >= MGB4_ERR_QUEUE_FULL) { + dev_dbg(dev, "frame queue error (%d)\n", (int)addr); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + return; + } + + rv = mgb4_dma_transfer(voutdev->mgbdev, voutdev->config->dma_channel, + true, addr, + vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0)); + if (rv < 0) { + dev_warn(dev, "DMA transfer error\n"); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } else { + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + } +} + +static irqreturn_t handler(int irq, void *ctx) +{ + struct mgb4_vout_dev *voutdev = (struct mgb4_vout_dev *)ctx; + struct mgb4_regs *video = &voutdev->mgbdev->video; + + schedule_work(&voutdev->dma_work); + + mgb4_write_reg(video, 0xB4, 1U << voutdev->config->irq); + + return IRQ_HANDLED; +} + +static int ser_init(struct mgb4_vout_dev *voutdev, int id) +{ + struct mgb4_i2c_client *ser = &voutdev->ser; + struct device *dev = &voutdev->mgbdev->pdev->dev; + const struct i2c_board_info *info = NULL; + const struct mgb4_i2c_kv *values = NULL; + size_t count = 0; + int rv; + + if (MGB4_IS_FPDL3(voutdev->mgbdev)) { + info = &fpdl3_ser_info[id]; + values = fpdl3_i2c; + count = ARRAY_SIZE(fpdl3_i2c); + } else if (MGB4_IS_GMSL1(voutdev->mgbdev)) { + info = &gmsl1_ser_info[id]; + values = gmsl1_i2c; + count = ARRAY_SIZE(gmsl1_i2c); + } + + if (!info) + return 0; + + rv = mgb4_i2c_init(ser, voutdev->mgbdev->i2c_adap, info, 8); + if (rv < 0) { + dev_err(dev, "failed to create serializer\n"); + return rv; + } + rv = mgb4_i2c_configure(ser, values, count); + if (rv < 0) { + dev_err(dev, "failed to configure serializer\n"); + goto err_i2c_dev; + } + + return 0; + +err_i2c_dev: + mgb4_i2c_free(ser); + + return rv; +} + +static void fpga_init(struct mgb4_vout_dev *voutdev) +{ + struct mgb4_regs *video = &voutdev->mgbdev->video; + const struct mgb4_vout_regs *regs = &voutdev->config->regs; + int dp = MGB4_IS_GMSL1(voutdev->mgbdev) ? 0 : 1; + u32 source = (voutdev->config->id + MGB4_VIN_DEVICES) << 2; + + mgb4_write_reg(video, regs->config, 0x00000001); + mgb4_write_reg(video, regs->resolution, (1280 << 16) | 640); + mgb4_write_reg(video, regs->hsync, 0x00283232); + mgb4_write_reg(video, regs->vsync, 0x40141F1E); + mgb4_write_reg(video, regs->frame_limit, MGB4_HW_FREQ / 60); + mgb4_write_reg(video, regs->padding, 0x00000000); + + voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, 61150 >> dp) << dp; + + mgb4_write_reg(video, regs->config, source | dp << 4); +} + +static void create_debugfs(struct mgb4_vout_dev *voutdev) +{ +#ifdef CONFIG_DEBUG_FS + struct mgb4_regs *video = &voutdev->mgbdev->video; + struct dentry *entry; + + if (IS_ERR_OR_NULL(voutdev->mgbdev->debugfs)) + return; + entry = debugfs_create_dir(voutdev->vdev.name, voutdev->mgbdev->debugfs); + if (IS_ERR(entry)) + return; + + voutdev->regs[0].name = "CONFIG"; + voutdev->regs[0].offset = voutdev->config->regs.config; + voutdev->regs[1].name = "STATUS"; + voutdev->regs[1].offset = voutdev->config->regs.status; + voutdev->regs[2].name = "RESOLUTION"; + voutdev->regs[2].offset = voutdev->config->regs.resolution; + voutdev->regs[3].name = "VIDEO_PARAMS_1"; + voutdev->regs[3].offset = voutdev->config->regs.hsync; + voutdev->regs[4].name = "VIDEO_PARAMS_2"; + voutdev->regs[4].offset = voutdev->config->regs.vsync; + voutdev->regs[5].name = "FRAME_LIMIT"; + voutdev->regs[5].offset = voutdev->config->regs.frame_limit; + voutdev->regs[6].name = "PADDING_PIXELS"; + voutdev->regs[6].offset = voutdev->config->regs.padding; + if (has_timeperframe(video)) { + voutdev->regs[7].name = "TIMER"; + voutdev->regs[7].offset = voutdev->config->regs.timer; + voutdev->regset.nregs = 8; + } else { + voutdev->regset.nregs = 7; + } + + voutdev->regset.base = video->membase; + voutdev->regset.regs = voutdev->regs; + + debugfs_create_regset32("registers", 0444, entry, &voutdev->regset); +#endif +} + +static const struct attribute_group **module_groups(struct mgb4_dev *mgbdev) +{ + if (MGB4_IS_FPDL3(mgbdev)) + return mgb4_fpdl3_out_groups; + else if (MGB4_IS_GMSL3(mgbdev)) + return mgb4_gmsl3_out_groups; + else if (MGB4_IS_GMSL1(mgbdev)) + return mgb4_gmsl1_out_groups; + else + return NULL; +} + +struct mgb4_vout_dev *mgb4_vout_create(struct mgb4_dev *mgbdev, int id) +{ + int rv, irq; + struct mgb4_vout_dev *voutdev; + struct pci_dev *pdev = mgbdev->pdev; + struct device *dev = &pdev->dev; + + voutdev = kzalloc_obj(*voutdev); + if (!voutdev) + return NULL; + + voutdev->mgbdev = mgbdev; + voutdev->config = &vout_cfg[id]; + + /* Frame queue */ + INIT_LIST_HEAD(&voutdev->buf_list); + spin_lock_init(&voutdev->qlock); + + /* DMA transfer stuff */ + INIT_WORK(&voutdev->dma_work, dma_transfer); + + /* IRQ callback */ + irq = xdma_get_user_irq(mgbdev->xdev, voutdev->config->irq); + rv = request_irq(irq, handler, 0, "mgb4-vout", voutdev); + if (rv) { + dev_err(dev, "failed to register irq handler\n"); + goto err_alloc; + } + + /* Set the FPGA registers default values */ + fpga_init(voutdev); + + /* Set the serializer default values */ + rv = ser_init(voutdev, id); + if (rv) + goto err_irq; + + /* V4L2 stuff init */ + rv = v4l2_device_register(dev, &voutdev->v4l2dev); + if (rv) { + dev_err(dev, "failed to register v4l2 device\n"); + goto err_irq; + } + + mutex_init(&voutdev->lock); + + voutdev->queue.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + voutdev->queue.io_modes = VB2_MMAP | VB2_DMABUF | VB2_WRITE; + voutdev->queue.buf_struct_size = sizeof(struct mgb4_frame_buffer); + voutdev->queue.ops = &queue_ops; + voutdev->queue.mem_ops = &vb2_dma_sg_memops; + voutdev->queue.gfp_flags = GFP_DMA32; + voutdev->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + voutdev->queue.min_queued_buffers = 2; + voutdev->queue.drv_priv = voutdev; + voutdev->queue.lock = &voutdev->lock; + voutdev->queue.dev = dev; + rv = vb2_queue_init(&voutdev->queue); + if (rv) { + dev_err(dev, "failed to initialize vb2 queue\n"); + goto err_v4l2_dev; + } + + snprintf(voutdev->vdev.name, sizeof(voutdev->vdev.name), "mgb4-out%d", + id + 1); + voutdev->vdev.device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_READWRITE + | V4L2_CAP_STREAMING; + voutdev->vdev.vfl_dir = VFL_DIR_TX; + voutdev->vdev.fops = &video_fops; + voutdev->vdev.ioctl_ops = &video_ioctl_ops; + voutdev->vdev.release = video_device_release_empty; + voutdev->vdev.v4l2_dev = &voutdev->v4l2dev; + voutdev->vdev.lock = &voutdev->lock; + voutdev->vdev.queue = &voutdev->queue; + video_set_drvdata(&voutdev->vdev, voutdev); + + rv = video_register_device(&voutdev->vdev, VFL_TYPE_VIDEO, -1); + if (rv) { + dev_err(dev, "failed to register video device\n"); + goto err_v4l2_dev; + } + + /* Module sysfs attributes */ + rv = device_add_groups(&voutdev->vdev.dev, module_groups(mgbdev)); + if (rv) { + dev_err(dev, "failed to create sysfs attributes\n"); + goto err_video_dev; + } + + create_debugfs(voutdev); + + return voutdev; + +err_video_dev: + video_unregister_device(&voutdev->vdev); +err_v4l2_dev: + v4l2_device_unregister(&voutdev->v4l2dev); +err_irq: + free_irq(irq, voutdev); +err_alloc: + kfree(voutdev); + + return NULL; +} + +void mgb4_vout_free(struct mgb4_vout_dev *voutdev) +{ + int irq = xdma_get_user_irq(voutdev->mgbdev->xdev, voutdev->config->irq); + + free_irq(irq, voutdev); + device_remove_groups(&voutdev->vdev.dev, module_groups(voutdev->mgbdev)); + mgb4_i2c_free(&voutdev->ser); + video_unregister_device(&voutdev->vdev); + v4l2_device_unregister(&voutdev->v4l2dev); + + kfree(voutdev); +} diff --git a/drivers/media/pci/mgb4/mgb4_vout.h b/drivers/media/pci/mgb4/mgb4_vout.h new file mode 100644 index 000000000..a07eeabdc --- /dev/null +++ b/drivers/media/pci/mgb4/mgb4_vout.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021-2023 Digiteq Automotive + * author: Martin Tuma + */ + +#ifndef __MGB4_VOUT_H__ +#define __MGB4_VOUT_H__ + +#include +#include +#include +#include +#include +#include "mgb4_i2c.h" + +struct mgb4_vout_regs { + u32 address; + u32 config; + u32 status; + u32 resolution; + u32 frame_limit; + u32 hsync; + u32 vsync; + u32 padding; + u32 timer; +}; + +struct mgb4_vout_config { + int id; + int dma_channel; + int irq; + struct mgb4_vout_regs regs; +}; + +struct mgb4_vout_dev { + struct mgb4_dev *mgbdev; + struct v4l2_device v4l2dev; + struct video_device vdev; + struct vb2_queue queue; + struct mutex lock; /* vdev lock */ + + spinlock_t qlock; /* buffer queue lock */ + struct list_head buf_list; + struct work_struct dma_work; + + u32 width; + u32 height; + u32 freq; + u32 padding; + + struct mgb4_i2c_client ser; + + const struct mgb4_vout_config *config; + +#ifdef CONFIG_DEBUG_FS + struct debugfs_regset32 regset; + struct debugfs_reg32 regs[sizeof(struct mgb4_vout_regs) / 4]; +#endif +}; + +struct mgb4_vout_dev *mgb4_vout_create(struct mgb4_dev *mgbdev, int id); +void mgb4_vout_free(struct mgb4_vout_dev *voutdev); + +#endif diff --git a/drivers/media/pci/netup_unidvb/Kconfig b/drivers/media/pci/netup_unidvb/Kconfig new file mode 100644 index 000000000..a1a46bd6a --- /dev/null +++ b/drivers/media/pci/netup_unidvb/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DVB_NETUP_UNIDVB + tristate "NetUP Universal DVB card support" + depends on DVB_CORE && VIDEO_DEV && PCI && I2C && SPI_MASTER + select VIDEOBUF2_DVB + select VIDEOBUF2_VMALLOC + select DVB_HORUS3A if MEDIA_SUBDRV_AUTOSELECT + select DVB_ASCOT2E if MEDIA_SUBDRV_AUTOSELECT + select DVB_HELENE if MEDIA_SUBDRV_AUTOSELECT + select DVB_LNBH25 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CXD2841ER if MEDIA_SUBDRV_AUTOSELECT + help + Support for NetUP PCI express Universal DVB card. + + Say Y when you want to support NetUP Dual Universal DVB card. + Card can receive two independent streams in following standards: + DVB-S/S2, T/T2, C/C2 + Two CI slots available for CAM modules. diff --git a/drivers/media/pci/netup_unidvb/Makefile b/drivers/media/pci/netup_unidvb/Makefile new file mode 100644 index 000000000..215bdafcc --- /dev/null +++ b/drivers/media/pci/netup_unidvb/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +netup-unidvb-objs += netup_unidvb_core.o +netup-unidvb-objs += netup_unidvb_i2c.o +netup-unidvb-objs += netup_unidvb_ci.o +netup-unidvb-objs += netup_unidvb_spi.o + +obj-$(CONFIG_DVB_NETUP_UNIDVB) += netup-unidvb.o + +ccflags-y += -I $(srctree)/drivers/media/dvb-frontends diff --git a/drivers/media/pci/netup_unidvb/netup_unidvb.h b/drivers/media/pci/netup_unidvb/netup_unidvb.h new file mode 100644 index 000000000..2a98202e9 --- /dev/null +++ b/drivers/media/pci/netup_unidvb/netup_unidvb.h @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * netup_unidvb.h + * + * Data type definitions for NetUP Universal Dual DVB-CI + * + * Copyright (C) 2014 NetUP Inc. + * Copyright (C) 2014 Sergey Kozlov + * Copyright (C) 2014 Abylay Ospan + */ + +#include +#include +#include +#include +#include +#include +#include + +#define NETUP_UNIDVB_NAME "netup_unidvb" +#define NETUP_UNIDVB_VERSION "0.0.1" +#define NETUP_VENDOR_ID 0x1b55 +#define NETUP_PCI_DEV_REVISION 0x2 + +/* IRQ-related regisers */ +#define REG_ISR 0x4890 +#define REG_ISR_MASKED 0x4892 +#define REG_IMASK_SET 0x4894 +#define REG_IMASK_CLEAR 0x4896 +/* REG_ISR register bits */ +#define NETUP_UNIDVB_IRQ_SPI (1 << 0) +#define NETUP_UNIDVB_IRQ_I2C0 (1 << 1) +#define NETUP_UNIDVB_IRQ_I2C1 (1 << 2) +#define NETUP_UNIDVB_IRQ_FRA0 (1 << 4) +#define NETUP_UNIDVB_IRQ_FRA1 (1 << 5) +#define NETUP_UNIDVB_IRQ_FRB0 (1 << 6) +#define NETUP_UNIDVB_IRQ_FRB1 (1 << 7) +#define NETUP_UNIDVB_IRQ_DMA1 (1 << 8) +#define NETUP_UNIDVB_IRQ_DMA2 (1 << 9) +#define NETUP_UNIDVB_IRQ_CI (1 << 10) +#define NETUP_UNIDVB_IRQ_CAM0 (1 << 11) +#define NETUP_UNIDVB_IRQ_CAM1 (1 << 12) + +/* NetUP Universal DVB card hardware revisions and it's PCI device id's: + * 1.3 - CXD2841ER demod, ASCOT2E and HORUS3A tuners + * 1.4 - CXD2854ER demod, HELENE tuner +*/ +enum netup_hw_rev { + NETUP_HW_REV_1_3 = 0x18F6, + NETUP_HW_REV_1_4 = 0x18F7 +}; + +struct netup_dma { + u8 num; + spinlock_t lock; + struct netup_unidvb_dev *ndev; + struct netup_dma_regs __iomem *regs; + u32 ring_buffer_size; + u8 *addr_virt; + dma_addr_t addr_phys; + u64 addr_last; + u32 high_addr; + u32 data_offset; + u32 data_size; + struct list_head free_buffers; + struct work_struct work; + struct timer_list timeout; +}; + +enum netup_i2c_state { + STATE_DONE, + STATE_WAIT, + STATE_WANT_READ, + STATE_WANT_WRITE, + STATE_ERROR +}; + +struct netup_i2c_regs; + +struct netup_i2c { + spinlock_t lock; + wait_queue_head_t wq; + struct i2c_adapter adap; + struct netup_unidvb_dev *dev; + struct netup_i2c_regs __iomem *regs; + struct i2c_msg *msg; + enum netup_i2c_state state; + u32 xmit_size; +}; + +struct netup_ci_state { + struct dvb_ca_en50221 ca; + u8 __iomem *membase8_config; + u8 __iomem *membase8_io; + struct netup_unidvb_dev *dev; + int status; + int nr; +}; + +struct netup_spi; + +struct netup_unidvb_dev { + struct pci_dev *pci_dev; + int pci_bus; + int pci_slot; + int pci_func; + int board_num; + int old_fw; + u32 __iomem *lmmio0; + u8 __iomem *bmmio0; + u32 __iomem *lmmio1; + u8 __iomem *bmmio1; + u8 *dma_virt; + dma_addr_t dma_phys; + u32 dma_size; + struct vb2_dvb_frontends frontends[2]; + struct netup_i2c i2c[2]; + struct workqueue_struct *wq; + struct netup_dma dma[2]; + struct netup_ci_state ci[2]; + struct netup_spi *spi; + enum netup_hw_rev rev; +}; + +int netup_i2c_register(struct netup_unidvb_dev *ndev); +void netup_i2c_unregister(struct netup_unidvb_dev *ndev); +irqreturn_t netup_ci_interrupt(struct netup_unidvb_dev *ndev); +irqreturn_t netup_i2c_interrupt(struct netup_i2c *i2c); +irqreturn_t netup_spi_interrupt(struct netup_spi *spi); +int netup_unidvb_ci_register(struct netup_unidvb_dev *dev, + int num, struct pci_dev *pci_dev); +void netup_unidvb_ci_unregister(struct netup_unidvb_dev *dev, int num); +int netup_spi_init(struct netup_unidvb_dev *ndev); +void netup_spi_release(struct netup_unidvb_dev *ndev); diff --git a/drivers/media/pci/netup_unidvb/netup_unidvb_ci.c b/drivers/media/pci/netup_unidvb/netup_unidvb_ci.c new file mode 100644 index 000000000..29d0739b4 --- /dev/null +++ b/drivers/media/pci/netup_unidvb/netup_unidvb_ci.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * netup_unidvb_ci.c + * + * DVB CAM support for NetUP Universal Dual DVB-CI + * + * Copyright (C) 2014 NetUP Inc. + * Copyright (C) 2014 Sergey Kozlov + * Copyright (C) 2014 Abylay Ospan + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "netup_unidvb.h" + +/* CI slot 0 base address */ +#define CAM0_CONFIG 0x0 +#define CAM0_IO 0x8000 +#define CAM0_MEM 0x10000 +#define CAM0_SZ 32 +/* CI slot 1 base address */ +#define CAM1_CONFIG 0x20000 +#define CAM1_IO 0x28000 +#define CAM1_MEM 0x30000 +#define CAM1_SZ 32 +/* ctrlstat registers */ +#define CAM_CTRLSTAT_READ_SET 0x4980 +#define CAM_CTRLSTAT_CLR 0x4982 +/* register bits */ +#define BIT_CAM_STCHG (1<<0) +#define BIT_CAM_PRESENT (1<<1) +#define BIT_CAM_RESET (1<<2) +#define BIT_CAM_BYPASS (1<<3) +#define BIT_CAM_READY (1<<4) +#define BIT_CAM_ERROR (1<<5) +#define BIT_CAM_OVERCURR (1<<6) +/* BIT_CAM_BYPASS bit shift for SLOT 1 */ +#define CAM1_SHIFT 8 + +irqreturn_t netup_ci_interrupt(struct netup_unidvb_dev *ndev) +{ + writew(0x101, ndev->bmmio0 + CAM_CTRLSTAT_CLR); + return IRQ_HANDLED; +} + +static int netup_unidvb_ci_slot_ts_ctl(struct dvb_ca_en50221 *en50221, + int slot) +{ + struct netup_ci_state *state = en50221->data; + struct netup_unidvb_dev *dev = state->dev; + u16 shift = (state->nr == 1) ? CAM1_SHIFT : 0; + + dev_dbg(&dev->pci_dev->dev, "%s(): CAM_CTRLSTAT=0x%x\n", + __func__, readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET)); + if (slot != 0) + return -EINVAL; + /* pass data to CAM module */ + writew(BIT_CAM_BYPASS << shift, dev->bmmio0 + CAM_CTRLSTAT_CLR); + dev_dbg(&dev->pci_dev->dev, "%s(): CAM_CTRLSTAT=0x%x done\n", + __func__, readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET)); + return 0; +} + +static int netup_unidvb_ci_slot_shutdown(struct dvb_ca_en50221 *en50221, + int slot) +{ + struct netup_ci_state *state = en50221->data; + struct netup_unidvb_dev *dev = state->dev; + + dev_dbg(&dev->pci_dev->dev, "%s()\n", __func__); + return 0; +} + +static int netup_unidvb_ci_slot_reset(struct dvb_ca_en50221 *en50221, + int slot) +{ + struct netup_ci_state *state = en50221->data; + struct netup_unidvb_dev *dev = state->dev; + unsigned long timeout = 0; + u16 shift = (state->nr == 1) ? CAM1_SHIFT : 0; + u16 ci_stat = 0; + int reset_counter = 3; + + dev_dbg(&dev->pci_dev->dev, "%s(): CAM_CTRLSTAT_READ_SET=0x%x\n", + __func__, readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET)); +reset: + timeout = jiffies + msecs_to_jiffies(5000); + /* start reset */ + writew(BIT_CAM_RESET << shift, dev->bmmio0 + CAM_CTRLSTAT_READ_SET); + dev_dbg(&dev->pci_dev->dev, "%s(): waiting for reset\n", __func__); + /* wait until reset done */ + while (time_before(jiffies, timeout)) { + ci_stat = readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET); + if (ci_stat & (BIT_CAM_READY << shift)) + break; + udelay(1000); + } + if (!(ci_stat & (BIT_CAM_READY << shift)) && reset_counter > 0) { + dev_dbg(&dev->pci_dev->dev, + "%s(): CAMP reset timeout! Will try again..\n", + __func__); + reset_counter--; + goto reset; + } + return 0; +} + +static int netup_unidvb_poll_ci_slot_status(struct dvb_ca_en50221 *en50221, + int slot, int open) +{ + struct netup_ci_state *state = en50221->data; + struct netup_unidvb_dev *dev = state->dev; + u16 shift = (state->nr == 1) ? CAM1_SHIFT : 0; + u16 ci_stat = 0; + + dev_dbg(&dev->pci_dev->dev, "%s(): CAM_CTRLSTAT_READ_SET=0x%x\n", + __func__, readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET)); + ci_stat = readw(dev->bmmio0 + CAM_CTRLSTAT_READ_SET); + if (ci_stat & (BIT_CAM_READY << shift)) { + state->status = DVB_CA_EN50221_POLL_CAM_PRESENT | + DVB_CA_EN50221_POLL_CAM_READY; + } else if (ci_stat & (BIT_CAM_PRESENT << shift)) { + state->status = DVB_CA_EN50221_POLL_CAM_PRESENT; + } else { + state->status = 0; + } + return state->status; +} + +static int netup_unidvb_ci_read_attribute_mem(struct dvb_ca_en50221 *en50221, + int slot, int addr) +{ + struct netup_ci_state *state = en50221->data; + struct netup_unidvb_dev *dev = state->dev; + u8 val = *((u8 __force *)state->membase8_config + addr); + + dev_dbg(&dev->pci_dev->dev, + "%s(): addr=0x%x val=0x%x\n", __func__, addr, val); + return val; +} + +static int netup_unidvb_ci_write_attribute_mem(struct dvb_ca_en50221 *en50221, + int slot, int addr, u8 data) +{ + struct netup_ci_state *state = en50221->data; + struct netup_unidvb_dev *dev = state->dev; + + dev_dbg(&dev->pci_dev->dev, + "%s(): addr=0x%x data=0x%x\n", __func__, addr, data); + *((u8 __force *)state->membase8_config + addr) = data; + return 0; +} + +static int netup_unidvb_ci_read_cam_ctl(struct dvb_ca_en50221 *en50221, + int slot, u8 addr) +{ + struct netup_ci_state *state = en50221->data; + struct netup_unidvb_dev *dev = state->dev; + u8 val = *((u8 __force *)state->membase8_io + addr); + + dev_dbg(&dev->pci_dev->dev, + "%s(): addr=0x%x val=0x%x\n", __func__, addr, val); + return val; +} + +static int netup_unidvb_ci_write_cam_ctl(struct dvb_ca_en50221 *en50221, + int slot, u8 addr, u8 data) +{ + struct netup_ci_state *state = en50221->data; + struct netup_unidvb_dev *dev = state->dev; + + dev_dbg(&dev->pci_dev->dev, + "%s(): addr=0x%x data=0x%x\n", __func__, addr, data); + *((u8 __force *)state->membase8_io + addr) = data; + return 0; +} + +int netup_unidvb_ci_register(struct netup_unidvb_dev *dev, + int num, struct pci_dev *pci_dev) +{ + int result; + struct netup_ci_state *state; + + if (num < 0 || num > 1) { + dev_err(&pci_dev->dev, "%s(): invalid CI adapter %d\n", + __func__, num); + return -EINVAL; + } + state = &dev->ci[num]; + state->nr = num; + state->membase8_config = dev->bmmio1 + + ((num == 0) ? CAM0_CONFIG : CAM1_CONFIG); + state->membase8_io = dev->bmmio1 + + ((num == 0) ? CAM0_IO : CAM1_IO); + state->dev = dev; + state->ca.owner = THIS_MODULE; + state->ca.read_attribute_mem = netup_unidvb_ci_read_attribute_mem; + state->ca.write_attribute_mem = netup_unidvb_ci_write_attribute_mem; + state->ca.read_cam_control = netup_unidvb_ci_read_cam_ctl; + state->ca.write_cam_control = netup_unidvb_ci_write_cam_ctl; + state->ca.slot_reset = netup_unidvb_ci_slot_reset; + state->ca.slot_shutdown = netup_unidvb_ci_slot_shutdown; + state->ca.slot_ts_enable = netup_unidvb_ci_slot_ts_ctl; + state->ca.poll_slot_status = netup_unidvb_poll_ci_slot_status; + state->ca.data = state; + result = dvb_ca_en50221_init(&dev->frontends[num].adapter, + &state->ca, 0, 1); + if (result < 0) { + dev_err(&pci_dev->dev, + "%s(): dvb_ca_en50221_init result %d\n", + __func__, result); + return result; + } + writew(NETUP_UNIDVB_IRQ_CI, dev->bmmio0 + REG_IMASK_SET); + dev_info(&pci_dev->dev, + "%s(): CI adapter %d init done\n", __func__, num); + return 0; +} + +void netup_unidvb_ci_unregister(struct netup_unidvb_dev *dev, int num) +{ + struct netup_ci_state *state; + + dev_dbg(&dev->pci_dev->dev, "%s()\n", __func__); + if (num < 0 || num > 1) { + dev_err(&dev->pci_dev->dev, "%s(): invalid CI adapter %d\n", + __func__, num); + return; + } + state = &dev->ci[num]; + dvb_ca_en50221_release(&state->ca); +} + diff --git a/drivers/media/pci/netup_unidvb/netup_unidvb_core.c b/drivers/media/pci/netup_unidvb/netup_unidvb_core.c new file mode 100644 index 000000000..00f81e4c8 --- /dev/null +++ b/drivers/media/pci/netup_unidvb/netup_unidvb_core.c @@ -0,0 +1,1028 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * netup_unidvb_core.c + * + * Main module for NetUP Universal Dual DVB-CI + * + * Copyright (C) 2014 NetUP Inc. + * Copyright (C) 2014 Sergey Kozlov + * Copyright (C) 2014 Abylay Ospan + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "netup_unidvb.h" +#include "cxd2841er.h" +#include "horus3a.h" +#include "ascot2e.h" +#include "helene.h" +#include "lnbh25.h" + +static int spi_enable; +module_param(spi_enable, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + +MODULE_DESCRIPTION("Driver for NetUP Dual Universal DVB CI PCIe card"); +MODULE_AUTHOR("info@netup.ru"); +MODULE_VERSION(NETUP_UNIDVB_VERSION); +MODULE_LICENSE("GPL"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +/* Avalon-MM PCI-E registers */ +#define AVL_PCIE_IENR 0x50 +#define AVL_PCIE_ISR 0x40 +#define AVL_IRQ_ENABLE 0x80 +#define AVL_IRQ_ASSERTED 0x80 +/* GPIO registers */ +#define GPIO_REG_IO 0x4880 +#define GPIO_REG_IO_TOGGLE 0x4882 +#define GPIO_REG_IO_SET 0x4884 +#define GPIO_REG_IO_CLEAR 0x4886 +/* GPIO bits */ +#define GPIO_FEA_RESET (1 << 0) +#define GPIO_FEB_RESET (1 << 1) +#define GPIO_RFA_CTL (1 << 2) +#define GPIO_RFB_CTL (1 << 3) +#define GPIO_FEA_TU_RESET (1 << 4) +#define GPIO_FEB_TU_RESET (1 << 5) +/* DMA base address */ +#define NETUP_DMA0_ADDR 0x4900 +#define NETUP_DMA1_ADDR 0x4940 +/* 8 DMA blocks * 128 packets * 188 bytes*/ +#define NETUP_DMA_BLOCKS_COUNT 8 +#define NETUP_DMA_PACKETS_COUNT 128 +/* DMA status bits */ +#define BIT_DMA_RUN 1 +#define BIT_DMA_ERROR 2 +#define BIT_DMA_IRQ 0x200 + +/** + * struct netup_dma_regs - the map of DMA module registers + * @ctrlstat_set: Control register, write to set control bits + * @ctrlstat_clear: Control register, write to clear control bits + * @start_addr_lo: DMA ring buffer start address, lower part + * @start_addr_hi: DMA ring buffer start address, higher part + * @size: DMA ring buffer size register + * * Bits [0-7]: DMA packet size, 188 bytes + * * Bits [16-23]: packets count in block, 128 packets + * * Bits [24-31]: blocks count, 8 blocks + * @timeout: DMA timeout in units of 8ns + * For example, value of 375000000 equals to 3 sec + * @curr_addr_lo: Current ring buffer head address, lower part + * @curr_addr_hi: Current ring buffer head address, higher part + * @stat_pkt_received: Statistic register, not tested + * @stat_pkt_accepted: Statistic register, not tested + * @stat_pkt_overruns: Statistic register, not tested + * @stat_pkt_underruns: Statistic register, not tested + * @stat_fifo_overruns: Statistic register, not tested + */ +struct netup_dma_regs { + __le32 ctrlstat_set; + __le32 ctrlstat_clear; + __le32 start_addr_lo; + __le32 start_addr_hi; + __le32 size; + __le32 timeout; + __le32 curr_addr_lo; + __le32 curr_addr_hi; + __le32 stat_pkt_received; + __le32 stat_pkt_accepted; + __le32 stat_pkt_overruns; + __le32 stat_pkt_underruns; + __le32 stat_fifo_overruns; +} __packed __aligned(1); + +struct netup_unidvb_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; + u32 size; +}; + +static int netup_unidvb_tuner_ctrl(void *priv, int is_dvb_tc); +static void netup_unidvb_queue_cleanup(struct netup_dma *dma); + +static struct cxd2841er_config demod_config = { + .i2c_addr = 0xc8, + .xtal = SONY_XTAL_24000, + .flags = CXD2841ER_USE_GATECTRL | CXD2841ER_ASCOT +}; + +static struct horus3a_config horus3a_conf = { + .i2c_address = 0xc0, + .xtal_freq_mhz = 16, + .set_tuner_callback = netup_unidvb_tuner_ctrl +}; + +static struct ascot2e_config ascot2e_conf = { + .i2c_address = 0xc2, + .set_tuner_callback = netup_unidvb_tuner_ctrl +}; + +static struct helene_config helene_conf = { + .i2c_address = 0xc0, + .xtal = SONY_HELENE_XTAL_24000, + .set_tuner_callback = netup_unidvb_tuner_ctrl +}; + +static struct lnbh25_config lnbh25_conf = { + .i2c_address = 0x10, + .data2_config = LNBH25_TEN | LNBH25_EXTM +}; + +static int netup_unidvb_tuner_ctrl(void *priv, int is_dvb_tc) +{ + u8 reg, mask; + struct netup_dma *dma = priv; + struct netup_unidvb_dev *ndev; + + if (!priv) + return -EINVAL; + ndev = dma->ndev; + dev_dbg(&ndev->pci_dev->dev, "%s(): num %d is_dvb_tc %d\n", + __func__, dma->num, is_dvb_tc); + reg = readb(ndev->bmmio0 + GPIO_REG_IO); + mask = (dma->num == 0) ? GPIO_RFA_CTL : GPIO_RFB_CTL; + + /* inverted tuner control in hw rev. 1.4 */ + if (ndev->rev == NETUP_HW_REV_1_4) + is_dvb_tc = !is_dvb_tc; + + if (!is_dvb_tc) + reg |= mask; + else + reg &= ~mask; + writeb(reg, ndev->bmmio0 + GPIO_REG_IO); + return 0; +} + +static void netup_unidvb_dev_enable(struct netup_unidvb_dev *ndev) +{ + u16 gpio_reg; + + /* enable PCI-E interrupts */ + writel(AVL_IRQ_ENABLE, ndev->bmmio0 + AVL_PCIE_IENR); + /* unreset frontends bits[0:1] */ + writeb(0x00, ndev->bmmio0 + GPIO_REG_IO); + msleep(100); + gpio_reg = + GPIO_FEA_RESET | GPIO_FEB_RESET | + GPIO_FEA_TU_RESET | GPIO_FEB_TU_RESET | + GPIO_RFA_CTL | GPIO_RFB_CTL; + writeb(gpio_reg, ndev->bmmio0 + GPIO_REG_IO); + dev_dbg(&ndev->pci_dev->dev, + "%s(): AVL_PCIE_IENR 0x%x GPIO_REG_IO 0x%x\n", + __func__, readl(ndev->bmmio0 + AVL_PCIE_IENR), + (int)readb(ndev->bmmio0 + GPIO_REG_IO)); + +} + +static void netup_unidvb_dma_enable(struct netup_dma *dma, int enable) +{ + u32 irq_mask = (dma->num == 0 ? + NETUP_UNIDVB_IRQ_DMA1 : NETUP_UNIDVB_IRQ_DMA2); + + dev_dbg(&dma->ndev->pci_dev->dev, + "%s(): DMA%d enable %d\n", __func__, dma->num, enable); + if (enable) { + writel(BIT_DMA_RUN, &dma->regs->ctrlstat_set); + writew(irq_mask, dma->ndev->bmmio0 + REG_IMASK_SET); + } else { + writel(BIT_DMA_RUN, &dma->regs->ctrlstat_clear); + writew(irq_mask, dma->ndev->bmmio0 + REG_IMASK_CLEAR); + } +} + +static irqreturn_t netup_dma_interrupt(struct netup_dma *dma) +{ + u64 addr_curr; + u32 size; + unsigned long flags; + struct device *dev = &dma->ndev->pci_dev->dev; + + spin_lock_irqsave(&dma->lock, flags); + addr_curr = ((u64)readl(&dma->regs->curr_addr_hi) << 32) | + (u64)readl(&dma->regs->curr_addr_lo) | dma->high_addr; + /* clear IRQ */ + writel(BIT_DMA_IRQ, &dma->regs->ctrlstat_clear); + /* sanity check */ + if (addr_curr < dma->addr_phys || + addr_curr > dma->addr_phys + dma->ring_buffer_size) { + if (addr_curr != 0) { + dev_err(dev, + "%s(): addr 0x%llx not from 0x%llx:0x%llx\n", + __func__, addr_curr, (u64)dma->addr_phys, + (u64)(dma->addr_phys + dma->ring_buffer_size)); + } + goto irq_handled; + } + size = (addr_curr >= dma->addr_last) ? + (u32)(addr_curr - dma->addr_last) : + (u32)(dma->ring_buffer_size - (dma->addr_last - addr_curr)); + if (dma->data_size != 0) { + printk_ratelimited("%s(): lost interrupt, data size %d\n", + __func__, dma->data_size); + dma->data_size += size; + } + if (dma->data_size == 0 || dma->data_size > dma->ring_buffer_size) { + dma->data_size = size; + dma->data_offset = (u32)(dma->addr_last - dma->addr_phys); + } + dma->addr_last = addr_curr; + queue_work(dma->ndev->wq, &dma->work); +irq_handled: + spin_unlock_irqrestore(&dma->lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t netup_unidvb_isr(int irq, void *dev_id) +{ + struct pci_dev *pci_dev = (struct pci_dev *)dev_id; + struct netup_unidvb_dev *ndev = pci_get_drvdata(pci_dev); + u32 reg40, reg_isr; + irqreturn_t iret = IRQ_NONE; + + /* disable interrupts */ + writel(0, ndev->bmmio0 + AVL_PCIE_IENR); + /* check IRQ source */ + reg40 = readl(ndev->bmmio0 + AVL_PCIE_ISR); + if ((reg40 & AVL_IRQ_ASSERTED) != 0) { + /* IRQ is being signaled */ + reg_isr = readw(ndev->bmmio0 + REG_ISR); + if (reg_isr & NETUP_UNIDVB_IRQ_SPI) + iret = netup_spi_interrupt(ndev->spi); + else if (!ndev->old_fw) { + if (reg_isr & NETUP_UNIDVB_IRQ_I2C0) { + iret = netup_i2c_interrupt(&ndev->i2c[0]); + } else if (reg_isr & NETUP_UNIDVB_IRQ_I2C1) { + iret = netup_i2c_interrupt(&ndev->i2c[1]); + } else if (reg_isr & NETUP_UNIDVB_IRQ_DMA1) { + iret = netup_dma_interrupt(&ndev->dma[0]); + } else if (reg_isr & NETUP_UNIDVB_IRQ_DMA2) { + iret = netup_dma_interrupt(&ndev->dma[1]); + } else if (reg_isr & NETUP_UNIDVB_IRQ_CI) { + iret = netup_ci_interrupt(ndev); + } else { + goto err; + } + } else { +err: + dev_err(&pci_dev->dev, + "%s(): unknown interrupt 0x%x\n", + __func__, reg_isr); + } + } + /* re-enable interrupts */ + writel(AVL_IRQ_ENABLE, ndev->bmmio0 + AVL_PCIE_IENR); + return iret; +} + +static int netup_unidvb_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct netup_dma *dma = vb2_get_drv_priv(vq); + unsigned int q_num_bufs = vb2_get_num_buffers(vq); + + dev_dbg(&dma->ndev->pci_dev->dev, "%s()\n", __func__); + + *nplanes = 1; + if (q_num_bufs + *nbuffers < VIDEO_MAX_FRAME) + *nbuffers = VIDEO_MAX_FRAME - q_num_bufs; + sizes[0] = PAGE_ALIGN(NETUP_DMA_PACKETS_COUNT * 188); + dev_dbg(&dma->ndev->pci_dev->dev, "%s() nbuffers=%d sizes[0]=%d\n", + __func__, *nbuffers, sizes[0]); + return 0; +} + +static int netup_unidvb_buf_prepare(struct vb2_buffer *vb) +{ + struct netup_dma *dma = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct netup_unidvb_buffer *buf = container_of(vbuf, + struct netup_unidvb_buffer, vb); + + dev_dbg(&dma->ndev->pci_dev->dev, "%s(): buf 0x%p\n", __func__, buf); + buf->size = 0; + return 0; +} + +static void netup_unidvb_buf_queue(struct vb2_buffer *vb) +{ + unsigned long flags; + struct netup_dma *dma = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct netup_unidvb_buffer *buf = container_of(vbuf, + struct netup_unidvb_buffer, vb); + + dev_dbg(&dma->ndev->pci_dev->dev, "%s(): %p\n", __func__, buf); + spin_lock_irqsave(&dma->lock, flags); + list_add_tail(&buf->list, &dma->free_buffers); + spin_unlock_irqrestore(&dma->lock, flags); + mod_timer(&dma->timeout, jiffies + msecs_to_jiffies(1000)); +} + +static int netup_unidvb_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct netup_dma *dma = vb2_get_drv_priv(q); + + dev_dbg(&dma->ndev->pci_dev->dev, "%s()\n", __func__); + netup_unidvb_dma_enable(dma, 1); + return 0; +} + +static void netup_unidvb_stop_streaming(struct vb2_queue *q) +{ + struct netup_dma *dma = vb2_get_drv_priv(q); + + dev_dbg(&dma->ndev->pci_dev->dev, "%s()\n", __func__); + netup_unidvb_dma_enable(dma, 0); + netup_unidvb_queue_cleanup(dma); +} + +static const struct vb2_ops dvb_qops = { + .queue_setup = netup_unidvb_queue_setup, + .buf_prepare = netup_unidvb_buf_prepare, + .buf_queue = netup_unidvb_buf_queue, + .start_streaming = netup_unidvb_start_streaming, + .stop_streaming = netup_unidvb_stop_streaming, +}; + +static int netup_unidvb_queue_init(struct netup_dma *dma, + struct vb2_queue *vb_queue) +{ + int res; + + /* Init videobuf2 queue structure */ + vb_queue->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vb_queue->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; + vb_queue->drv_priv = dma; + vb_queue->buf_struct_size = sizeof(struct netup_unidvb_buffer); + vb_queue->ops = &dvb_qops; + vb_queue->mem_ops = &vb2_vmalloc_memops; + vb_queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + res = vb2_queue_init(vb_queue); + if (res != 0) { + dev_err(&dma->ndev->pci_dev->dev, + "%s(): vb2_queue_init failed (%d)\n", __func__, res); + } + return res; +} + +static int netup_unidvb_dvb_init(struct netup_unidvb_dev *ndev, + int num) +{ + int fe_count = 2; + int i = 0; + struct vb2_dvb_frontend *fes[2]; + u8 fe_name[32]; + + if (ndev->rev == NETUP_HW_REV_1_3) + demod_config.xtal = SONY_XTAL_20500; + else + demod_config.xtal = SONY_XTAL_24000; + + if (num < 0 || num > 1) { + dev_dbg(&ndev->pci_dev->dev, + "%s(): unable to init DVB bus %d\n", __func__, num); + return -ENODEV; + } + mutex_init(&ndev->frontends[num].lock); + INIT_LIST_HEAD(&ndev->frontends[num].felist); + + for (i = 0; i < fe_count; i++) { + if (vb2_dvb_alloc_frontend(&ndev->frontends[num], i+1) + == NULL) { + dev_err(&ndev->pci_dev->dev, + "%s(): unable to allocate vb2_dvb_frontend\n", + __func__); + return -ENOMEM; + } + } + + for (i = 0; i < fe_count; i++) { + fes[i] = vb2_dvb_get_frontend(&ndev->frontends[num], i+1); + if (fes[i] == NULL) { + dev_err(&ndev->pci_dev->dev, + "%s(): frontends has not been allocated\n", + __func__); + return -EINVAL; + } + } + + for (i = 0; i < fe_count; i++) { + netup_unidvb_queue_init(&ndev->dma[num], &fes[i]->dvb.dvbq); + snprintf(fe_name, sizeof(fe_name), "netup_fe%d", i); + fes[i]->dvb.name = fe_name; + } + + fes[0]->dvb.frontend = dvb_attach(cxd2841er_attach_s, + &demod_config, &ndev->i2c[num].adap); + if (fes[0]->dvb.frontend == NULL) { + dev_dbg(&ndev->pci_dev->dev, + "%s(): unable to attach DVB-S/S2 frontend\n", + __func__); + goto frontend_detach; + } + + if (ndev->rev == NETUP_HW_REV_1_3) { + horus3a_conf.set_tuner_priv = &ndev->dma[num]; + if (!dvb_attach(horus3a_attach, fes[0]->dvb.frontend, + &horus3a_conf, &ndev->i2c[num].adap)) { + dev_dbg(&ndev->pci_dev->dev, + "%s(): unable to attach HORUS3A DVB-S/S2 tuner frontend\n", + __func__); + goto frontend_detach; + } + } else { + helene_conf.set_tuner_priv = &ndev->dma[num]; + if (!dvb_attach(helene_attach_s, fes[0]->dvb.frontend, + &helene_conf, &ndev->i2c[num].adap)) { + dev_err(&ndev->pci_dev->dev, + "%s(): unable to attach HELENE DVB-S/S2 tuner frontend\n", + __func__); + goto frontend_detach; + } + } + + if (!dvb_attach(lnbh25_attach, fes[0]->dvb.frontend, + &lnbh25_conf, &ndev->i2c[num].adap)) { + dev_dbg(&ndev->pci_dev->dev, + "%s(): unable to attach SEC frontend\n", __func__); + goto frontend_detach; + } + + /* DVB-T/T2 frontend */ + fes[1]->dvb.frontend = dvb_attach(cxd2841er_attach_t_c, + &demod_config, &ndev->i2c[num].adap); + if (fes[1]->dvb.frontend == NULL) { + dev_dbg(&ndev->pci_dev->dev, + "%s(): unable to attach Ter frontend\n", __func__); + goto frontend_detach; + } + fes[1]->dvb.frontend->id = 1; + if (ndev->rev == NETUP_HW_REV_1_3) { + ascot2e_conf.set_tuner_priv = &ndev->dma[num]; + if (!dvb_attach(ascot2e_attach, fes[1]->dvb.frontend, + &ascot2e_conf, &ndev->i2c[num].adap)) { + dev_dbg(&ndev->pci_dev->dev, + "%s(): unable to attach Ter tuner frontend\n", + __func__); + goto frontend_detach; + } + } else { + helene_conf.set_tuner_priv = &ndev->dma[num]; + if (!dvb_attach(helene_attach, fes[1]->dvb.frontend, + &helene_conf, &ndev->i2c[num].adap)) { + dev_err(&ndev->pci_dev->dev, + "%s(): unable to attach HELENE Ter tuner frontend\n", + __func__); + goto frontend_detach; + } + } + + if (vb2_dvb_register_bus(&ndev->frontends[num], + THIS_MODULE, NULL, + &ndev->pci_dev->dev, NULL, adapter_nr, 1)) { + dev_dbg(&ndev->pci_dev->dev, + "%s(): unable to register DVB bus %d\n", + __func__, num); + goto frontend_detach; + } + dev_info(&ndev->pci_dev->dev, "DVB init done, num=%d\n", num); + return 0; +frontend_detach: + vb2_dvb_dealloc_frontends(&ndev->frontends[num]); + return -EINVAL; +} + +static void netup_unidvb_dvb_fini(struct netup_unidvb_dev *ndev, int num) +{ + if (num < 0 || num > 1) { + dev_err(&ndev->pci_dev->dev, + "%s(): unable to unregister DVB bus %d\n", + __func__, num); + return; + } + vb2_dvb_unregister_bus(&ndev->frontends[num]); + dev_info(&ndev->pci_dev->dev, + "%s(): DVB bus %d unregistered\n", __func__, num); +} + +static int netup_unidvb_dvb_setup(struct netup_unidvb_dev *ndev) +{ + int res; + + res = netup_unidvb_dvb_init(ndev, 0); + if (res) + return res; + res = netup_unidvb_dvb_init(ndev, 1); + if (res) { + netup_unidvb_dvb_fini(ndev, 0); + return res; + } + return 0; +} + +static int netup_unidvb_ring_copy(struct netup_dma *dma, + struct netup_unidvb_buffer *buf) +{ + u32 copy_bytes, ring_bytes; + u32 buff_bytes = NETUP_DMA_PACKETS_COUNT * 188 - buf->size; + u8 *p = vb2_plane_vaddr(&buf->vb.vb2_buf, 0); + struct netup_unidvb_dev *ndev = dma->ndev; + + if (p == NULL) { + dev_err(&ndev->pci_dev->dev, + "%s(): buffer is NULL\n", __func__); + return -EINVAL; + } + p += buf->size; + if (dma->data_offset + dma->data_size > dma->ring_buffer_size) { + ring_bytes = dma->ring_buffer_size - dma->data_offset; + copy_bytes = (ring_bytes > buff_bytes) ? + buff_bytes : ring_bytes; + memcpy_fromio(p, (u8 __iomem *)(dma->addr_virt + dma->data_offset), copy_bytes); + p += copy_bytes; + buf->size += copy_bytes; + buff_bytes -= copy_bytes; + dma->data_size -= copy_bytes; + dma->data_offset += copy_bytes; + if (dma->data_offset == dma->ring_buffer_size) + dma->data_offset = 0; + } + if (buff_bytes > 0) { + ring_bytes = dma->data_size; + copy_bytes = (ring_bytes > buff_bytes) ? + buff_bytes : ring_bytes; + memcpy_fromio(p, (u8 __iomem *)(dma->addr_virt + dma->data_offset), copy_bytes); + buf->size += copy_bytes; + dma->data_size -= copy_bytes; + dma->data_offset += copy_bytes; + if (dma->data_offset == dma->ring_buffer_size) + dma->data_offset = 0; + } + return 0; +} + +static void netup_unidvb_dma_worker(struct work_struct *work) +{ + struct netup_dma *dma = container_of(work, struct netup_dma, work); + struct netup_unidvb_dev *ndev = dma->ndev; + struct netup_unidvb_buffer *buf; + unsigned long flags; + + spin_lock_irqsave(&dma->lock, flags); + if (dma->data_size == 0) { + dev_dbg(&ndev->pci_dev->dev, + "%s(): data_size == 0\n", __func__); + goto work_done; + } + while (dma->data_size > 0) { + if (list_empty(&dma->free_buffers)) { + dev_dbg(&ndev->pci_dev->dev, + "%s(): no free buffers\n", __func__); + goto work_done; + } + buf = list_first_entry(&dma->free_buffers, + struct netup_unidvb_buffer, list); + if (buf->size >= NETUP_DMA_PACKETS_COUNT * 188) { + dev_dbg(&ndev->pci_dev->dev, + "%s(): buffer overflow, size %d\n", + __func__, buf->size); + goto work_done; + } + if (netup_unidvb_ring_copy(dma, buf)) + goto work_done; + if (buf->size == NETUP_DMA_PACKETS_COUNT * 188) { + list_del(&buf->list); + dev_dbg(&ndev->pci_dev->dev, + "%s(): buffer %p done, size %d\n", + __func__, buf, buf->size); + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, buf->size); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + } + } +work_done: + dma->data_size = 0; + spin_unlock_irqrestore(&dma->lock, flags); +} + +static void netup_unidvb_queue_cleanup(struct netup_dma *dma) +{ + struct netup_unidvb_buffer *buf; + unsigned long flags; + + spin_lock_irqsave(&dma->lock, flags); + while (!list_empty(&dma->free_buffers)) { + buf = list_first_entry(&dma->free_buffers, + struct netup_unidvb_buffer, list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&dma->lock, flags); +} + +static void netup_unidvb_dma_timeout(struct timer_list *t) +{ + struct netup_dma *dma = timer_container_of(dma, t, timeout); + struct netup_unidvb_dev *ndev = dma->ndev; + + dev_dbg(&ndev->pci_dev->dev, "%s()\n", __func__); + netup_unidvb_queue_cleanup(dma); +} + +static int netup_unidvb_dma_init(struct netup_unidvb_dev *ndev, int num) +{ + struct netup_dma *dma; + struct device *dev = &ndev->pci_dev->dev; + + if (num < 0 || num > 1) { + dev_err(dev, "%s(): unable to register DMA%d\n", + __func__, num); + return -ENODEV; + } + dma = &ndev->dma[num]; + dev_info(dev, "%s(): starting DMA%d\n", __func__, num); + dma->num = num; + dma->ndev = ndev; + spin_lock_init(&dma->lock); + INIT_WORK(&dma->work, netup_unidvb_dma_worker); + INIT_LIST_HEAD(&dma->free_buffers); + timer_setup(&dma->timeout, netup_unidvb_dma_timeout, 0); + dma->ring_buffer_size = ndev->dma_size / 2; + dma->addr_virt = ndev->dma_virt + dma->ring_buffer_size * num; + dma->addr_phys = (dma_addr_t)((u64)ndev->dma_phys + + dma->ring_buffer_size * num); + dev_info(dev, "%s(): DMA%d buffer virt/phys 0x%p/0x%llx size %d\n", + __func__, num, dma->addr_virt, + (unsigned long long)dma->addr_phys, + dma->ring_buffer_size); + memset_io((u8 __iomem *)dma->addr_virt, 0, dma->ring_buffer_size); + dma->addr_last = dma->addr_phys; + dma->high_addr = (u32)(dma->addr_phys & 0xC0000000); + dma->regs = (struct netup_dma_regs __iomem *)(num == 0 ? + ndev->bmmio0 + NETUP_DMA0_ADDR : + ndev->bmmio0 + NETUP_DMA1_ADDR); + writel((NETUP_DMA_BLOCKS_COUNT << 24) | + (NETUP_DMA_PACKETS_COUNT << 8) | 188, &dma->regs->size); + writel((u32)(dma->addr_phys & 0x3FFFFFFF), &dma->regs->start_addr_lo); + writel(0, &dma->regs->start_addr_hi); + writel(dma->high_addr, ndev->bmmio0 + 0x1000); + writel(375000000, &dma->regs->timeout); + msleep(1000); + writel(BIT_DMA_IRQ, &dma->regs->ctrlstat_clear); + return 0; +} + +static void netup_unidvb_dma_fini(struct netup_unidvb_dev *ndev, int num) +{ + struct netup_dma *dma; + + if (num < 0 || num > 1) + return; + dev_dbg(&ndev->pci_dev->dev, "%s(): num %d\n", __func__, num); + dma = &ndev->dma[num]; + netup_unidvb_dma_enable(dma, 0); + msleep(50); + cancel_work_sync(&dma->work); + timer_delete_sync(&dma->timeout); +} + +static int netup_unidvb_dma_setup(struct netup_unidvb_dev *ndev) +{ + int res; + + res = netup_unidvb_dma_init(ndev, 0); + if (res) + return res; + res = netup_unidvb_dma_init(ndev, 1); + if (res) { + netup_unidvb_dma_fini(ndev, 0); + return res; + } + netup_unidvb_dma_enable(&ndev->dma[0], 0); + netup_unidvb_dma_enable(&ndev->dma[1], 0); + return 0; +} + +static int netup_unidvb_ci_setup(struct netup_unidvb_dev *ndev, + struct pci_dev *pci_dev) +{ + int res; + + writew(NETUP_UNIDVB_IRQ_CI, ndev->bmmio0 + REG_IMASK_SET); + res = netup_unidvb_ci_register(ndev, 0, pci_dev); + if (res) + return res; + res = netup_unidvb_ci_register(ndev, 1, pci_dev); + if (res) + netup_unidvb_ci_unregister(ndev, 0); + return res; +} + +static int netup_unidvb_request_mmio(struct pci_dev *pci_dev) +{ + if (!request_mem_region(pci_resource_start(pci_dev, 0), + pci_resource_len(pci_dev, 0), NETUP_UNIDVB_NAME)) { + dev_err(&pci_dev->dev, + "%s(): unable to request MMIO bar 0 at 0x%llx\n", + __func__, + (unsigned long long)pci_resource_start(pci_dev, 0)); + return -EBUSY; + } + if (!request_mem_region(pci_resource_start(pci_dev, 1), + pci_resource_len(pci_dev, 1), NETUP_UNIDVB_NAME)) { + dev_err(&pci_dev->dev, + "%s(): unable to request MMIO bar 1 at 0x%llx\n", + __func__, + (unsigned long long)pci_resource_start(pci_dev, 1)); + release_mem_region(pci_resource_start(pci_dev, 0), + pci_resource_len(pci_dev, 0)); + return -EBUSY; + } + return 0; +} + +static int netup_unidvb_request_modules(struct device *dev) +{ + static const char * const modules[] = { + "lnbh25", "ascot2e", "horus3a", "cxd2841er", "helene", NULL + }; + const char * const *curr_mod = modules; + int err; + + while (*curr_mod != NULL) { + err = request_module(*curr_mod); + if (err) { + dev_warn(dev, "request_module(%s) failed: %d\n", + *curr_mod, err); + } + ++curr_mod; + } + return 0; +} + +static int netup_unidvb_initdev(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + u8 board_revision; + u16 board_vendor; + struct netup_unidvb_dev *ndev; + int old_firmware = 0; + + netup_unidvb_request_modules(&pci_dev->dev); + + /* Check card revision */ + if (pci_dev->revision != NETUP_PCI_DEV_REVISION) { + dev_err(&pci_dev->dev, + "netup_unidvb: expected card revision %d, got %d\n", + NETUP_PCI_DEV_REVISION, pci_dev->revision); + dev_err(&pci_dev->dev, + "Please upgrade firmware!\n"); + dev_err(&pci_dev->dev, + "Instructions on http://www.netup.tv\n"); + old_firmware = 1; + spi_enable = 1; + } + + /* allocate device context */ + ndev = kzalloc_obj(*ndev); + if (!ndev) + goto dev_alloc_err; + + /* detect hardware revision */ + if (pci_dev->device == NETUP_HW_REV_1_3) + ndev->rev = NETUP_HW_REV_1_3; + else + ndev->rev = NETUP_HW_REV_1_4; + + dev_info(&pci_dev->dev, + "%s(): board (0x%x) hardware revision 0x%x\n", + __func__, pci_dev->device, ndev->rev); + + ndev->old_fw = old_firmware; + ndev->wq = create_singlethread_workqueue(NETUP_UNIDVB_NAME); + if (!ndev->wq) { + dev_err(&pci_dev->dev, + "%s(): unable to create workqueue\n", __func__); + goto wq_create_err; + } + ndev->pci_dev = pci_dev; + ndev->pci_bus = pci_dev->bus->number; + ndev->pci_slot = PCI_SLOT(pci_dev->devfn); + ndev->pci_func = PCI_FUNC(pci_dev->devfn); + ndev->board_num = ndev->pci_bus*10 + ndev->pci_slot; + pci_set_drvdata(pci_dev, ndev); + /* PCI init */ + dev_info(&pci_dev->dev, "%s(): PCI device (%d). Bus:0x%x Slot:0x%x\n", + __func__, ndev->board_num, ndev->pci_bus, ndev->pci_slot); + + if (pci_enable_device(pci_dev)) { + dev_err(&pci_dev->dev, "%s(): pci_enable_device failed\n", + __func__); + goto pci_enable_err; + } + /* read PCI info */ + pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &board_revision); + pci_read_config_word(pci_dev, PCI_VENDOR_ID, &board_vendor); + if (board_vendor != NETUP_VENDOR_ID) { + dev_err(&pci_dev->dev, "%s(): unknown board vendor 0x%x", + __func__, board_vendor); + goto pci_detect_err; + } + dev_info(&pci_dev->dev, + "%s(): board vendor 0x%x, revision 0x%x\n", + __func__, board_vendor, board_revision); + pci_set_master(pci_dev); + if (dma_set_mask(&pci_dev->dev, 0xffffffff) < 0) { + dev_err(&pci_dev->dev, + "%s(): 32bit PCI DMA is not supported\n", __func__); + goto pci_detect_err; + } + dev_info(&pci_dev->dev, "%s(): using 32bit PCI DMA\n", __func__); + /* Clear "no snoop" and "relaxed ordering" bits, use default MRRS. */ + pcie_capability_clear_and_set_word(pci_dev, PCI_EXP_DEVCTL, + PCI_EXP_DEVCTL_READRQ | PCI_EXP_DEVCTL_RELAX_EN | + PCI_EXP_DEVCTL_NOSNOOP_EN, 0); + /* Adjust PCIe completion timeout. */ + pcie_capability_clear_and_set_word(pci_dev, + PCI_EXP_DEVCTL2, PCI_EXP_DEVCTL2_COMP_TIMEOUT, 0x2); + + if (netup_unidvb_request_mmio(pci_dev)) { + dev_err(&pci_dev->dev, + "%s(): unable to request MMIO regions\n", __func__); + goto pci_detect_err; + } + ndev->lmmio0 = ioremap(pci_resource_start(pci_dev, 0), + pci_resource_len(pci_dev, 0)); + if (!ndev->lmmio0) { + dev_err(&pci_dev->dev, + "%s(): unable to remap MMIO bar 0\n", __func__); + goto pci_bar0_error; + } + ndev->lmmio1 = ioremap(pci_resource_start(pci_dev, 1), + pci_resource_len(pci_dev, 1)); + if (!ndev->lmmio1) { + dev_err(&pci_dev->dev, + "%s(): unable to remap MMIO bar 1\n", __func__); + goto pci_bar1_error; + } + ndev->bmmio0 = (u8 __iomem *)ndev->lmmio0; + ndev->bmmio1 = (u8 __iomem *)ndev->lmmio1; + dev_info(&pci_dev->dev, + "%s(): PCI MMIO at 0x%p (%d); 0x%p (%d); IRQ %d", + __func__, + ndev->lmmio0, (u32)pci_resource_len(pci_dev, 0), + ndev->lmmio1, (u32)pci_resource_len(pci_dev, 1), + pci_dev->irq); + + ndev->dma_size = 2 * 188 * + NETUP_DMA_BLOCKS_COUNT * NETUP_DMA_PACKETS_COUNT; + ndev->dma_virt = dma_alloc_coherent(&pci_dev->dev, + ndev->dma_size, &ndev->dma_phys, GFP_KERNEL); + if (!ndev->dma_virt) { + dev_err(&pci_dev->dev, "%s(): unable to allocate DMA buffer\n", + __func__); + goto dma_alloc_err; + } + netup_unidvb_dev_enable(ndev); + if (spi_enable && netup_spi_init(ndev)) { + dev_warn(&pci_dev->dev, + "netup_unidvb: SPI flash setup failed\n"); + goto spi_setup_err; + } + if (old_firmware) { + dev_err(&pci_dev->dev, + "netup_unidvb: card initialization was incomplete\n"); + return 0; + } + if (netup_i2c_register(ndev)) { + dev_err(&pci_dev->dev, "netup_unidvb: I2C setup failed\n"); + goto i2c_setup_err; + } + /* enable I2C IRQs */ + writew(NETUP_UNIDVB_IRQ_I2C0 | NETUP_UNIDVB_IRQ_I2C1, + ndev->bmmio0 + REG_IMASK_SET); + usleep_range(5000, 10000); + if (netup_unidvb_dvb_setup(ndev)) { + dev_err(&pci_dev->dev, "netup_unidvb: DVB setup failed\n"); + goto dvb_setup_err; + } + if (netup_unidvb_ci_setup(ndev, pci_dev)) { + dev_err(&pci_dev->dev, "netup_unidvb: CI setup failed\n"); + goto ci_setup_err; + } + if (netup_unidvb_dma_setup(ndev)) { + dev_err(&pci_dev->dev, "netup_unidvb: DMA setup failed\n"); + goto dma_setup_err; + } + + if (request_irq(pci_dev->irq, netup_unidvb_isr, IRQF_SHARED, + "netup_unidvb", pci_dev) < 0) { + dev_err(&pci_dev->dev, + "%s(): can't get IRQ %d\n", __func__, pci_dev->irq); + goto dma_setup_err; + } + + dev_info(&pci_dev->dev, + "netup_unidvb: device has been initialized\n"); + return 0; +dma_setup_err: + netup_unidvb_ci_unregister(ndev, 0); + netup_unidvb_ci_unregister(ndev, 1); +ci_setup_err: + netup_unidvb_dvb_fini(ndev, 0); + netup_unidvb_dvb_fini(ndev, 1); +dvb_setup_err: + netup_i2c_unregister(ndev); +i2c_setup_err: + if (ndev->spi) + netup_spi_release(ndev); +spi_setup_err: + dma_free_coherent(&pci_dev->dev, ndev->dma_size, + ndev->dma_virt, ndev->dma_phys); +dma_alloc_err: + iounmap(ndev->lmmio1); +pci_bar1_error: + iounmap(ndev->lmmio0); +pci_bar0_error: + release_mem_region(pci_resource_start(pci_dev, 0), + pci_resource_len(pci_dev, 0)); + release_mem_region(pci_resource_start(pci_dev, 1), + pci_resource_len(pci_dev, 1)); +pci_detect_err: + pci_disable_device(pci_dev); +pci_enable_err: + pci_set_drvdata(pci_dev, NULL); + destroy_workqueue(ndev->wq); +wq_create_err: + kfree(ndev); +dev_alloc_err: + dev_err(&pci_dev->dev, + "%s(): failed to initialize device\n", __func__); + return -EIO; +} + +static void netup_unidvb_finidev(struct pci_dev *pci_dev) +{ + struct netup_unidvb_dev *ndev = pci_get_drvdata(pci_dev); + + dev_info(&pci_dev->dev, "%s(): trying to stop device\n", __func__); + if (!ndev->old_fw) { + netup_unidvb_dma_fini(ndev, 0); + netup_unidvb_dma_fini(ndev, 1); + netup_unidvb_ci_unregister(ndev, 0); + netup_unidvb_ci_unregister(ndev, 1); + netup_unidvb_dvb_fini(ndev, 0); + netup_unidvb_dvb_fini(ndev, 1); + netup_i2c_unregister(ndev); + } + if (ndev->spi) + netup_spi_release(ndev); + writew(0xffff, ndev->bmmio0 + REG_IMASK_CLEAR); + dma_free_coherent(&ndev->pci_dev->dev, ndev->dma_size, + ndev->dma_virt, ndev->dma_phys); + free_irq(pci_dev->irq, pci_dev); + iounmap(ndev->lmmio0); + iounmap(ndev->lmmio1); + release_mem_region(pci_resource_start(pci_dev, 0), + pci_resource_len(pci_dev, 0)); + release_mem_region(pci_resource_start(pci_dev, 1), + pci_resource_len(pci_dev, 1)); + pci_disable_device(pci_dev); + pci_set_drvdata(pci_dev, NULL); + destroy_workqueue(ndev->wq); + kfree(ndev); + dev_info(&pci_dev->dev, + "%s(): device has been successfully stopped\n", __func__); +} + + +static const struct pci_device_id netup_unidvb_pci_tbl[] = { + { PCI_DEVICE(0x1b55, 0x18f6) }, /* hw rev. 1.3 */ + { PCI_DEVICE(0x1b55, 0x18f7) }, /* hw rev. 1.4 */ + { } +}; +MODULE_DEVICE_TABLE(pci, netup_unidvb_pci_tbl); + +static struct pci_driver netup_unidvb_pci_driver = { + .name = "netup_unidvb", + .id_table = netup_unidvb_pci_tbl, + .probe = netup_unidvb_initdev, + .remove = netup_unidvb_finidev, +}; + +module_pci_driver(netup_unidvb_pci_driver); diff --git a/drivers/media/pci/netup_unidvb/netup_unidvb_i2c.c b/drivers/media/pci/netup_unidvb/netup_unidvb_i2c.c new file mode 100644 index 000000000..1c885d620 --- /dev/null +++ b/drivers/media/pci/netup_unidvb/netup_unidvb_i2c.c @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * netup_unidvb_i2c.c + * + * Internal I2C bus driver for NetUP Universal Dual DVB-CI + * + * Copyright (C) 2014 NetUP Inc. + * Copyright (C) 2014 Sergey Kozlov + * Copyright (C) 2014 Abylay Ospan + */ + +#include +#include +#include +#include +#include "netup_unidvb.h" + +#define NETUP_I2C_BUS0_ADDR 0x4800 +#define NETUP_I2C_BUS1_ADDR 0x4840 +#define NETUP_I2C_TIMEOUT 1000 + +/* twi_ctrl0_stat reg bits */ +#define TWI_IRQEN_COMPL 0x1 +#define TWI_IRQEN_ANACK 0x2 +#define TWI_IRQEN_DNACK 0x4 +#define TWI_IRQ_COMPL (TWI_IRQEN_COMPL << 8) +#define TWI_IRQ_ANACK (TWI_IRQEN_ANACK << 8) +#define TWI_IRQ_DNACK (TWI_IRQEN_DNACK << 8) +#define TWI_IRQ_TX 0x800 +#define TWI_IRQ_RX 0x1000 +#define TWI_IRQEN (TWI_IRQEN_COMPL | TWI_IRQEN_ANACK | TWI_IRQEN_DNACK) +/* twi_addr_ctrl1 reg bits*/ +#define TWI_TRANSFER 0x100 +#define TWI_NOSTOP 0x200 +#define TWI_SOFT_RESET 0x2000 +/* twi_clkdiv reg value */ +#define TWI_CLKDIV 156 +/* fifo_stat_ctrl reg bits */ +#define FIFO_IRQEN 0x8000 +#define FIFO_RESET 0x4000 +/* FIFO size */ +#define FIFO_SIZE 16 + +struct netup_i2c_fifo_regs { + union { + __u8 data8; + __le16 data16; + __le32 data32; + }; + __u8 padding[4]; + __le16 stat_ctrl; +} __packed __aligned(1); + +struct netup_i2c_regs { + __le16 clkdiv; + __le16 twi_ctrl0_stat; + __le16 twi_addr_ctrl1; + __le16 length; + __u8 padding1[8]; + struct netup_i2c_fifo_regs tx_fifo; + __u8 padding2[6]; + struct netup_i2c_fifo_regs rx_fifo; +} __packed __aligned(1); + +irqreturn_t netup_i2c_interrupt(struct netup_i2c *i2c) +{ + u16 reg, tmp; + unsigned long flags; + irqreturn_t iret = IRQ_HANDLED; + + spin_lock_irqsave(&i2c->lock, flags); + reg = readw(&i2c->regs->twi_ctrl0_stat); + writew(reg & ~TWI_IRQEN, &i2c->regs->twi_ctrl0_stat); + dev_dbg(i2c->adap.dev.parent, + "%s(): twi_ctrl0_state 0x%x\n", __func__, reg); + if ((reg & TWI_IRQEN_COMPL) != 0 && (reg & TWI_IRQ_COMPL)) { + dev_dbg(i2c->adap.dev.parent, + "%s(): TWI_IRQEN_COMPL\n", __func__); + i2c->state = STATE_DONE; + goto irq_ok; + } + if ((reg & TWI_IRQEN_ANACK) != 0 && (reg & TWI_IRQ_ANACK)) { + dev_dbg(i2c->adap.dev.parent, + "%s(): TWI_IRQEN_ANACK\n", __func__); + i2c->state = STATE_ERROR; + goto irq_ok; + } + if ((reg & TWI_IRQEN_DNACK) != 0 && (reg & TWI_IRQ_DNACK)) { + dev_dbg(i2c->adap.dev.parent, + "%s(): TWI_IRQEN_DNACK\n", __func__); + i2c->state = STATE_ERROR; + goto irq_ok; + } + if ((reg & TWI_IRQ_RX) != 0) { + tmp = readw(&i2c->regs->rx_fifo.stat_ctrl); + writew(tmp & ~FIFO_IRQEN, &i2c->regs->rx_fifo.stat_ctrl); + i2c->state = STATE_WANT_READ; + dev_dbg(i2c->adap.dev.parent, + "%s(): want read\n", __func__); + goto irq_ok; + } + if ((reg & TWI_IRQ_TX) != 0) { + tmp = readw(&i2c->regs->tx_fifo.stat_ctrl); + writew(tmp & ~FIFO_IRQEN, &i2c->regs->tx_fifo.stat_ctrl); + i2c->state = STATE_WANT_WRITE; + dev_dbg(i2c->adap.dev.parent, + "%s(): want write\n", __func__); + goto irq_ok; + } + dev_warn(&i2c->adap.dev, "%s(): not mine interrupt\n", __func__); + iret = IRQ_NONE; +irq_ok: + spin_unlock_irqrestore(&i2c->lock, flags); + if (iret == IRQ_HANDLED) + wake_up(&i2c->wq); + return iret; +} + +static void netup_i2c_reset(struct netup_i2c *i2c) +{ + dev_dbg(i2c->adap.dev.parent, "%s()\n", __func__); + i2c->state = STATE_DONE; + writew(TWI_SOFT_RESET, &i2c->regs->twi_addr_ctrl1); + writew(TWI_CLKDIV, &i2c->regs->clkdiv); + writew(FIFO_RESET, &i2c->regs->tx_fifo.stat_ctrl); + writew(FIFO_RESET, &i2c->regs->rx_fifo.stat_ctrl); + writew(0x800, &i2c->regs->tx_fifo.stat_ctrl); + writew(0x800, &i2c->regs->rx_fifo.stat_ctrl); +} + +static void netup_i2c_fifo_tx(struct netup_i2c *i2c) +{ + u8 data; + u32 fifo_space = FIFO_SIZE - + (readw(&i2c->regs->tx_fifo.stat_ctrl) & 0x3f); + u32 msg_length = i2c->msg->len - i2c->xmit_size; + + msg_length = min(msg_length, fifo_space); + while (msg_length--) { + data = i2c->msg->buf[i2c->xmit_size++]; + writeb(data, &i2c->regs->tx_fifo.data8); + dev_dbg(i2c->adap.dev.parent, + "%s(): write 0x%02x\n", __func__, data); + } + if (i2c->xmit_size < i2c->msg->len) { + dev_dbg(i2c->adap.dev.parent, + "%s(): TX IRQ enabled\n", __func__); + writew(readw(&i2c->regs->tx_fifo.stat_ctrl) | FIFO_IRQEN, + &i2c->regs->tx_fifo.stat_ctrl); + } +} + +static void netup_i2c_fifo_rx(struct netup_i2c *i2c) +{ + u8 data; + u32 fifo_size = readw(&i2c->regs->rx_fifo.stat_ctrl) & 0x3f; + + dev_dbg(i2c->adap.dev.parent, + "%s(): RX fifo size %d\n", __func__, fifo_size); + while (fifo_size--) { + data = readb(&i2c->regs->rx_fifo.data8); + if ((i2c->msg->flags & I2C_M_RD) != 0 && + i2c->xmit_size < i2c->msg->len) { + i2c->msg->buf[i2c->xmit_size++] = data; + dev_dbg(i2c->adap.dev.parent, + "%s(): read 0x%02x\n", __func__, data); + } + } + if (i2c->xmit_size < i2c->msg->len) { + dev_dbg(i2c->adap.dev.parent, + "%s(): RX IRQ enabled\n", __func__); + writew(readw(&i2c->regs->rx_fifo.stat_ctrl) | FIFO_IRQEN, + &i2c->regs->rx_fifo.stat_ctrl); + } +} + +static void netup_i2c_start_xfer(struct netup_i2c *i2c) +{ + u16 rdflag = ((i2c->msg->flags & I2C_M_RD) ? 1 : 0); + u16 reg = readw(&i2c->regs->twi_ctrl0_stat); + + writew(TWI_IRQEN | reg, &i2c->regs->twi_ctrl0_stat); + writew(i2c->msg->len, &i2c->regs->length); + writew(TWI_TRANSFER | (i2c->msg->addr << 1) | rdflag, + &i2c->regs->twi_addr_ctrl1); + dev_dbg(i2c->adap.dev.parent, + "%s(): length %d twi_addr_ctrl1 0x%x twi_ctrl0_stat 0x%x\n", + __func__, readw(&i2c->regs->length), + readw(&i2c->regs->twi_addr_ctrl1), + readw(&i2c->regs->twi_ctrl0_stat)); + i2c->state = STATE_WAIT; + i2c->xmit_size = 0; + if (!rdflag) + netup_i2c_fifo_tx(i2c); + else + writew(FIFO_IRQEN | readw(&i2c->regs->rx_fifo.stat_ctrl), + &i2c->regs->rx_fifo.stat_ctrl); +} + +static int netup_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + unsigned long flags; + int i, trans_done, res = num; + struct netup_i2c *i2c = i2c_get_adapdata(adap); + u16 reg; + + spin_lock_irqsave(&i2c->lock, flags); + if (i2c->state != STATE_DONE) { + dev_dbg(i2c->adap.dev.parent, + "%s(): i2c->state == %d, resetting I2C\n", + __func__, i2c->state); + netup_i2c_reset(i2c); + } + dev_dbg(i2c->adap.dev.parent, "%s() num %d\n", __func__, num); + for (i = 0; i < num; i++) { + i2c->msg = &msgs[i]; + netup_i2c_start_xfer(i2c); + trans_done = 0; + while (!trans_done) { + spin_unlock_irqrestore(&i2c->lock, flags); + if (wait_event_timeout(i2c->wq, + i2c->state != STATE_WAIT, + msecs_to_jiffies(NETUP_I2C_TIMEOUT))) { + spin_lock_irqsave(&i2c->lock, flags); + switch (i2c->state) { + case STATE_WANT_READ: + netup_i2c_fifo_rx(i2c); + break; + case STATE_WANT_WRITE: + netup_i2c_fifo_tx(i2c); + break; + case STATE_DONE: + if ((i2c->msg->flags & I2C_M_RD) != 0 && + i2c->xmit_size != i2c->msg->len) + netup_i2c_fifo_rx(i2c); + dev_dbg(i2c->adap.dev.parent, + "%s(): msg %d OK\n", + __func__, i); + trans_done = 1; + break; + case STATE_ERROR: + res = -EIO; + dev_dbg(i2c->adap.dev.parent, + "%s(): error state\n", + __func__); + goto done; + default: + dev_dbg(i2c->adap.dev.parent, + "%s(): invalid state %d\n", + __func__, i2c->state); + res = -EINVAL; + goto done; + } + if (!trans_done) { + i2c->state = STATE_WAIT; + reg = readw( + &i2c->regs->twi_ctrl0_stat); + writew(TWI_IRQEN | reg, + &i2c->regs->twi_ctrl0_stat); + } + spin_unlock_irqrestore(&i2c->lock, flags); + } else { + spin_lock_irqsave(&i2c->lock, flags); + dev_dbg(i2c->adap.dev.parent, + "%s(): wait timeout\n", __func__); + res = -ETIMEDOUT; + goto done; + } + spin_lock_irqsave(&i2c->lock, flags); + } + } +done: + spin_unlock_irqrestore(&i2c->lock, flags); + dev_dbg(i2c->adap.dev.parent, "%s(): result %d\n", __func__, res); + return res; +} + +static u32 netup_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm netup_i2c_algorithm = { + .master_xfer = netup_i2c_xfer, + .functionality = netup_i2c_func, +}; + +static const struct i2c_adapter netup_i2c_adapter = { + .owner = THIS_MODULE, + .name = NETUP_UNIDVB_NAME, + .class = I2C_CLASS_HWMON, + .algo = &netup_i2c_algorithm, +}; + +static int netup_i2c_init(struct netup_unidvb_dev *ndev, int bus_num) +{ + int ret; + struct netup_i2c *i2c; + + if (bus_num < 0 || bus_num > 1) { + dev_err(&ndev->pci_dev->dev, + "%s(): invalid bus_num %d\n", __func__, bus_num); + return -EINVAL; + } + i2c = &ndev->i2c[bus_num]; + spin_lock_init(&i2c->lock); + init_waitqueue_head(&i2c->wq); + i2c->regs = (struct netup_i2c_regs __iomem *)(ndev->bmmio0 + + (bus_num == 0 ? NETUP_I2C_BUS0_ADDR : NETUP_I2C_BUS1_ADDR)); + netup_i2c_reset(i2c); + i2c->adap = netup_i2c_adapter; + i2c->adap.dev.parent = &ndev->pci_dev->dev; + i2c_set_adapdata(&i2c->adap, i2c); + ret = i2c_add_adapter(&i2c->adap); + if (ret) + return ret; + dev_info(&ndev->pci_dev->dev, + "%s(): registered I2C bus %d at 0x%x\n", + __func__, + bus_num, (bus_num == 0 ? + NETUP_I2C_BUS0_ADDR : + NETUP_I2C_BUS1_ADDR)); + return 0; +} + +static void netup_i2c_remove(struct netup_unidvb_dev *ndev, int bus_num) +{ + struct netup_i2c *i2c; + + if (bus_num < 0 || bus_num > 1) { + dev_err(&ndev->pci_dev->dev, + "%s(): invalid bus number %d\n", __func__, bus_num); + return; + } + i2c = &ndev->i2c[bus_num]; + netup_i2c_reset(i2c); + /* remove adapter */ + i2c_del_adapter(&i2c->adap); + dev_info(&ndev->pci_dev->dev, + "netup_i2c_remove: unregistered I2C bus %d\n", bus_num); +} + +int netup_i2c_register(struct netup_unidvb_dev *ndev) +{ + int ret; + + ret = netup_i2c_init(ndev, 0); + if (ret) + return ret; + ret = netup_i2c_init(ndev, 1); + if (ret) { + netup_i2c_remove(ndev, 0); + return ret; + } + return 0; +} + +void netup_i2c_unregister(struct netup_unidvb_dev *ndev) +{ + netup_i2c_remove(ndev, 0); + netup_i2c_remove(ndev, 1); +} + diff --git a/drivers/media/pci/netup_unidvb/netup_unidvb_spi.c b/drivers/media/pci/netup_unidvb/netup_unidvb_spi.c new file mode 100644 index 000000000..34d6d52f1 --- /dev/null +++ b/drivers/media/pci/netup_unidvb/netup_unidvb_spi.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * netup_unidvb_spi.c + * + * Internal SPI driver for NetUP Universal Dual DVB-CI + * + * Copyright (C) 2014 NetUP Inc. + * Copyright (C) 2014 Sergey Kozlov + * Copyright (C) 2014 Abylay Ospan + */ + +#include "netup_unidvb.h" +#include +#include +#include +#include + +#define NETUP_SPI_CTRL_IRQ 0x1000 +#define NETUP_SPI_CTRL_IMASK 0x2000 +#define NETUP_SPI_CTRL_START 0x8000 +#define NETUP_SPI_CTRL_LAST_CS 0x4000 + +#define NETUP_SPI_TIMEOUT 6000 + +enum netup_spi_state { + SPI_STATE_START, + SPI_STATE_DONE, +}; + +struct netup_spi_regs { + __u8 data[1024]; + __le16 control_stat; + __le16 clock_divider; +} __packed __aligned(1); + +struct netup_spi { + struct device *dev; + struct spi_controller *ctlr; + struct netup_spi_regs __iomem *regs; + u8 __iomem *mmio; + spinlock_t lock; + wait_queue_head_t waitq; + enum netup_spi_state state; +}; + +static char netup_spi_name[64] = "fpga"; + +static struct mtd_partition netup_spi_flash_partitions = { + .name = netup_spi_name, + .size = 0x1000000, /* 16MB */ + .offset = 0, + .mask_flags = MTD_CAP_ROM +}; + +static struct flash_platform_data spi_flash_data = { + .name = "netup0_m25p128", + .parts = &netup_spi_flash_partitions, + .nr_parts = 1, +}; + +static struct spi_board_info netup_spi_board = { + .modalias = "m25p128", + .max_speed_hz = 11000000, + .chip_select = 0, + .mode = SPI_MODE_0, + .platform_data = &spi_flash_data, +}; + +irqreturn_t netup_spi_interrupt(struct netup_spi *spi) +{ + u16 reg; + unsigned long flags; + + if (!spi) + return IRQ_NONE; + + spin_lock_irqsave(&spi->lock, flags); + reg = readw(&spi->regs->control_stat); + if (!(reg & NETUP_SPI_CTRL_IRQ)) { + spin_unlock_irqrestore(&spi->lock, flags); + dev_dbg(&spi->ctlr->dev, + "%s(): not mine interrupt\n", __func__); + return IRQ_NONE; + } + writew(reg | NETUP_SPI_CTRL_IRQ, &spi->regs->control_stat); + reg = readw(&spi->regs->control_stat); + writew(reg & ~NETUP_SPI_CTRL_IMASK, &spi->regs->control_stat); + spi->state = SPI_STATE_DONE; + wake_up(&spi->waitq); + spin_unlock_irqrestore(&spi->lock, flags); + dev_dbg(&spi->ctlr->dev, + "%s(): SPI interrupt handled\n", __func__); + return IRQ_HANDLED; +} + +static int netup_spi_transfer(struct spi_controller *ctlr, + struct spi_message *msg) +{ + struct netup_spi *spi = spi_controller_get_devdata(ctlr); + struct spi_transfer *t; + int result = 0; + u32 tr_size; + + /* reset CS */ + writew(NETUP_SPI_CTRL_LAST_CS, &spi->regs->control_stat); + writew(0, &spi->regs->control_stat); + list_for_each_entry(t, &msg->transfers, transfer_list) { + tr_size = t->len; + while (tr_size) { + u32 frag_offset = t->len - tr_size; + u32 frag_size = (tr_size > sizeof(spi->regs->data)) ? + sizeof(spi->regs->data) : tr_size; + int frag_last = 0; + + if (list_is_last(&t->transfer_list, + &msg->transfers) && + frag_offset + frag_size == t->len) { + frag_last = 1; + } + if (t->tx_buf) { + memcpy_toio(spi->regs->data, + t->tx_buf + frag_offset, + frag_size); + } else { + memset_io(spi->regs->data, + 0, frag_size); + } + spi->state = SPI_STATE_START; + writew((frag_size & 0x3ff) | + NETUP_SPI_CTRL_IMASK | + NETUP_SPI_CTRL_START | + (frag_last ? NETUP_SPI_CTRL_LAST_CS : 0), + &spi->regs->control_stat); + dev_dbg(&spi->ctlr->dev, + "%s(): control_stat 0x%04x\n", + __func__, readw(&spi->regs->control_stat)); + wait_event_timeout(spi->waitq, + spi->state != SPI_STATE_START, + msecs_to_jiffies(NETUP_SPI_TIMEOUT)); + if (spi->state == SPI_STATE_DONE) { + if (t->rx_buf) { + memcpy_fromio(t->rx_buf + frag_offset, + spi->regs->data, frag_size); + } + } else { + if (spi->state == SPI_STATE_START) { + dev_dbg(&spi->ctlr->dev, + "%s(): transfer timeout\n", + __func__); + } else { + dev_dbg(&spi->ctlr->dev, + "%s(): invalid state %d\n", + __func__, spi->state); + } + result = -EIO; + goto done; + } + tr_size -= frag_size; + msg->actual_length += frag_size; + } + } +done: + msg->status = result; + spi_finalize_current_message(ctlr); + return result; +} + +static int netup_spi_setup(struct spi_device *spi) +{ + return 0; +} + +int netup_spi_init(struct netup_unidvb_dev *ndev) +{ + struct spi_controller *ctlr; + struct netup_spi *nspi; + + ctlr = devm_spi_alloc_host(&ndev->pci_dev->dev, + sizeof(struct netup_spi)); + if (!ctlr) { + dev_err(&ndev->pci_dev->dev, + "%s(): unable to alloc SPI host\n", __func__); + return -EINVAL; + } + nspi = spi_controller_get_devdata(ctlr); + ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST; + ctlr->bus_num = -1; + ctlr->num_chipselect = 1; + ctlr->transfer_one_message = netup_spi_transfer; + ctlr->setup = netup_spi_setup; + spin_lock_init(&nspi->lock); + init_waitqueue_head(&nspi->waitq); + nspi->ctlr = ctlr; + nspi->regs = (struct netup_spi_regs __iomem *)(ndev->bmmio0 + 0x4000); + writew(2, &nspi->regs->clock_divider); + writew(NETUP_UNIDVB_IRQ_SPI, ndev->bmmio0 + REG_IMASK_SET); + ndev->spi = nspi; + if (spi_register_controller(ctlr)) { + ndev->spi = NULL; + dev_err(&ndev->pci_dev->dev, + "%s(): unable to register SPI bus\n", __func__); + return -EINVAL; + } + snprintf(netup_spi_name, + sizeof(netup_spi_name), + "fpga_%02x:%02x.%01x", + ndev->pci_bus, + ndev->pci_slot, + ndev->pci_func); + if (!spi_new_device(ctlr, &netup_spi_board)) { + spi_unregister_controller(ctlr); + ndev->spi = NULL; + dev_err(&ndev->pci_dev->dev, + "%s(): unable to create SPI device\n", __func__); + return -EINVAL; + } + dev_dbg(&ndev->pci_dev->dev, "%s(): SPI init OK\n", __func__); + return 0; +} + +void netup_spi_release(struct netup_unidvb_dev *ndev) +{ + u16 reg; + unsigned long flags; + struct netup_spi *spi = ndev->spi; + + if (!spi) + return; + + spi_unregister_controller(spi->ctlr); + spin_lock_irqsave(&spi->lock, flags); + reg = readw(&spi->regs->control_stat); + writew(reg | NETUP_SPI_CTRL_IRQ, &spi->regs->control_stat); + reg = readw(&spi->regs->control_stat); + writew(reg & ~NETUP_SPI_CTRL_IMASK, &spi->regs->control_stat); + spin_unlock_irqrestore(&spi->lock, flags); + ndev->spi = NULL; +} + + diff --git a/drivers/media/pci/ngene/Kconfig b/drivers/media/pci/ngene/Kconfig new file mode 100644 index 000000000..396408756 --- /dev/null +++ b/drivers/media/pci/ngene/Kconfig @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DVB_NGENE + tristate "Micronas nGene support" + depends on DVB_CORE && PCI && I2C + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV6110x if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV090x if MEDIA_SUBDRV_AUTOSELECT + select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT + select DVB_DRXK if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA18271C2DD if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MT2131 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0367 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CXD2841ER if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18212 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0910 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV6111 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LNBH25 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CXD2099 if MEDIA_SUBDRV_AUTOSELECT + help + Support for Micronas PCI express cards with nGene bridge. + diff --git a/drivers/media/pci/ngene/Makefile b/drivers/media/pci/ngene/Makefile new file mode 100644 index 000000000..5d16090e0 --- /dev/null +++ b/drivers/media/pci/ngene/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the nGene device driver +# + +ngene-objs := ngene-core.o ngene-i2c.o ngene-cards.o ngene-dvb.o + +obj-$(CONFIG_DVB_NGENE) += ngene.o + +ccflags-y += -I $(srctree)/drivers/media/dvb-frontends/ +ccflags-y += -I $(srctree)/drivers/media/tuners/ diff --git a/drivers/media/pci/ngene/ngene-cards.c b/drivers/media/pci/ngene/ngene-cards.c new file mode 100644 index 000000000..a1a83f3bb --- /dev/null +++ b/drivers/media/pci/ngene/ngene-cards.c @@ -0,0 +1,1238 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ngene-cards.c: nGene PCIe bridge driver - card specific info + * + * Copyright (C) 2005-2007 Micronas + * + * Copyright (C) 2008-2009 Ralph Metzler + * Modifications for new nGene firmware, + * support for EEPROM-copying, + * support for new dual DVB-S2 card prototype + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include + +#include "ngene.h" + +/* demods/tuners */ +#include "stv6110x.h" +#include "stv090x.h" +#include "lnbh24.h" +#include "lgdt330x.h" +#include "mt2131.h" +#include "tda18271c2dd.h" +#include "drxk.h" +#include "drxd.h" +#include "dvb-pll.h" +#include "stv0367.h" +#include "stv0367_priv.h" +#include "tda18212.h" +#include "cxd2841er.h" +#include "stv0910.h" +#include "stv6111.h" +#include "lnbh25.h" + +/****************************************************************************/ +/* I2C transfer functions used for demod/tuner probing***********************/ +/****************************************************************************/ + +static int i2c_io(struct i2c_adapter *adapter, u8 adr, + u8 *wbuf, u32 wlen, u8 *rbuf, u32 rlen) +{ + struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0, + .buf = wbuf, .len = wlen }, + {.addr = adr, .flags = I2C_M_RD, + .buf = rbuf, .len = rlen } }; + return (i2c_transfer(adapter, msgs, 2) == 2) ? 0 : -1; +} + +static int i2c_write(struct i2c_adapter *adap, u8 adr, u8 *data, int len) +{ + struct i2c_msg msg = {.addr = adr, .flags = 0, + .buf = data, .len = len}; + + return (i2c_transfer(adap, &msg, 1) == 1) ? 0 : -1; +} + +static int i2c_write_reg(struct i2c_adapter *adap, u8 adr, + u8 reg, u8 val) +{ + u8 msg[2] = {reg, val}; + + return i2c_write(adap, adr, msg, 2); +} + +static int i2c_read(struct i2c_adapter *adapter, u8 adr, u8 *val) +{ + struct i2c_msg msgs[1] = {{.addr = adr, .flags = I2C_M_RD, + .buf = val, .len = 1 } }; + return (i2c_transfer(adapter, msgs, 1) == 1) ? 0 : -1; +} + +static int i2c_read_reg16(struct i2c_adapter *adapter, u8 adr, + u16 reg, u8 *val) +{ + u8 msg[2] = {reg >> 8, reg & 0xff}; + struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0, + .buf = msg, .len = 2}, + {.addr = adr, .flags = I2C_M_RD, + .buf = val, .len = 1} }; + return (i2c_transfer(adapter, msgs, 2) == 2) ? 0 : -1; +} + +static int i2c_read_regs(struct i2c_adapter *adapter, + u8 adr, u8 reg, u8 *val, u8 len) +{ + struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0, + .buf = ®, .len = 1}, + {.addr = adr, .flags = I2C_M_RD, + .buf = val, .len = len} }; + + return (i2c_transfer(adapter, msgs, 2) == 2) ? 0 : -1; +} + +static int i2c_read_reg(struct i2c_adapter *adapter, u8 adr, u8 reg, u8 *val) +{ + return i2c_read_regs(adapter, adr, reg, val, 1); +} + +/****************************************************************************/ +/* Demod/tuner attachment ***************************************************/ +/****************************************************************************/ + +static struct i2c_adapter *i2c_adapter_from_chan(struct ngene_channel *chan) +{ + /* tuner 1+2: i2c adapter #0, tuner 3+4: i2c adapter #1 */ + if (chan->number < 2) + return &chan->dev->channel[0].i2c_adapter; + + return &chan->dev->channel[1].i2c_adapter; +} + +static int tuner_attach_stv6110(struct ngene_channel *chan) +{ + struct device *pdev = &chan->dev->pci_dev->dev; + struct i2c_adapter *i2c = i2c_adapter_from_chan(chan); + struct stv090x_config *feconf = (struct stv090x_config *) + chan->dev->card_info->fe_config[chan->number]; + struct stv6110x_config *tunerconf = (struct stv6110x_config *) + chan->dev->card_info->tuner_config[chan->number]; + const struct stv6110x_devctl *ctl; + + ctl = dvb_attach(stv6110x_attach, chan->fe, tunerconf, i2c); + if (ctl == NULL) { + dev_err(pdev, "No STV6110X found!\n"); + return -ENODEV; + } + + feconf->tuner_init = ctl->tuner_init; + feconf->tuner_sleep = ctl->tuner_sleep; + feconf->tuner_set_mode = ctl->tuner_set_mode; + feconf->tuner_set_frequency = ctl->tuner_set_frequency; + feconf->tuner_get_frequency = ctl->tuner_get_frequency; + feconf->tuner_set_bandwidth = ctl->tuner_set_bandwidth; + feconf->tuner_get_bandwidth = ctl->tuner_get_bandwidth; + feconf->tuner_set_bbgain = ctl->tuner_set_bbgain; + feconf->tuner_get_bbgain = ctl->tuner_get_bbgain; + feconf->tuner_set_refclk = ctl->tuner_set_refclk; + feconf->tuner_get_status = ctl->tuner_get_status; + + return 0; +} + +static int tuner_attach_stv6111(struct ngene_channel *chan) +{ + struct device *pdev = &chan->dev->pci_dev->dev; + struct i2c_adapter *i2c = i2c_adapter_from_chan(chan); + struct dvb_frontend *fe; + u8 adr = 4 + ((chan->number & 1) ? 0x63 : 0x60); + + fe = dvb_attach(stv6111_attach, chan->fe, i2c, adr); + if (!fe) { + fe = dvb_attach(stv6111_attach, chan->fe, i2c, adr & ~4); + if (!fe) { + dev_err(pdev, "stv6111_attach() failed!\n"); + return -ENODEV; + } + } + return 0; +} + +static int drxk_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct ngene_channel *chan = fe->sec_priv; + int status; + + if (enable) { + down(&chan->dev->pll_mutex); + status = chan->gate_ctrl(fe, 1); + } else { + status = chan->gate_ctrl(fe, 0); + up(&chan->dev->pll_mutex); + } + return status; +} + +static int tuner_attach_tda18271(struct ngene_channel *chan) +{ + struct device *pdev = &chan->dev->pci_dev->dev; + struct i2c_adapter *i2c = i2c_adapter_from_chan(chan); + struct dvb_frontend *fe; + + if (chan->fe->ops.i2c_gate_ctrl) + chan->fe->ops.i2c_gate_ctrl(chan->fe, 1); + fe = dvb_attach(tda18271c2dd_attach, chan->fe, i2c, 0x60); + if (chan->fe->ops.i2c_gate_ctrl) + chan->fe->ops.i2c_gate_ctrl(chan->fe, 0); + if (!fe) { + dev_err(pdev, "No TDA18271 found!\n"); + return -ENODEV; + } + + return 0; +} + +static int tuner_tda18212_ping(struct ngene_channel *chan, + struct i2c_adapter *i2c, + unsigned short adr) +{ + struct device *pdev = &chan->dev->pci_dev->dev; + u8 tda_id[2]; + u8 subaddr = 0x00; + + dev_dbg(pdev, "stv0367-tda18212 tuner ping\n"); + if (chan->fe->ops.i2c_gate_ctrl) + chan->fe->ops.i2c_gate_ctrl(chan->fe, 1); + + if (i2c_read_regs(i2c, adr, subaddr, tda_id, sizeof(tda_id)) < 0) + dev_dbg(pdev, "tda18212 ping 1 fail\n"); + if (i2c_read_regs(i2c, adr, subaddr, tda_id, sizeof(tda_id)) < 0) + dev_warn(pdev, "tda18212 ping failed, expect problems\n"); + + if (chan->fe->ops.i2c_gate_ctrl) + chan->fe->ops.i2c_gate_ctrl(chan->fe, 0); + + return 0; +} + +static int tuner_attach_tda18212(struct ngene_channel *chan, u32 dmdtype) +{ + struct device *pdev = &chan->dev->pci_dev->dev; + struct i2c_adapter *i2c = i2c_adapter_from_chan(chan); + struct i2c_client *client; + struct tda18212_config config = { + .fe = chan->fe, + .if_dvbt_6 = 3550, + .if_dvbt_7 = 3700, + .if_dvbt_8 = 4150, + .if_dvbt2_6 = 3250, + .if_dvbt2_7 = 4000, + .if_dvbt2_8 = 4000, + .if_dvbc = 5000, + }; + u8 addr = (chan->number & 1) ? 0x63 : 0x60; + + /* + * due to a hardware quirk with the I2C gate on the stv0367+tda18212 + * combo, the tda18212 must be probed by reading it's id _twice_ when + * cold started, or it very likely will fail. + */ + if (dmdtype == DEMOD_TYPE_STV0367) + tuner_tda18212_ping(chan, i2c, addr); + + /* perform tuner probe/init/attach */ + client = dvb_module_probe("tda18212", NULL, i2c, addr, &config); + if (!client) + goto err; + + chan->i2c_client[0] = client; + chan->i2c_client_fe = 1; + + return 0; +err: + dev_err(pdev, "TDA18212 tuner not found. Device is not fully operational.\n"); + return -ENODEV; +} + +static int tuner_attach_probe(struct ngene_channel *chan) +{ + switch (chan->demod_type) { + case DEMOD_TYPE_STV090X: + return tuner_attach_stv6110(chan); + case DEMOD_TYPE_DRXK: + return tuner_attach_tda18271(chan); + case DEMOD_TYPE_STV0367: + case DEMOD_TYPE_SONY_CT2: + case DEMOD_TYPE_SONY_ISDBT: + case DEMOD_TYPE_SONY_C2T2: + case DEMOD_TYPE_SONY_C2T2I: + return tuner_attach_tda18212(chan, chan->demod_type); + case DEMOD_TYPE_STV0910: + return tuner_attach_stv6111(chan); + } + + return -EINVAL; +} + +static int demod_attach_stv0900(struct ngene_channel *chan) +{ + struct device *pdev = &chan->dev->pci_dev->dev; + struct i2c_adapter *i2c = i2c_adapter_from_chan(chan); + struct stv090x_config *feconf = (struct stv090x_config *) + chan->dev->card_info->fe_config[chan->number]; + + chan->fe = dvb_attach(stv090x_attach, feconf, i2c, + (chan->number & 1) == 0 ? STV090x_DEMODULATOR_0 + : STV090x_DEMODULATOR_1); + if (chan->fe == NULL) { + dev_err(pdev, "No STV0900 found!\n"); + return -ENODEV; + } + + /* store channel info */ + if (feconf->tuner_i2c_lock) + chan->fe->analog_demod_priv = chan; + + if (!dvb_attach(lnbh24_attach, chan->fe, i2c, 0, + 0, chan->dev->card_info->lnb[chan->number])) { + dev_err(pdev, "No LNBH24 found!\n"); + dvb_frontend_detach(chan->fe); + chan->fe = NULL; + return -ENODEV; + } + + return 0; +} + +static struct stv0910_cfg stv0910_p = { + .adr = 0x68, + .parallel = 1, + .rptlvl = 4, + .clk = 30000000, + .tsspeed = 0x28, +}; + +static struct lnbh25_config lnbh25_cfg = { + .i2c_address = 0x0c << 1, + .data2_config = LNBH25_TEN +}; + +static int demod_attach_stv0910(struct ngene_channel *chan, + struct i2c_adapter *i2c) +{ + struct device *pdev = &chan->dev->pci_dev->dev; + struct stv0910_cfg cfg = stv0910_p; + struct lnbh25_config lnbcfg = lnbh25_cfg; + + chan->fe = dvb_attach(stv0910_attach, i2c, &cfg, (chan->number & 1)); + if (!chan->fe) { + cfg.adr = 0x6c; + chan->fe = dvb_attach(stv0910_attach, i2c, + &cfg, (chan->number & 1)); + } + if (!chan->fe) { + dev_err(pdev, "stv0910_attach() failed!\n"); + return -ENODEV; + } + + /* + * attach lnbh25 - leftshift by one as the lnbh25 driver expects 8bit + * i2c addresses + */ + lnbcfg.i2c_address = (((chan->number & 1) ? 0x0d : 0x0c) << 1); + if (!dvb_attach(lnbh25_attach, chan->fe, &lnbcfg, i2c)) { + lnbcfg.i2c_address = (((chan->number & 1) ? 0x09 : 0x08) << 1); + if (!dvb_attach(lnbh25_attach, chan->fe, &lnbcfg, i2c)) { + dev_err(pdev, "lnbh25_attach() failed!\n"); + dvb_frontend_detach(chan->fe); + chan->fe = NULL; + return -ENODEV; + } + } + + return 0; +} + +static struct stv0367_config ddb_stv0367_config[] = { + { + .demod_address = 0x1f, + .xtal = 27000000, + .if_khz = 0, + .if_iq_mode = FE_TER_NORMAL_IF_TUNER, + .ts_mode = STV0367_SERIAL_PUNCT_CLOCK, + .clk_pol = STV0367_CLOCKPOLARITY_DEFAULT, + }, { + .demod_address = 0x1e, + .xtal = 27000000, + .if_khz = 0, + .if_iq_mode = FE_TER_NORMAL_IF_TUNER, + .ts_mode = STV0367_SERIAL_PUNCT_CLOCK, + .clk_pol = STV0367_CLOCKPOLARITY_DEFAULT, + }, +}; + +static int demod_attach_stv0367(struct ngene_channel *chan, + struct i2c_adapter *i2c) +{ + struct device *pdev = &chan->dev->pci_dev->dev; + + chan->fe = dvb_attach(stv0367ddb_attach, + &ddb_stv0367_config[(chan->number & 1)], i2c); + + if (!chan->fe) { + dev_err(pdev, "stv0367ddb_attach() failed!\n"); + return -ENODEV; + } + + chan->fe->sec_priv = chan; + chan->gate_ctrl = chan->fe->ops.i2c_gate_ctrl; + chan->fe->ops.i2c_gate_ctrl = drxk_gate_ctrl; + return 0; +} + +static int demod_attach_cxd28xx(struct ngene_channel *chan, + struct i2c_adapter *i2c, int osc24) +{ + struct device *pdev = &chan->dev->pci_dev->dev; + struct cxd2841er_config cfg; + + /* the cxd2841er driver expects 8bit/shifted I2C addresses */ + cfg.i2c_addr = ((chan->number & 1) ? 0x6d : 0x6c) << 1; + + cfg.xtal = osc24 ? SONY_XTAL_24000 : SONY_XTAL_20500; + cfg.flags = CXD2841ER_AUTO_IFHZ | CXD2841ER_EARLY_TUNE | + CXD2841ER_NO_WAIT_LOCK | CXD2841ER_NO_AGCNEG | + CXD2841ER_TSBITS | CXD2841ER_TS_SERIAL; + + /* attach frontend */ + chan->fe = dvb_attach(cxd2841er_attach_t_c, &cfg, i2c); + + if (!chan->fe) { + dev_err(pdev, "CXD28XX attach failed!\n"); + return -ENODEV; + } + + chan->fe->sec_priv = chan; + chan->gate_ctrl = chan->fe->ops.i2c_gate_ctrl; + chan->fe->ops.i2c_gate_ctrl = drxk_gate_ctrl; + return 0; +} + +static void cineS2_tuner_i2c_lock(struct dvb_frontend *fe, int lock) +{ + struct ngene_channel *chan = fe->analog_demod_priv; + + if (lock) + down(&chan->dev->pll_mutex); + else + up(&chan->dev->pll_mutex); +} + +static int port_has_stv0900(struct i2c_adapter *i2c, int port) +{ + u8 val; + if (i2c_read_reg16(i2c, 0x68+port/2, 0xf100, &val) < 0) + return 0; + return 1; +} + +static int port_has_drxk(struct i2c_adapter *i2c, int port) +{ + u8 val; + + if (i2c_read(i2c, 0x29+port, &val) < 0) + return 0; + return 1; +} + +static int port_has_stv0367(struct i2c_adapter *i2c) +{ + u8 val; + + if (i2c_read_reg16(i2c, 0x1e, 0xf000, &val) < 0) + return 0; + if (val != 0x60) + return 0; + if (i2c_read_reg16(i2c, 0x1f, 0xf000, &val) < 0) + return 0; + if (val != 0x60) + return 0; + return 1; +} + +int ngene_port_has_cxd2099(struct i2c_adapter *i2c, u8 *type) +{ + u8 val; + u8 probe[4] = { 0xe0, 0x00, 0x00, 0x00 }, data[4]; + struct i2c_msg msgs[2] = {{ .addr = 0x40, .flags = 0, + .buf = probe, .len = 4 }, + { .addr = 0x40, .flags = I2C_M_RD, + .buf = data, .len = 4 } }; + val = i2c_transfer(i2c, msgs, 2); + if (val != 2) + return 0; + + if (data[0] == 0x02 && data[1] == 0x2b && data[3] == 0x43) + *type = 2; + else + *type = 1; + return 1; +} + +static int demod_attach_drxk(struct ngene_channel *chan, + struct i2c_adapter *i2c) +{ + struct device *pdev = &chan->dev->pci_dev->dev; + struct drxk_config config; + + memset(&config, 0, sizeof(config)); + config.microcode_name = "drxk_a3.mc"; + config.qam_demod_parameter_count = 4; + config.adr = 0x29 + (chan->number ^ 2); + + chan->fe = dvb_attach(drxk_attach, &config, i2c); + if (!chan->fe) { + dev_err(pdev, "No DRXK found!\n"); + return -ENODEV; + } + chan->fe->sec_priv = chan; + chan->gate_ctrl = chan->fe->ops.i2c_gate_ctrl; + chan->fe->ops.i2c_gate_ctrl = drxk_gate_ctrl; + return 0; +} + +/****************************************************************************/ +/* XO2 related lists and functions ******************************************/ +/****************************************************************************/ + +static char *xo2names[] = { + "DUAL DVB-S2", + "DUAL DVB-C/T/T2", + "DUAL DVB-ISDBT", + "DUAL DVB-C/C2/T/T2", + "DUAL ATSC", + "DUAL DVB-C/C2/T/T2/I", +}; + +static int init_xo2(struct ngene_channel *chan, struct i2c_adapter *i2c) +{ + struct device *pdev = &chan->dev->pci_dev->dev; + u8 addr = 0x10; + u8 val, data[2]; + int res; + + res = i2c_read_regs(i2c, addr, 0x04, data, 2); + if (res < 0) + return res; + + if (data[0] != 0x01) { + dev_info(pdev, "Invalid XO2 on channel %d\n", chan->number); + return -1; + } + + i2c_read_reg(i2c, addr, 0x08, &val); + if (val != 0) { + i2c_write_reg(i2c, addr, 0x08, 0x00); + msleep(100); + } + /* Enable tuner power, disable pll, reset demods */ + i2c_write_reg(i2c, addr, 0x08, 0x04); + usleep_range(2000, 3000); + /* Release demod resets */ + i2c_write_reg(i2c, addr, 0x08, 0x07); + + /* + * speed: 0=55,1=75,2=90,3=104 MBit/s + * Note: The ngene hardware must be run at 75 MBit/s compared + * to more modern ddbridge hardware which runs at 90 MBit/s, + * else there will be issues with the data transport and non- + * working secondary/slave demods/tuners. + */ + i2c_write_reg(i2c, addr, 0x09, 1); + + i2c_write_reg(i2c, addr, 0x0a, 0x01); + i2c_write_reg(i2c, addr, 0x0b, 0x01); + + usleep_range(2000, 3000); + /* Start XO2 PLL */ + i2c_write_reg(i2c, addr, 0x08, 0x87); + + return 0; +} + +static int port_has_xo2(struct i2c_adapter *i2c, u8 *type, u8 *id) +{ + u8 probe[1] = { 0x00 }, data[4]; + u8 addr = 0x10; + + *type = NGENE_XO2_TYPE_NONE; + + if (i2c_io(i2c, addr, probe, 1, data, 4)) + return 0; + if (data[0] == 'D' && data[1] == 'F') { + *id = data[2]; + *type = NGENE_XO2_TYPE_DUOFLEX; + return 1; + } + if (data[0] == 'C' && data[1] == 'I') { + *id = data[2]; + *type = NGENE_XO2_TYPE_CI; + return 1; + } + return 0; +} + +/****************************************************************************/ +/* Probing and port/channel handling ****************************************/ +/****************************************************************************/ + +static int cineS2_probe(struct ngene_channel *chan) +{ + struct device *pdev = &chan->dev->pci_dev->dev; + struct i2c_adapter *i2c = i2c_adapter_from_chan(chan); + struct stv090x_config *fe_conf; + u8 buf[3]; + u8 xo2_type, xo2_id, xo2_demodtype; + u8 sony_osc24 = 0; + struct i2c_msg i2c_msg = { .flags = 0, .buf = buf }; + int rc; + + if (port_has_xo2(i2c, &xo2_type, &xo2_id)) { + xo2_id >>= 2; + dev_dbg(pdev, "XO2 on channel %d (type %d, id %d)\n", + chan->number, xo2_type, xo2_id); + + switch (xo2_type) { + case NGENE_XO2_TYPE_DUOFLEX: + if (chan->number & 1) + dev_dbg(pdev, + "skipping XO2 init on odd channel %d", + chan->number); + else + init_xo2(chan, i2c); + + xo2_demodtype = DEMOD_TYPE_XO2 + xo2_id; + + switch (xo2_demodtype) { + case DEMOD_TYPE_SONY_CT2: + case DEMOD_TYPE_SONY_ISDBT: + case DEMOD_TYPE_SONY_C2T2: + case DEMOD_TYPE_SONY_C2T2I: + dev_info(pdev, "%s (XO2) on channel %d\n", + xo2names[xo2_id], chan->number); + chan->demod_type = xo2_demodtype; + if (xo2_demodtype == DEMOD_TYPE_SONY_C2T2I) + sony_osc24 = 1; + + demod_attach_cxd28xx(chan, i2c, sony_osc24); + break; + case DEMOD_TYPE_STV0910: + dev_info(pdev, "%s (XO2) on channel %d\n", + xo2names[xo2_id], chan->number); + chan->demod_type = xo2_demodtype; + demod_attach_stv0910(chan, i2c); + break; + default: + dev_warn(pdev, + "Unsupported XO2 module on channel %d\n", + chan->number); + return -ENODEV; + } + break; + case NGENE_XO2_TYPE_CI: + dev_info(pdev, "DuoFlex CI modules not supported\n"); + return -ENODEV; + default: + dev_info(pdev, "Unsupported XO2 module type\n"); + return -ENODEV; + } + } else if (port_has_stv0900(i2c, chan->number)) { + chan->demod_type = DEMOD_TYPE_STV090X; + fe_conf = chan->dev->card_info->fe_config[chan->number]; + /* demod found, attach it */ + rc = demod_attach_stv0900(chan); + if (rc < 0 || chan->number < 2) + return rc; + + /* demod #2: reprogram outputs DPN1 & DPN2 */ + i2c_msg.addr = fe_conf->address; + i2c_msg.len = 3; + buf[0] = 0xf1; + switch (chan->number) { + case 2: + buf[1] = 0x5c; + buf[2] = 0xc2; + break; + case 3: + buf[1] = 0x61; + buf[2] = 0xcc; + break; + default: + return -ENODEV; + } + rc = i2c_transfer(i2c, &i2c_msg, 1); + if (rc != 1) { + dev_err(pdev, "Could not setup DPNx\n"); + return -EIO; + } + } else if (port_has_drxk(i2c, chan->number^2)) { + chan->demod_type = DEMOD_TYPE_DRXK; + demod_attach_drxk(chan, i2c); + } else if (port_has_stv0367(i2c)) { + chan->demod_type = DEMOD_TYPE_STV0367; + dev_info(pdev, "STV0367 on channel %d\n", chan->number); + demod_attach_stv0367(chan, i2c); + } else { + dev_info(pdev, "No demod found on chan %d\n", chan->number); + return -ENODEV; + } + return 0; +} + + +static struct lgdt330x_config aver_m780 = { + .demod_chip = LGDT3303, + .serial_mpeg = 0x00, /* PARALLEL */ + .clock_polarity_flip = 1, +}; + +static struct mt2131_config m780_tunerconfig = { + 0xc0 >> 1 +}; + +/* A single func to attach the demo and tuner, rather than + * use two sep funcs like the current design mandates. + */ +static int demod_attach_lg330x(struct ngene_channel *chan) +{ + struct device *pdev = &chan->dev->pci_dev->dev; + + chan->fe = dvb_attach(lgdt330x_attach, &aver_m780, + 0xb2 >> 1, &chan->i2c_adapter); + if (chan->fe == NULL) { + dev_err(pdev, "No LGDT330x found!\n"); + return -ENODEV; + } + + dvb_attach(mt2131_attach, chan->fe, &chan->i2c_adapter, + &m780_tunerconfig, 0); + + return (chan->fe) ? 0 : -ENODEV; +} + +static int demod_attach_drxd(struct ngene_channel *chan) +{ + struct device *pdev = &chan->dev->pci_dev->dev; + struct drxd_config *feconf; + + feconf = chan->dev->card_info->fe_config[chan->number]; + + chan->fe = dvb_attach(drxd_attach, feconf, chan, + &chan->i2c_adapter, &chan->dev->pci_dev->dev); + if (!chan->fe) { + dev_err(pdev, "No DRXD found!\n"); + return -ENODEV; + } + return 0; +} + +static int tuner_attach_dtt7520x(struct ngene_channel *chan) +{ + struct device *pdev = &chan->dev->pci_dev->dev; + struct drxd_config *feconf; + + feconf = chan->dev->card_info->fe_config[chan->number]; + + if (!dvb_attach(dvb_pll_attach, chan->fe, feconf->pll_address, + &chan->i2c_adapter, + feconf->pll_type)) { + dev_err(pdev, "No pll(%d) found!\n", feconf->pll_type); + return -ENODEV; + } + return 0; +} + +/****************************************************************************/ +/* EEPROM TAGS **************************************************************/ +/****************************************************************************/ + +#define MICNG_EE_START 0x0100 +#define MICNG_EE_END 0x0FF0 + +#define MICNG_EETAG_END0 0x0000 +#define MICNG_EETAG_END1 0xFFFF + +/* 0x0001 - 0x000F reserved for housekeeping */ +/* 0xFFFF - 0xFFFE reserved for housekeeping */ + +/* Micronas assigned tags + EEProm tags for hardware support */ + +#define MICNG_EETAG_DRXD1_OSCDEVIATION 0x1000 /* 2 Bytes data */ +#define MICNG_EETAG_DRXD2_OSCDEVIATION 0x1001 /* 2 Bytes data */ + +#define MICNG_EETAG_MT2060_1_1STIF 0x1100 /* 2 Bytes data */ +#define MICNG_EETAG_MT2060_2_1STIF 0x1101 /* 2 Bytes data */ + +/* Tag range for OEMs */ + +#define MICNG_EETAG_OEM_FIRST 0xC000 +#define MICNG_EETAG_OEM_LAST 0xFFEF + +static int i2c_write_eeprom(struct i2c_adapter *adapter, + u8 adr, u16 reg, u8 data) +{ + struct device *pdev = adapter->dev.parent; + u8 m[3] = {(reg >> 8), (reg & 0xff), data}; + struct i2c_msg msg = {.addr = adr, .flags = 0, .buf = m, + .len = sizeof(m)}; + + if (i2c_transfer(adapter, &msg, 1) != 1) { + dev_err(pdev, "Error writing EEPROM!\n"); + return -EIO; + } + return 0; +} + +static int i2c_read_eeprom(struct i2c_adapter *adapter, + u8 adr, u16 reg, u8 *data, int len) +{ + struct device *pdev = adapter->dev.parent; + u8 msg[2] = {(reg >> 8), (reg & 0xff)}; + struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0, + .buf = msg, .len = 2 }, + {.addr = adr, .flags = I2C_M_RD, + .buf = data, .len = len} }; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + dev_err(pdev, "Error reading EEPROM\n"); + return -EIO; + } + return 0; +} + +static int ReadEEProm(struct i2c_adapter *adapter, + u16 Tag, u32 MaxLen, u8 *data, u32 *pLength) +{ + struct device *pdev = adapter->dev.parent; + int status = 0; + u16 Addr = MICNG_EE_START, Length, tag = 0; + u8 EETag[3]; + + while (Addr + sizeof(u16) + 1 < MICNG_EE_END) { + if (i2c_read_eeprom(adapter, 0x50, Addr, EETag, sizeof(EETag))) + return -1; + tag = (EETag[0] << 8) | EETag[1]; + if (tag == MICNG_EETAG_END0 || tag == MICNG_EETAG_END1) + return -1; + if (tag == Tag) + break; + Addr += sizeof(u16) + 1 + EETag[2]; + } + if (Addr + sizeof(u16) + 1 + EETag[2] > MICNG_EE_END) { + dev_err(pdev, "Reached EOEE @ Tag = %04x Length = %3d\n", + tag, EETag[2]); + return -1; + } + Length = EETag[2]; + if (Length > MaxLen) + Length = (u16) MaxLen; + if (Length > 0) { + Addr += sizeof(u16) + 1; + status = i2c_read_eeprom(adapter, 0x50, Addr, data, Length); + if (!status) { + *pLength = EETag[2]; +#if 0 + if (Length < EETag[2]) + status = STATUS_BUFFER_OVERFLOW; +#endif + } + } + return status; +} + +static int WriteEEProm(struct i2c_adapter *adapter, + u16 Tag, u32 Length, u8 *data) +{ + struct device *pdev = adapter->dev.parent; + int status = 0; + u16 Addr = MICNG_EE_START; + u8 EETag[3]; + u16 tag = 0; + int retry, i; + + while (Addr + sizeof(u16) + 1 < MICNG_EE_END) { + if (i2c_read_eeprom(adapter, 0x50, Addr, EETag, sizeof(EETag))) + return -1; + tag = (EETag[0] << 8) | EETag[1]; + if (tag == MICNG_EETAG_END0 || tag == MICNG_EETAG_END1) + return -1; + if (tag == Tag) + break; + Addr += sizeof(u16) + 1 + EETag[2]; + } + if (Addr + sizeof(u16) + 1 + EETag[2] > MICNG_EE_END) { + dev_err(pdev, "Reached EOEE @ Tag = %04x Length = %3d\n", + tag, EETag[2]); + return -1; + } + + if (Length > EETag[2]) + return -EINVAL; + /* Note: We write the data one byte at a time to avoid + issues with page sizes. (which are different for + each manufacture and eeprom size) + */ + Addr += sizeof(u16) + 1; + for (i = 0; i < Length; i++, Addr++) { + status = i2c_write_eeprom(adapter, 0x50, Addr, data[i]); + + if (status) + break; + + /* Poll for finishing write cycle */ + retry = 10; + while (retry) { + u8 Tmp; + + msleep(50); + status = i2c_read_eeprom(adapter, 0x50, Addr, &Tmp, 1); + if (status) + break; + if (Tmp != data[i]) + dev_err(pdev, "eeprom write error\n"); + retry -= 1; + } + if (status) { + dev_err(pdev, "Timeout polling eeprom\n"); + break; + } + } + return status; +} + +static int eeprom_read_ushort(struct i2c_adapter *adapter, u16 tag, u16 *data) +{ + int stat; + u8 buf[2]; + u32 len = 0; + + stat = ReadEEProm(adapter, tag, 2, buf, &len); + if (stat) + return stat; + if (len != 2) + return -EINVAL; + + *data = (buf[0] << 8) | buf[1]; + return 0; +} + +static int eeprom_write_ushort(struct i2c_adapter *adapter, u16 tag, u16 data) +{ + u8 buf[2]; + + buf[0] = data >> 8; + buf[1] = data & 0xff; + return WriteEEProm(adapter, tag, 2, buf); +} + +static s16 osc_deviation(void *priv, s16 deviation, int flag) +{ + struct ngene_channel *chan = priv; + struct device *pdev = &chan->dev->pci_dev->dev; + struct i2c_adapter *adap = &chan->i2c_adapter; + u16 data = 0; + + if (flag) { + data = (u16) deviation; + dev_info(pdev, "write deviation %d\n", + deviation); + eeprom_write_ushort(adap, 0x1000 + chan->number, data); + } else { + if (eeprom_read_ushort(adap, 0x1000 + chan->number, &data)) + data = 0; + dev_info(pdev, "read deviation %d\n", + (s16)data); + } + + return (s16) data; +} + +/****************************************************************************/ +/* Switch control (I2C gates, etc.) *****************************************/ +/****************************************************************************/ + + +static struct stv090x_config fe_cineS2 = { + .device = STV0900, + .demod_mode = STV090x_DUAL, + .clk_mode = STV090x_CLK_EXT, + + .xtal = 27000000, + .address = 0x68, + + .ts1_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + .ts2_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + + .repeater_level = STV090x_RPTLEVEL_16, + + .adc1_range = STV090x_ADC_1Vpp, + .adc2_range = STV090x_ADC_1Vpp, + + .diseqc_envelope_mode = true, + + .tuner_i2c_lock = cineS2_tuner_i2c_lock, +}; + +static struct stv090x_config fe_cineS2_2 = { + .device = STV0900, + .demod_mode = STV090x_DUAL, + .clk_mode = STV090x_CLK_EXT, + + .xtal = 27000000, + .address = 0x69, + + .ts1_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + .ts2_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + + .repeater_level = STV090x_RPTLEVEL_16, + + .adc1_range = STV090x_ADC_1Vpp, + .adc2_range = STV090x_ADC_1Vpp, + + .diseqc_envelope_mode = true, + + .tuner_i2c_lock = cineS2_tuner_i2c_lock, +}; + +static struct stv6110x_config tuner_cineS2_0 = { + .addr = 0x60, + .refclk = 27000000, + .clk_div = 1, +}; + +static struct stv6110x_config tuner_cineS2_1 = { + .addr = 0x63, + .refclk = 27000000, + .clk_div = 1, +}; + +static const struct ngene_info ngene_info_cineS2 = { + .type = NGENE_SIDEWINDER, + .name = "Linux4Media cineS2 DVB-S2 Twin Tuner", + .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN}, + .demod_attach = {demod_attach_stv0900, demod_attach_stv0900}, + .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110}, + .fe_config = {&fe_cineS2, &fe_cineS2}, + .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1}, + .lnb = {0x0b, 0x08}, + .tsf = {3, 3}, + .fw_version = 18, + .msi_supported = true, +}; + +static const struct ngene_info ngene_info_satixS2 = { + .type = NGENE_SIDEWINDER, + .name = "Mystique SaTiX-S2 Dual", + .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN}, + .demod_attach = {demod_attach_stv0900, demod_attach_stv0900}, + .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110}, + .fe_config = {&fe_cineS2, &fe_cineS2}, + .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1}, + .lnb = {0x0b, 0x08}, + .tsf = {3, 3}, + .fw_version = 18, + .msi_supported = true, +}; + +static const struct ngene_info ngene_info_satixS2v2 = { + .type = NGENE_SIDEWINDER, + .name = "Mystique SaTiX-S2 Dual (v2)", + .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, + NGENE_IO_TSOUT}, + .demod_attach = {demod_attach_stv0900, demod_attach_stv0900, cineS2_probe, cineS2_probe}, + .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110, tuner_attach_probe, tuner_attach_probe}, + .fe_config = {&fe_cineS2, &fe_cineS2, &fe_cineS2_2, &fe_cineS2_2}, + .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1, &tuner_cineS2_0, &tuner_cineS2_1}, + .lnb = {0x0a, 0x08, 0x0b, 0x09}, + .tsf = {3, 3}, + .fw_version = 18, + .msi_supported = true, +}; + +static const struct ngene_info ngene_info_cineS2v5 = { + .type = NGENE_SIDEWINDER, + .name = "Linux4Media cineS2 DVB-S2 Twin Tuner (v5)", + .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, + NGENE_IO_TSOUT}, + .demod_attach = {demod_attach_stv0900, demod_attach_stv0900, cineS2_probe, cineS2_probe}, + .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110, tuner_attach_probe, tuner_attach_probe}, + .fe_config = {&fe_cineS2, &fe_cineS2, &fe_cineS2_2, &fe_cineS2_2}, + .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1, &tuner_cineS2_0, &tuner_cineS2_1}, + .lnb = {0x0a, 0x08, 0x0b, 0x09}, + .tsf = {3, 3}, + .fw_version = 18, + .msi_supported = true, +}; + + +static const struct ngene_info ngene_info_duoFlex = { + .type = NGENE_SIDEWINDER, + .name = "Digital Devices DuoFlex PCIe or miniPCIe", + .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, + NGENE_IO_TSOUT}, + .demod_attach = {cineS2_probe, cineS2_probe, cineS2_probe, cineS2_probe}, + .tuner_attach = {tuner_attach_probe, tuner_attach_probe, tuner_attach_probe, tuner_attach_probe}, + .fe_config = {&fe_cineS2, &fe_cineS2, &fe_cineS2_2, &fe_cineS2_2}, + .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1, &tuner_cineS2_0, &tuner_cineS2_1}, + .lnb = {0x0a, 0x08, 0x0b, 0x09}, + .tsf = {3, 3}, + .fw_version = 18, + .msi_supported = true, +}; + +static const struct ngene_info ngene_info_m780 = { + .type = NGENE_APP, + .name = "Aver M780 ATSC/QAM-B", + + /* Channel 0 is analog, which is currently unsupported */ + .io_type = { NGENE_IO_NONE, NGENE_IO_TSIN }, + .demod_attach = { NULL, demod_attach_lg330x }, + + /* Ensure these are NULL else the frame will call them (as funcs) */ + .tuner_attach = { NULL, NULL, NULL, NULL }, + .fe_config = { NULL, &aver_m780 }, + .avf = { 0 }, + + /* A custom electrical interface config for the demod to bridge */ + .tsf = { 4, 4 }, + .fw_version = 15, +}; + +static struct drxd_config fe_terratec_dvbt_0 = { + .index = 0, + .demod_address = 0x70, + .demod_revision = 0xa2, + .demoda_address = 0x00, + .pll_address = 0x60, + .pll_type = DVB_PLL_THOMSON_DTT7520X, + .clock = 20000, + .osc_deviation = osc_deviation, +}; + +static struct drxd_config fe_terratec_dvbt_1 = { + .index = 1, + .demod_address = 0x71, + .demod_revision = 0xa2, + .demoda_address = 0x00, + .pll_address = 0x60, + .pll_type = DVB_PLL_THOMSON_DTT7520X, + .clock = 20000, + .osc_deviation = osc_deviation, +}; + +static const struct ngene_info ngene_info_terratec = { + .type = NGENE_TERRATEC, + .name = "Terratec Integra/Cinergy2400i Dual DVB-T", + .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN}, + .demod_attach = {demod_attach_drxd, demod_attach_drxd}, + .tuner_attach = {tuner_attach_dtt7520x, tuner_attach_dtt7520x}, + .fe_config = {&fe_terratec_dvbt_0, &fe_terratec_dvbt_1}, + .i2c_access = 1, +}; + +/****************************************************************************/ + + + +/****************************************************************************/ +/* PCI Subsystem ID *********************************************************/ +/****************************************************************************/ + +#define NGENE_ID(_subvend, _subdev, _driverdata) { \ + PCI_DEVICE_SUB(NGENE_VID, NGENE_PID, (_subvend), (_subdev)), \ + .driver_data = (unsigned long) &_driverdata } + +/****************************************************************************/ + +static const struct pci_device_id ngene_id_tbl[] = { + NGENE_ID(0x18c3, 0xab04, ngene_info_cineS2), + NGENE_ID(0x18c3, 0xab05, ngene_info_cineS2v5), + NGENE_ID(0x18c3, 0xabc3, ngene_info_cineS2), + NGENE_ID(0x18c3, 0xabc4, ngene_info_cineS2), + NGENE_ID(0x18c3, 0xdb01, ngene_info_satixS2), + NGENE_ID(0x18c3, 0xdb02, ngene_info_satixS2v2), + NGENE_ID(0x18c3, 0xdd00, ngene_info_cineS2v5), + NGENE_ID(0x18c3, 0xdd10, ngene_info_duoFlex), + NGENE_ID(0x18c3, 0xdd20, ngene_info_duoFlex), + NGENE_ID(0x1461, 0x062e, ngene_info_m780), + NGENE_ID(0x153b, 0x1167, ngene_info_terratec), + { } +}; +MODULE_DEVICE_TABLE(pci, ngene_id_tbl); + +/****************************************************************************/ +/* Init/Exit ****************************************************************/ +/****************************************************************************/ + +static pci_ers_result_t ngene_error_detected(struct pci_dev *dev, + pci_channel_state_t state) +{ + dev_err(&dev->dev, "PCI error\n"); + if (state == pci_channel_io_perm_failure) + return PCI_ERS_RESULT_DISCONNECT; + if (state == pci_channel_io_frozen) + return PCI_ERS_RESULT_NEED_RESET; + return PCI_ERS_RESULT_CAN_RECOVER; +} + +static pci_ers_result_t ngene_slot_reset(struct pci_dev *dev) +{ + dev_info(&dev->dev, "slot reset\n"); + return 0; +} + +static void ngene_resume(struct pci_dev *dev) +{ + dev_info(&dev->dev, "resume\n"); +} + +static const struct pci_error_handlers ngene_errors = { + .error_detected = ngene_error_detected, + .slot_reset = ngene_slot_reset, + .resume = ngene_resume, +}; + +static struct pci_driver ngene_pci_driver = { + .name = "ngene", + .id_table = ngene_id_tbl, + .probe = ngene_probe, + .remove = ngene_remove, + .err_handler = &ngene_errors, + .shutdown = ngene_shutdown, +}; + +static __init int module_init_ngene(void) +{ + /* pr_*() since we don't have a device to use with dev_*() yet */ + pr_info("nGene PCIE bridge driver, Copyright (C) 2005-2007 Micronas\n"); + + return pci_register_driver(&ngene_pci_driver); +} + +static __exit void module_exit_ngene(void) +{ + pci_unregister_driver(&ngene_pci_driver); +} + +module_init(module_init_ngene); +module_exit(module_exit_ngene); + +MODULE_DESCRIPTION("nGene"); +MODULE_AUTHOR("Micronas, Ralph Metzler, Manfred Voelkel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/ngene/ngene-core.c b/drivers/media/pci/ngene/ngene-core.c new file mode 100644 index 000000000..db6796240 --- /dev/null +++ b/drivers/media/pci/ngene/ngene-core.c @@ -0,0 +1,1713 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ngene.c: nGene PCIe bridge driver + * + * Copyright (C) 2005-2007 Micronas + * + * Copyright (C) 2008-2009 Ralph Metzler + * Modifications for new nGene firmware, + * support for EEPROM-copying, + * support for new dual DVB-S2 card prototype + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ngene.h" + +static int one_adapter; +module_param(one_adapter, int, 0444); +MODULE_PARM_DESC(one_adapter, "Use only one adapter."); + +static int shutdown_workaround; +module_param(shutdown_workaround, int, 0644); +MODULE_PARM_DESC(shutdown_workaround, "Activate workaround for shutdown problem with some chipsets."); + +static int debug; +module_param(debug, int, 0444); +MODULE_PARM_DESC(debug, "Print debugging information."); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +#define ngwriteb(dat, adr) writeb((dat), dev->iomem + (adr)) +#define ngwritel(dat, adr) writel((dat), dev->iomem + (adr)) +#define ngwriteb(dat, adr) writeb((dat), dev->iomem + (adr)) +#define ngreadl(adr) readl(dev->iomem + (adr)) +#define ngreadb(adr) readb(dev->iomem + (adr)) +#define ngcpyto(adr, src, count) memcpy_toio(dev->iomem + (adr), (src), (count)) +#define ngcpyfrom(dst, adr, count) memcpy_fromio((dst), dev->iomem + (adr), (count)) + +/****************************************************************************/ +/* nGene interrupt handler **************************************************/ +/****************************************************************************/ + +static void event_bh_work(struct work_struct *t) +{ + struct ngene *dev = from_work(dev, t, event_bh_work); + + while (dev->EventQueueReadIndex != dev->EventQueueWriteIndex) { + struct EVENT_BUFFER Event = + dev->EventQueue[dev->EventQueueReadIndex]; + dev->EventQueueReadIndex = + (dev->EventQueueReadIndex + 1) & (EVENT_QUEUE_SIZE - 1); + + if ((Event.UARTStatus & 0x01) && (dev->TxEventNotify)) + dev->TxEventNotify(dev, Event.TimeStamp); + if ((Event.UARTStatus & 0x02) && (dev->RxEventNotify)) + dev->RxEventNotify(dev, Event.TimeStamp, + Event.RXCharacter); + } +} + +static void demux_bh_work(struct work_struct *t) +{ + struct ngene_channel *chan = from_work(chan, t, demux_bh_work); + struct device *pdev = &chan->dev->pci_dev->dev; + struct SBufferHeader *Cur = chan->nextBuffer; + + spin_lock_irq(&chan->state_lock); + + while (Cur->ngeneBuffer.SR.Flags & 0x80) { + if (chan->mode & NGENE_IO_TSOUT) { + u32 Flags = chan->DataFormatFlags; + if (Cur->ngeneBuffer.SR.Flags & 0x20) + Flags |= BEF_OVERFLOW; + if (chan->pBufferExchange) { + if (!chan->pBufferExchange(chan, + Cur->Buffer1, + chan->Capture1Length, + Cur->ngeneBuffer.SR. + Clock, Flags)) { + /* + We didn't get data + Clear in service flag to make sure we + get called on next interrupt again. + leave fill/empty (0x80) flag alone + to avoid hardware running out of + buffers during startup, we hold only + in run state ( the source may be late + delivering data ) + */ + + if (chan->HWState == HWSTATE_RUN) { + Cur->ngeneBuffer.SR.Flags &= + ~0x40; + break; + /* Stop processing stream */ + } + } else { + /* We got a valid buffer, + so switch to run state */ + chan->HWState = HWSTATE_RUN; + } + } else { + dev_err(pdev, "OOPS\n"); + if (chan->HWState == HWSTATE_RUN) { + Cur->ngeneBuffer.SR.Flags &= ~0x40; + break; /* Stop processing stream */ + } + } + if (chan->AudioDTOUpdated) { + dev_info(pdev, "Update AudioDTO = %d\n", + chan->AudioDTOValue); + Cur->ngeneBuffer.SR.DTOUpdate = + chan->AudioDTOValue; + chan->AudioDTOUpdated = 0; + } + } else { + if (chan->HWState == HWSTATE_RUN) { + u32 Flags = chan->DataFormatFlags; + IBufferExchange *exch1 = chan->pBufferExchange; + IBufferExchange *exch2 = chan->pBufferExchange2; + if (Cur->ngeneBuffer.SR.Flags & 0x01) + Flags |= BEF_EVEN_FIELD; + if (Cur->ngeneBuffer.SR.Flags & 0x20) + Flags |= BEF_OVERFLOW; + spin_unlock_irq(&chan->state_lock); + if (exch1) + exch1(chan, Cur->Buffer1, + chan->Capture1Length, + Cur->ngeneBuffer.SR.Clock, + Flags); + if (exch2) + exch2(chan, Cur->Buffer2, + chan->Capture2Length, + Cur->ngeneBuffer.SR.Clock, + Flags); + spin_lock_irq(&chan->state_lock); + } else if (chan->HWState != HWSTATE_STOP) + chan->HWState = HWSTATE_RUN; + } + Cur->ngeneBuffer.SR.Flags = 0x00; + Cur = Cur->Next; + } + chan->nextBuffer = Cur; + + spin_unlock_irq(&chan->state_lock); +} + +static irqreturn_t irq_handler(int irq, void *dev_id) +{ + struct ngene *dev = (struct ngene *)dev_id; + struct device *pdev = &dev->pci_dev->dev; + u32 icounts = 0; + irqreturn_t rc = IRQ_NONE; + u32 i = MAX_STREAM; + u8 *tmpCmdDoneByte; + + if (dev->BootFirmware) { + icounts = ngreadl(NGENE_INT_COUNTS); + if (icounts != dev->icounts) { + ngwritel(0, FORCE_NMI); + dev->cmd_done = 1; + wake_up(&dev->cmd_wq); + dev->icounts = icounts; + rc = IRQ_HANDLED; + } + return rc; + } + + ngwritel(0, FORCE_NMI); + + spin_lock(&dev->cmd_lock); + tmpCmdDoneByte = dev->CmdDoneByte; + if (tmpCmdDoneByte && + (*tmpCmdDoneByte || + (dev->ngenetohost[0] == 1 && dev->ngenetohost[1] != 0))) { + dev->CmdDoneByte = NULL; + dev->cmd_done = 1; + wake_up(&dev->cmd_wq); + rc = IRQ_HANDLED; + } + spin_unlock(&dev->cmd_lock); + + if (dev->EventBuffer->EventStatus & 0x80) { + u8 nextWriteIndex = + (dev->EventQueueWriteIndex + 1) & + (EVENT_QUEUE_SIZE - 1); + if (nextWriteIndex != dev->EventQueueReadIndex) { + dev->EventQueue[dev->EventQueueWriteIndex] = + *(dev->EventBuffer); + dev->EventQueueWriteIndex = nextWriteIndex; + } else { + dev_err(pdev, "event overflow\n"); + dev->EventQueueOverflowCount += 1; + dev->EventQueueOverflowFlag = 1; + } + dev->EventBuffer->EventStatus &= ~0x80; + queue_work(system_bh_wq, &dev->event_bh_work); + rc = IRQ_HANDLED; + } + + while (i > 0) { + i--; + spin_lock(&dev->channel[i].state_lock); + /* if (dev->channel[i].State>=KSSTATE_RUN) { */ + if (dev->channel[i].nextBuffer) { + if ((dev->channel[i].nextBuffer-> + ngeneBuffer.SR.Flags & 0xC0) == 0x80) { + dev->channel[i].nextBuffer-> + ngeneBuffer.SR.Flags |= 0x40; + queue_work(system_bh_wq, + &dev->channel[i].demux_bh_work); + rc = IRQ_HANDLED; + } + } + spin_unlock(&dev->channel[i].state_lock); + } + + /* Request might have been processed by a previous call. */ + return IRQ_HANDLED; +} + +/****************************************************************************/ +/* nGene command interface **************************************************/ +/****************************************************************************/ + +static void dump_command_io(struct ngene *dev) +{ + struct device *pdev = &dev->pci_dev->dev; + u8 buf[8], *b; + + ngcpyfrom(buf, HOST_TO_NGENE, 8); + dev_err(pdev, "host_to_ngene (%04x): %*ph\n", HOST_TO_NGENE, 8, buf); + + ngcpyfrom(buf, NGENE_TO_HOST, 8); + dev_err(pdev, "ngene_to_host (%04x): %*ph\n", NGENE_TO_HOST, 8, buf); + + b = dev->hosttongene; + dev_err(pdev, "dev->hosttongene (%p): %*ph\n", b, 8, b); + + b = dev->ngenetohost; + dev_err(pdev, "dev->ngenetohost (%p): %*ph\n", b, 8, b); +} + +static int ngene_command_mutex(struct ngene *dev, struct ngene_command *com) +{ + struct device *pdev = &dev->pci_dev->dev; + int ret; + u8 *tmpCmdDoneByte; + + dev->cmd_done = 0; + + if (com->cmd.hdr.Opcode == CMD_FWLOAD_PREPARE) { + dev->BootFirmware = 1; + dev->icounts = ngreadl(NGENE_INT_COUNTS); + ngwritel(0, NGENE_COMMAND); + ngwritel(0, NGENE_COMMAND_HI); + ngwritel(0, NGENE_STATUS); + ngwritel(0, NGENE_STATUS_HI); + ngwritel(0, NGENE_EVENT); + ngwritel(0, NGENE_EVENT_HI); + } else if (com->cmd.hdr.Opcode == CMD_FWLOAD_FINISH) { + u64 fwio = dev->PAFWInterfaceBuffer; + + ngwritel(fwio & 0xffffffff, NGENE_COMMAND); + ngwritel(fwio >> 32, NGENE_COMMAND_HI); + ngwritel((fwio + 256) & 0xffffffff, NGENE_STATUS); + ngwritel((fwio + 256) >> 32, NGENE_STATUS_HI); + ngwritel((fwio + 512) & 0xffffffff, NGENE_EVENT); + ngwritel((fwio + 512) >> 32, NGENE_EVENT_HI); + } + + memcpy(dev->FWInterfaceBuffer, com->cmd.raw8, com->in_len + 2); + + if (dev->BootFirmware) + ngcpyto(HOST_TO_NGENE, com->cmd.raw8, com->in_len + 2); + + spin_lock_irq(&dev->cmd_lock); + tmpCmdDoneByte = dev->ngenetohost + com->out_len; + if (!com->out_len) + tmpCmdDoneByte++; + *tmpCmdDoneByte = 0; + dev->ngenetohost[0] = 0; + dev->ngenetohost[1] = 0; + dev->CmdDoneByte = tmpCmdDoneByte; + spin_unlock_irq(&dev->cmd_lock); + + /* Notify 8051. */ + ngwritel(1, FORCE_INT); + + ret = wait_event_timeout(dev->cmd_wq, dev->cmd_done == 1, 2 * HZ); + if (!ret) { + /*ngwritel(0, FORCE_NMI);*/ + + dev_err(pdev, "Command timeout cmd=%02x prev=%02x\n", + com->cmd.hdr.Opcode, dev->prev_cmd); + dump_command_io(dev); + return -1; + } + if (com->cmd.hdr.Opcode == CMD_FWLOAD_FINISH) + dev->BootFirmware = 0; + + dev->prev_cmd = com->cmd.hdr.Opcode; + + if (!com->out_len) + return 0; + + memcpy(com->cmd.raw8, dev->ngenetohost, com->out_len); + + return 0; +} + +int ngene_command(struct ngene *dev, struct ngene_command *com) +{ + int result; + + mutex_lock(&dev->cmd_mutex); + result = ngene_command_mutex(dev, com); + mutex_unlock(&dev->cmd_mutex); + return result; +} + + +static int ngene_command_load_firmware(struct ngene *dev, + u8 *ngene_fw, u32 size) +{ +#define FIRSTCHUNK (1024) + u32 cleft; + struct ngene_command com; + + com.cmd.hdr.Opcode = CMD_FWLOAD_PREPARE; + com.cmd.hdr.Length = 0; + com.in_len = 0; + com.out_len = 0; + + ngene_command(dev, &com); + + cleft = (size + 3) & ~3; + if (cleft > FIRSTCHUNK) { + ngcpyto(PROGRAM_SRAM + FIRSTCHUNK, ngene_fw + FIRSTCHUNK, + cleft - FIRSTCHUNK); + cleft = FIRSTCHUNK; + } + ngcpyto(DATA_FIFO_AREA, ngene_fw, cleft); + + memset(&com, 0, sizeof(struct ngene_command)); + com.cmd.hdr.Opcode = CMD_FWLOAD_FINISH; + com.cmd.hdr.Length = 4; + com.cmd.FWLoadFinish.Address = DATA_FIFO_AREA; + com.cmd.FWLoadFinish.Length = (unsigned short)cleft; + com.in_len = 4; + com.out_len = 0; + + return ngene_command(dev, &com); +} + + +static int ngene_command_config_buf(struct ngene *dev, u8 config) +{ + struct ngene_command com; + + com.cmd.hdr.Opcode = CMD_CONFIGURE_BUFFER; + com.cmd.hdr.Length = 1; + com.cmd.ConfigureBuffers.config = config; + com.in_len = 1; + com.out_len = 0; + + if (ngene_command(dev, &com) < 0) + return -EIO; + return 0; +} + +static int ngene_command_config_free_buf(struct ngene *dev, u8 *config) +{ + struct ngene_command com; + + com.cmd.hdr.Opcode = CMD_CONFIGURE_FREE_BUFFER; + com.cmd.hdr.Length = 6; + memcpy(&com.cmd.ConfigureFreeBuffers.config, config, 6); + com.in_len = 6; + com.out_len = 0; + + if (ngene_command(dev, &com) < 0) + return -EIO; + + return 0; +} + +int ngene_command_gpio_set(struct ngene *dev, u8 select, u8 level) +{ + struct ngene_command com; + + com.cmd.hdr.Opcode = CMD_SET_GPIO_PIN; + com.cmd.hdr.Length = 1; + com.cmd.SetGpioPin.select = select | (level << 7); + com.in_len = 1; + com.out_len = 0; + + return ngene_command(dev, &com); +} + + +/* + 02000640 is sample on rising edge. + 02000740 is sample on falling edge. + 02000040 is ignore "valid" signal + + 0: FD_CTL1 Bit 7,6 must be 0,1 + 7 disable(fw controlled) + 6 0-AUX,1-TS + 5 0-par,1-ser + 4 0-lsb/1-msb + 3,2 reserved + 1,0 0-no sync, 1-use ext. start, 2-use 0x47, 3-both + 1: FD_CTL2 has 3-valid must be hi, 2-use valid, 1-edge + 2: FD_STA is read-only. 0-sync + 3: FD_INSYNC is number of 47s to trigger "in sync". + 4: FD_OUTSYNC is number of 47s to trigger "out of sync". + 5: FD_MAXBYTE1 is low-order of bytes per packet. + 6: FD_MAXBYTE2 is high-order of bytes per packet. + 7: Top byte is unused. +*/ + +/****************************************************************************/ + +static u8 TSFeatureDecoderSetup[8 * 5] = { + 0x42, 0x00, 0x00, 0x02, 0x02, 0xbc, 0x00, 0x00, + 0x40, 0x06, 0x00, 0x02, 0x02, 0xbc, 0x00, 0x00, /* DRXH */ + 0x71, 0x07, 0x00, 0x02, 0x02, 0xbc, 0x00, 0x00, /* DRXHser */ + 0x72, 0x00, 0x00, 0x02, 0x02, 0xbc, 0x00, 0x00, /* S2ser */ + 0x40, 0x07, 0x00, 0x02, 0x02, 0xbc, 0x00, 0x00, /* LGDT3303 */ +}; + +/* Set NGENE I2S Config to 16 bit packed */ +static u8 I2SConfiguration[] = { + 0x00, 0x10, 0x00, 0x00, + 0x80, 0x10, 0x00, 0x00, +}; + +static u8 SPDIFConfiguration[10] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* Set NGENE I2S Config to transport stream compatible mode */ + +static u8 TS_I2SConfiguration[4] = { 0x3E, 0x18, 0x00, 0x00 }; + +static u8 TS_I2SOutConfiguration[4] = { 0x80, 0x04, 0x00, 0x00 }; + +static u8 ITUDecoderSetup[4][16] = { + {0x1c, 0x13, 0x01, 0x68, 0x3d, 0x90, 0x14, 0x20, /* SDTV */ + 0x00, 0x00, 0x01, 0xb0, 0x9c, 0x00, 0x00, 0x00}, + {0x9c, 0x03, 0x23, 0xC0, 0x60, 0x0E, 0x13, 0x00, + 0x00, 0x00, 0x00, 0x01, 0xB0, 0x00, 0x00, 0x00}, + {0x9f, 0x00, 0x23, 0xC0, 0x60, 0x0F, 0x13, 0x00, /* HDTV 1080i50 */ + 0x00, 0x00, 0x00, 0x01, 0xB0, 0x00, 0x00, 0x00}, + {0x9c, 0x01, 0x23, 0xC0, 0x60, 0x0E, 0x13, 0x00, /* HDTV 1080i60 */ + 0x00, 0x00, 0x00, 0x01, 0xB0, 0x00, 0x00, 0x00}, +}; + +/* + * 50 48 60 gleich + * 27p50 9f 00 22 80 42 69 18 ... + * 27p60 93 00 22 80 82 69 1c ... + */ + +/* Maxbyte to 1144 (for raw data) */ +static u8 ITUFeatureDecoderSetup[8] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x04, 0x00 +}; + +void FillTSBuffer(void *Buffer, int Length, u32 Flags) +{ + u32 *ptr = Buffer; + + memset(Buffer, TS_FILLER, Length); + while (Length > 0) { + if (Flags & DF_SWAP32) + *ptr = 0x471FFF10; + else + *ptr = 0x10FF1F47; + ptr += (188 / 4); + Length -= 188; + } +} + + +static void flush_buffers(struct ngene_channel *chan) +{ + u8 val; + + do { + msleep(1); + spin_lock_irq(&chan->state_lock); + val = chan->nextBuffer->ngeneBuffer.SR.Flags & 0x80; + spin_unlock_irq(&chan->state_lock); + } while (val); +} + +static void clear_buffers(struct ngene_channel *chan) +{ + struct SBufferHeader *Cur = chan->nextBuffer; + + do { + memset(&Cur->ngeneBuffer.SR, 0, sizeof(Cur->ngeneBuffer.SR)); + if (chan->mode & NGENE_IO_TSOUT) + FillTSBuffer(Cur->Buffer1, + chan->Capture1Length, + chan->DataFormatFlags); + Cur = Cur->Next; + } while (Cur != chan->nextBuffer); + + if (chan->mode & NGENE_IO_TSOUT) { + chan->nextBuffer->ngeneBuffer.SR.DTOUpdate = + chan->AudioDTOValue; + chan->AudioDTOUpdated = 0; + + Cur = chan->TSIdleBuffer.Head; + + do { + memset(&Cur->ngeneBuffer.SR, 0, + sizeof(Cur->ngeneBuffer.SR)); + FillTSBuffer(Cur->Buffer1, + chan->Capture1Length, + chan->DataFormatFlags); + Cur = Cur->Next; + } while (Cur != chan->TSIdleBuffer.Head); + } +} + +static int ngene_command_stream_control(struct ngene *dev, u8 stream, + u8 control, u8 mode, u8 flags) +{ + struct device *pdev = &dev->pci_dev->dev; + struct ngene_channel *chan = &dev->channel[stream]; + struct ngene_command com; + u16 BsUVI = ((stream & 1) ? 0x9400 : 0x9300); + u16 BsSDI = ((stream & 1) ? 0x9600 : 0x9500); + u16 BsSPI = ((stream & 1) ? 0x9800 : 0x9700); + u16 BsSDO = 0x9B00; + + memset(&com, 0, sizeof(com)); + com.cmd.hdr.Opcode = CMD_CONTROL; + com.cmd.hdr.Length = sizeof(struct FW_STREAM_CONTROL) - 2; + com.cmd.StreamControl.Stream = stream | (control ? 8 : 0); + if (chan->mode & NGENE_IO_TSOUT) + com.cmd.StreamControl.Stream |= 0x07; + com.cmd.StreamControl.Control = control | + (flags & SFLAG_ORDER_LUMA_CHROMA); + com.cmd.StreamControl.Mode = mode; + com.in_len = sizeof(struct FW_STREAM_CONTROL); + com.out_len = 0; + + dev_dbg(pdev, "Stream=%02x, Control=%02x, Mode=%02x\n", + com.cmd.StreamControl.Stream, com.cmd.StreamControl.Control, + com.cmd.StreamControl.Mode); + + chan->Mode = mode; + + if (!(control & 0x80)) { + spin_lock_irq(&chan->state_lock); + if (chan->State == KSSTATE_RUN) { + chan->State = KSSTATE_ACQUIRE; + chan->HWState = HWSTATE_STOP; + spin_unlock_irq(&chan->state_lock); + if (ngene_command(dev, &com) < 0) + return -1; + /* clear_buffers(chan); */ + flush_buffers(chan); + return 0; + } + spin_unlock_irq(&chan->state_lock); + return 0; + } + + if (mode & SMODE_AUDIO_CAPTURE) { + com.cmd.StreamControl.CaptureBlockCount = + chan->Capture1Length / AUDIO_BLOCK_SIZE; + com.cmd.StreamControl.Buffer_Address = chan->RingBuffer.PAHead; + } else if (mode & SMODE_TRANSPORT_STREAM) { + com.cmd.StreamControl.CaptureBlockCount = + chan->Capture1Length / TS_BLOCK_SIZE; + com.cmd.StreamControl.MaxLinesPerField = + chan->Capture1Length / TS_BLOCK_SIZE; + com.cmd.StreamControl.Buffer_Address = + chan->TSRingBuffer.PAHead; + if (chan->mode & NGENE_IO_TSOUT) { + com.cmd.StreamControl.BytesPerVBILine = + chan->Capture1Length / TS_BLOCK_SIZE; + com.cmd.StreamControl.Stream |= 0x07; + } + } else { + com.cmd.StreamControl.BytesPerVideoLine = chan->nBytesPerLine; + com.cmd.StreamControl.MaxLinesPerField = chan->nLines; + com.cmd.StreamControl.MinLinesPerField = 100; + com.cmd.StreamControl.Buffer_Address = chan->RingBuffer.PAHead; + + if (mode & SMODE_VBI_CAPTURE) { + com.cmd.StreamControl.MaxVBILinesPerField = + chan->nVBILines; + com.cmd.StreamControl.MinVBILinesPerField = 0; + com.cmd.StreamControl.BytesPerVBILine = + chan->nBytesPerVBILine; + } + if (flags & SFLAG_COLORBAR) + com.cmd.StreamControl.Stream |= 0x04; + } + + spin_lock_irq(&chan->state_lock); + if (mode & SMODE_AUDIO_CAPTURE) { + chan->nextBuffer = chan->RingBuffer.Head; + if (mode & SMODE_AUDIO_SPDIF) { + com.cmd.StreamControl.SetupDataLen = + sizeof(SPDIFConfiguration); + com.cmd.StreamControl.SetupDataAddr = BsSPI; + memcpy(com.cmd.StreamControl.SetupData, + SPDIFConfiguration, sizeof(SPDIFConfiguration)); + } else { + com.cmd.StreamControl.SetupDataLen = 4; + com.cmd.StreamControl.SetupDataAddr = BsSDI; + memcpy(com.cmd.StreamControl.SetupData, + I2SConfiguration + + 4 * dev->card_info->i2s[stream], 4); + } + } else if (mode & SMODE_TRANSPORT_STREAM) { + chan->nextBuffer = chan->TSRingBuffer.Head; + if (stream >= STREAM_AUDIOIN1) { + if (chan->mode & NGENE_IO_TSOUT) { + com.cmd.StreamControl.SetupDataLen = + sizeof(TS_I2SOutConfiguration); + com.cmd.StreamControl.SetupDataAddr = BsSDO; + memcpy(com.cmd.StreamControl.SetupData, + TS_I2SOutConfiguration, + sizeof(TS_I2SOutConfiguration)); + } else { + com.cmd.StreamControl.SetupDataLen = + sizeof(TS_I2SConfiguration); + com.cmd.StreamControl.SetupDataAddr = BsSDI; + memcpy(com.cmd.StreamControl.SetupData, + TS_I2SConfiguration, + sizeof(TS_I2SConfiguration)); + } + } else { + com.cmd.StreamControl.SetupDataLen = 8; + com.cmd.StreamControl.SetupDataAddr = BsUVI + 0x10; + memcpy(com.cmd.StreamControl.SetupData, + TSFeatureDecoderSetup + + 8 * dev->card_info->tsf[stream], 8); + } + } else { + chan->nextBuffer = chan->RingBuffer.Head; + com.cmd.StreamControl.SetupDataLen = + 16 + sizeof(ITUFeatureDecoderSetup); + com.cmd.StreamControl.SetupDataAddr = BsUVI; + memcpy(com.cmd.StreamControl.SetupData, + ITUDecoderSetup[chan->itumode], 16); + memcpy(com.cmd.StreamControl.SetupData + 16, + ITUFeatureDecoderSetup, sizeof(ITUFeatureDecoderSetup)); + } + clear_buffers(chan); + chan->State = KSSTATE_RUN; + if (mode & SMODE_TRANSPORT_STREAM) + chan->HWState = HWSTATE_RUN; + else + chan->HWState = HWSTATE_STARTUP; + spin_unlock_irq(&chan->state_lock); + + if (ngene_command(dev, &com) < 0) + return -1; + + return 0; +} + +void set_transfer(struct ngene_channel *chan, int state) +{ + struct device *pdev = &chan->dev->pci_dev->dev; + u8 control = 0, mode = 0, flags = 0; + struct ngene *dev = chan->dev; + int ret; + + /* + dev_info(pdev, "st %d\n", state); + msleep(100); + */ + + if (state) { + if (chan->running) { + dev_info(pdev, "already running\n"); + return; + } + } else { + if (!chan->running) { + dev_info(pdev, "already stopped\n"); + return; + } + } + + if (dev->card_info->switch_ctrl) + dev->card_info->switch_ctrl(chan, 1, state ^ 1); + + if (state) { + spin_lock_irq(&chan->state_lock); + + /* dev_info(pdev, "lock=%08x\n", + ngreadl(0x9310)); */ + dvb_ringbuffer_flush(&dev->tsout_rbuf); + control = 0x80; + if (chan->mode & (NGENE_IO_TSIN | NGENE_IO_TSOUT)) { + chan->Capture1Length = 512 * 188; + mode = SMODE_TRANSPORT_STREAM; + } + if (chan->mode & NGENE_IO_TSOUT) { + chan->pBufferExchange = tsout_exchange; + /* 0x66666666 = 50MHz *2^33 /250MHz */ + chan->AudioDTOValue = 0x80000000; + chan->AudioDTOUpdated = 1; + } + if (chan->mode & NGENE_IO_TSIN) + chan->pBufferExchange = tsin_exchange; + spin_unlock_irq(&chan->state_lock); + } + /* else dev_info(pdev, "lock=%08x\n", + ngreadl(0x9310)); */ + + mutex_lock(&dev->stream_mutex); + ret = ngene_command_stream_control(dev, chan->number, + control, mode, flags); + mutex_unlock(&dev->stream_mutex); + + if (!ret) + chan->running = state; + else + dev_err(pdev, "%s %d failed\n", __func__, state); + if (!state) { + spin_lock_irq(&chan->state_lock); + chan->pBufferExchange = NULL; + dvb_ringbuffer_flush(&dev->tsout_rbuf); + spin_unlock_irq(&chan->state_lock); + } +} + + +/****************************************************************************/ +/* nGene hardware init and release functions ********************************/ +/****************************************************************************/ + +static void free_ringbuffer(struct ngene *dev, struct SRingBufferDescriptor *rb) +{ + struct SBufferHeader *Cur = rb->Head; + u32 j; + + if (!Cur) + return; + + for (j = 0; j < rb->NumBuffers; j++, Cur = Cur->Next) { + if (Cur->Buffer1) + dma_free_coherent(&dev->pci_dev->dev, + rb->Buffer1Length, Cur->Buffer1, + Cur->scList1->Address); + + if (Cur->Buffer2) + dma_free_coherent(&dev->pci_dev->dev, + rb->Buffer2Length, Cur->Buffer2, + Cur->scList2->Address); + } + + if (rb->SCListMem) + dma_free_coherent(&dev->pci_dev->dev, rb->SCListMemSize, + rb->SCListMem, rb->PASCListMem); + + dma_free_coherent(&dev->pci_dev->dev, rb->MemSize, rb->Head, + rb->PAHead); +} + +static void free_idlebuffer(struct ngene *dev, + struct SRingBufferDescriptor *rb, + struct SRingBufferDescriptor *tb) +{ + int j; + struct SBufferHeader *Cur = tb->Head; + + if (!rb->Head) + return; + free_ringbuffer(dev, rb); + for (j = 0; j < tb->NumBuffers; j++, Cur = Cur->Next) { + Cur->Buffer2 = NULL; + Cur->scList2 = NULL; + Cur->ngeneBuffer.Address_of_first_entry_2 = 0; + Cur->ngeneBuffer.Number_of_entries_2 = 0; + } +} + +static void free_common_buffers(struct ngene *dev) +{ + u32 i; + struct ngene_channel *chan; + + for (i = STREAM_VIDEOIN1; i < MAX_STREAM; i++) { + chan = &dev->channel[i]; + free_idlebuffer(dev, &chan->TSIdleBuffer, &chan->TSRingBuffer); + free_ringbuffer(dev, &chan->RingBuffer); + free_ringbuffer(dev, &chan->TSRingBuffer); + } + + if (dev->OverflowBuffer) + dma_free_coherent(&dev->pci_dev->dev, OVERFLOW_BUFFER_SIZE, + dev->OverflowBuffer, dev->PAOverflowBuffer); + + if (dev->FWInterfaceBuffer) + dma_free_coherent(&dev->pci_dev->dev, 4096, + dev->FWInterfaceBuffer, + dev->PAFWInterfaceBuffer); +} + +/****************************************************************************/ +/* Ring buffer handling *****************************************************/ +/****************************************************************************/ + +static int create_ring_buffer(struct pci_dev *pci_dev, + struct SRingBufferDescriptor *descr, u32 NumBuffers) +{ + dma_addr_t tmp; + struct SBufferHeader *Head; + u32 i; + u32 MemSize = SIZEOF_SBufferHeader * NumBuffers; + u64 PARingBufferHead; + u64 PARingBufferCur; + u64 PARingBufferNext; + struct SBufferHeader *Cur, *Next; + + descr->Head = NULL; + descr->MemSize = 0; + descr->PAHead = 0; + descr->NumBuffers = 0; + + if (MemSize < 4096) + MemSize = 4096; + + Head = dma_alloc_coherent(&pci_dev->dev, MemSize, &tmp, GFP_KERNEL); + PARingBufferHead = tmp; + + if (!Head) + return -ENOMEM; + + PARingBufferCur = PARingBufferHead; + Cur = Head; + + for (i = 0; i < NumBuffers - 1; i++) { + Next = (struct SBufferHeader *) + (((u8 *) Cur) + SIZEOF_SBufferHeader); + PARingBufferNext = PARingBufferCur + SIZEOF_SBufferHeader; + Cur->Next = Next; + Cur->ngeneBuffer.Next = PARingBufferNext; + Cur = Next; + PARingBufferCur = PARingBufferNext; + } + /* Last Buffer points back to first one */ + Cur->Next = Head; + Cur->ngeneBuffer.Next = PARingBufferHead; + + descr->Head = Head; + descr->MemSize = MemSize; + descr->PAHead = PARingBufferHead; + descr->NumBuffers = NumBuffers; + + return 0; +} + +static int AllocateRingBuffers(struct pci_dev *pci_dev, + dma_addr_t of, + struct SRingBufferDescriptor *pRingBuffer, + u32 Buffer1Length, u32 Buffer2Length) +{ + dma_addr_t tmp; + u32 i, j; + u32 SCListMemSize = pRingBuffer->NumBuffers + * ((Buffer2Length != 0) ? (NUM_SCATTER_GATHER_ENTRIES * 2) : + NUM_SCATTER_GATHER_ENTRIES) + * sizeof(struct HW_SCATTER_GATHER_ELEMENT); + + u64 PASCListMem; + struct HW_SCATTER_GATHER_ELEMENT *SCListEntry; + u64 PASCListEntry; + struct SBufferHeader *Cur; + void *SCListMem; + + if (SCListMemSize < 4096) + SCListMemSize = 4096; + + SCListMem = dma_alloc_coherent(&pci_dev->dev, SCListMemSize, &tmp, + GFP_KERNEL); + + PASCListMem = tmp; + if (SCListMem == NULL) + return -ENOMEM; + + pRingBuffer->SCListMem = SCListMem; + pRingBuffer->PASCListMem = PASCListMem; + pRingBuffer->SCListMemSize = SCListMemSize; + pRingBuffer->Buffer1Length = Buffer1Length; + pRingBuffer->Buffer2Length = Buffer2Length; + + SCListEntry = SCListMem; + PASCListEntry = PASCListMem; + Cur = pRingBuffer->Head; + + for (i = 0; i < pRingBuffer->NumBuffers; i += 1, Cur = Cur->Next) { + u64 PABuffer; + + void *Buffer = dma_alloc_coherent(&pci_dev->dev, + Buffer1Length, &tmp, GFP_KERNEL); + PABuffer = tmp; + + if (Buffer == NULL) + return -ENOMEM; + + Cur->Buffer1 = Buffer; + + SCListEntry->Address = PABuffer; + SCListEntry->Length = Buffer1Length; + + Cur->scList1 = SCListEntry; + Cur->ngeneBuffer.Address_of_first_entry_1 = PASCListEntry; + Cur->ngeneBuffer.Number_of_entries_1 = + NUM_SCATTER_GATHER_ENTRIES; + + SCListEntry += 1; + PASCListEntry += sizeof(struct HW_SCATTER_GATHER_ELEMENT); + +#if NUM_SCATTER_GATHER_ENTRIES > 1 + for (j = 0; j < NUM_SCATTER_GATHER_ENTRIES - 1; j += 1) { + SCListEntry->Address = of; + SCListEntry->Length = OVERFLOW_BUFFER_SIZE; + SCListEntry += 1; + PASCListEntry += + sizeof(struct HW_SCATTER_GATHER_ELEMENT); + } +#endif + + if (!Buffer2Length) + continue; + + Buffer = dma_alloc_coherent(&pci_dev->dev, Buffer2Length, + &tmp, GFP_KERNEL); + PABuffer = tmp; + + if (Buffer == NULL) + return -ENOMEM; + + Cur->Buffer2 = Buffer; + + SCListEntry->Address = PABuffer; + SCListEntry->Length = Buffer2Length; + + Cur->scList2 = SCListEntry; + Cur->ngeneBuffer.Address_of_first_entry_2 = PASCListEntry; + Cur->ngeneBuffer.Number_of_entries_2 = + NUM_SCATTER_GATHER_ENTRIES; + + SCListEntry += 1; + PASCListEntry += sizeof(struct HW_SCATTER_GATHER_ELEMENT); + +#if NUM_SCATTER_GATHER_ENTRIES > 1 + for (j = 0; j < NUM_SCATTER_GATHER_ENTRIES - 1; j++) { + SCListEntry->Address = of; + SCListEntry->Length = OVERFLOW_BUFFER_SIZE; + SCListEntry += 1; + PASCListEntry += + sizeof(struct HW_SCATTER_GATHER_ELEMENT); + } +#endif + + } + + return 0; +} + +static int FillTSIdleBuffer(struct SRingBufferDescriptor *pIdleBuffer, + struct SRingBufferDescriptor *pRingBuffer) +{ + /* Copy pointer to scatter gather list in TSRingbuffer + structure for buffer 2 + Load number of buffer + */ + u32 n = pRingBuffer->NumBuffers; + + /* Point to first buffer entry */ + struct SBufferHeader *Cur = pRingBuffer->Head; + int i; + /* Loop through all buffer and set Buffer 2 pointers to TSIdlebuffer */ + for (i = 0; i < n; i++) { + Cur->Buffer2 = pIdleBuffer->Head->Buffer1; + Cur->scList2 = pIdleBuffer->Head->scList1; + Cur->ngeneBuffer.Address_of_first_entry_2 = + pIdleBuffer->Head->ngeneBuffer. + Address_of_first_entry_1; + Cur->ngeneBuffer.Number_of_entries_2 = + pIdleBuffer->Head->ngeneBuffer.Number_of_entries_1; + Cur = Cur->Next; + } + return 0; +} + +static u32 RingBufferSizes[MAX_STREAM] = { + RING_SIZE_VIDEO, + RING_SIZE_VIDEO, + RING_SIZE_AUDIO, + RING_SIZE_AUDIO, + RING_SIZE_AUDIO, +}; + +static u32 Buffer1Sizes[MAX_STREAM] = { + MAX_VIDEO_BUFFER_SIZE, + MAX_VIDEO_BUFFER_SIZE, + MAX_AUDIO_BUFFER_SIZE, + MAX_AUDIO_BUFFER_SIZE, + MAX_AUDIO_BUFFER_SIZE +}; + +static u32 Buffer2Sizes[MAX_STREAM] = { + MAX_VBI_BUFFER_SIZE, + MAX_VBI_BUFFER_SIZE, + 0, + 0, + 0 +}; + + +static int AllocCommonBuffers(struct ngene *dev) +{ + int status = 0, i; + + dev->FWInterfaceBuffer = dma_alloc_coherent(&dev->pci_dev->dev, 4096, + &dev->PAFWInterfaceBuffer, + GFP_KERNEL); + if (!dev->FWInterfaceBuffer) + return -ENOMEM; + dev->hosttongene = dev->FWInterfaceBuffer; + dev->ngenetohost = dev->FWInterfaceBuffer + 256; + dev->EventBuffer = dev->FWInterfaceBuffer + 512; + + dev->OverflowBuffer = dma_alloc_coherent(&dev->pci_dev->dev, + OVERFLOW_BUFFER_SIZE, + &dev->PAOverflowBuffer, GFP_KERNEL); + if (!dev->OverflowBuffer) + return -ENOMEM; + + for (i = STREAM_VIDEOIN1; i < MAX_STREAM; i++) { + int type = dev->card_info->io_type[i]; + + dev->channel[i].State = KSSTATE_STOP; + + if (type & (NGENE_IO_TV | NGENE_IO_HDTV | NGENE_IO_AIN)) { + status = create_ring_buffer(dev->pci_dev, + &dev->channel[i].RingBuffer, + RingBufferSizes[i]); + if (status < 0) + break; + + if (type & (NGENE_IO_TV | NGENE_IO_AIN)) { + status = AllocateRingBuffers(dev->pci_dev, + dev-> + PAOverflowBuffer, + &dev->channel[i]. + RingBuffer, + Buffer1Sizes[i], + Buffer2Sizes[i]); + if (status < 0) + break; + } else if (type & NGENE_IO_HDTV) { + status = AllocateRingBuffers(dev->pci_dev, + dev-> + PAOverflowBuffer, + &dev->channel[i]. + RingBuffer, + MAX_HDTV_BUFFER_SIZE, + 0); + if (status < 0) + break; + } + } + + if (type & (NGENE_IO_TSIN | NGENE_IO_TSOUT)) { + + status = create_ring_buffer(dev->pci_dev, + &dev->channel[i]. + TSRingBuffer, RING_SIZE_TS); + if (status < 0) + break; + + status = AllocateRingBuffers(dev->pci_dev, + dev->PAOverflowBuffer, + &dev->channel[i]. + TSRingBuffer, + MAX_TS_BUFFER_SIZE, 0); + if (status) + break; + } + + if (type & NGENE_IO_TSOUT) { + status = create_ring_buffer(dev->pci_dev, + &dev->channel[i]. + TSIdleBuffer, 1); + if (status < 0) + break; + status = AllocateRingBuffers(dev->pci_dev, + dev->PAOverflowBuffer, + &dev->channel[i]. + TSIdleBuffer, + MAX_TS_BUFFER_SIZE, 0); + if (status) + break; + FillTSIdleBuffer(&dev->channel[i].TSIdleBuffer, + &dev->channel[i].TSRingBuffer); + } + } + return status; +} + +static void ngene_release_buffers(struct ngene *dev) +{ + if (dev->iomem) + iounmap(dev->iomem); + free_common_buffers(dev); + vfree(dev->tsout_buf); + vfree(dev->tsin_buf); + vfree(dev->ain_buf); + vfree(dev->vin_buf); + vfree(dev); +} + +static int ngene_get_buffers(struct ngene *dev) +{ + if (AllocCommonBuffers(dev)) + return -ENOMEM; + if (dev->card_info->io_type[4] & NGENE_IO_TSOUT) { + dev->tsout_buf = vmalloc(TSOUT_BUF_SIZE); + if (!dev->tsout_buf) + return -ENOMEM; + dvb_ringbuffer_init(&dev->tsout_rbuf, + dev->tsout_buf, TSOUT_BUF_SIZE); + } + if (dev->card_info->io_type[2]&NGENE_IO_TSIN) { + dev->tsin_buf = vmalloc(TSIN_BUF_SIZE); + if (!dev->tsin_buf) + return -ENOMEM; + dvb_ringbuffer_init(&dev->tsin_rbuf, + dev->tsin_buf, TSIN_BUF_SIZE); + } + if (dev->card_info->io_type[2] & NGENE_IO_AIN) { + dev->ain_buf = vmalloc(AIN_BUF_SIZE); + if (!dev->ain_buf) + return -ENOMEM; + dvb_ringbuffer_init(&dev->ain_rbuf, dev->ain_buf, AIN_BUF_SIZE); + } + if (dev->card_info->io_type[0] & NGENE_IO_HDTV) { + dev->vin_buf = vmalloc(VIN_BUF_SIZE); + if (!dev->vin_buf) + return -ENOMEM; + dvb_ringbuffer_init(&dev->vin_rbuf, dev->vin_buf, VIN_BUF_SIZE); + } + dev->iomem = ioremap(pci_resource_start(dev->pci_dev, 0), + pci_resource_len(dev->pci_dev, 0)); + if (!dev->iomem) + return -ENOMEM; + + return 0; +} + +static void ngene_init(struct ngene *dev) +{ + struct device *pdev = &dev->pci_dev->dev; + int i; + + INIT_WORK(&dev->event_bh_work, event_bh_work); + + memset_io(dev->iomem + 0xc000, 0x00, 0x220); + memset_io(dev->iomem + 0xc400, 0x00, 0x100); + + for (i = 0; i < MAX_STREAM; i++) { + dev->channel[i].dev = dev; + dev->channel[i].number = i; + } + + dev->fw_interface_version = 0; + + ngwritel(0, NGENE_INT_ENABLE); + + dev->icounts = ngreadl(NGENE_INT_COUNTS); + + dev->device_version = ngreadl(DEV_VER) & 0x0f; + dev_info(pdev, "Device version %d\n", dev->device_version); +} + +static int ngene_load_firm(struct ngene *dev) +{ + struct device *pdev = &dev->pci_dev->dev; + u32 size; + const struct firmware *fw = NULL; + u8 *ngene_fw; + char *fw_name; + int err, version; + + version = dev->card_info->fw_version; + + switch (version) { + default: + case 15: + version = 15; + size = 23466; + fw_name = "ngene_15.fw"; + dev->cmd_timeout_workaround = true; + break; + case 16: + size = 23498; + fw_name = "ngene_16.fw"; + dev->cmd_timeout_workaround = true; + break; + case 17: + size = 24446; + fw_name = "ngene_17.fw"; + dev->cmd_timeout_workaround = true; + break; + case 18: + size = 0; + fw_name = "ngene_18.fw"; + break; + } + + if (request_firmware(&fw, fw_name, &dev->pci_dev->dev) < 0) { + dev_err(pdev, "Could not load firmware file %s.\n", fw_name); + dev_info(pdev, "Copy %s to your hotplug directory!\n", + fw_name); + return -1; + } + if (size == 0) + size = fw->size; + if (size != fw->size) { + dev_err(pdev, "Firmware %s has invalid size!", fw_name); + err = -1; + } else { + dev_info(pdev, "Loading firmware file %s.\n", fw_name); + ngene_fw = (u8 *) fw->data; + err = ngene_command_load_firmware(dev, ngene_fw, size); + } + + release_firmware(fw); + + return err; +} + +static void ngene_stop(struct ngene *dev) +{ + mutex_destroy(&dev->cmd_mutex); + i2c_del_adapter(&(dev->channel[0].i2c_adapter)); + i2c_del_adapter(&(dev->channel[1].i2c_adapter)); + ngwritel(0, NGENE_INT_ENABLE); + ngwritel(0, NGENE_COMMAND); + ngwritel(0, NGENE_COMMAND_HI); + ngwritel(0, NGENE_STATUS); + ngwritel(0, NGENE_STATUS_HI); + ngwritel(0, NGENE_EVENT); + ngwritel(0, NGENE_EVENT_HI); + free_irq(dev->pci_dev->irq, dev); +#ifdef CONFIG_PCI_MSI + if (dev->msi_enabled) + pci_disable_msi(dev->pci_dev); +#endif +} + +static int ngene_buffer_config(struct ngene *dev) +{ + int stat; + + if (dev->card_info->fw_version >= 17) { + u8 tsin12_config[6] = { 0x60, 0x60, 0x00, 0x00, 0x00, 0x00 }; + u8 tsin1234_config[6] = { 0x30, 0x30, 0x00, 0x30, 0x30, 0x00 }; + u8 tsio1235_config[6] = { 0x30, 0x30, 0x00, 0x28, 0x00, 0x38 }; + u8 *bconf = tsin12_config; + + if (dev->card_info->io_type[2]&NGENE_IO_TSIN && + dev->card_info->io_type[3]&NGENE_IO_TSIN) { + bconf = tsin1234_config; + if (dev->card_info->io_type[4]&NGENE_IO_TSOUT && + dev->ci.en) + bconf = tsio1235_config; + } + stat = ngene_command_config_free_buf(dev, bconf); + } else { + int bconf = BUFFER_CONFIG_4422; + + if (dev->card_info->io_type[3] == NGENE_IO_TSIN) + bconf = BUFFER_CONFIG_3333; + stat = ngene_command_config_buf(dev, bconf); + } + return stat; +} + + +static int ngene_start(struct ngene *dev) +{ + int stat; + int i; + + pci_set_master(dev->pci_dev); + ngene_init(dev); + + stat = request_irq(dev->pci_dev->irq, irq_handler, + IRQF_SHARED, "nGene", + (void *)dev); + if (stat < 0) + return stat; + + init_waitqueue_head(&dev->cmd_wq); + init_waitqueue_head(&dev->tx_wq); + init_waitqueue_head(&dev->rx_wq); + mutex_init(&dev->cmd_mutex); + mutex_init(&dev->stream_mutex); + sema_init(&dev->pll_mutex, 1); + mutex_init(&dev->i2c_switch_mutex); + spin_lock_init(&dev->cmd_lock); + for (i = 0; i < MAX_STREAM; i++) + spin_lock_init(&dev->channel[i].state_lock); + ngwritel(1, TIMESTAMPS); + + ngwritel(1, NGENE_INT_ENABLE); + + stat = ngene_load_firm(dev); + if (stat < 0) + goto fail; + +#ifdef CONFIG_PCI_MSI + /* enable MSI if kernel and card support it */ + if (pci_msi_enabled() && dev->card_info->msi_supported) { + struct device *pdev = &dev->pci_dev->dev; + unsigned long flags; + + ngwritel(0, NGENE_INT_ENABLE); + free_irq(dev->pci_dev->irq, dev); + stat = pci_enable_msi(dev->pci_dev); + if (stat) { + dev_info(pdev, "MSI not available\n"); + flags = IRQF_SHARED; + } else { + flags = 0; + dev->msi_enabled = true; + } + stat = request_irq(dev->pci_dev->irq, irq_handler, + flags, "nGene", dev); + if (stat < 0) + goto fail2; + ngwritel(1, NGENE_INT_ENABLE); + } +#endif + + stat = ngene_i2c_init(dev, 0); + if (stat < 0) + goto fail; + + stat = ngene_i2c_init(dev, 1); + if (stat < 0) + goto fail; + + return 0; + +fail: + ngwritel(0, NGENE_INT_ENABLE); + free_irq(dev->pci_dev->irq, dev); +#ifdef CONFIG_PCI_MSI +fail2: + if (dev->msi_enabled) + pci_disable_msi(dev->pci_dev); +#endif + return stat; +} + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +static void release_channel(struct ngene_channel *chan) +{ + struct dvb_demux *dvbdemux = &chan->demux; + struct ngene *dev = chan->dev; + + if (chan->running) + set_transfer(chan, 0); + + cancel_work_sync(&chan->demux_bh_work); + + if (chan->ci_dev) { + dvb_unregister_device(chan->ci_dev); + chan->ci_dev = NULL; + } + + if (chan->fe2) + dvb_unregister_frontend(chan->fe2); + + if (chan->fe) { + dvb_unregister_frontend(chan->fe); + + /* release I2C client (tuner) if needed */ + if (chan->i2c_client_fe) { + dvb_module_release(chan->i2c_client[0]); + chan->i2c_client[0] = NULL; + } + + dvb_frontend_detach(chan->fe); + chan->fe = NULL; + } + + if (chan->has_demux) { + dvb_net_release(&chan->dvbnet); + dvbdemux->dmx.close(&dvbdemux->dmx); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, + &chan->hw_frontend); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, + &chan->mem_frontend); + dvb_dmxdev_release(&chan->dmxdev); + dvb_dmx_release(&chan->demux); + chan->has_demux = false; + } + + if (chan->has_adapter) { + dvb_unregister_adapter(&dev->adapter[chan->number]); + chan->has_adapter = false; + } +} + +static int init_channel(struct ngene_channel *chan) +{ + int ret = 0, nr = chan->number; + struct dvb_adapter *adapter = NULL; + struct dvb_demux *dvbdemux = &chan->demux; + struct ngene *dev = chan->dev; + struct ngene_info *ni = dev->card_info; + int io = ni->io_type[nr]; + + INIT_WORK(&chan->demux_bh_work, demux_bh_work); + chan->users = 0; + chan->type = io; + chan->mode = chan->type; /* for now only one mode */ + chan->i2c_client_fe = 0; /* be sure this is set to zero */ + + if (io & NGENE_IO_TSIN) { + chan->fe = NULL; + if (ni->demod_attach[nr]) { + ret = ni->demod_attach[nr](chan); + if (ret < 0) + goto err; + } + if (chan->fe && ni->tuner_attach[nr]) { + ret = ni->tuner_attach[nr](chan); + if (ret < 0) + goto err; + } + } + + if (!dev->ci.en && (io & NGENE_IO_TSOUT)) + return 0; + + if (io & (NGENE_IO_TSIN | NGENE_IO_TSOUT)) { + if (nr >= STREAM_AUDIOIN1) + chan->DataFormatFlags = DF_SWAP32; + + if (nr == 0 || !one_adapter || dev->first_adapter == NULL) { + adapter = &dev->adapter[nr]; + ret = dvb_register_adapter(adapter, "nGene", + THIS_MODULE, + &chan->dev->pci_dev->dev, + adapter_nr); + if (ret < 0) + goto err; + if (dev->first_adapter == NULL) + dev->first_adapter = adapter; + chan->has_adapter = true; + } else + adapter = dev->first_adapter; + } + + if (dev->ci.en && (io & NGENE_IO_TSOUT)) { + ret = dvb_ca_en50221_init(adapter, dev->ci.en, 0, 1); + if (ret != 0) + goto err; + set_transfer(chan, 1); + chan->dev->channel[2].DataFormatFlags = DF_SWAP32; + set_transfer(&chan->dev->channel[2], 1); + dvb_register_device(adapter, &chan->ci_dev, + &ngene_dvbdev_ci, (void *) chan, + DVB_DEVICE_SEC, 0); + if (!chan->ci_dev) + goto err; + } + + if (chan->fe) { + if (dvb_register_frontend(adapter, chan->fe) < 0) + goto err; + chan->has_demux = true; + } + if (chan->fe2) { + if (dvb_register_frontend(adapter, chan->fe2) < 0) + goto err; + if (chan->fe) { + chan->fe2->tuner_priv = chan->fe->tuner_priv; + memcpy(&chan->fe2->ops.tuner_ops, + &chan->fe->ops.tuner_ops, + sizeof(struct dvb_tuner_ops)); + } + } + + if (chan->has_demux) { + ret = my_dvb_dmx_ts_card_init(dvbdemux, "SW demux", + ngene_start_feed, + ngene_stop_feed, chan); + ret = my_dvb_dmxdev_ts_card_init(&chan->dmxdev, &chan->demux, + &chan->hw_frontend, + &chan->mem_frontend, adapter); + ret = dvb_net_init(adapter, &chan->dvbnet, &chan->demux.dmx); + } + + return ret; + +err: + if (chan->fe) { + dvb_frontend_detach(chan->fe); + chan->fe = NULL; + } + release_channel(chan); + return 0; +} + +static int init_channels(struct ngene *dev) +{ + int i, j; + + for (i = 0; i < MAX_STREAM; i++) { + dev->channel[i].number = i; + if (init_channel(&dev->channel[i]) < 0) { + for (j = i - 1; j >= 0; j--) + release_channel(&dev->channel[j]); + return -1; + } + } + return 0; +} + +static const struct cxd2099_cfg cxd_cfgtmpl = { + .bitrate = 62000, + .polarity = 0, + .clock_mode = 0, +}; + +static void cxd_attach(struct ngene *dev) +{ + struct device *pdev = &dev->pci_dev->dev; + struct ngene_ci *ci = &dev->ci; + struct cxd2099_cfg cxd_cfg = cxd_cfgtmpl; + struct i2c_client *client; + int ret; + u8 type; + + /* check for CXD2099AR presence before attaching */ + ret = ngene_port_has_cxd2099(&dev->channel[0].i2c_adapter, &type); + if (!ret) { + dev_dbg(pdev, "No CXD2099AR found\n"); + return; + } + + if (type != 1) { + dev_warn(pdev, "CXD2099AR is uninitialized!\n"); + return; + } + + cxd_cfg.en = &ci->en; + client = dvb_module_probe("cxd2099", NULL, + &dev->channel[0].i2c_adapter, + 0x40, &cxd_cfg); + if (!client) + goto err; + + ci->dev = dev; + dev->channel[0].i2c_client[0] = client; + return; + +err: + dev_err(pdev, "CXD2099AR attach failed\n"); + return; +} + +static void cxd_detach(struct ngene *dev) +{ + struct ngene_ci *ci = &dev->ci; + + dvb_ca_en50221_release(ci->en); + + dvb_module_release(dev->channel[0].i2c_client[0]); + dev->channel[0].i2c_client[0] = NULL; + ci->en = NULL; +} + +/***********************************/ +/* workaround for shutdown failure */ +/***********************************/ + +static void ngene_unlink(struct ngene *dev) +{ + struct ngene_command com; + + com.cmd.hdr.Opcode = CMD_MEM_WRITE; + com.cmd.hdr.Length = 3; + com.cmd.MemoryWrite.address = 0x910c; + com.cmd.MemoryWrite.data = 0xff; + com.in_len = 3; + com.out_len = 1; + + mutex_lock(&dev->cmd_mutex); + ngwritel(0, NGENE_INT_ENABLE); + ngene_command_mutex(dev, &com); + mutex_unlock(&dev->cmd_mutex); +} + +void ngene_shutdown(struct pci_dev *pdev) +{ + struct ngene *dev = pci_get_drvdata(pdev); + + if (!dev || !shutdown_workaround) + return; + + dev_info(&pdev->dev, "shutdown workaround...\n"); + ngene_unlink(dev); + pci_disable_device(pdev); +} + +/****************************************************************************/ +/* device probe/remove calls ************************************************/ +/****************************************************************************/ + +void ngene_remove(struct pci_dev *pdev) +{ + struct ngene *dev = pci_get_drvdata(pdev); + int i; + + cancel_work_sync(&dev->event_bh_work); + for (i = MAX_STREAM - 1; i >= 0; i--) + release_channel(&dev->channel[i]); + if (dev->ci.en) + cxd_detach(dev); + ngene_stop(dev); + ngene_release_buffers(dev); + pci_disable_device(pdev); +} + +int ngene_probe(struct pci_dev *pci_dev, const struct pci_device_id *id) +{ + struct ngene *dev; + int stat = 0; + + if (pci_enable_device(pci_dev) < 0) + return -ENODEV; + + dev = vzalloc(sizeof(struct ngene)); + if (dev == NULL) { + stat = -ENOMEM; + goto fail0; + } + + dev->pci_dev = pci_dev; + dev->card_info = (struct ngene_info *)id->driver_data; + dev_info(&pci_dev->dev, "Found %s\n", dev->card_info->name); + + pci_set_drvdata(pci_dev, dev); + + /* Alloc buffers and start nGene */ + stat = ngene_get_buffers(dev); + if (stat < 0) + goto fail1; + stat = ngene_start(dev); + if (stat < 0) + goto fail1; + + cxd_attach(dev); + + stat = ngene_buffer_config(dev); + if (stat < 0) + goto fail1; + + + dev->i2c_current_bus = -1; + + /* Register DVB adapters and devices for both channels */ + stat = init_channels(dev); + if (stat < 0) + goto fail2; + + return 0; + +fail2: + ngene_stop(dev); +fail1: + ngene_release_buffers(dev); +fail0: + pci_disable_device(pci_dev); + return stat; +} diff --git a/drivers/media/pci/ngene/ngene-dvb.c b/drivers/media/pci/ngene/ngene-dvb.c new file mode 100644 index 000000000..fda24ba3d --- /dev/null +++ b/drivers/media/pci/ngene/ngene-dvb.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ngene-dvb.c: nGene PCIe bridge driver - DVB functions + * + * Copyright (C) 2005-2007 Micronas + * + * Copyright (C) 2008-2009 Ralph Metzler + * Modifications for new nGene firmware, + * support for EEPROM-copying, + * support for new dual DVB-S2 card prototype + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ngene.h" + +static int ci_tsfix = 1; +module_param(ci_tsfix, int, 0444); +MODULE_PARM_DESC(ci_tsfix, "Detect and fix TS buffer offset shifts in conjunction with CI expansions (default: 1/enabled)"); + +/****************************************************************************/ +/* COMMAND API interface ****************************************************/ +/****************************************************************************/ + +static ssize_t ts_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct dvb_device *dvbdev = file->private_data; + struct ngene_channel *chan = dvbdev->priv; + struct ngene *dev = chan->dev; + + if (wait_event_interruptible(dev->tsout_rbuf.queue, + dvb_ringbuffer_free + (&dev->tsout_rbuf) >= count) < 0) + return 0; + + dvb_ringbuffer_write_user(&dev->tsout_rbuf, buf, count); + + return count; +} + +static ssize_t ts_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct dvb_device *dvbdev = file->private_data; + struct ngene_channel *chan = dvbdev->priv; + struct ngene *dev = chan->dev; + int left, avail; + + left = count; + while (left) { + if (wait_event_interruptible( + dev->tsin_rbuf.queue, + dvb_ringbuffer_avail(&dev->tsin_rbuf) > 0) < 0) + return -EAGAIN; + avail = dvb_ringbuffer_avail(&dev->tsin_rbuf); + if (avail > left) + avail = left; + dvb_ringbuffer_read_user(&dev->tsin_rbuf, buf, avail); + left -= avail; + buf += avail; + } + return count; +} + +static __poll_t ts_poll(struct file *file, poll_table *wait) +{ + struct dvb_device *dvbdev = file->private_data; + struct ngene_channel *chan = dvbdev->priv; + struct ngene *dev = chan->dev; + struct dvb_ringbuffer *rbuf = &dev->tsin_rbuf; + struct dvb_ringbuffer *wbuf = &dev->tsout_rbuf; + __poll_t mask = 0; + + poll_wait(file, &rbuf->queue, wait); + poll_wait(file, &wbuf->queue, wait); + + if (!dvb_ringbuffer_empty(rbuf)) + mask |= EPOLLIN | EPOLLRDNORM; + if (dvb_ringbuffer_free(wbuf) >= 188) + mask |= EPOLLOUT | EPOLLWRNORM; + + return mask; +} + +static const struct file_operations ci_fops = { + .owner = THIS_MODULE, + .read = ts_read, + .write = ts_write, + .open = dvb_generic_open, + .release = dvb_generic_release, + .poll = ts_poll, + .mmap = NULL, +}; + +struct dvb_device ngene_dvbdev_ci = { + .priv = NULL, + .readers = 1, + .writers = 1, + .users = 2, + .fops = &ci_fops, +}; + + +/****************************************************************************/ +/* DVB functions and API interface ******************************************/ +/****************************************************************************/ + +static void swap_buffer(u32 *p, u32 len) +{ + while (len) { + *p = swab32(*p); + p++; + len -= 4; + } +} + +/* start of filler packet */ +static u8 fill_ts[] = { 0x47, 0x1f, 0xff, 0x10, TS_FILLER }; + +static int tsin_find_offset(void *buf, u32 len) +{ + int i, l; + + l = len - sizeof(fill_ts); + if (l <= 0) + return -1; + + for (i = 0; i < l; i++) { + if (((char *)buf)[i] == 0x47) { + if (!memcmp(buf + i, fill_ts, sizeof(fill_ts))) + return i % 188; + } + } + + return -1; +} + +static inline void tsin_copy_stripped(struct ngene *dev, void *buf) +{ + if (memcmp(buf, fill_ts, sizeof(fill_ts)) != 0) { + if (dvb_ringbuffer_free(&dev->tsin_rbuf) >= 188) { + dvb_ringbuffer_write(&dev->tsin_rbuf, buf, 188); + wake_up(&dev->tsin_rbuf.queue); + } + } +} + +void *tsin_exchange(void *priv, void *buf, u32 len, u32 clock, u32 flags) +{ + struct ngene_channel *chan = priv; + struct ngene *dev = chan->dev; + int tsoff; + + if (flags & DF_SWAP32) + swap_buffer(buf, len); + + if (dev->ci.en && chan->number == 2) { + /* blindly copy buffers if ci_tsfix is disabled */ + if (!ci_tsfix) { + while (len >= 188) { + tsin_copy_stripped(dev, buf); + + buf += 188; + len -= 188; + } + return NULL; + } + + /* ci_tsfix = 1 */ + + /* + * since the remainder of the TS packet which got cut off + * in the previous tsin_exchange() run is at the beginning + * of the new TS buffer, append this to the temp buffer and + * send it to the DVB ringbuffer afterwards. + */ + if (chan->tsin_offset) { + memcpy(&chan->tsin_buffer[(188 - chan->tsin_offset)], + buf, chan->tsin_offset); + tsin_copy_stripped(dev, &chan->tsin_buffer); + + buf += chan->tsin_offset; + len -= chan->tsin_offset; + } + + /* + * copy TS packets to the DVB ringbuffer and detect new offset + * shifts by checking for a valid TS SYNC byte + */ + while (len >= 188) { + if (*((char *)buf) != 0x47) { + /* + * no SYNC header, find new offset shift + * (max. 188 bytes, tsoff will be mod 188) + */ + tsoff = tsin_find_offset(buf, len); + if (tsoff > 0) { + chan->tsin_offset += tsoff; + chan->tsin_offset %= 188; + + buf += tsoff; + len -= tsoff; + + dev_info(&dev->pci_dev->dev, + "%s(): tsin_offset shift by %d on channel %d\n", + __func__, tsoff, + chan->number); + + /* + * offset corrected. re-check remaining + * len for a full TS frame, break and + * skip to fragment handling if < 188. + */ + if (len < 188) + break; + } + } + + tsin_copy_stripped(dev, buf); + + buf += 188; + len -= 188; + } + + /* + * if a fragment is left, copy to temp buffer. The remainder + * will be appended in the next tsin_exchange() iteration. + */ + if (len > 0 && len < 188) + memcpy(&chan->tsin_buffer, buf, len); + + return NULL; + } + + if (chan->users > 0) + dvb_dmx_swfilter(&chan->demux, buf, len); + + return NULL; +} + +void *tsout_exchange(void *priv, void *buf, u32 len, u32 clock, u32 flags) +{ + struct ngene_channel *chan = priv; + struct ngene *dev = chan->dev; + u32 alen; + + alen = dvb_ringbuffer_avail(&dev->tsout_rbuf); + alen -= alen % 188; + + if (alen < len) + FillTSBuffer(buf + alen, len - alen, flags); + else + alen = len; + dvb_ringbuffer_read(&dev->tsout_rbuf, buf, alen); + if (flags & DF_SWAP32) + swap_buffer((u32 *)buf, alen); + wake_up_interruptible(&dev->tsout_rbuf.queue); + return buf; +} + + + +int ngene_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct ngene_channel *chan = dvbdmx->priv; + + if (chan->users == 0) { + if (!chan->dev->cmd_timeout_workaround || !chan->running) + set_transfer(chan, 1); + } + + return ++chan->users; +} + +int ngene_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct ngene_channel *chan = dvbdmx->priv; + + if (--chan->users) + return chan->users; + + if (!chan->dev->cmd_timeout_workaround) + set_transfer(chan, 0); + + return 0; +} + +int my_dvb_dmx_ts_card_init(struct dvb_demux *dvbdemux, char *id, + int (*start_feed)(struct dvb_demux_feed *), + int (*stop_feed)(struct dvb_demux_feed *), + void *priv) +{ + dvbdemux->priv = priv; + + dvbdemux->filternum = 256; + dvbdemux->feednum = 256; + dvbdemux->start_feed = start_feed; + dvbdemux->stop_feed = stop_feed; + dvbdemux->write_to_decoder = NULL; + dvbdemux->dmx.capabilities = (DMX_TS_FILTERING | + DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING); + return dvb_dmx_init(dvbdemux); +} + +int my_dvb_dmxdev_ts_card_init(struct dmxdev *dmxdev, + struct dvb_demux *dvbdemux, + struct dmx_frontend *hw_frontend, + struct dmx_frontend *mem_frontend, + struct dvb_adapter *dvb_adapter) +{ + int ret; + + dmxdev->filternum = 256; + dmxdev->demux = &dvbdemux->dmx; + dmxdev->capabilities = 0; + ret = dvb_dmxdev_init(dmxdev, dvb_adapter); + if (ret < 0) + return ret; + + hw_frontend->source = DMX_FRONTEND_0; + dvbdemux->dmx.add_frontend(&dvbdemux->dmx, hw_frontend); + mem_frontend->source = DMX_MEMORY_FE; + dvbdemux->dmx.add_frontend(&dvbdemux->dmx, mem_frontend); + return dvbdemux->dmx.connect_frontend(&dvbdemux->dmx, hw_frontend); +} diff --git a/drivers/media/pci/ngene/ngene-i2c.c b/drivers/media/pci/ngene/ngene-i2c.c new file mode 100644 index 000000000..2e9e9774d --- /dev/null +++ b/drivers/media/pci/ngene/ngene-i2c.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ngene-i2c.c: nGene PCIe bridge driver i2c functions + * + * Copyright (C) 2005-2007 Micronas + * + * Copyright (C) 2008-2009 Ralph Metzler + * Modifications for new nGene firmware, + * support for EEPROM-copying, + * support for new dual DVB-S2 card prototype + */ + +/* FIXME - some of these can probably be removed */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ngene.h" + +/* Firmware command for i2c operations */ +static int ngene_command_i2c_read(struct ngene *dev, u8 adr, + u8 *out, u8 outlen, u8 *in, u8 inlen, int flag) +{ + struct ngene_command com; + + com.cmd.hdr.Opcode = CMD_I2C_READ; + com.cmd.hdr.Length = outlen + 3; + com.cmd.I2CRead.Device = adr << 1; + memcpy(com.cmd.I2CRead.Data, out, outlen); + com.cmd.I2CRead.Data[outlen] = inlen; + com.cmd.I2CRead.Data[outlen + 1] = 0; + com.in_len = outlen + 3; + com.out_len = inlen + 1; + + if (ngene_command(dev, &com) < 0) + return -EIO; + + if ((com.cmd.raw8[0] >> 1) != adr) + return -EIO; + + if (flag) + memcpy(in, com.cmd.raw8, inlen + 1); + else + memcpy(in, com.cmd.raw8 + 1, inlen); + return 0; +} + +static int ngene_command_i2c_write(struct ngene *dev, u8 adr, + u8 *out, u8 outlen) +{ + struct ngene_command com; + + + com.cmd.hdr.Opcode = CMD_I2C_WRITE; + com.cmd.hdr.Length = outlen + 1; + com.cmd.I2CRead.Device = adr << 1; + memcpy(com.cmd.I2CRead.Data, out, outlen); + com.in_len = outlen + 1; + com.out_len = 1; + + if (ngene_command(dev, &com) < 0) + return -EIO; + + if (com.cmd.raw8[0] == 1) + return -EIO; + + return 0; +} + +static void ngene_i2c_set_bus(struct ngene *dev, int bus) +{ + if (!(dev->card_info->i2c_access & 2)) + return; + if (dev->i2c_current_bus == bus) + return; + + switch (bus) { + case 0: + ngene_command_gpio_set(dev, 3, 0); + ngene_command_gpio_set(dev, 2, 1); + break; + + case 1: + ngene_command_gpio_set(dev, 2, 0); + ngene_command_gpio_set(dev, 3, 1); + break; + } + dev->i2c_current_bus = bus; +} + +static int ngene_i2c_master_xfer(struct i2c_adapter *adapter, + struct i2c_msg msg[], int num) +{ + struct ngene_channel *chan = + (struct ngene_channel *)i2c_get_adapdata(adapter); + struct ngene *dev = chan->dev; + + mutex_lock(&dev->i2c_switch_mutex); + ngene_i2c_set_bus(dev, chan->number); + + if (num == 2 && msg[1].flags & I2C_M_RD && !(msg[0].flags & I2C_M_RD)) + if (!ngene_command_i2c_read(dev, msg[0].addr, + msg[0].buf, msg[0].len, + msg[1].buf, msg[1].len, 0)) + goto done; + + if (num == 1 && !(msg[0].flags & I2C_M_RD)) + if (!ngene_command_i2c_write(dev, msg[0].addr, + msg[0].buf, msg[0].len)) + goto done; + if (num == 1 && (msg[0].flags & I2C_M_RD)) + if (!ngene_command_i2c_read(dev, msg[0].addr, NULL, 0, + msg[0].buf, msg[0].len, 0)) + goto done; + + mutex_unlock(&dev->i2c_switch_mutex); + return -EIO; + +done: + mutex_unlock(&dev->i2c_switch_mutex); + return num; +} + + +static u32 ngene_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm ngene_i2c_algo = { + .master_xfer = ngene_i2c_master_xfer, + .functionality = ngene_i2c_functionality, +}; + +int ngene_i2c_init(struct ngene *dev, int dev_nr) +{ + struct i2c_adapter *adap = &(dev->channel[dev_nr].i2c_adapter); + + i2c_set_adapdata(adap, &(dev->channel[dev_nr])); + + strscpy(adap->name, "nGene", sizeof(adap->name)); + + adap->algo = &ngene_i2c_algo; + adap->algo_data = (void *)&(dev->channel[dev_nr]); + adap->dev.parent = &dev->pci_dev->dev; + + return i2c_add_adapter(adap); +} + diff --git a/drivers/media/pci/ngene/ngene.h b/drivers/media/pci/ngene/ngene.h new file mode 100644 index 000000000..9f989420b --- /dev/null +++ b/drivers/media/pci/ngene/ngene.h @@ -0,0 +1,853 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ngene.h: nGene PCIe bridge driver + * + * Copyright (C) 2005-2007 Micronas + */ + +#ifndef _NGENE_H_ +#define _NGENE_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "cxd2099.h" + +#define DEVICE_NAME "ngene" + +#define NGENE_VID 0x18c3 +#define NGENE_PID 0x0720 + +#ifndef VIDEO_CAP_VC1 +#define VIDEO_CAP_AVC 128 +#define VIDEO_CAP_H264 128 +#define VIDEO_CAP_VC1 256 +#define VIDEO_CAP_WMV9 256 +#define VIDEO_CAP_MPEG4 512 +#endif + +#define DEMOD_TYPE_STV090X 0 +#define DEMOD_TYPE_DRXK 1 +#define DEMOD_TYPE_STV0367 2 + +#define DEMOD_TYPE_XO2 32 +#define DEMOD_TYPE_STV0910 (DEMOD_TYPE_XO2 + 0) +#define DEMOD_TYPE_SONY_CT2 (DEMOD_TYPE_XO2 + 1) +#define DEMOD_TYPE_SONY_ISDBT (DEMOD_TYPE_XO2 + 2) +#define DEMOD_TYPE_SONY_C2T2 (DEMOD_TYPE_XO2 + 3) +#define DEMOD_TYPE_ST_ATSC (DEMOD_TYPE_XO2 + 4) +#define DEMOD_TYPE_SONY_C2T2I (DEMOD_TYPE_XO2 + 5) + +#define NGENE_XO2_TYPE_NONE 0 +#define NGENE_XO2_TYPE_DUOFLEX 1 +#define NGENE_XO2_TYPE_CI 2 + +enum STREAM { + STREAM_VIDEOIN1 = 0, /* ITU656 or TS Input */ + STREAM_VIDEOIN2, + STREAM_AUDIOIN1, /* I2S or SPI Input */ + STREAM_AUDIOIN2, + STREAM_AUDIOOUT, + MAX_STREAM +}; + +enum SMODE_BITS { + SMODE_AUDIO_SPDIF = 0x20, + SMODE_AVSYNC = 0x10, + SMODE_TRANSPORT_STREAM = 0x08, + SMODE_AUDIO_CAPTURE = 0x04, + SMODE_VBI_CAPTURE = 0x02, + SMODE_VIDEO_CAPTURE = 0x01 +}; + +enum STREAM_FLAG_BITS { + SFLAG_CHROMA_FORMAT_2COMP = 0x01, /* Chroma Format : 2's complement */ + SFLAG_CHROMA_FORMAT_OFFSET = 0x00, /* Chroma Format : Binary offset */ + SFLAG_ORDER_LUMA_CHROMA = 0x02, /* Byte order: Y,Cb,Y,Cr */ + SFLAG_ORDER_CHROMA_LUMA = 0x00, /* Byte order: Cb,Y,Cr,Y */ + SFLAG_COLORBAR = 0x04, /* Select colorbar */ +}; + +#define PROGRAM_ROM 0x0000 +#define PROGRAM_SRAM 0x1000 +#define PERIPHERALS0 0x8000 +#define PERIPHERALS1 0x9000 +#define SHARED_BUFFER 0xC000 + +#define HOST_TO_NGENE (SHARED_BUFFER+0x0000) +#define NGENE_TO_HOST (SHARED_BUFFER+0x0100) +#define NGENE_COMMAND (SHARED_BUFFER+0x0200) +#define NGENE_COMMAND_HI (SHARED_BUFFER+0x0204) +#define NGENE_STATUS (SHARED_BUFFER+0x0208) +#define NGENE_STATUS_HI (SHARED_BUFFER+0x020C) +#define NGENE_EVENT (SHARED_BUFFER+0x0210) +#define NGENE_EVENT_HI (SHARED_BUFFER+0x0214) +#define VARIABLES (SHARED_BUFFER+0x0210) + +#define NGENE_INT_COUNTS (SHARED_BUFFER+0x0260) +#define NGENE_INT_ENABLE (SHARED_BUFFER+0x0264) +#define NGENE_VBI_LINE_COUNT (SHARED_BUFFER+0x0268) + +#define BUFFER_GP_XMIT (SHARED_BUFFER+0x0800) +#define BUFFER_GP_RECV (SHARED_BUFFER+0x0900) +#define EEPROM_AREA (SHARED_BUFFER+0x0A00) + +#define SG_V_IN_1 (SHARED_BUFFER+0x0A80) +#define SG_VBI_1 (SHARED_BUFFER+0x0B00) +#define SG_A_IN_1 (SHARED_BUFFER+0x0B80) +#define SG_V_IN_2 (SHARED_BUFFER+0x0C00) +#define SG_VBI_2 (SHARED_BUFFER+0x0C80) +#define SG_A_IN_2 (SHARED_BUFFER+0x0D00) +#define SG_V_OUT (SHARED_BUFFER+0x0D80) +#define SG_A_OUT2 (SHARED_BUFFER+0x0E00) + +#define DATA_A_IN_1 (SHARED_BUFFER+0x0E80) +#define DATA_A_IN_2 (SHARED_BUFFER+0x0F00) +#define DATA_A_OUT (SHARED_BUFFER+0x0F80) +#define DATA_V_IN_1 (SHARED_BUFFER+0x1000) +#define DATA_V_IN_2 (SHARED_BUFFER+0x2000) +#define DATA_V_OUT (SHARED_BUFFER+0x3000) + +#define DATA_FIFO_AREA (SHARED_BUFFER+0x1000) + +#define TIMESTAMPS 0xA000 +#define SCRATCHPAD 0xA080 +#define FORCE_INT 0xA088 +#define FORCE_NMI 0xA090 +#define INT_STATUS 0xA0A0 + +#define DEV_VER 0x9004 + +#define FW_DEBUG_DEFAULT (PROGRAM_SRAM+0x00FF) + +struct SG_ADDR { + u64 start; + u64 curr; + u16 curr_ptr; + u16 elements; + u32 pad[3]; +} __attribute__ ((__packed__)); + +struct SHARED_MEMORY { + /* C000 */ + u32 HostToNgene[64]; + + /* C100 */ + u32 NgeneToHost[64]; + + /* C200 */ + u64 NgeneCommand; + u64 NgeneStatus; + u64 NgeneEvent; + + /* C210 */ + u8 pad1[0xc260 - 0xc218]; + + /* C260 */ + u32 IntCounts; + u32 IntEnable; + + /* C268 */ + u8 pad2[0xd000 - 0xc268]; + +} __attribute__ ((__packed__)); + +struct BUFFER_STREAM_RESULTS { + u32 Clock; /* Stream time in 100ns units */ + u16 RemainingLines; /* Remaining lines in this field. + 0 for complete field */ + u8 FieldCount; /* Video field number */ + u8 Flags; /* Bit 7 = Done, Bit 6 = seen, Bit 5 = overflow, + Bit 0 = FieldID */ + u16 BlockCount; /* Audio block count (unused) */ + u8 Reserved[2]; + u32 DTOUpdate; +} __attribute__ ((__packed__)); + +struct HW_SCATTER_GATHER_ELEMENT { + u64 Address; + u32 Length; + u32 Reserved; +} __attribute__ ((__packed__)); + +struct BUFFER_HEADER { + u64 Next; + struct BUFFER_STREAM_RESULTS SR; + + u32 Number_of_entries_1; + u32 Reserved5; + u64 Address_of_first_entry_1; + + u32 Number_of_entries_2; + u32 Reserved7; + u64 Address_of_first_entry_2; +} __attribute__ ((__packed__)); + +struct EVENT_BUFFER { + u32 TimeStamp; + u8 GPIOStatus; + u8 UARTStatus; + u8 RXCharacter; + u8 EventStatus; + u32 Reserved[2]; +} __attribute__ ((__packed__)); + +/* Firmware commands. */ + +enum OPCODES { + CMD_NOP = 0, + CMD_FWLOAD_PREPARE = 0x01, + CMD_FWLOAD_FINISH = 0x02, + CMD_I2C_READ = 0x03, + CMD_I2C_WRITE = 0x04, + + CMD_I2C_WRITE_NOSTOP = 0x05, + CMD_I2C_CONTINUE_WRITE = 0x06, + CMD_I2C_CONTINUE_WRITE_NOSTOP = 0x07, + + CMD_DEBUG_OUTPUT = 0x09, + + CMD_CONTROL = 0x10, + CMD_CONFIGURE_BUFFER = 0x11, + CMD_CONFIGURE_FREE_BUFFER = 0x12, + + CMD_SPI_READ = 0x13, + CMD_SPI_WRITE = 0x14, + + CMD_MEM_READ = 0x20, + CMD_MEM_WRITE = 0x21, + CMD_SFR_READ = 0x22, + CMD_SFR_WRITE = 0x23, + CMD_IRAM_READ = 0x24, + CMD_IRAM_WRITE = 0x25, + CMD_SET_GPIO_PIN = 0x26, + CMD_SET_GPIO_INT = 0x27, + CMD_CONFIGURE_UART = 0x28, + CMD_WRITE_UART = 0x29, + MAX_CMD +}; + +enum RESPONSES { + OK = 0, + ERROR = 1 +}; + +struct FW_HEADER { + u8 Opcode; + u8 Length; +} __attribute__ ((__packed__)); + +struct FW_I2C_WRITE { + struct FW_HEADER hdr; + u8 Device; + u8 Data[250]; +} __attribute__ ((__packed__)); + +struct FW_I2C_CONTINUE_WRITE { + struct FW_HEADER hdr; + u8 Data[250]; +} __attribute__ ((__packed__)); + +struct FW_I2C_READ { + struct FW_HEADER hdr; + u8 Device; + u8 Data[252]; /* followed by two bytes of read data count */ +} __attribute__ ((__packed__)); + +struct FW_SPI_WRITE { + struct FW_HEADER hdr; + u8 ModeSelect; + u8 Data[250]; +} __attribute__ ((__packed__)); + +struct FW_SPI_READ { + struct FW_HEADER hdr; + u8 ModeSelect; + u8 Data[252]; /* followed by two bytes of read data count */ +} __attribute__ ((__packed__)); + +struct FW_FWLOAD_PREPARE { + struct FW_HEADER hdr; +} __attribute__ ((__packed__)); + +struct FW_FWLOAD_FINISH { + struct FW_HEADER hdr; + u16 Address; /* address of final block */ + u16 Length; +} __attribute__ ((__packed__)); + +/* + * Meaning of FW_STREAM_CONTROL::Mode bits: + * Bit 7: Loopback PEXin to PEXout using TVOut channel + * Bit 6: AVLOOP + * Bit 5: Audio select; 0=I2S, 1=SPDIF + * Bit 4: AVSYNC + * Bit 3: Enable transport stream + * Bit 2: Enable audio capture + * Bit 1: Enable ITU-Video VBI capture + * Bit 0: Enable ITU-Video capture + * + * Meaning of FW_STREAM_CONTROL::Control bits (see UVI1_CTL) + * Bit 7: continuous capture + * Bit 6: capture one field + * Bit 5: capture one frame + * Bit 4: unused + * Bit 3: starting field; 0=odd, 1=even + * Bit 2: sample size; 0=8-bit, 1=10-bit + * Bit 1: data format; 0=UYVY, 1=YUY2 + * Bit 0: resets buffer pointers +*/ + +enum FSC_MODE_BITS { + SMODE_LOOPBACK = 0x80, + SMODE_AVLOOP = 0x40, + _SMODE_AUDIO_SPDIF = 0x20, + _SMODE_AVSYNC = 0x10, + _SMODE_TRANSPORT_STREAM = 0x08, + _SMODE_AUDIO_CAPTURE = 0x04, + _SMODE_VBI_CAPTURE = 0x02, + _SMODE_VIDEO_CAPTURE = 0x01 +}; + + +/* Meaning of FW_STREAM_CONTROL::Stream bits: + * Bit 3: Audio sample count: 0 = relative, 1 = absolute + * Bit 2: color bar select; 1=color bars, 0=CV3 decoder + * Bits 1-0: stream select, UVI1, UVI2, TVOUT + */ + +struct FW_STREAM_CONTROL { + struct FW_HEADER hdr; + u8 Stream; /* Stream number (UVI1, UVI2, TVOUT) */ + u8 Control; /* Value written to UVI1_CTL */ + u8 Mode; /* Controls clock source */ + u8 SetupDataLen; /* Length of setup data, MSB=1 write + backwards */ + u16 CaptureBlockCount; /* Blocks (a 256 Bytes) to capture per buffer + for TS and Audio */ + u64 Buffer_Address; /* Address of first buffer header */ + u16 BytesPerVideoLine; + u16 MaxLinesPerField; + u16 MinLinesPerField; + u16 Reserved_1; + u16 BytesPerVBILine; + u16 MaxVBILinesPerField; + u16 MinVBILinesPerField; + u16 SetupDataAddr; /* ngene relative address of setup data */ + u8 SetupData[32]; /* setup data */ +} __attribute__((__packed__)); + +#define AUDIO_BLOCK_SIZE 256 +#define TS_BLOCK_SIZE 256 + +struct FW_MEM_READ { + struct FW_HEADER hdr; + u16 address; +} __attribute__ ((__packed__)); + +struct FW_MEM_WRITE { + struct FW_HEADER hdr; + u16 address; + u8 data; +} __attribute__ ((__packed__)); + +struct FW_SFR_IRAM_READ { + struct FW_HEADER hdr; + u8 address; +} __attribute__ ((__packed__)); + +struct FW_SFR_IRAM_WRITE { + struct FW_HEADER hdr; + u8 address; + u8 data; +} __attribute__ ((__packed__)); + +struct FW_SET_GPIO_PIN { + struct FW_HEADER hdr; + u8 select; +} __attribute__ ((__packed__)); + +struct FW_SET_GPIO_INT { + struct FW_HEADER hdr; + u8 select; +} __attribute__ ((__packed__)); + +struct FW_SET_DEBUGMODE { + struct FW_HEADER hdr; + u8 debug_flags; +} __attribute__ ((__packed__)); + +struct FW_CONFIGURE_BUFFERS { + struct FW_HEADER hdr; + u8 config; +} __attribute__ ((__packed__)); + +enum _BUFFER_CONFIGS { + /* 4k UVI1, 4k UVI2, 2k AUD1, 2k AUD2 (standard usage) */ + BUFFER_CONFIG_4422 = 0, + /* 3k UVI1, 3k UVI2, 3k AUD1, 3k AUD2 (4x TS input usage) */ + BUFFER_CONFIG_3333 = 1, + /* 8k UVI1, 0k UVI2, 2k AUD1, 2k I2SOut (HDTV decoder usage) */ + BUFFER_CONFIG_8022 = 2, + BUFFER_CONFIG_FW17 = 255, /* Use new FW 17 command */ +}; + +struct FW_CONFIGURE_FREE_BUFFERS { + struct FW_HEADER hdr; + struct { + u8 UVI1_BufferLength; + u8 UVI2_BufferLength; + u8 TVO_BufferLength; + u8 AUD1_BufferLength; + u8 AUD2_BufferLength; + u8 TVA_BufferLength; + } __packed config; +} __attribute__ ((__packed__)); + +struct FW_CONFIGURE_UART { + struct FW_HEADER hdr; + u8 UartControl; +} __attribute__ ((__packed__)); + +enum _UART_CONFIG { + _UART_BAUDRATE_19200 = 0, + _UART_BAUDRATE_9600 = 1, + _UART_BAUDRATE_4800 = 2, + _UART_BAUDRATE_2400 = 3, + _UART_RX_ENABLE = 0x40, + _UART_TX_ENABLE = 0x80, +}; + +struct FW_WRITE_UART { + struct FW_HEADER hdr; + u8 Data[252]; +} __attribute__ ((__packed__)); + + +struct ngene_command { + u32 in_len; + u32 out_len; + union { + u32 raw[64]; + u8 raw8[256]; + struct FW_HEADER hdr; + struct FW_I2C_WRITE I2CWrite; + struct FW_I2C_CONTINUE_WRITE I2CContinueWrite; + struct FW_I2C_READ I2CRead; + struct FW_STREAM_CONTROL StreamControl; + struct FW_FWLOAD_PREPARE FWLoadPrepare; + struct FW_FWLOAD_FINISH FWLoadFinish; + struct FW_MEM_READ MemoryRead; + struct FW_MEM_WRITE MemoryWrite; + struct FW_SFR_IRAM_READ SfrIramRead; + struct FW_SFR_IRAM_WRITE SfrIramWrite; + struct FW_SPI_WRITE SPIWrite; + struct FW_SPI_READ SPIRead; + struct FW_SET_GPIO_PIN SetGpioPin; + struct FW_SET_GPIO_INT SetGpioInt; + struct FW_SET_DEBUGMODE SetDebugMode; + struct FW_CONFIGURE_BUFFERS ConfigureBuffers; + struct FW_CONFIGURE_FREE_BUFFERS ConfigureFreeBuffers; + struct FW_CONFIGURE_UART ConfigureUart; + struct FW_WRITE_UART WriteUart; + } cmd; +} __attribute__ ((__packed__)); + +#define NGENE_INTERFACE_VERSION 0x103 +#define MAX_VIDEO_BUFFER_SIZE (417792) /* 288*1440 rounded up to next page */ +#define MAX_AUDIO_BUFFER_SIZE (8192) /* Gives room for about 23msec@48KHz */ +#define MAX_VBI_BUFFER_SIZE (28672) /* 1144*18 rounded up to next page */ +#define MAX_TS_BUFFER_SIZE (98304) /* 512*188 rounded up to next page */ +#define MAX_HDTV_BUFFER_SIZE (2080768) /* 541*1920*2 rounded up to next page + Max: (1920x1080i60) */ + +#define OVERFLOW_BUFFER_SIZE (8192) + +#define RING_SIZE_VIDEO 4 +#define RING_SIZE_AUDIO 8 +#define RING_SIZE_TS 8 + +#define NUM_SCATTER_GATHER_ENTRIES 8 + +#define MAX_DMA_LENGTH (((MAX_VIDEO_BUFFER_SIZE + MAX_VBI_BUFFER_SIZE) * \ + RING_SIZE_VIDEO * 2) + \ + (MAX_AUDIO_BUFFER_SIZE * RING_SIZE_AUDIO * 2) + \ + (MAX_TS_BUFFER_SIZE * RING_SIZE_TS * 4) + \ + (RING_SIZE_VIDEO * PAGE_SIZE * 2) + \ + (RING_SIZE_AUDIO * PAGE_SIZE * 2) + \ + (RING_SIZE_TS * PAGE_SIZE * 4) + \ + 8 * PAGE_SIZE + OVERFLOW_BUFFER_SIZE + PAGE_SIZE) + +#define EVENT_QUEUE_SIZE 16 + +/* Gathers the current state of a single channel. */ + +struct SBufferHeader { + struct BUFFER_HEADER ngeneBuffer; /* Physical descriptor */ + struct SBufferHeader *Next; + void *Buffer1; + struct HW_SCATTER_GATHER_ELEMENT *scList1; + void *Buffer2; + struct HW_SCATTER_GATHER_ELEMENT *scList2; +}; + +/* Sizeof SBufferHeader aligned to next 64 Bit boundary (hw restriction) */ +#define SIZEOF_SBufferHeader ((sizeof(struct SBufferHeader) + 63) & ~63) + +enum HWSTATE { + HWSTATE_STOP, + HWSTATE_STARTUP, + HWSTATE_RUN, + HWSTATE_PAUSE, +}; + +enum KSSTATE { + KSSTATE_STOP, + KSSTATE_ACQUIRE, + KSSTATE_PAUSE, + KSSTATE_RUN, +}; + +struct SRingBufferDescriptor { + struct SBufferHeader *Head; /* Points to first buffer in ring buffer + structure*/ + u64 PAHead; /* Physical address of first buffer */ + u32 MemSize; /* Memory size of allocated ring buffers + (needed for freeing) */ + u32 NumBuffers; /* Number of buffers in the ring */ + u32 Buffer1Length; /* Allocated length of Buffer 1 */ + u32 Buffer2Length; /* Allocated length of Buffer 2 */ + void *SCListMem; /* Memory to hold scatter gather lists for this + ring */ + u64 PASCListMem; /* Physical address .. */ + u32 SCListMemSize; /* Size of this memory */ +}; + +enum STREAMMODEFLAGS { + StreamMode_NONE = 0, /* Stream not used */ + StreamMode_ANALOG = 1, /* Analog: Stream 0,1 = Video, 2,3 = Audio */ + StreamMode_TSIN = 2, /* Transport stream input (all) */ + StreamMode_HDTV = 4, /* HDTV: Maximum 1920x1080p30,1920x1080i60 + (only stream 0) */ + StreamMode_TSOUT = 8, /* Transport stream output (only stream 3) */ +}; + + +enum BufferExchangeFlags { + BEF_EVEN_FIELD = 0x00000001, + BEF_CONTINUATION = 0x00000002, + BEF_MORE_DATA = 0x00000004, + BEF_OVERFLOW = 0x00000008, + DF_SWAP32 = 0x00010000, +}; + +typedef void *(IBufferExchange)(void *, void *, u32, u32, u32); + +struct MICI_STREAMINFO { + IBufferExchange *pExchange; + IBufferExchange *pExchangeVBI; /* Secondary (VBI, ancillary) */ + u8 Stream; + u8 Flags; + u8 Mode; + u8 Reserved; + u16 nLinesVideo; + u16 nBytesPerLineVideo; + u16 nLinesVBI; + u16 nBytesPerLineVBI; + u32 CaptureLength; /* Used for audio and transport stream */ +}; + +/****************************************************************************/ +/* STRUCTS ******************************************************************/ +/****************************************************************************/ + +/* sound hardware definition */ +#define MIXER_ADDR_TVTUNER 0 +#define MIXER_ADDR_LAST 0 + +struct ngene_channel; + +/*struct sound chip*/ + +struct mychip { + struct ngene_channel *chan; + struct snd_card *card; + struct pci_dev *pci; + struct snd_pcm_substream *substream; + struct snd_pcm *pcm; + unsigned long port; + int irq; + spinlock_t mixer_lock; + spinlock_t lock; + int mixer_volume[MIXER_ADDR_LAST + 1][2]; + int capture_source[MIXER_ADDR_LAST + 1][2]; +}; + +struct ngene_channel { + struct device device; + struct i2c_adapter i2c_adapter; + struct i2c_client *i2c_client[1]; + int i2c_client_fe; + + struct ngene *dev; + int number; + int type; + int mode; + bool has_adapter; + bool has_demux; + int demod_type; + int (*gate_ctrl)(struct dvb_frontend *, int); + + struct dvb_frontend *fe; + struct dvb_frontend *fe2; + struct dmxdev dmxdev; + struct dvb_demux demux; + struct dvb_net dvbnet; + struct dmx_frontend hw_frontend; + struct dmx_frontend mem_frontend; + int users; + struct video_device *v4l_dev; + struct dvb_device *ci_dev; + struct work_struct demux_bh_work; + + struct SBufferHeader *nextBuffer; + enum KSSTATE State; + enum HWSTATE HWState; + u8 Stream; + u8 Flags; + u8 Mode; + IBufferExchange *pBufferExchange; + IBufferExchange *pBufferExchange2; + + spinlock_t state_lock; + u16 nLines; + u16 nBytesPerLine; + u16 nVBILines; + u16 nBytesPerVBILine; + u16 itumode; + u32 Capture1Length; + u32 Capture2Length; + struct SRingBufferDescriptor RingBuffer; + struct SRingBufferDescriptor TSRingBuffer; + struct SRingBufferDescriptor TSIdleBuffer; + + u32 DataFormatFlags; + + int AudioDTOUpdated; + u32 AudioDTOValue; + + int (*set_tone)(struct dvb_frontend *, enum fe_sec_tone_mode); + u8 lnbh; + + /* stuff from analog driver */ + + int minor; + struct mychip *mychip; + struct snd_card *soundcard; + u8 *evenbuffer; + u8 dma_on; + int soundstreamon; + int audiomute; + int soundbuffisallocated; + int sndbuffflag; + int tun_rdy; + int dec_rdy; + int tun_dec_rdy; + int lastbufferflag; + + struct ngene_tvnorm *tvnorms; + int tvnorm_num; + int tvnorm; + + int running; + + int tsin_offset; + u8 tsin_buffer[188]; +}; + + +struct ngene_ci { + struct device device; + struct i2c_adapter i2c_adapter; + + struct ngene *dev; + struct dvb_ca_en50221 *en; +}; + +struct ngene; + +typedef void (rx_cb_t)(struct ngene *, u32, u8); +typedef void (tx_cb_t)(struct ngene *, u32); + +struct ngene { + int nr; + struct pci_dev *pci_dev; + unsigned char __iomem *iomem; + + /*struct i2c_adapter i2c_adapter;*/ + + u32 device_version; + u32 fw_interface_version; + u32 icounts; + bool msi_enabled; + bool cmd_timeout_workaround; + + u8 *CmdDoneByte; + int BootFirmware; + void *OverflowBuffer; + dma_addr_t PAOverflowBuffer; + void *FWInterfaceBuffer; + dma_addr_t PAFWInterfaceBuffer; + u8 *ngenetohost; + u8 *hosttongene; + + struct EVENT_BUFFER EventQueue[EVENT_QUEUE_SIZE]; + int EventQueueOverflowCount; + int EventQueueOverflowFlag; + struct work_struct event_bh_work; + struct EVENT_BUFFER *EventBuffer; + int EventQueueWriteIndex; + int EventQueueReadIndex; + + wait_queue_head_t cmd_wq; + int cmd_done; + struct mutex cmd_mutex; + struct mutex stream_mutex; + struct semaphore pll_mutex; + struct mutex i2c_switch_mutex; + int i2c_current_channel; + int i2c_current_bus; + spinlock_t cmd_lock; + + struct dvb_adapter adapter[MAX_STREAM]; + struct dvb_adapter *first_adapter; /* "one_adapter" modprobe opt */ + struct ngene_channel channel[MAX_STREAM]; + + struct ngene_info *card_info; + + tx_cb_t *TxEventNotify; + rx_cb_t *RxEventNotify; + int tx_busy; + wait_queue_head_t tx_wq; + wait_queue_head_t rx_wq; +#define UART_RBUF_LEN 4096 + u8 uart_rbuf[UART_RBUF_LEN]; + int uart_rp, uart_wp; + +#define TS_FILLER 0x6f + + u8 *tsout_buf; +#define TSOUT_BUF_SIZE (512*188*8) + struct dvb_ringbuffer tsout_rbuf; + + u8 *tsin_buf; +#define TSIN_BUF_SIZE (512*188*8) + struct dvb_ringbuffer tsin_rbuf; + + u8 *ain_buf; +#define AIN_BUF_SIZE (128*1024) + struct dvb_ringbuffer ain_rbuf; + + + u8 *vin_buf; +#define VIN_BUF_SIZE (4*1920*1080) + struct dvb_ringbuffer vin_rbuf; + + unsigned long exp_val; + int prev_cmd; + + struct ngene_ci ci; +}; + +struct ngene_info { + int type; +#define NGENE_APP 0 +#define NGENE_TERRATEC 1 +#define NGENE_SIDEWINDER 2 +#define NGENE_RACER 3 +#define NGENE_VIPER 4 +#define NGENE_PYTHON 5 +#define NGENE_VBOX_V1 6 +#define NGENE_VBOX_V2 7 + + int fw_version; + bool msi_supported; + char *name; + + int io_type[MAX_STREAM]; +#define NGENE_IO_NONE 0 +#define NGENE_IO_TV 1 +#define NGENE_IO_HDTV 2 +#define NGENE_IO_TSIN 4 +#define NGENE_IO_TSOUT 8 +#define NGENE_IO_AIN 16 + + void *fe_config[4]; + void *tuner_config[4]; + + int (*demod_attach[4])(struct ngene_channel *); + int (*tuner_attach[4])(struct ngene_channel *); + + u8 avf[4]; + u8 msp[4]; + u8 demoda[4]; + u8 lnb[4]; + int i2c_access; + u8 ntsc; + u8 tsf[4]; + u8 i2s[4]; + + int (*gate_ctrl)(struct dvb_frontend *, int); + int (*switch_ctrl)(struct ngene_channel *, int, int); +}; + + +/* Provided by ngene-core.c */ +int ngene_probe(struct pci_dev *pci_dev, const struct pci_device_id *id); +void ngene_remove(struct pci_dev *pdev); +void ngene_shutdown(struct pci_dev *pdev); +int ngene_command(struct ngene *dev, struct ngene_command *com); +int ngene_command_gpio_set(struct ngene *dev, u8 select, u8 level); +void set_transfer(struct ngene_channel *chan, int state); +void FillTSBuffer(void *Buffer, int Length, u32 Flags); + +/* Provided by ngene-cards.c */ +int ngene_port_has_cxd2099(struct i2c_adapter *i2c, u8 *type); + +/* Provided by ngene-i2c.c */ +int ngene_i2c_init(struct ngene *dev, int dev_nr); + +/* Provided by ngene-dvb.c */ +extern struct dvb_device ngene_dvbdev_ci; +void *tsout_exchange(void *priv, void *buf, u32 len, u32 clock, u32 flags); +void *tsin_exchange(void *priv, void *buf, u32 len, u32 clock, u32 flags); +int ngene_start_feed(struct dvb_demux_feed *dvbdmxfeed); +int ngene_stop_feed(struct dvb_demux_feed *dvbdmxfeed); +int my_dvb_dmx_ts_card_init(struct dvb_demux *dvbdemux, char *id, + int (*start_feed)(struct dvb_demux_feed *), + int (*stop_feed)(struct dvb_demux_feed *), + void *priv); +int my_dvb_dmxdev_ts_card_init(struct dmxdev *dmxdev, + struct dvb_demux *dvbdemux, + struct dmx_frontend *hw_frontend, + struct dmx_frontend *mem_frontend, + struct dvb_adapter *dvb_adapter); + +#endif + +/* LocalWords: Endif + */ diff --git a/drivers/media/pci/pluto2/Kconfig b/drivers/media/pci/pluto2/Kconfig new file mode 100644 index 000000000..de8316976 --- /dev/null +++ b/drivers/media/pci/pluto2/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DVB_PLUTO2 + tristate "Pluto2 cards" + depends on DVB_CORE && PCI && I2C + select I2C_ALGOBIT + select DVB_TDA1004X + help + Support for PCI cards based on the Pluto2 FPGA like the Satelco + Easywatch Mobile Terrestrial DVB-T Receiver. + + Since these cards have no MPEG decoder onboard, they transmit + only compressed MPEG data over the PCI bus, so you need + an external software decoder to watch TV on your computer. + + Say Y or M if you own such a device and want to use it. + diff --git a/drivers/media/pci/pluto2/Makefile b/drivers/media/pci/pluto2/Makefile new file mode 100644 index 000000000..055347964 --- /dev/null +++ b/drivers/media/pci/pluto2/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_DVB_PLUTO2) += pluto2.o + +ccflags-y += -I $(srctree)/drivers/media/dvb-frontends/ diff --git a/drivers/media/pci/pluto2/pluto2.c b/drivers/media/pci/pluto2/pluto2.c new file mode 100644 index 000000000..77b3e4eec --- /dev/null +++ b/drivers/media/pci/pluto2/pluto2.c @@ -0,0 +1,784 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * pluto2.c - Satelco Easywatch Mobile Terrestrial Receiver [DVB-T] + * + * Copyright (C) 2005 Andreas Oberritter + * + * based on pluto2.c 1.10 - http://instinct-wp8.no-ip.org/pluto/ + * by Dany Salman + * Copyright (c) 2004 TDF + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "tda1004x.h" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +#define DRIVER_NAME "pluto2" + +#define REG_PIDn(n) ((n) << 2) /* PID n pattern registers */ +#define REG_PCAR 0x0020 /* PC address register */ +#define REG_TSCR 0x0024 /* TS ctrl & status */ +#define REG_MISC 0x0028 /* miscellaneous */ +#define REG_MMAC 0x002c /* MSB MAC address */ +#define REG_IMAC 0x0030 /* ISB MAC address */ +#define REG_LMAC 0x0034 /* LSB MAC address */ +#define REG_SPID 0x0038 /* SPI data */ +#define REG_SLCS 0x003c /* serial links ctrl/status */ + +#define PID0_NOFIL (0x0001 << 16) +#define PIDn_ENP (0x0001 << 15) +#define PID0_END (0x0001 << 14) +#define PID0_AFIL (0x0001 << 13) +#define PIDn_PID (0x1fff << 0) + +#define TSCR_NBPACKETS (0x00ff << 24) +#define TSCR_DEM (0x0001 << 17) +#define TSCR_DE (0x0001 << 16) +#define TSCR_RSTN (0x0001 << 15) +#define TSCR_MSKO (0x0001 << 14) +#define TSCR_MSKA (0x0001 << 13) +#define TSCR_MSKL (0x0001 << 12) +#define TSCR_OVR (0x0001 << 11) +#define TSCR_AFUL (0x0001 << 10) +#define TSCR_LOCK (0x0001 << 9) +#define TSCR_IACK (0x0001 << 8) +#define TSCR_ADEF (0x007f << 0) + +#define MISC_DVR (0x0fff << 4) +#define MISC_ALED (0x0001 << 3) +#define MISC_FRST (0x0001 << 2) +#define MISC_LED1 (0x0001 << 1) +#define MISC_LED0 (0x0001 << 0) + +#define SPID_SPIDR (0x00ff << 0) + +#define SLCS_SCL (0x0001 << 7) +#define SLCS_SDA (0x0001 << 6) +#define SLCS_CSN (0x0001 << 2) +#define SLCS_OVR (0x0001 << 1) +#define SLCS_SWC (0x0001 << 0) + +#define TS_DMA_PACKETS (8) +#define TS_DMA_BYTES (188 * TS_DMA_PACKETS) + +#define I2C_ADDR_TDA10046 0x10 +#define I2C_ADDR_TUA6034 0xc2 +#define NHWFILTERS 8 + +struct pluto { + /* pci */ + struct pci_dev *pdev; + u8 __iomem *io_mem; + + /* dvb */ + struct dmx_frontend hw_frontend; + struct dmx_frontend mem_frontend; + struct dmxdev dmxdev; + struct dvb_adapter dvb_adapter; + struct dvb_demux demux; + struct dvb_frontend *fe; + struct dvb_net dvbnet; + unsigned int full_ts_users; + unsigned int users; + + /* i2c */ + struct i2c_algo_bit_data i2c_bit; + struct i2c_adapter i2c_adap; + unsigned int i2cbug; + + /* irq */ + unsigned int overflow; + unsigned int dead; + + /* dma */ + dma_addr_t dma_addr; + u8 dma_buf[TS_DMA_BYTES]; + u8 dummy[4096]; +}; + +static inline struct pluto *feed_to_pluto(struct dvb_demux_feed *feed) +{ + return container_of(feed->demux, struct pluto, demux); +} + +static inline struct pluto *frontend_to_pluto(struct dvb_frontend *fe) +{ + return container_of(fe->dvb, struct pluto, dvb_adapter); +} + +static inline u32 pluto_readreg(struct pluto *pluto, u32 reg) +{ + return readl(&pluto->io_mem[reg]); +} + +static inline void pluto_writereg(struct pluto *pluto, u32 reg, u32 val) +{ + writel(val, &pluto->io_mem[reg]); +} + +static inline void pluto_rw(struct pluto *pluto, u32 reg, u32 mask, u32 bits) +{ + u32 val = readl(&pluto->io_mem[reg]); + val &= ~mask; + val |= bits; + writel(val, &pluto->io_mem[reg]); +} + +static void pluto_write_tscr(struct pluto *pluto, u32 val) +{ + /* set the number of packets */ + val &= ~TSCR_ADEF; + val |= TS_DMA_PACKETS / 2; + + pluto_writereg(pluto, REG_TSCR, val); +} + +static void pluto_setsda(void *data, int state) +{ + struct pluto *pluto = data; + + if (state) + pluto_rw(pluto, REG_SLCS, SLCS_SDA, SLCS_SDA); + else + pluto_rw(pluto, REG_SLCS, SLCS_SDA, 0); +} + +static void pluto_setscl(void *data, int state) +{ + struct pluto *pluto = data; + + if (state) + pluto_rw(pluto, REG_SLCS, SLCS_SCL, SLCS_SCL); + else + pluto_rw(pluto, REG_SLCS, SLCS_SCL, 0); + + /* try to detect i2c_inb() to workaround hardware bug: + * reset SDA to high after SCL has been set to low */ + if ((state) && (pluto->i2cbug == 0)) { + pluto->i2cbug = 1; + } else { + if ((!state) && (pluto->i2cbug == 1)) + pluto_setsda(pluto, 1); + pluto->i2cbug = 0; + } +} + +static int pluto_getsda(void *data) +{ + struct pluto *pluto = data; + + return pluto_readreg(pluto, REG_SLCS) & SLCS_SDA; +} + +static int pluto_getscl(void *data) +{ + struct pluto *pluto = data; + + return pluto_readreg(pluto, REG_SLCS) & SLCS_SCL; +} + +static void pluto_reset_frontend(struct pluto *pluto, int reenable) +{ + u32 val = pluto_readreg(pluto, REG_MISC); + + if (val & MISC_FRST) { + val &= ~MISC_FRST; + pluto_writereg(pluto, REG_MISC, val); + } + if (reenable) { + val |= MISC_FRST; + pluto_writereg(pluto, REG_MISC, val); + } +} + +static void pluto_reset_ts(struct pluto *pluto, int reenable) +{ + u32 val = pluto_readreg(pluto, REG_TSCR); + + if (val & TSCR_RSTN) { + val &= ~TSCR_RSTN; + pluto_write_tscr(pluto, val); + } + if (reenable) { + val |= TSCR_RSTN; + pluto_write_tscr(pluto, val); + } +} + +static void pluto_set_dma_addr(struct pluto *pluto) +{ + pluto_writereg(pluto, REG_PCAR, pluto->dma_addr); +} + +static int pluto_dma_map(struct pluto *pluto) +{ + pluto->dma_addr = dma_map_single(&pluto->pdev->dev, pluto->dma_buf, + TS_DMA_BYTES, DMA_FROM_DEVICE); + + return dma_mapping_error(&pluto->pdev->dev, pluto->dma_addr); +} + +static void pluto_dma_unmap(struct pluto *pluto) +{ + dma_unmap_single(&pluto->pdev->dev, pluto->dma_addr, TS_DMA_BYTES, + DMA_FROM_DEVICE); +} + +static int pluto_start_feed(struct dvb_demux_feed *f) +{ + struct pluto *pluto = feed_to_pluto(f); + + /* enable PID filtering */ + if (pluto->users++ == 0) + pluto_rw(pluto, REG_PIDn(0), PID0_AFIL | PID0_NOFIL, 0); + + if ((f->pid < 0x2000) && (f->index < NHWFILTERS)) + pluto_rw(pluto, REG_PIDn(f->index), PIDn_ENP | PIDn_PID, PIDn_ENP | f->pid); + else if (pluto->full_ts_users++ == 0) + pluto_rw(pluto, REG_PIDn(0), PID0_NOFIL, PID0_NOFIL); + + return 0; +} + +static int pluto_stop_feed(struct dvb_demux_feed *f) +{ + struct pluto *pluto = feed_to_pluto(f); + + /* disable PID filtering */ + if (--pluto->users == 0) + pluto_rw(pluto, REG_PIDn(0), PID0_AFIL, PID0_AFIL); + + if ((f->pid < 0x2000) && (f->index < NHWFILTERS)) + pluto_rw(pluto, REG_PIDn(f->index), PIDn_ENP | PIDn_PID, 0x1fff); + else if (--pluto->full_ts_users == 0) + pluto_rw(pluto, REG_PIDn(0), PID0_NOFIL, 0); + + return 0; +} + +static void pluto_dma_end(struct pluto *pluto, unsigned int nbpackets) +{ + /* synchronize the DMA transfer with the CPU + * first so that we see updated contents. */ + dma_sync_single_for_cpu(&pluto->pdev->dev, pluto->dma_addr, + TS_DMA_BYTES, DMA_FROM_DEVICE); + + /* Workaround for broken hardware: + * [1] On startup NBPACKETS seems to contain an uninitialized value, + * but no packets have been transferred. + * [2] Sometimes (actually very often) NBPACKETS stays at zero + * although one packet has been transferred. + * [3] Sometimes (actually rarely), the card gets into an erroneous + * mode where it continuously generates interrupts, claiming it + * has received nbpackets>TS_DMA_PACKETS packets, but no packet + * has been transferred. Only a reset seems to solve this + */ + if ((nbpackets == 0) || (nbpackets > TS_DMA_PACKETS)) { + unsigned int i = 0; + while (pluto->dma_buf[i] == 0x47) + i += 188; + nbpackets = i / 188; + if (i == 0) { + pluto_reset_ts(pluto, 1); + dev_printk(KERN_DEBUG, &pluto->pdev->dev, "resetting TS because of invalid packet counter\n"); + } + } + + dvb_dmx_swfilter_packets(&pluto->demux, pluto->dma_buf, nbpackets); + + /* clear the dma buffer. this is needed to be able to identify + * new valid ts packets above */ + memset(pluto->dma_buf, 0, nbpackets * 188); + + /* reset the dma address */ + pluto_set_dma_addr(pluto); + + /* sync the buffer and give it back to the card */ + dma_sync_single_for_device(&pluto->pdev->dev, pluto->dma_addr, + TS_DMA_BYTES, DMA_FROM_DEVICE); +} + +static irqreturn_t pluto_irq(int irq, void *dev_id) +{ + struct pluto *pluto = dev_id; + u32 tscr; + + /* check whether an interrupt occurred on this device */ + tscr = pluto_readreg(pluto, REG_TSCR); + if (!(tscr & (TSCR_DE | TSCR_OVR))) + return IRQ_NONE; + + if (tscr == 0xffffffff) { + if (pluto->dead == 0) + dev_err(&pluto->pdev->dev, "card has hung or been ejected.\n"); + /* It's dead Jim */ + pluto->dead = 1; + return IRQ_HANDLED; + } + + /* dma end interrupt */ + if (tscr & TSCR_DE) { + pluto_dma_end(pluto, (tscr & TSCR_NBPACKETS) >> 24); + /* overflow interrupt */ + if (tscr & TSCR_OVR) + pluto->overflow++; + if (pluto->overflow) { + dev_err(&pluto->pdev->dev, "overflow irq (%d)\n", + pluto->overflow); + pluto_reset_ts(pluto, 1); + pluto->overflow = 0; + } + } else if (tscr & TSCR_OVR) { + pluto->overflow++; + } + + /* ACK the interrupt */ + pluto_write_tscr(pluto, tscr | TSCR_IACK); + + return IRQ_HANDLED; +} + +static void pluto_enable_irqs(struct pluto *pluto) +{ + u32 val = pluto_readreg(pluto, REG_TSCR); + + /* disable AFUL and LOCK interrupts */ + val |= (TSCR_MSKA | TSCR_MSKL); + /* enable DMA and OVERFLOW interrupts */ + val &= ~(TSCR_DEM | TSCR_MSKO); + /* clear pending interrupts */ + val |= TSCR_IACK; + + pluto_write_tscr(pluto, val); +} + +static void pluto_disable_irqs(struct pluto *pluto) +{ + u32 val = pluto_readreg(pluto, REG_TSCR); + + /* disable all interrupts */ + val |= (TSCR_DEM | TSCR_MSKO | TSCR_MSKA | TSCR_MSKL); + /* clear pending interrupts */ + val |= TSCR_IACK; + + pluto_write_tscr(pluto, val); +} + +static int pluto_hw_init(struct pluto *pluto) +{ + pluto_reset_frontend(pluto, 1); + + /* set automatic LED control by FPGA */ + pluto_rw(pluto, REG_MISC, MISC_ALED, MISC_ALED); + + /* set data endianness */ +#ifdef __LITTLE_ENDIAN + pluto_rw(pluto, REG_PIDn(0), PID0_END, PID0_END); +#else + pluto_rw(pluto, REG_PIDn(0), PID0_END, 0); +#endif + /* map DMA and set address */ + pluto_dma_map(pluto); + pluto_set_dma_addr(pluto); + + /* enable interrupts */ + pluto_enable_irqs(pluto); + + /* reset TS logic */ + pluto_reset_ts(pluto, 1); + + return 0; +} + +static void pluto_hw_exit(struct pluto *pluto) +{ + /* disable interrupts */ + pluto_disable_irqs(pluto); + + pluto_reset_ts(pluto, 0); + + /* LED: disable automatic control, enable yellow, disable green */ + pluto_rw(pluto, REG_MISC, MISC_ALED | MISC_LED1 | MISC_LED0, MISC_LED1); + + /* unmap DMA */ + pluto_dma_unmap(pluto); + + pluto_reset_frontend(pluto, 0); +} + +static inline u32 divide(u32 numerator, u32 denominator) +{ + if (denominator == 0) + return ~0; + + return DIV_ROUND_CLOSEST(numerator, denominator); +} + +/* LG Innotek TDTE-E001P (Infineon TUA6034) */ +static int lg_tdtpe001p_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct pluto *pluto = frontend_to_pluto(fe); + struct i2c_msg msg; + int ret; + u8 buf[4]; + u32 div; + + // Fref = 166.667 Hz + // Fref * 3 = 500.000 Hz + // IF = 36166667 + // IF / Fref = 217 + //div = divide(p->frequency + 36166667, 166667); + div = divide(p->frequency * 3, 500000) + 217; + buf[0] = (div >> 8) & 0x7f; + buf[1] = (div >> 0) & 0xff; + + if (p->frequency < 611000000) + buf[2] = 0xb4; + else if (p->frequency < 811000000) + buf[2] = 0xbc; + else + buf[2] = 0xf4; + + // VHF: 174-230 MHz + // center: 350 MHz + // UHF: 470-862 MHz + if (p->frequency < 350000000) + buf[3] = 0x02; + else + buf[3] = 0x04; + + if (p->bandwidth_hz == 8000000) + buf[3] |= 0x08; + + msg.addr = I2C_ADDR_TUA6034 >> 1; + msg.flags = 0; + msg.buf = buf; + msg.len = sizeof(buf); + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + ret = i2c_transfer(&pluto->i2c_adap, &msg, 1); + if (ret < 0) + return ret; + else if (ret == 0) + return -EREMOTEIO; + + return 0; +} + +static int pluto2_request_firmware(struct dvb_frontend *fe, + const struct firmware **fw, char *name) +{ + struct pluto *pluto = frontend_to_pluto(fe); + + return request_firmware(fw, name, &pluto->pdev->dev); +} + +static struct tda1004x_config pluto2_fe_config = { + .demod_address = I2C_ADDR_TDA10046 >> 1, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_DEFAULT, + .if_freq = TDA10046_FREQ_3617, + .request_firmware = pluto2_request_firmware, +}; + +static int frontend_init(struct pluto *pluto) +{ + int ret; + + pluto->fe = tda10046_attach(&pluto2_fe_config, &pluto->i2c_adap); + if (!pluto->fe) { + dev_err(&pluto->pdev->dev, "could not attach frontend\n"); + return -ENODEV; + } + pluto->fe->ops.tuner_ops.set_params = lg_tdtpe001p_tuner_set_params; + + ret = dvb_register_frontend(&pluto->dvb_adapter, pluto->fe); + if (ret < 0) { + if (pluto->fe->ops.release) + pluto->fe->ops.release(pluto->fe); + return ret; + } + + return 0; +} + +static void pluto_read_rev(struct pluto *pluto) +{ + u32 val = pluto_readreg(pluto, REG_MISC) & MISC_DVR; + dev_info(&pluto->pdev->dev, "board revision %d.%d\n", + (val >> 12) & 0x0f, (val >> 4) & 0xff); +} + +static void pluto_read_mac(struct pluto *pluto, u8 *mac) +{ + u32 val = pluto_readreg(pluto, REG_MMAC); + mac[0] = (val >> 8) & 0xff; + mac[1] = (val >> 0) & 0xff; + + val = pluto_readreg(pluto, REG_IMAC); + mac[2] = (val >> 8) & 0xff; + mac[3] = (val >> 0) & 0xff; + + val = pluto_readreg(pluto, REG_LMAC); + mac[4] = (val >> 8) & 0xff; + mac[5] = (val >> 0) & 0xff; + + dev_info(&pluto->pdev->dev, "MAC %pM\n", mac); +} + +static int pluto_read_serial(struct pluto *pluto) +{ + struct pci_dev *pdev = pluto->pdev; + unsigned int i, j; + u8 __iomem *cis; + + cis = pci_iomap(pdev, 1, 0); + if (!cis) + return -EIO; + + dev_info(&pdev->dev, "S/N "); + + for (i = 0xe0; i < 0x100; i += 4) { + u32 val = readl(&cis[i]); + for (j = 0; j < 32; j += 8) { + if ((val & 0xff) == 0xff) + goto out; + printk(KERN_CONT "%c", val & 0xff); + val >>= 8; + } + } +out: + printk(KERN_CONT "\n"); + pci_iounmap(pdev, cis); + + return 0; +} + +static int pluto2_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct pluto *pluto; + struct dvb_adapter *dvb_adapter; + struct dvb_demux *dvbdemux; + struct dmx_demux *dmx; + int ret = -ENOMEM; + + pluto = kzalloc_obj(struct pluto); + if (!pluto) + goto out; + + pluto->pdev = pdev; + + ret = pci_enable_device(pdev); + if (ret < 0) + goto err_kfree; + + /* enable interrupts */ + pci_write_config_dword(pdev, 0x6c, 0x8000); + + ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (ret < 0) + goto err_pci_disable_device; + + pci_set_master(pdev); + + ret = pci_request_regions(pdev, DRIVER_NAME); + if (ret < 0) + goto err_pci_disable_device; + + pluto->io_mem = pci_iomap(pdev, 0, 0x40); + if (!pluto->io_mem) { + ret = -EIO; + goto err_pci_release_regions; + } + + pci_set_drvdata(pdev, pluto); + + ret = request_irq(pdev->irq, pluto_irq, IRQF_SHARED, DRIVER_NAME, pluto); + if (ret < 0) + goto err_pci_iounmap; + + ret = pluto_hw_init(pluto); + if (ret < 0) + goto err_free_irq; + + /* i2c */ + i2c_set_adapdata(&pluto->i2c_adap, pluto); + strscpy(pluto->i2c_adap.name, DRIVER_NAME, sizeof(pluto->i2c_adap.name)); + pluto->i2c_adap.owner = THIS_MODULE; + pluto->i2c_adap.dev.parent = &pdev->dev; + pluto->i2c_adap.algo_data = &pluto->i2c_bit; + pluto->i2c_bit.data = pluto; + pluto->i2c_bit.setsda = pluto_setsda; + pluto->i2c_bit.setscl = pluto_setscl; + pluto->i2c_bit.getsda = pluto_getsda; + pluto->i2c_bit.getscl = pluto_getscl; + pluto->i2c_bit.udelay = 10; + pluto->i2c_bit.timeout = 10; + + /* Raise SCL and SDA */ + pluto_setsda(pluto, 1); + pluto_setscl(pluto, 1); + + ret = i2c_bit_add_bus(&pluto->i2c_adap); + if (ret < 0) + goto err_pluto_hw_exit; + + /* dvb */ + ret = dvb_register_adapter(&pluto->dvb_adapter, DRIVER_NAME, + THIS_MODULE, &pdev->dev, adapter_nr); + if (ret < 0) + goto err_i2c_del_adapter; + + dvb_adapter = &pluto->dvb_adapter; + + pluto_read_rev(pluto); + pluto_read_serial(pluto); + pluto_read_mac(pluto, dvb_adapter->proposed_mac); + + dvbdemux = &pluto->demux; + dvbdemux->filternum = 256; + dvbdemux->feednum = 256; + dvbdemux->start_feed = pluto_start_feed; + dvbdemux->stop_feed = pluto_stop_feed; + dvbdemux->dmx.capabilities = (DMX_TS_FILTERING | + DMX_SECTION_FILTERING | DMX_MEMORY_BASED_FILTERING); + ret = dvb_dmx_init(dvbdemux); + if (ret < 0) + goto err_dvb_unregister_adapter; + + dmx = &dvbdemux->dmx; + + pluto->hw_frontend.source = DMX_FRONTEND_0; + pluto->mem_frontend.source = DMX_MEMORY_FE; + pluto->dmxdev.filternum = NHWFILTERS; + pluto->dmxdev.demux = dmx; + + ret = dvb_dmxdev_init(&pluto->dmxdev, dvb_adapter); + if (ret < 0) + goto err_dvb_dmx_release; + + ret = dmx->add_frontend(dmx, &pluto->hw_frontend); + if (ret < 0) + goto err_dvb_dmxdev_release; + + ret = dmx->add_frontend(dmx, &pluto->mem_frontend); + if (ret < 0) + goto err_remove_hw_frontend; + + ret = dmx->connect_frontend(dmx, &pluto->hw_frontend); + if (ret < 0) + goto err_remove_mem_frontend; + + ret = frontend_init(pluto); + if (ret < 0) + goto err_disconnect_frontend; + + dvb_net_init(dvb_adapter, &pluto->dvbnet, dmx); +out: + return ret; + +err_disconnect_frontend: + dmx->disconnect_frontend(dmx); +err_remove_mem_frontend: + dmx->remove_frontend(dmx, &pluto->mem_frontend); +err_remove_hw_frontend: + dmx->remove_frontend(dmx, &pluto->hw_frontend); +err_dvb_dmxdev_release: + dvb_dmxdev_release(&pluto->dmxdev); +err_dvb_dmx_release: + dvb_dmx_release(dvbdemux); +err_dvb_unregister_adapter: + dvb_unregister_adapter(dvb_adapter); +err_i2c_del_adapter: + i2c_del_adapter(&pluto->i2c_adap); +err_pluto_hw_exit: + pluto_hw_exit(pluto); +err_free_irq: + free_irq(pdev->irq, pluto); +err_pci_iounmap: + pci_iounmap(pdev, pluto->io_mem); +err_pci_release_regions: + pci_release_regions(pdev); +err_pci_disable_device: + pci_disable_device(pdev); +err_kfree: + kfree(pluto); + goto out; +} + +static void pluto2_remove(struct pci_dev *pdev) +{ + struct pluto *pluto = pci_get_drvdata(pdev); + struct dvb_adapter *dvb_adapter = &pluto->dvb_adapter; + struct dvb_demux *dvbdemux = &pluto->demux; + struct dmx_demux *dmx = &dvbdemux->dmx; + + dmx->close(dmx); + dvb_net_release(&pluto->dvbnet); + if (pluto->fe) + dvb_unregister_frontend(pluto->fe); + + dmx->disconnect_frontend(dmx); + dmx->remove_frontend(dmx, &pluto->mem_frontend); + dmx->remove_frontend(dmx, &pluto->hw_frontend); + dvb_dmxdev_release(&pluto->dmxdev); + dvb_dmx_release(dvbdemux); + dvb_unregister_adapter(dvb_adapter); + i2c_del_adapter(&pluto->i2c_adap); + pluto_hw_exit(pluto); + free_irq(pdev->irq, pluto); + pci_iounmap(pdev, pluto->io_mem); + pci_release_regions(pdev); + pci_disable_device(pdev); + kfree(pluto); +} + +#ifndef PCI_VENDOR_ID_SCM +#define PCI_VENDOR_ID_SCM 0x0432 +#endif +#ifndef PCI_DEVICE_ID_PLUTO2 +#define PCI_DEVICE_ID_PLUTO2 0x0001 +#endif + +static const struct pci_device_id pluto2_id_table[] = { + { + PCI_VDEVICE(SCM, PCI_DEVICE_ID_PLUTO2), + }, { + /* empty */ + }, +}; + +MODULE_DEVICE_TABLE(pci, pluto2_id_table); + +static struct pci_driver pluto2_driver = { + .name = DRIVER_NAME, + .id_table = pluto2_id_table, + .probe = pluto2_probe, + .remove = pluto2_remove, +}; + +module_pci_driver(pluto2_driver); + +MODULE_AUTHOR("Andreas Oberritter "); +MODULE_DESCRIPTION("Pluto2 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/pt1/Kconfig b/drivers/media/pci/pt1/Kconfig new file mode 100644 index 000000000..5c524728f --- /dev/null +++ b/drivers/media/pci/pt1/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DVB_PT1 + tristate "PT1 cards" + depends on DVB_CORE && PCI && I2C + select DVB_TC90522 if MEDIA_SUBDRV_AUTOSELECT + select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_QM1D1B0004 if MEDIA_SUBDRV_AUTOSELECT + help + Support for Earthsoft PT1 PCI cards. + + Since these cards have no MPEG decoder onboard, they transmit + only compressed MPEG data over the PCI bus, so you need + an external software decoder to watch TV on your computer. + + Say Y or M if you own such a device and want to use it. + diff --git a/drivers/media/pci/pt1/Makefile b/drivers/media/pci/pt1/Makefile new file mode 100644 index 000000000..45b21a923 --- /dev/null +++ b/drivers/media/pci/pt1/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +earth-pt1-objs := pt1.o + +obj-$(CONFIG_DVB_PT1) += earth-pt1.o + +ccflags-y += -I $(srctree)/drivers/media/dvb-frontends +ccflags-y += -I $(srctree)/drivers/media/tuners diff --git a/drivers/media/pci/pt1/pt1.c b/drivers/media/pci/pt1/pt1.c new file mode 100644 index 000000000..7cce6576b --- /dev/null +++ b/drivers/media/pci/pt1/pt1.c @@ -0,0 +1,1482 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * driver for Earthsoft PT1/PT2 + * + * Copyright (C) 2009 HIRANO Takahito + * + * based on pt1dvr - http://pt1dvr.sourceforge.jp/ + * by Tomoaki Ishikawa + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tc90522.h" +#include "qm1d1b0004.h" +#include "dvb-pll.h" + +#define DRIVER_NAME "earth-pt1" + +#define PT1_PAGE_SHIFT 12 +#define PT1_PAGE_SIZE (1 << PT1_PAGE_SHIFT) +#define PT1_NR_UPACKETS 1024 +#define PT1_NR_BUFS 511 + +struct pt1_buffer_page { + __le32 upackets[PT1_NR_UPACKETS]; +}; + +struct pt1_table_page { + __le32 next_pfn; + __le32 buf_pfns[PT1_NR_BUFS]; +}; + +struct pt1_buffer { + struct pt1_buffer_page *page; + dma_addr_t addr; +}; + +struct pt1_table { + struct pt1_table_page *page; + dma_addr_t addr; + struct pt1_buffer bufs[PT1_NR_BUFS]; +}; + +enum pt1_fe_clk { + PT1_FE_CLK_20MHZ, /* PT1 */ + PT1_FE_CLK_25MHZ, /* PT2 */ +}; + +#define PT1_NR_ADAPS 4 + +struct pt1_adapter; + +struct pt1 { + struct pci_dev *pdev; + void __iomem *regs; + struct i2c_adapter i2c_adap; + int i2c_running; + struct pt1_adapter *adaps[PT1_NR_ADAPS]; + struct pt1_table *tables; + struct task_struct *kthread; + int table_index; + int buf_index; + + struct mutex lock; + int power; + int reset; + + enum pt1_fe_clk fe_clk; +}; + +struct pt1_adapter { + struct pt1 *pt1; + int index; + + u8 *buf; + int upacket_count; + int packet_count; + int st_count; + + struct dvb_adapter adap; + struct dvb_demux demux; + int users; + struct dmxdev dmxdev; + struct dvb_frontend *fe; + struct i2c_client *demod_i2c_client; + struct i2c_client *tuner_i2c_client; + int (*orig_set_voltage)(struct dvb_frontend *fe, + enum fe_sec_voltage voltage); + int (*orig_sleep)(struct dvb_frontend *fe); + int (*orig_init)(struct dvb_frontend *fe); + + enum fe_sec_voltage voltage; + int sleep; +}; + +union pt1_tuner_config { + struct qm1d1b0004_config qm1d1b0004; + struct dvb_pll_config tda6651; +}; + +struct pt1_config { + struct i2c_board_info demod_info; + struct tc90522_config demod_cfg; + + struct i2c_board_info tuner_info; + union pt1_tuner_config tuner_cfg; +}; + +static const struct pt1_config pt1_configs[PT1_NR_ADAPS] = { + { + .demod_info = { + I2C_BOARD_INFO(TC90522_I2C_DEV_SAT, 0x1b), + }, + .tuner_info = { + I2C_BOARD_INFO("qm1d1b0004", 0x60), + }, + }, + { + .demod_info = { + I2C_BOARD_INFO(TC90522_I2C_DEV_TER, 0x1a), + }, + .tuner_info = { + I2C_BOARD_INFO("tda665x_earthpt1", 0x61), + }, + }, + { + .demod_info = { + I2C_BOARD_INFO(TC90522_I2C_DEV_SAT, 0x19), + }, + .tuner_info = { + I2C_BOARD_INFO("qm1d1b0004", 0x60), + }, + }, + { + .demod_info = { + I2C_BOARD_INFO(TC90522_I2C_DEV_TER, 0x18), + }, + .tuner_info = { + I2C_BOARD_INFO("tda665x_earthpt1", 0x61), + }, + }, +}; + +static const u8 va1j5jf8007s_20mhz_configs[][2] = { + {0x04, 0x02}, {0x0d, 0x55}, {0x11, 0x40}, {0x13, 0x80}, {0x17, 0x01}, + {0x1c, 0x0a}, {0x1d, 0xaa}, {0x1e, 0x20}, {0x1f, 0x88}, {0x51, 0xb0}, + {0x52, 0x89}, {0x53, 0xb3}, {0x5a, 0x2d}, {0x5b, 0xd3}, {0x85, 0x69}, + {0x87, 0x04}, {0x8e, 0x02}, {0xa3, 0xf7}, {0xa5, 0xc0}, +}; + +static const u8 va1j5jf8007s_25mhz_configs[][2] = { + {0x04, 0x02}, {0x11, 0x40}, {0x13, 0x80}, {0x17, 0x01}, {0x1c, 0x0a}, + {0x1d, 0xaa}, {0x1e, 0x20}, {0x1f, 0x88}, {0x51, 0xb0}, {0x52, 0x89}, + {0x53, 0xb3}, {0x5a, 0x2d}, {0x5b, 0xd3}, {0x85, 0x69}, {0x87, 0x04}, + {0x8e, 0x26}, {0xa3, 0xf7}, {0xa5, 0xc0}, +}; + +static const u8 va1j5jf8007t_20mhz_configs[][2] = { + {0x03, 0x90}, {0x14, 0x8f}, {0x1c, 0x2a}, {0x1d, 0xa8}, {0x1e, 0xa2}, + {0x22, 0x83}, {0x31, 0x0d}, {0x32, 0xe0}, {0x39, 0xd3}, {0x3a, 0x00}, + {0x3b, 0x11}, {0x3c, 0x3f}, + {0x5c, 0x40}, {0x5f, 0x80}, {0x75, 0x02}, {0x76, 0x4e}, {0x77, 0x03}, + {0xef, 0x01} +}; + +static const u8 va1j5jf8007t_25mhz_configs[][2] = { + {0x03, 0x90}, {0x1c, 0x2a}, {0x1d, 0xa8}, {0x1e, 0xa2}, {0x22, 0x83}, + {0x3a, 0x04}, {0x3b, 0x11}, {0x3c, 0x3f}, {0x5c, 0x40}, {0x5f, 0x80}, + {0x75, 0x0a}, {0x76, 0x4c}, {0x77, 0x03}, {0xef, 0x01} +}; + +static int config_demod(struct i2c_client *cl, enum pt1_fe_clk clk) +{ + int ret; + bool is_sat; + const u8 (*cfg_data)[2]; + int i, len; + + is_sat = !strncmp(cl->name, TC90522_I2C_DEV_SAT, + strlen(TC90522_I2C_DEV_SAT)); + if (is_sat) { + struct i2c_msg msg[2]; + u8 wbuf, rbuf; + + wbuf = 0x07; + msg[0].addr = cl->addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = &wbuf; + + msg[1].addr = cl->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 1; + msg[1].buf = &rbuf; + ret = i2c_transfer(cl->adapter, msg, 2); + if (ret < 0) + return ret; + if (rbuf != 0x41) + return -EIO; + } + + /* frontend init */ + if (clk == PT1_FE_CLK_20MHZ) { + if (is_sat) { + cfg_data = va1j5jf8007s_20mhz_configs; + len = ARRAY_SIZE(va1j5jf8007s_20mhz_configs); + } else { + cfg_data = va1j5jf8007t_20mhz_configs; + len = ARRAY_SIZE(va1j5jf8007t_20mhz_configs); + } + } else { + if (is_sat) { + cfg_data = va1j5jf8007s_25mhz_configs; + len = ARRAY_SIZE(va1j5jf8007s_25mhz_configs); + } else { + cfg_data = va1j5jf8007t_25mhz_configs; + len = ARRAY_SIZE(va1j5jf8007t_25mhz_configs); + } + } + + for (i = 0; i < len; i++) { + ret = i2c_master_send(cl, cfg_data[i], 2); + if (ret < 0) + return ret; + } + return 0; +} + +/* + * Init registers for (each pair of) terrestrial/satellite block in demod. + * Note that resetting terr. block also resets its peer sat. block as well. + * This function must be called before configuring any demod block + * (before pt1_wakeup(), fe->ops.init()). + */ +static int pt1_demod_block_init(struct pt1 *pt1) +{ + struct i2c_client *cl; + u8 buf[2] = {0x01, 0x80}; + int ret; + int i; + + /* reset all terr. & sat. pairs first */ + for (i = 0; i < PT1_NR_ADAPS; i++) { + cl = pt1->adaps[i]->demod_i2c_client; + if (strncmp(cl->name, TC90522_I2C_DEV_TER, + strlen(TC90522_I2C_DEV_TER))) + continue; + + ret = i2c_master_send(cl, buf, 2); + if (ret < 0) + return ret; + usleep_range(30000, 50000); + } + + for (i = 0; i < PT1_NR_ADAPS; i++) { + cl = pt1->adaps[i]->demod_i2c_client; + if (strncmp(cl->name, TC90522_I2C_DEV_SAT, + strlen(TC90522_I2C_DEV_SAT))) + continue; + + ret = i2c_master_send(cl, buf, 2); + if (ret < 0) + return ret; + usleep_range(30000, 50000); + } + return 0; +} + +static void pt1_write_reg(struct pt1 *pt1, int reg, u32 data) +{ + writel(data, pt1->regs + reg * 4); +} + +static u32 pt1_read_reg(struct pt1 *pt1, int reg) +{ + return readl(pt1->regs + reg * 4); +} + +static unsigned int pt1_nr_tables = 8; +module_param_named(nr_tables, pt1_nr_tables, uint, 0); + +static void pt1_increment_table_count(struct pt1 *pt1) +{ + pt1_write_reg(pt1, 0, 0x00000020); +} + +static void pt1_init_table_count(struct pt1 *pt1) +{ + pt1_write_reg(pt1, 0, 0x00000010); +} + +static void pt1_register_tables(struct pt1 *pt1, u32 first_pfn) +{ + pt1_write_reg(pt1, 5, first_pfn); + pt1_write_reg(pt1, 0, 0x0c000040); +} + +static void pt1_unregister_tables(struct pt1 *pt1) +{ + pt1_write_reg(pt1, 0, 0x08080000); +} + +static int pt1_sync(struct pt1 *pt1) +{ + int i; + for (i = 0; i < 57; i++) { + if (pt1_read_reg(pt1, 0) & 0x20000000) + return 0; + pt1_write_reg(pt1, 0, 0x00000008); + } + dev_err(&pt1->pdev->dev, "could not sync\n"); + return -EIO; +} + +static u64 pt1_identify(struct pt1 *pt1) +{ + int i; + u64 id = 0; + for (i = 0; i < 57; i++) { + id |= (u64)(pt1_read_reg(pt1, 0) >> 30 & 1) << i; + pt1_write_reg(pt1, 0, 0x00000008); + } + return id; +} + +static int pt1_unlock(struct pt1 *pt1) +{ + int i; + pt1_write_reg(pt1, 0, 0x00000008); + for (i = 0; i < 3; i++) { + if (pt1_read_reg(pt1, 0) & 0x80000000) + return 0; + usleep_range(1000, 2000); + } + dev_err(&pt1->pdev->dev, "could not unlock\n"); + return -EIO; +} + +static int pt1_reset_pci(struct pt1 *pt1) +{ + int i; + pt1_write_reg(pt1, 0, 0x01010000); + pt1_write_reg(pt1, 0, 0x01000000); + for (i = 0; i < 10; i++) { + if (pt1_read_reg(pt1, 0) & 0x00000001) + return 0; + usleep_range(1000, 2000); + } + dev_err(&pt1->pdev->dev, "could not reset PCI\n"); + return -EIO; +} + +static int pt1_reset_ram(struct pt1 *pt1) +{ + int i; + pt1_write_reg(pt1, 0, 0x02020000); + pt1_write_reg(pt1, 0, 0x02000000); + for (i = 0; i < 10; i++) { + if (pt1_read_reg(pt1, 0) & 0x00000002) + return 0; + usleep_range(1000, 2000); + } + dev_err(&pt1->pdev->dev, "could not reset RAM\n"); + return -EIO; +} + +static int pt1_do_enable_ram(struct pt1 *pt1) +{ + int i, j; + u32 status; + status = pt1_read_reg(pt1, 0) & 0x00000004; + pt1_write_reg(pt1, 0, 0x00000002); + for (i = 0; i < 10; i++) { + for (j = 0; j < 1024; j++) { + if ((pt1_read_reg(pt1, 0) & 0x00000004) != status) + return 0; + } + usleep_range(1000, 2000); + } + dev_err(&pt1->pdev->dev, "could not enable RAM\n"); + return -EIO; +} + +static int pt1_enable_ram(struct pt1 *pt1) +{ + int i, ret; + int phase; + usleep_range(1000, 2000); + phase = pt1->pdev->device == 0x211a ? 128 : 166; + for (i = 0; i < phase; i++) { + ret = pt1_do_enable_ram(pt1); + if (ret < 0) + return ret; + } + return 0; +} + +static void pt1_disable_ram(struct pt1 *pt1) +{ + pt1_write_reg(pt1, 0, 0x0b0b0000); +} + +static void pt1_set_stream(struct pt1 *pt1, int index, int enabled) +{ + pt1_write_reg(pt1, 2, 1 << (index + 8) | enabled << index); +} + +static void pt1_init_streams(struct pt1 *pt1) +{ + int i; + for (i = 0; i < PT1_NR_ADAPS; i++) + pt1_set_stream(pt1, i, 0); +} + +static int pt1_filter(struct pt1 *pt1, struct pt1_buffer_page *page) +{ + u32 upacket; + int i; + int index; + struct pt1_adapter *adap; + int offset; + u8 *buf; + int sc; + + if (!page->upackets[PT1_NR_UPACKETS - 1]) + return 0; + + for (i = 0; i < PT1_NR_UPACKETS; i++) { + upacket = le32_to_cpu(page->upackets[i]); + index = (upacket >> 29) - 1; + if (index < 0 || index >= PT1_NR_ADAPS) + continue; + + adap = pt1->adaps[index]; + if (upacket >> 25 & 1) + adap->upacket_count = 0; + else if (!adap->upacket_count) + continue; + + if (upacket >> 24 & 1) + printk_ratelimited(KERN_INFO "earth-pt1: device buffer overflowing. table[%d] buf[%d]\n", + pt1->table_index, pt1->buf_index); + sc = upacket >> 26 & 0x7; + if (adap->st_count != -1 && sc != ((adap->st_count + 1) & 0x7)) + printk_ratelimited(KERN_INFO "earth-pt1: data loss in streamID(adapter)[%d]\n", + index); + adap->st_count = sc; + + buf = adap->buf; + offset = adap->packet_count * 188 + adap->upacket_count * 3; + buf[offset] = upacket >> 16; + buf[offset + 1] = upacket >> 8; + if (adap->upacket_count != 62) + buf[offset + 2] = upacket; + + if (++adap->upacket_count >= 63) { + adap->upacket_count = 0; + if (++adap->packet_count >= 21) { + dvb_dmx_swfilter_packets(&adap->demux, buf, 21); + adap->packet_count = 0; + } + } + } + + page->upackets[PT1_NR_UPACKETS - 1] = 0; + return 1; +} + +static int pt1_thread(void *data) +{ + struct pt1 *pt1; + struct pt1_buffer_page *page; + bool was_frozen; + +#define PT1_FETCH_DELAY 10 +#define PT1_FETCH_DELAY_DELTA 2 + + pt1 = data; + set_freezable(); + + while (!kthread_freezable_should_stop(&was_frozen)) { + if (was_frozen) { + int i; + + for (i = 0; i < PT1_NR_ADAPS; i++) + pt1_set_stream(pt1, i, !!pt1->adaps[i]->users); + } + + page = pt1->tables[pt1->table_index].bufs[pt1->buf_index].page; + if (!pt1_filter(pt1, page)) { + ktime_t delay; + + delay = ktime_set(0, PT1_FETCH_DELAY * NSEC_PER_MSEC); + set_current_state(TASK_INTERRUPTIBLE); + schedule_hrtimeout_range(&delay, + PT1_FETCH_DELAY_DELTA * NSEC_PER_MSEC, + HRTIMER_MODE_REL); + continue; + } + + if (++pt1->buf_index >= PT1_NR_BUFS) { + pt1_increment_table_count(pt1); + pt1->buf_index = 0; + if (++pt1->table_index >= pt1_nr_tables) + pt1->table_index = 0; + } + } + + return 0; +} + +static void pt1_free_page(struct pt1 *pt1, void *page, dma_addr_t addr) +{ + dma_free_coherent(&pt1->pdev->dev, PT1_PAGE_SIZE, page, addr); +} + +static void *pt1_alloc_page(struct pt1 *pt1, dma_addr_t *addrp, u32 *pfnp) +{ + void *page; + dma_addr_t addr; + + page = dma_alloc_coherent(&pt1->pdev->dev, PT1_PAGE_SIZE, &addr, + GFP_KERNEL); + if (page == NULL) + return NULL; + + BUG_ON(addr & (PT1_PAGE_SIZE - 1)); + BUG_ON(addr >> PT1_PAGE_SHIFT >> 31 >> 1); + + *addrp = addr; + *pfnp = addr >> PT1_PAGE_SHIFT; + return page; +} + +static void pt1_cleanup_buffer(struct pt1 *pt1, struct pt1_buffer *buf) +{ + pt1_free_page(pt1, buf->page, buf->addr); +} + +static int +pt1_init_buffer(struct pt1 *pt1, struct pt1_buffer *buf, u32 *pfnp) +{ + struct pt1_buffer_page *page; + dma_addr_t addr; + + page = pt1_alloc_page(pt1, &addr, pfnp); + if (page == NULL) + return -ENOMEM; + + page->upackets[PT1_NR_UPACKETS - 1] = 0; + + buf->page = page; + buf->addr = addr; + return 0; +} + +static void pt1_cleanup_table(struct pt1 *pt1, struct pt1_table *table) +{ + int i; + + for (i = 0; i < PT1_NR_BUFS; i++) + pt1_cleanup_buffer(pt1, &table->bufs[i]); + + pt1_free_page(pt1, table->page, table->addr); +} + +static int +pt1_init_table(struct pt1 *pt1, struct pt1_table *table, u32 *pfnp) +{ + struct pt1_table_page *page; + dma_addr_t addr; + int i, ret; + u32 buf_pfn; + + page = pt1_alloc_page(pt1, &addr, pfnp); + if (page == NULL) + return -ENOMEM; + + for (i = 0; i < PT1_NR_BUFS; i++) { + ret = pt1_init_buffer(pt1, &table->bufs[i], &buf_pfn); + if (ret < 0) + goto err; + + page->buf_pfns[i] = cpu_to_le32(buf_pfn); + } + + pt1_increment_table_count(pt1); + table->page = page; + table->addr = addr; + return 0; + +err: + while (i--) + pt1_cleanup_buffer(pt1, &table->bufs[i]); + + pt1_free_page(pt1, page, addr); + return ret; +} + +static void pt1_cleanup_tables(struct pt1 *pt1) +{ + struct pt1_table *tables; + int i; + + tables = pt1->tables; + pt1_unregister_tables(pt1); + + for (i = 0; i < pt1_nr_tables; i++) + pt1_cleanup_table(pt1, &tables[i]); + + vfree(tables); +} + +static int pt1_init_tables(struct pt1 *pt1) +{ + struct pt1_table *tables; + int i, ret; + u32 first_pfn, pfn; + + if (!pt1_nr_tables) + return 0; + + tables = vmalloc_array(pt1_nr_tables, sizeof(struct pt1_table)); + if (tables == NULL) + return -ENOMEM; + + pt1_init_table_count(pt1); + + i = 0; + ret = pt1_init_table(pt1, &tables[0], &first_pfn); + if (ret) + goto err; + i++; + + while (i < pt1_nr_tables) { + ret = pt1_init_table(pt1, &tables[i], &pfn); + if (ret) + goto err; + tables[i - 1].page->next_pfn = cpu_to_le32(pfn); + i++; + } + + tables[pt1_nr_tables - 1].page->next_pfn = cpu_to_le32(first_pfn); + + pt1_register_tables(pt1, first_pfn); + pt1->tables = tables; + return 0; + +err: + while (i--) + pt1_cleanup_table(pt1, &tables[i]); + + vfree(tables); + return ret; +} + +static int pt1_start_polling(struct pt1 *pt1) +{ + int ret = 0; + + mutex_lock(&pt1->lock); + if (!pt1->kthread) { + pt1->kthread = kthread_run(pt1_thread, pt1, "earth-pt1"); + if (IS_ERR(pt1->kthread)) { + ret = PTR_ERR(pt1->kthread); + pt1->kthread = NULL; + } + } + mutex_unlock(&pt1->lock); + return ret; +} + +static int pt1_start_feed(struct dvb_demux_feed *feed) +{ + struct pt1_adapter *adap; + adap = container_of(feed->demux, struct pt1_adapter, demux); + if (!adap->users++) { + int ret; + + ret = pt1_start_polling(adap->pt1); + if (ret) + return ret; + pt1_set_stream(adap->pt1, adap->index, 1); + } + return 0; +} + +static void pt1_stop_polling(struct pt1 *pt1) +{ + int i, count; + + mutex_lock(&pt1->lock); + for (i = 0, count = 0; i < PT1_NR_ADAPS; i++) + count += pt1->adaps[i]->users; + + if (count == 0 && pt1->kthread) { + kthread_stop(pt1->kthread); + pt1->kthread = NULL; + } + mutex_unlock(&pt1->lock); +} + +static int pt1_stop_feed(struct dvb_demux_feed *feed) +{ + struct pt1_adapter *adap; + adap = container_of(feed->demux, struct pt1_adapter, demux); + if (!--adap->users) { + pt1_set_stream(adap->pt1, adap->index, 0); + pt1_stop_polling(adap->pt1); + } + return 0; +} + +static void +pt1_update_power(struct pt1 *pt1) +{ + int bits; + int i; + struct pt1_adapter *adap; + static const int sleep_bits[] = { + 1 << 4, + 1 << 6 | 1 << 7, + 1 << 5, + 1 << 6 | 1 << 8, + }; + + bits = pt1->power | !pt1->reset << 3; + mutex_lock(&pt1->lock); + for (i = 0; i < PT1_NR_ADAPS; i++) { + adap = pt1->adaps[i]; + switch (adap->voltage) { + case SEC_VOLTAGE_13: /* actually 11V */ + bits |= 1 << 2; + break; + case SEC_VOLTAGE_18: /* actually 15V */ + bits |= 1 << 1 | 1 << 2; + break; + default: + break; + } + + /* XXX: The bits should be changed depending on adap->sleep. */ + bits |= sleep_bits[i]; + } + pt1_write_reg(pt1, 1, bits); + mutex_unlock(&pt1->lock); +} + +static int pt1_set_voltage(struct dvb_frontend *fe, enum fe_sec_voltage voltage) +{ + struct pt1_adapter *adap; + + adap = container_of(fe->dvb, struct pt1_adapter, adap); + adap->voltage = voltage; + pt1_update_power(adap->pt1); + + if (adap->orig_set_voltage) + return adap->orig_set_voltage(fe, voltage); + else + return 0; +} + +static int pt1_sleep(struct dvb_frontend *fe) +{ + struct pt1_adapter *adap; + int ret; + + adap = container_of(fe->dvb, struct pt1_adapter, adap); + + ret = 0; + if (adap->orig_sleep) + ret = adap->orig_sleep(fe); + + adap->sleep = 1; + pt1_update_power(adap->pt1); + return ret; +} + +static int pt1_wakeup(struct dvb_frontend *fe) +{ + struct pt1_adapter *adap; + int ret; + + adap = container_of(fe->dvb, struct pt1_adapter, adap); + adap->sleep = 0; + pt1_update_power(adap->pt1); + usleep_range(1000, 2000); + + ret = config_demod(adap->demod_i2c_client, adap->pt1->fe_clk); + if (ret == 0 && adap->orig_init) + ret = adap->orig_init(fe); + return ret; +} + +static void pt1_free_adapter(struct pt1_adapter *adap) +{ + adap->demux.dmx.close(&adap->demux.dmx); + dvb_dmxdev_release(&adap->dmxdev); + dvb_dmx_release(&adap->demux); + dvb_unregister_adapter(&adap->adap); + free_page((unsigned long)adap->buf); + kfree(adap); +} + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static struct pt1_adapter * +pt1_alloc_adapter(struct pt1 *pt1) +{ + struct pt1_adapter *adap; + void *buf; + struct dvb_adapter *dvb_adap; + struct dvb_demux *demux; + struct dmxdev *dmxdev; + int ret; + + adap = kzalloc_obj(struct pt1_adapter); + if (!adap) { + ret = -ENOMEM; + goto err; + } + + adap->pt1 = pt1; + + adap->voltage = SEC_VOLTAGE_OFF; + adap->sleep = 1; + + buf = (u8 *)__get_free_page(GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto err_kfree; + } + + adap->buf = buf; + adap->upacket_count = 0; + adap->packet_count = 0; + adap->st_count = -1; + + dvb_adap = &adap->adap; + dvb_adap->priv = adap; + ret = dvb_register_adapter(dvb_adap, DRIVER_NAME, THIS_MODULE, + &pt1->pdev->dev, adapter_nr); + if (ret < 0) + goto err_free_page; + + demux = &adap->demux; + demux->dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING; + demux->priv = adap; + demux->feednum = 256; + demux->filternum = 256; + demux->start_feed = pt1_start_feed; + demux->stop_feed = pt1_stop_feed; + demux->write_to_decoder = NULL; + ret = dvb_dmx_init(demux); + if (ret < 0) + goto err_unregister_adapter; + + dmxdev = &adap->dmxdev; + dmxdev->filternum = 256; + dmxdev->demux = &demux->dmx; + dmxdev->capabilities = 0; + ret = dvb_dmxdev_init(dmxdev, dvb_adap); + if (ret < 0) + goto err_dmx_release; + + return adap; + +err_dmx_release: + dvb_dmx_release(demux); +err_unregister_adapter: + dvb_unregister_adapter(dvb_adap); +err_free_page: + free_page((unsigned long)buf); +err_kfree: + kfree(adap); +err: + return ERR_PTR(ret); +} + +static void pt1_cleanup_adapters(struct pt1 *pt1) +{ + int i; + for (i = 0; i < PT1_NR_ADAPS; i++) + pt1_free_adapter(pt1->adaps[i]); +} + +static int pt1_init_adapters(struct pt1 *pt1) +{ + int i; + struct pt1_adapter *adap; + int ret; + + for (i = 0; i < PT1_NR_ADAPS; i++) { + adap = pt1_alloc_adapter(pt1); + if (IS_ERR(adap)) { + ret = PTR_ERR(adap); + goto err; + } + + adap->index = i; + pt1->adaps[i] = adap; + } + return 0; + +err: + while (i--) + pt1_free_adapter(pt1->adaps[i]); + + return ret; +} + +static void pt1_cleanup_frontend(struct pt1_adapter *adap) +{ + dvb_unregister_frontend(adap->fe); + dvb_module_release(adap->tuner_i2c_client); + dvb_module_release(adap->demod_i2c_client); +} + +static int pt1_init_frontend(struct pt1_adapter *adap, struct dvb_frontend *fe) +{ + int ret; + + adap->orig_set_voltage = fe->ops.set_voltage; + adap->orig_sleep = fe->ops.sleep; + adap->orig_init = fe->ops.init; + fe->ops.set_voltage = pt1_set_voltage; + fe->ops.sleep = pt1_sleep; + fe->ops.init = pt1_wakeup; + + ret = dvb_register_frontend(&adap->adap, fe); + if (ret < 0) + return ret; + + adap->fe = fe; + return 0; +} + +static void pt1_cleanup_frontends(struct pt1 *pt1) +{ + int i; + for (i = 0; i < PT1_NR_ADAPS; i++) + pt1_cleanup_frontend(pt1->adaps[i]); +} + +static int pt1_init_frontends(struct pt1 *pt1) +{ + int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(pt1_configs); i++) { + const struct i2c_board_info *info; + struct tc90522_config dcfg; + struct i2c_client *cl; + + info = &pt1_configs[i].demod_info; + dcfg = pt1_configs[i].demod_cfg; + dcfg.tuner_i2c = NULL; + + ret = -ENODEV; + cl = dvb_module_probe("tc90522", info->type, &pt1->i2c_adap, + info->addr, &dcfg); + if (!cl) + goto fe_unregister; + pt1->adaps[i]->demod_i2c_client = cl; + + if (!strncmp(cl->name, TC90522_I2C_DEV_SAT, + strlen(TC90522_I2C_DEV_SAT))) { + struct qm1d1b0004_config tcfg; + + info = &pt1_configs[i].tuner_info; + tcfg = pt1_configs[i].tuner_cfg.qm1d1b0004; + tcfg.fe = dcfg.fe; + cl = dvb_module_probe("qm1d1b0004", + info->type, dcfg.tuner_i2c, + info->addr, &tcfg); + } else { + struct dvb_pll_config tcfg; + + info = &pt1_configs[i].tuner_info; + tcfg = pt1_configs[i].tuner_cfg.tda6651; + tcfg.fe = dcfg.fe; + cl = dvb_module_probe("dvb_pll", + info->type, dcfg.tuner_i2c, + info->addr, &tcfg); + } + if (!cl) + goto demod_release; + pt1->adaps[i]->tuner_i2c_client = cl; + + ret = pt1_init_frontend(pt1->adaps[i], dcfg.fe); + if (ret < 0) + goto tuner_release; + } + + ret = pt1_demod_block_init(pt1); + if (ret < 0) + goto fe_unregister; + + return 0; + +tuner_release: + dvb_module_release(pt1->adaps[i]->tuner_i2c_client); +demod_release: + dvb_module_release(pt1->adaps[i]->demod_i2c_client); +fe_unregister: + dev_warn(&pt1->pdev->dev, "failed to init FE(%d).\n", i); + i--; + for (; i >= 0; i--) { + dvb_unregister_frontend(pt1->adaps[i]->fe); + dvb_module_release(pt1->adaps[i]->tuner_i2c_client); + dvb_module_release(pt1->adaps[i]->demod_i2c_client); + } + return ret; +} + +static void pt1_i2c_emit(struct pt1 *pt1, int addr, int busy, int read_enable, + int clock, int data, int next_addr) +{ + pt1_write_reg(pt1, 4, addr << 18 | busy << 13 | read_enable << 12 | + !clock << 11 | !data << 10 | next_addr); +} + +static void pt1_i2c_write_bit(struct pt1 *pt1, int addr, int *addrp, int data) +{ + pt1_i2c_emit(pt1, addr, 1, 0, 0, data, addr + 1); + pt1_i2c_emit(pt1, addr + 1, 1, 0, 1, data, addr + 2); + pt1_i2c_emit(pt1, addr + 2, 1, 0, 0, data, addr + 3); + *addrp = addr + 3; +} + +static void pt1_i2c_read_bit(struct pt1 *pt1, int addr, int *addrp) +{ + pt1_i2c_emit(pt1, addr, 1, 0, 0, 1, addr + 1); + pt1_i2c_emit(pt1, addr + 1, 1, 0, 1, 1, addr + 2); + pt1_i2c_emit(pt1, addr + 2, 1, 1, 1, 1, addr + 3); + pt1_i2c_emit(pt1, addr + 3, 1, 0, 0, 1, addr + 4); + *addrp = addr + 4; +} + +static void pt1_i2c_write_byte(struct pt1 *pt1, int addr, int *addrp, int data) +{ + int i; + for (i = 0; i < 8; i++) + pt1_i2c_write_bit(pt1, addr, &addr, data >> (7 - i) & 1); + pt1_i2c_write_bit(pt1, addr, &addr, 1); + *addrp = addr; +} + +static void pt1_i2c_read_byte(struct pt1 *pt1, int addr, int *addrp, int last) +{ + int i; + for (i = 0; i < 8; i++) + pt1_i2c_read_bit(pt1, addr, &addr); + pt1_i2c_write_bit(pt1, addr, &addr, last); + *addrp = addr; +} + +static void pt1_i2c_prepare(struct pt1 *pt1, int addr, int *addrp) +{ + pt1_i2c_emit(pt1, addr, 1, 0, 1, 1, addr + 1); + pt1_i2c_emit(pt1, addr + 1, 1, 0, 1, 0, addr + 2); + pt1_i2c_emit(pt1, addr + 2, 1, 0, 0, 0, addr + 3); + *addrp = addr + 3; +} + +static void +pt1_i2c_write_msg(struct pt1 *pt1, int addr, int *addrp, struct i2c_msg *msg) +{ + int i; + pt1_i2c_prepare(pt1, addr, &addr); + pt1_i2c_write_byte(pt1, addr, &addr, msg->addr << 1); + for (i = 0; i < msg->len; i++) + pt1_i2c_write_byte(pt1, addr, &addr, msg->buf[i]); + *addrp = addr; +} + +static void +pt1_i2c_read_msg(struct pt1 *pt1, int addr, int *addrp, struct i2c_msg *msg) +{ + int i; + pt1_i2c_prepare(pt1, addr, &addr); + pt1_i2c_write_byte(pt1, addr, &addr, msg->addr << 1 | 1); + for (i = 0; i < msg->len; i++) + pt1_i2c_read_byte(pt1, addr, &addr, i == msg->len - 1); + *addrp = addr; +} + +static int pt1_i2c_end(struct pt1 *pt1, int addr) +{ + pt1_i2c_emit(pt1, addr, 1, 0, 0, 0, addr + 1); + pt1_i2c_emit(pt1, addr + 1, 1, 0, 1, 0, addr + 2); + pt1_i2c_emit(pt1, addr + 2, 1, 0, 1, 1, 0); + + pt1_write_reg(pt1, 0, 0x00000004); + do { + if (signal_pending(current)) + return -EINTR; + usleep_range(1000, 2000); + } while (pt1_read_reg(pt1, 0) & 0x00000080); + return 0; +} + +static void pt1_i2c_begin(struct pt1 *pt1, int *addrp) +{ + int addr = 0; + + pt1_i2c_emit(pt1, addr, 0, 0, 1, 1, addr /* itself */); + addr = addr + 1; + + if (!pt1->i2c_running) { + pt1_i2c_emit(pt1, addr, 1, 0, 1, 1, addr + 1); + pt1_i2c_emit(pt1, addr + 1, 1, 0, 1, 0, addr + 2); + addr = addr + 2; + pt1->i2c_running = 1; + } + *addrp = addr; +} + +static int pt1_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + struct pt1 *pt1; + int i; + struct i2c_msg *msg, *next_msg; + int addr, ret; + u16 len; + u32 word; + + pt1 = i2c_get_adapdata(adap); + + for (i = 0; i < num; i++) { + msg = &msgs[i]; + if (msg->flags & I2C_M_RD) + return -ENOTSUPP; + + if (i + 1 < num) + next_msg = &msgs[i + 1]; + else + next_msg = NULL; + + if (next_msg && next_msg->flags & I2C_M_RD) { + i++; + + len = next_msg->len; + if (len > 4) + return -ENOTSUPP; + + pt1_i2c_begin(pt1, &addr); + pt1_i2c_write_msg(pt1, addr, &addr, msg); + pt1_i2c_read_msg(pt1, addr, &addr, next_msg); + ret = pt1_i2c_end(pt1, addr); + if (ret < 0) + return ret; + + word = pt1_read_reg(pt1, 2); + while (len--) { + next_msg->buf[len] = word; + word >>= 8; + } + } else { + pt1_i2c_begin(pt1, &addr); + pt1_i2c_write_msg(pt1, addr, &addr, msg); + ret = pt1_i2c_end(pt1, addr); + if (ret < 0) + return ret; + } + } + + return num; +} + +static u32 pt1_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C; +} + +static const struct i2c_algorithm pt1_i2c_algo = { + .master_xfer = pt1_i2c_xfer, + .functionality = pt1_i2c_func, +}; + +static void pt1_i2c_wait(struct pt1 *pt1) +{ + int i; + for (i = 0; i < 128; i++) + pt1_i2c_emit(pt1, 0, 0, 0, 1, 1, 0); +} + +static void pt1_i2c_init(struct pt1 *pt1) +{ + int i; + for (i = 0; i < 1024; i++) + pt1_i2c_emit(pt1, i, 0, 0, 1, 1, 0); +} + +#ifdef CONFIG_PM_SLEEP + +static int pt1_suspend(struct device *dev) +{ + struct pt1 *pt1 = dev_get_drvdata(dev); + + pt1_init_streams(pt1); + pt1_disable_ram(pt1); + pt1->power = 0; + pt1->reset = 1; + pt1_update_power(pt1); + return 0; +} + +static int pt1_resume(struct device *dev) +{ + struct pt1 *pt1 = dev_get_drvdata(dev); + int ret; + int i; + + pt1->power = 0; + pt1->reset = 1; + pt1_update_power(pt1); + + pt1_i2c_init(pt1); + pt1_i2c_wait(pt1); + + ret = pt1_sync(pt1); + if (ret < 0) + goto resume_err; + + pt1_identify(pt1); + + ret = pt1_unlock(pt1); + if (ret < 0) + goto resume_err; + + ret = pt1_reset_pci(pt1); + if (ret < 0) + goto resume_err; + + ret = pt1_reset_ram(pt1); + if (ret < 0) + goto resume_err; + + ret = pt1_enable_ram(pt1); + if (ret < 0) + goto resume_err; + + pt1_init_streams(pt1); + + pt1->power = 1; + pt1_update_power(pt1); + msleep(20); + + pt1->reset = 0; + pt1_update_power(pt1); + usleep_range(1000, 2000); + + ret = pt1_demod_block_init(pt1); + if (ret < 0) + goto resume_err; + + for (i = 0; i < PT1_NR_ADAPS; i++) + dvb_frontend_reinitialise(pt1->adaps[i]->fe); + + pt1_init_table_count(pt1); + for (i = 0; i < pt1_nr_tables; i++) { + int j; + + for (j = 0; j < PT1_NR_BUFS; j++) + pt1->tables[i].bufs[j].page->upackets[PT1_NR_UPACKETS-1] + = 0; + pt1_increment_table_count(pt1); + } + pt1_register_tables(pt1, pt1->tables[0].addr >> PT1_PAGE_SHIFT); + + pt1->table_index = 0; + pt1->buf_index = 0; + for (i = 0; i < PT1_NR_ADAPS; i++) { + pt1->adaps[i]->upacket_count = 0; + pt1->adaps[i]->packet_count = 0; + pt1->adaps[i]->st_count = -1; + } + + return 0; + +resume_err: + dev_info(&pt1->pdev->dev, "failed to resume PT1/PT2."); + return 0; /* resume anyway */ +} + +#endif /* CONFIG_PM_SLEEP */ + +static void pt1_remove(struct pci_dev *pdev) +{ + struct pt1 *pt1; + void __iomem *regs; + + pt1 = pci_get_drvdata(pdev); + regs = pt1->regs; + + if (pt1->kthread) + kthread_stop(pt1->kthread); + pt1_cleanup_tables(pt1); + pt1_cleanup_frontends(pt1); + pt1_disable_ram(pt1); + pt1->power = 0; + pt1->reset = 1; + pt1_update_power(pt1); + pt1_cleanup_adapters(pt1); + i2c_del_adapter(&pt1->i2c_adap); + kfree(pt1); + pci_iounmap(pdev, regs); + pci_release_regions(pdev); + pci_disable_device(pdev); +} + +static int pt1_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int ret; + void __iomem *regs; + struct pt1 *pt1; + struct i2c_adapter *i2c_adap; + + ret = pci_enable_device(pdev); + if (ret < 0) + goto err; + + ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (ret < 0) + goto err_pci_disable_device; + + pci_set_master(pdev); + + ret = pci_request_regions(pdev, DRIVER_NAME); + if (ret < 0) + goto err_pci_disable_device; + + regs = pci_iomap(pdev, 0, 0); + if (!regs) { + ret = -EIO; + goto err_pci_release_regions; + } + + pt1 = kzalloc_obj(struct pt1); + if (!pt1) { + ret = -ENOMEM; + goto err_pci_iounmap; + } + + mutex_init(&pt1->lock); + pt1->pdev = pdev; + pt1->regs = regs; + pt1->fe_clk = (pdev->device == 0x211a) ? + PT1_FE_CLK_20MHZ : PT1_FE_CLK_25MHZ; + pci_set_drvdata(pdev, pt1); + + ret = pt1_init_adapters(pt1); + if (ret < 0) + goto err_kfree; + + mutex_init(&pt1->lock); + + pt1->power = 0; + pt1->reset = 1; + pt1_update_power(pt1); + + i2c_adap = &pt1->i2c_adap; + i2c_adap->algo = &pt1_i2c_algo; + i2c_adap->algo_data = NULL; + i2c_adap->dev.parent = &pdev->dev; + strscpy(i2c_adap->name, DRIVER_NAME, sizeof(i2c_adap->name)); + i2c_set_adapdata(i2c_adap, pt1); + ret = i2c_add_adapter(i2c_adap); + if (ret < 0) + goto err_pt1_cleanup_adapters; + + pt1_i2c_init(pt1); + pt1_i2c_wait(pt1); + + ret = pt1_sync(pt1); + if (ret < 0) + goto err_i2c_del_adapter; + + pt1_identify(pt1); + + ret = pt1_unlock(pt1); + if (ret < 0) + goto err_i2c_del_adapter; + + ret = pt1_reset_pci(pt1); + if (ret < 0) + goto err_i2c_del_adapter; + + ret = pt1_reset_ram(pt1); + if (ret < 0) + goto err_i2c_del_adapter; + + ret = pt1_enable_ram(pt1); + if (ret < 0) + goto err_i2c_del_adapter; + + pt1_init_streams(pt1); + + pt1->power = 1; + pt1_update_power(pt1); + msleep(20); + + pt1->reset = 0; + pt1_update_power(pt1); + usleep_range(1000, 2000); + + ret = pt1_init_frontends(pt1); + if (ret < 0) + goto err_pt1_disable_ram; + + ret = pt1_init_tables(pt1); + if (ret < 0) + goto err_pt1_cleanup_frontends; + + return 0; + +err_pt1_cleanup_frontends: + pt1_cleanup_frontends(pt1); +err_pt1_disable_ram: + pt1_disable_ram(pt1); + pt1->power = 0; + pt1->reset = 1; + pt1_update_power(pt1); +err_i2c_del_adapter: + i2c_del_adapter(i2c_adap); +err_pt1_cleanup_adapters: + pt1_cleanup_adapters(pt1); +err_kfree: + kfree(pt1); +err_pci_iounmap: + pci_iounmap(pdev, regs); +err_pci_release_regions: + pci_release_regions(pdev); +err_pci_disable_device: + pci_disable_device(pdev); +err: + return ret; + +} + +static const struct pci_device_id pt1_id_table[] = { + { PCI_DEVICE(0x10ee, 0x211a) }, + { PCI_DEVICE(0x10ee, 0x222a) }, + { }, +}; +MODULE_DEVICE_TABLE(pci, pt1_id_table); + +static SIMPLE_DEV_PM_OPS(pt1_pm_ops, pt1_suspend, pt1_resume); + +static struct pci_driver pt1_driver = { + .name = DRIVER_NAME, + .probe = pt1_probe, + .remove = pt1_remove, + .id_table = pt1_id_table, + .driver.pm = &pt1_pm_ops, +}; + +module_pci_driver(pt1_driver); + +MODULE_AUTHOR("Takahito HIRANO "); +MODULE_DESCRIPTION("Earthsoft PT1/PT2 Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/pt3/Kconfig b/drivers/media/pci/pt3/Kconfig new file mode 100644 index 000000000..af193d8d3 --- /dev/null +++ b/drivers/media/pci/pt3/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DVB_PT3 + tristate "Earthsoft PT3 cards" + depends on DVB_CORE && PCI && I2C + select DVB_TC90522 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_QM1D1C0042 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MXL301RF if MEDIA_SUBDRV_AUTOSELECT + help + Support for Earthsoft PT3 PCIe cards. + + Say Y or M if you own such a device and want to use it. diff --git a/drivers/media/pci/pt3/Makefile b/drivers/media/pci/pt3/Makefile new file mode 100644 index 000000000..da6b265f4 --- /dev/null +++ b/drivers/media/pci/pt3/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 + +earth-pt3-objs += pt3.o pt3_i2c.o pt3_dma.o + +obj-$(CONFIG_DVB_PT3) += earth-pt3.o + +ccflags-y += -I $(srctree)/drivers/media/dvb-frontends +ccflags-y += -I $(srctree)/drivers/media/tuners diff --git a/drivers/media/pci/pt3/pt3.c b/drivers/media/pci/pt3/pt3.c new file mode 100644 index 000000000..753161889 --- /dev/null +++ b/drivers/media/pci/pt3/pt3.c @@ -0,0 +1,807 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Earthsoft PT3 driver + * + * Copyright (C) 2014 Akihiro Tsukada + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "pt3.h" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static bool one_adapter; +module_param(one_adapter, bool, 0444); +MODULE_PARM_DESC(one_adapter, "Place FE's together under one adapter."); + +static int num_bufs = 4; +module_param(num_bufs, int, 0444); +MODULE_PARM_DESC(num_bufs, "Number of DMA buffer (188KiB) per FE."); + + +static const struct i2c_algorithm pt3_i2c_algo = { + .master_xfer = &pt3_i2c_master_xfer, + .functionality = &pt3_i2c_functionality, +}; + +static const struct pt3_adap_config adap_conf[PT3_NUM_FE] = { + { + .demod_info = { + I2C_BOARD_INFO(TC90522_I2C_DEV_SAT, 0x11), + }, + .tuner_info = { + I2C_BOARD_INFO("qm1d1c0042", 0x63), + }, + .tuner_cfg.qm1d1c0042 = { + .lpf = 1, + }, + .init_freq = 1049480 - 300, + }, + { + .demod_info = { + I2C_BOARD_INFO(TC90522_I2C_DEV_TER, 0x10), + }, + .tuner_info = { + I2C_BOARD_INFO("mxl301rf", 0x62), + }, + .init_freq = 515142857, + }, + { + .demod_info = { + I2C_BOARD_INFO(TC90522_I2C_DEV_SAT, 0x13), + }, + .tuner_info = { + I2C_BOARD_INFO("qm1d1c0042", 0x60), + }, + .tuner_cfg.qm1d1c0042 = { + .lpf = 1, + }, + .init_freq = 1049480 + 300, + }, + { + .demod_info = { + I2C_BOARD_INFO(TC90522_I2C_DEV_TER, 0x12), + }, + .tuner_info = { + I2C_BOARD_INFO("mxl301rf", 0x61), + }, + .init_freq = 521142857, + }, +}; + + +struct reg_val { + u8 reg; + u8 val; +}; + +static int +pt3_demod_write(struct pt3_adapter *adap, const struct reg_val *data, int num) +{ + struct i2c_msg msg; + int i, ret; + + ret = 0; + msg.addr = adap->i2c_demod->addr; + msg.flags = 0; + msg.len = 2; + for (i = 0; i < num; i++) { + msg.buf = (u8 *)&data[i]; + ret = i2c_transfer(adap->i2c_demod->adapter, &msg, 1); + if (ret == 0) + ret = -EREMOTE; + if (ret < 0) + return ret; + } + return 0; +} + +static inline void pt3_lnb_ctrl(struct pt3_board *pt3, bool on) +{ + iowrite32((on ? 0x0f : 0x0c), pt3->regs[0] + REG_SYSTEM_W); +} + +static inline struct pt3_adapter *pt3_find_adapter(struct dvb_frontend *fe) +{ + struct pt3_board *pt3; + int i; + + if (one_adapter) { + pt3 = fe->dvb->priv; + for (i = 0; i < PT3_NUM_FE; i++) + if (pt3->adaps[i]->fe == fe) + return pt3->adaps[i]; + } + return container_of(fe->dvb, struct pt3_adapter, dvb_adap); +} + +/* + * all 4 tuners in PT3 are packaged in a can module (Sharp VA4M6JC2103). + * it seems that they share the power lines and Amp power line and + * adaps[3] controls those powers. + */ +static int +pt3_set_tuner_power(struct pt3_board *pt3, bool tuner_on, bool amp_on) +{ + struct reg_val rv = { 0x1e, 0x99 }; + + if (tuner_on) + rv.val |= 0x40; + if (amp_on) + rv.val |= 0x04; + return pt3_demod_write(pt3->adaps[PT3_NUM_FE - 1], &rv, 1); +} + +static int pt3_set_lna(struct dvb_frontend *fe) +{ + struct pt3_adapter *adap; + struct pt3_board *pt3; + u32 val; + int ret; + + /* LNA is shared btw. 2 TERR-tuners */ + + adap = pt3_find_adapter(fe); + val = fe->dtv_property_cache.lna; + if (val == LNA_AUTO || val == adap->cur_lna) + return 0; + + pt3 = adap->dvb_adap.priv; + if (mutex_lock_interruptible(&pt3->lock)) + return -ERESTARTSYS; + if (val) + pt3->lna_on_cnt++; + else + pt3->lna_on_cnt--; + + if (val && pt3->lna_on_cnt <= 1) { + pt3->lna_on_cnt = 1; + ret = pt3_set_tuner_power(pt3, true, true); + } else if (!val && pt3->lna_on_cnt <= 0) { + pt3->lna_on_cnt = 0; + ret = pt3_set_tuner_power(pt3, true, false); + } else + ret = 0; + mutex_unlock(&pt3->lock); + adap->cur_lna = (val != 0); + return ret; +} + +static int pt3_set_voltage(struct dvb_frontend *fe, enum fe_sec_voltage volt) +{ + struct pt3_adapter *adap; + struct pt3_board *pt3; + bool on; + + /* LNB power is shared btw. 2 SAT-tuners */ + + adap = pt3_find_adapter(fe); + on = (volt != SEC_VOLTAGE_OFF); + if (on == adap->cur_lnb) + return 0; + adap->cur_lnb = on; + pt3 = adap->dvb_adap.priv; + if (mutex_lock_interruptible(&pt3->lock)) + return -ERESTARTSYS; + if (on) + pt3->lnb_on_cnt++; + else + pt3->lnb_on_cnt--; + + if (on && pt3->lnb_on_cnt <= 1) { + pt3->lnb_on_cnt = 1; + pt3_lnb_ctrl(pt3, true); + } else if (!on && pt3->lnb_on_cnt <= 0) { + pt3->lnb_on_cnt = 0; + pt3_lnb_ctrl(pt3, false); + } + mutex_unlock(&pt3->lock); + return 0; +} + +/* register values used in pt3_fe_init() */ + +static const struct reg_val init0_sat[] = { + { 0x03, 0x01 }, + { 0x1e, 0x10 }, +}; +static const struct reg_val init0_ter[] = { + { 0x01, 0x40 }, + { 0x1c, 0x10 }, +}; +static const struct reg_val cfg_sat[] = { + { 0x1c, 0x15 }, + { 0x1f, 0x04 }, +}; +static const struct reg_val cfg_ter[] = { + { 0x1d, 0x01 }, +}; + +/* + * pt3_fe_init: initialize demod sub modules and ISDB-T tuners all at once. + * + * As for demod IC (TC90522) and ISDB-T tuners (MxL301RF), + * the i2c sequences for init'ing them are not public and hidden in a ROM, + * and include the board specific configurations as well. + * They are stored in a lump and cannot be taken out / accessed separately, + * thus cannot be moved to the FE/tuner driver. + */ +static int pt3_fe_init(struct pt3_board *pt3) +{ + int i, ret; + struct dvb_frontend *fe; + + pt3_i2c_reset(pt3); + ret = pt3_init_all_demods(pt3); + if (ret < 0) { + dev_warn(&pt3->pdev->dev, "Failed to init demod chips\n"); + return ret; + } + + /* additional config? */ + for (i = 0; i < PT3_NUM_FE; i++) { + fe = pt3->adaps[i]->fe; + + if (fe->ops.delsys[0] == SYS_ISDBS) + ret = pt3_demod_write(pt3->adaps[i], + init0_sat, ARRAY_SIZE(init0_sat)); + else + ret = pt3_demod_write(pt3->adaps[i], + init0_ter, ARRAY_SIZE(init0_ter)); + if (ret < 0) { + dev_warn(&pt3->pdev->dev, + "demod[%d] failed in init sequence0\n", i); + return ret; + } + ret = fe->ops.init(fe); + if (ret < 0) + return ret; + } + + usleep_range(2000, 4000); + ret = pt3_set_tuner_power(pt3, true, false); + if (ret < 0) { + dev_warn(&pt3->pdev->dev, "Failed to control tuner module\n"); + return ret; + } + + /* output pin configuration */ + for (i = 0; i < PT3_NUM_FE; i++) { + fe = pt3->adaps[i]->fe; + if (fe->ops.delsys[0] == SYS_ISDBS) + ret = pt3_demod_write(pt3->adaps[i], + cfg_sat, ARRAY_SIZE(cfg_sat)); + else + ret = pt3_demod_write(pt3->adaps[i], + cfg_ter, ARRAY_SIZE(cfg_ter)); + if (ret < 0) { + dev_warn(&pt3->pdev->dev, + "demod[%d] failed in init sequence1\n", i); + return ret; + } + } + usleep_range(4000, 6000); + + for (i = 0; i < PT3_NUM_FE; i++) { + fe = pt3->adaps[i]->fe; + if (fe->ops.delsys[0] != SYS_ISDBS) + continue; + /* init and wake-up ISDB-S tuners */ + ret = fe->ops.tuner_ops.init(fe); + if (ret < 0) { + dev_warn(&pt3->pdev->dev, + "Failed to init SAT-tuner[%d]\n", i); + return ret; + } + } + ret = pt3_init_all_mxl301rf(pt3); + if (ret < 0) { + dev_warn(&pt3->pdev->dev, "Failed to init TERR-tuners\n"); + return ret; + } + + ret = pt3_set_tuner_power(pt3, true, true); + if (ret < 0) { + dev_warn(&pt3->pdev->dev, "Failed to control tuner module\n"); + return ret; + } + + /* Wake up all tuners and make an initial tuning, + * in order to avoid interference among the tuners in the module, + * according to the doc from the manufacturer. + */ + for (i = 0; i < PT3_NUM_FE; i++) { + fe = pt3->adaps[i]->fe; + ret = 0; + if (fe->ops.delsys[0] == SYS_ISDBT) + ret = fe->ops.tuner_ops.init(fe); + /* set only when called from pt3_probe(), not resume() */ + if (ret == 0 && fe->dtv_property_cache.frequency == 0) { + fe->dtv_property_cache.frequency = + adap_conf[i].init_freq; + ret = fe->ops.tuner_ops.set_params(fe); + } + if (ret < 0) { + dev_warn(&pt3->pdev->dev, + "Failed in initial tuning of tuner[%d]\n", i); + return ret; + } + } + + /* and sleep again, waiting to be opened by users. */ + for (i = 0; i < PT3_NUM_FE; i++) { + fe = pt3->adaps[i]->fe; + if (fe->ops.tuner_ops.sleep) + ret = fe->ops.tuner_ops.sleep(fe); + if (ret < 0) + break; + if (fe->ops.sleep) + ret = fe->ops.sleep(fe); + if (ret < 0) + break; + if (fe->ops.delsys[0] == SYS_ISDBS) + fe->ops.set_voltage = &pt3_set_voltage; + else + fe->ops.set_lna = &pt3_set_lna; + } + if (i < PT3_NUM_FE) { + dev_warn(&pt3->pdev->dev, "FE[%d] failed to standby\n", i); + return ret; + } + return 0; +} + + +static int pt3_attach_fe(struct pt3_board *pt3, int i) +{ + const struct i2c_board_info *info; + struct tc90522_config cfg; + struct i2c_client *cl; + struct dvb_adapter *dvb_adap; + int ret; + + info = &adap_conf[i].demod_info; + cfg = adap_conf[i].demod_cfg; + cfg.tuner_i2c = NULL; + + ret = -ENODEV; + cl = dvb_module_probe("tc90522", info->type, &pt3->i2c_adap, + info->addr, &cfg); + if (!cl) + return -ENODEV; + pt3->adaps[i]->i2c_demod = cl; + + if (!strncmp(cl->name, TC90522_I2C_DEV_SAT, + strlen(TC90522_I2C_DEV_SAT))) { + struct qm1d1c0042_config tcfg; + + tcfg = adap_conf[i].tuner_cfg.qm1d1c0042; + tcfg.fe = cfg.fe; + info = &adap_conf[i].tuner_info; + cl = dvb_module_probe("qm1d1c0042", info->type, cfg.tuner_i2c, + info->addr, &tcfg); + } else { + struct mxl301rf_config tcfg; + + tcfg = adap_conf[i].tuner_cfg.mxl301rf; + tcfg.fe = cfg.fe; + info = &adap_conf[i].tuner_info; + cl = dvb_module_probe("mxl301rf", info->type, cfg.tuner_i2c, + info->addr, &tcfg); + } + if (!cl) + goto err_demod_module_release; + pt3->adaps[i]->i2c_tuner = cl; + + dvb_adap = &pt3->adaps[one_adapter ? 0 : i]->dvb_adap; + ret = dvb_register_frontend(dvb_adap, cfg.fe); + if (ret < 0) + goto err_tuner_module_release; + pt3->adaps[i]->fe = cfg.fe; + return 0; + +err_tuner_module_release: + dvb_module_release(pt3->adaps[i]->i2c_tuner); +err_demod_module_release: + dvb_module_release(pt3->adaps[i]->i2c_demod); + + return ret; +} + + +static int pt3_fetch_thread(void *data) +{ + struct pt3_adapter *adap = data; + ktime_t delay; + bool was_frozen; + +#define PT3_INITIAL_BUF_DROPS 4 +#define PT3_FETCH_DELAY 10 +#define PT3_FETCH_DELAY_DELTA 2 + + pt3_init_dmabuf(adap); + adap->num_discard = PT3_INITIAL_BUF_DROPS; + + dev_dbg(adap->dvb_adap.device, "PT3: [%s] started\n", + adap->thread->comm); + set_freezable(); + while (!kthread_freezable_should_stop(&was_frozen)) { + if (was_frozen) + adap->num_discard = PT3_INITIAL_BUF_DROPS; + + pt3_proc_dma(adap); + + delay = ktime_set(0, PT3_FETCH_DELAY * NSEC_PER_MSEC); + set_current_state(TASK_UNINTERRUPTIBLE|TASK_FREEZABLE); + schedule_hrtimeout_range(&delay, + PT3_FETCH_DELAY_DELTA * NSEC_PER_MSEC, + HRTIMER_MODE_REL); + } + dev_dbg(adap->dvb_adap.device, "PT3: [%s] exited\n", + adap->thread->comm); + return 0; +} + +static int pt3_start_streaming(struct pt3_adapter *adap) +{ + struct task_struct *thread; + + /* start fetching thread */ + thread = kthread_run(pt3_fetch_thread, adap, "pt3-ad%i-dmx%i", + adap->dvb_adap.num, adap->dmxdev.dvbdev->id); + if (IS_ERR(thread)) { + int ret = PTR_ERR(thread); + + adap->thread = NULL; + dev_warn(adap->dvb_adap.device, + "PT3 (adap:%d, dmx:%d): failed to start kthread\n", + adap->dvb_adap.num, adap->dmxdev.dvbdev->id); + return ret; + } + adap->thread = thread; + + return pt3_start_dma(adap); +} + +static int pt3_stop_streaming(struct pt3_adapter *adap) +{ + int ret; + + ret = pt3_stop_dma(adap); + if (ret) + dev_warn(adap->dvb_adap.device, + "PT3: failed to stop streaming of adap:%d/FE:%d\n", + adap->dvb_adap.num, adap->fe->id); + + /* kill the fetching thread */ + ret = kthread_stop(adap->thread); + adap->thread = NULL; + return ret; +} + +static int pt3_start_feed(struct dvb_demux_feed *feed) +{ + struct pt3_adapter *adap; + + if (signal_pending(current)) + return -EINTR; + + adap = container_of(feed->demux, struct pt3_adapter, demux); + adap->num_feeds++; + if (adap->num_feeds > 1) + return 0; + + return pt3_start_streaming(adap); + +} + +static int pt3_stop_feed(struct dvb_demux_feed *feed) +{ + struct pt3_adapter *adap; + + adap = container_of(feed->demux, struct pt3_adapter, demux); + + adap->num_feeds--; + if (adap->num_feeds > 0 || !adap->thread) + return 0; + adap->num_feeds = 0; + + return pt3_stop_streaming(adap); +} + + +static int pt3_alloc_adapter(struct pt3_board *pt3, int index) +{ + int ret; + struct pt3_adapter *adap; + struct dvb_adapter *da; + + adap = kzalloc_obj(*adap); + if (!adap) + return -ENOMEM; + + pt3->adaps[index] = adap; + adap->adap_idx = index; + + if (index == 0 || !one_adapter) { + ret = dvb_register_adapter(&adap->dvb_adap, "PT3 DVB", + THIS_MODULE, &pt3->pdev->dev, adapter_nr); + if (ret < 0) { + dev_err(&pt3->pdev->dev, + "failed to register adapter dev\n"); + goto err_mem; + } + da = &adap->dvb_adap; + } else + da = &pt3->adaps[0]->dvb_adap; + + adap->dvb_adap.priv = pt3; + adap->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING; + adap->demux.priv = adap; + adap->demux.feednum = 256; + adap->demux.filternum = 256; + adap->demux.start_feed = pt3_start_feed; + adap->demux.stop_feed = pt3_stop_feed; + ret = dvb_dmx_init(&adap->demux); + if (ret < 0) { + dev_err(&pt3->pdev->dev, "failed to init dmx dev\n"); + goto err_adap; + } + + adap->dmxdev.filternum = 256; + adap->dmxdev.demux = &adap->demux.dmx; + ret = dvb_dmxdev_init(&adap->dmxdev, da); + if (ret < 0) { + dev_err(&pt3->pdev->dev, "failed to init dmxdev\n"); + goto err_demux; + } + + ret = pt3_alloc_dmabuf(adap); + if (ret) { + dev_err(&pt3->pdev->dev, "failed to alloc DMA buffers\n"); + goto err_dmabuf; + } + + return 0; + +err_dmabuf: + pt3_free_dmabuf(adap); + dvb_dmxdev_release(&adap->dmxdev); +err_demux: + dvb_dmx_release(&adap->demux); +err_adap: + if (index == 0 || !one_adapter) + dvb_unregister_adapter(da); +err_mem: + kfree(adap); + pt3->adaps[index] = NULL; + return ret; +} + +static void pt3_cleanup_adapter(struct pt3_board *pt3, int index) +{ + struct pt3_adapter *adap; + struct dmx_demux *dmx; + + adap = pt3->adaps[index]; + if (adap == NULL) + return; + + /* stop demux kthread */ + if (adap->thread) + pt3_stop_streaming(adap); + + dmx = &adap->demux.dmx; + dmx->close(dmx); + if (adap->fe) { + adap->fe->callback = NULL; + if (adap->fe->frontend_priv) + dvb_unregister_frontend(adap->fe); + dvb_module_release(adap->i2c_tuner); + dvb_module_release(adap->i2c_demod); + } + pt3_free_dmabuf(adap); + dvb_dmxdev_release(&adap->dmxdev); + dvb_dmx_release(&adap->demux); + if (index == 0 || !one_adapter) + dvb_unregister_adapter(&adap->dvb_adap); + kfree(adap); + pt3->adaps[index] = NULL; +} + +#ifdef CONFIG_PM_SLEEP + +static int pt3_suspend(struct device *dev) +{ + struct pt3_board *pt3 = dev_get_drvdata(dev); + int i; + struct pt3_adapter *adap; + + for (i = 0; i < PT3_NUM_FE; i++) { + adap = pt3->adaps[i]; + if (adap->num_feeds > 0) + pt3_stop_dma(adap); + dvb_frontend_suspend(adap->fe); + pt3_free_dmabuf(adap); + } + + pt3_lnb_ctrl(pt3, false); + pt3_set_tuner_power(pt3, false, false); + return 0; +} + +static int pt3_resume(struct device *dev) +{ + struct pt3_board *pt3 = dev_get_drvdata(dev); + int i, ret; + struct pt3_adapter *adap; + + ret = pt3_fe_init(pt3); + if (ret) + return ret; + + if (pt3->lna_on_cnt > 0) + pt3_set_tuner_power(pt3, true, true); + if (pt3->lnb_on_cnt > 0) + pt3_lnb_ctrl(pt3, true); + + for (i = 0; i < PT3_NUM_FE; i++) { + adap = pt3->adaps[i]; + dvb_frontend_resume(adap->fe); + ret = pt3_alloc_dmabuf(adap); + if (ret) { + dev_err(&pt3->pdev->dev, "failed to alloc DMA bufs\n"); + continue; + } + if (adap->num_feeds > 0) + pt3_start_dma(adap); + } + + return 0; +} + +#endif /* CONFIG_PM_SLEEP */ + + +static void pt3_remove(struct pci_dev *pdev) +{ + struct pt3_board *pt3; + int i; + + pt3 = pci_get_drvdata(pdev); + for (i = PT3_NUM_FE - 1; i >= 0; i--) + pt3_cleanup_adapter(pt3, i); + i2c_del_adapter(&pt3->i2c_adap); +} + +static int pt3_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + u8 rev; + u32 ver; + int i, ret; + void __iomem *iomem; + struct pt3_board *pt3; + struct i2c_adapter *i2c; + + if (pci_read_config_byte(pdev, PCI_REVISION_ID, &rev) || rev != 1) + return -ENODEV; + + ret = pcim_enable_device(pdev); + if (ret < 0) + return -ENODEV; + pci_set_master(pdev); + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (ret) { + dev_err(&pdev->dev, "Failed to set DMA mask\n"); + return ret; + } + + pt3 = devm_kzalloc(&pdev->dev, sizeof(*pt3), GFP_KERNEL); + if (!pt3) + return -ENOMEM; + pci_set_drvdata(pdev, pt3); + pt3->pdev = pdev; + mutex_init(&pt3->lock); + + iomem = pcim_iomap_region(pdev, 0, DRV_NAME); + if (IS_ERR(iomem)) + return PTR_ERR(iomem); + pt3->regs[0] = iomem; + + iomem = pcim_iomap_region(pdev, 2, DRV_NAME); + if (IS_ERR(iomem)) + return PTR_ERR(iomem); + pt3->regs[1] = iomem; + + ver = ioread32(pt3->regs[0] + REG_VERSION); + if ((ver >> 16) != 0x0301) { + dev_warn(&pdev->dev, "PT%d, I/F-ver.:%d not supported\n", + ver >> 24, (ver & 0x00ff0000) >> 16); + return -ENODEV; + } + + pt3->num_bufs = clamp_val(num_bufs, MIN_DATA_BUFS, MAX_DATA_BUFS); + + pt3->i2c_buf = devm_kmalloc(&pdev->dev, sizeof(*pt3->i2c_buf), GFP_KERNEL); + if (!pt3->i2c_buf) + return -ENOMEM; + i2c = &pt3->i2c_adap; + i2c->owner = THIS_MODULE; + i2c->algo = &pt3_i2c_algo; + i2c->algo_data = NULL; + i2c->dev.parent = &pdev->dev; + strscpy(i2c->name, DRV_NAME, sizeof(i2c->name)); + i2c_set_adapdata(i2c, pt3); + ret = i2c_add_adapter(i2c); + if (ret < 0) + return ret; + + for (i = 0; i < PT3_NUM_FE; i++) { + ret = pt3_alloc_adapter(pt3, i); + if (ret < 0) + break; + + ret = pt3_attach_fe(pt3, i); + if (ret < 0) + break; + } + if (i < PT3_NUM_FE) { + dev_err(&pdev->dev, "Failed to create FE%d\n", i); + goto err_cleanup_adapters; + } + + ret = pt3_fe_init(pt3); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to init frontends\n"); + i = PT3_NUM_FE - 1; + goto err_cleanup_adapters; + } + + dev_info(&pdev->dev, + "successfully init'ed PT%d (fw:0x%02x, I/F:0x%02x)\n", + ver >> 24, (ver >> 8) & 0xff, (ver >> 16) & 0xff); + return 0; + +err_cleanup_adapters: + while (i >= 0) + pt3_cleanup_adapter(pt3, i--); + i2c_del_adapter(i2c); + return ret; +} + +static const struct pci_device_id pt3_id_table[] = { + { PCI_DEVICE_SUB(0x1172, 0x4c15, 0xee8d, 0x0368) }, + { }, +}; +MODULE_DEVICE_TABLE(pci, pt3_id_table); + +static SIMPLE_DEV_PM_OPS(pt3_pm_ops, pt3_suspend, pt3_resume); + +static struct pci_driver pt3_driver = { + .name = DRV_NAME, + .probe = pt3_probe, + .remove = pt3_remove, + .id_table = pt3_id_table, + + .driver.pm = &pt3_pm_ops, +}; + +module_pci_driver(pt3_driver); + +MODULE_DESCRIPTION("Earthsoft PT3 Driver"); +MODULE_AUTHOR("Akihiro TSUKADA"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/pt3/pt3.h b/drivers/media/pci/pt3/pt3.h new file mode 100644 index 000000000..a53124438 --- /dev/null +++ b/drivers/media/pci/pt3/pt3.h @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Earthsoft PT3 driver + * + * Copyright (C) 2014 Akihiro Tsukada + */ + +#ifndef PT3_H +#define PT3_H + +#include +#include + +#include +#include +#include + +#include "tc90522.h" +#include "mxl301rf.h" +#include "qm1d1c0042.h" + +#define DRV_NAME KBUILD_MODNAME + +#define PT3_NUM_FE 4 + +/* + * register index of the FPGA chip + */ +#define REG_VERSION 0x00 +#define REG_BUS 0x04 +#define REG_SYSTEM_W 0x08 +#define REG_SYSTEM_R 0x0c +#define REG_I2C_W 0x10 +#define REG_I2C_R 0x14 +#define REG_RAM_W 0x18 +#define REG_RAM_R 0x1c +#define REG_DMA_BASE 0x40 /* regs for FE[i] = REG_DMA_BASE + 0x18 * i */ +#define OFST_DMA_DESC_L 0x00 +#define OFST_DMA_DESC_H 0x04 +#define OFST_DMA_CTL 0x08 +#define OFST_TS_CTL 0x0c +#define OFST_STATUS 0x10 +#define OFST_TS_ERR 0x14 + +/* + * internal buffer for I2C + */ +#define PT3_I2C_MAX 4091 +struct pt3_i2cbuf { + u8 data[PT3_I2C_MAX]; + u8 tmp; + u32 num_cmds; +}; + +/* + * DMA things + */ +#define TS_PACKET_SZ 188 +/* DMA transfers must not cross 4GiB, so use one page / transfer */ +#define DATA_XFER_SZ 4096 +#define DATA_BUF_XFERS 47 +/* (num_bufs * DATA_BUF_SZ) % TS_PACKET_SZ must be 0 */ +#define DATA_BUF_SZ (DATA_BUF_XFERS * DATA_XFER_SZ) +#define MAX_DATA_BUFS 16 +#define MIN_DATA_BUFS 2 + +#define DESCS_IN_PAGE (PAGE_SIZE / sizeof(struct xfer_desc)) +#define MAX_NUM_XFERS (MAX_DATA_BUFS * DATA_BUF_XFERS) +#define MAX_DESC_BUFS DIV_ROUND_UP(MAX_NUM_XFERS, DESCS_IN_PAGE) + +/* DMA transfer description. + * device is passed a pointer to this struct, dma-reads it, + * and gets the DMA buffer ring for storing TS data. + */ +struct xfer_desc { + u32 addr_l; /* bus address of target data buffer */ + u32 addr_h; + u32 size; + u32 next_l; /* bus address of the next xfer_desc */ + u32 next_h; +}; + +/* A DMA mapping of a page containing xfer_desc's */ +struct xfer_desc_buffer { + dma_addr_t b_addr; + struct xfer_desc *descs; /* PAGE_SIZE (xfer_desc[DESCS_IN_PAGE]) */ +}; + +/* A DMA mapping of a data buffer */ +struct dma_data_buffer { + dma_addr_t b_addr; + u8 *data; /* size: u8[PAGE_SIZE] */ +}; + +/* + * device things + */ +struct pt3_adap_config { + struct i2c_board_info demod_info; + struct tc90522_config demod_cfg; + + struct i2c_board_info tuner_info; + union tuner_config { + struct qm1d1c0042_config qm1d1c0042; + struct mxl301rf_config mxl301rf; + } tuner_cfg; + u32 init_freq; +}; + +struct pt3_adapter { + struct dvb_adapter dvb_adap; /* dvb_adap.priv => struct pt3_board */ + int adap_idx; + + struct dvb_demux demux; + struct dmxdev dmxdev; + struct dvb_frontend *fe; + struct i2c_client *i2c_demod; + struct i2c_client *i2c_tuner; + + /* data fetch thread */ + struct task_struct *thread; + int num_feeds; + + bool cur_lna; + bool cur_lnb; /* current LNB power status (on/off) */ + + /* items below are for DMA */ + struct dma_data_buffer buffer[MAX_DATA_BUFS]; + int buf_idx; + int buf_ofs; + int num_bufs; /* == pt3_board->num_bufs */ + int num_discard; /* how many access units to discard initially */ + + struct xfer_desc_buffer desc_buf[MAX_DESC_BUFS]; + int num_desc_bufs; /* == num_bufs * DATA_BUF_XFERS / DESCS_IN_PAGE */ +}; + + +struct pt3_board { + struct pci_dev *pdev; + void __iomem *regs[2]; + /* regs[0]: registers, regs[1]: internal memory, used for I2C */ + + struct mutex lock; + + /* LNB power shared among sat-FEs */ + int lnb_on_cnt; /* LNB power on count */ + + /* LNA shared among terr-FEs */ + int lna_on_cnt; /* booster enabled count */ + + int num_bufs; /* number of DMA buffers allocated/mapped per FE */ + + struct i2c_adapter i2c_adap; + struct pt3_i2cbuf *i2c_buf; + + struct pt3_adapter *adaps[PT3_NUM_FE]; +}; + + +/* + * prototypes + */ +extern int pt3_alloc_dmabuf(struct pt3_adapter *adap); +extern void pt3_init_dmabuf(struct pt3_adapter *adap); +extern void pt3_free_dmabuf(struct pt3_adapter *adap); +extern int pt3_start_dma(struct pt3_adapter *adap); +extern int pt3_stop_dma(struct pt3_adapter *adap); +extern int pt3_proc_dma(struct pt3_adapter *adap); + +extern int pt3_i2c_master_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num); +extern u32 pt3_i2c_functionality(struct i2c_adapter *adap); +extern void pt3_i2c_reset(struct pt3_board *pt3); +extern int pt3_init_all_demods(struct pt3_board *pt3); +extern int pt3_init_all_mxl301rf(struct pt3_board *pt3); +#endif /* PT3_H */ diff --git a/drivers/media/pci/pt3/pt3_dma.c b/drivers/media/pci/pt3/pt3_dma.c new file mode 100644 index 000000000..de677b90e --- /dev/null +++ b/drivers/media/pci/pt3/pt3_dma.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Earthsoft PT3 driver + * + * Copyright (C) 2014 Akihiro Tsukada + */ +#include +#include +#include + +#include "pt3.h" + +#define PT3_ACCESS_UNIT (TS_PACKET_SZ * 128) +#define PT3_BUF_CANARY (0x74) + +static u32 get_dma_base(int idx) +{ + int i; + + i = (idx == 1 || idx == 2) ? 3 - idx : idx; + return REG_DMA_BASE + 0x18 * i; +} + +int pt3_stop_dma(struct pt3_adapter *adap) +{ + struct pt3_board *pt3 = adap->dvb_adap.priv; + u32 base; + u32 stat; + int retry; + + base = get_dma_base(adap->adap_idx); + stat = ioread32(pt3->regs[0] + base + OFST_STATUS); + if (!(stat & 0x01)) + return 0; + + iowrite32(0x02, pt3->regs[0] + base + OFST_DMA_CTL); + for (retry = 0; retry < 5; retry++) { + stat = ioread32(pt3->regs[0] + base + OFST_STATUS); + if (!(stat & 0x01)) + return 0; + msleep(50); + } + return -EIO; +} + +int pt3_start_dma(struct pt3_adapter *adap) +{ + struct pt3_board *pt3 = adap->dvb_adap.priv; + u32 base = get_dma_base(adap->adap_idx); + + iowrite32(0x02, pt3->regs[0] + base + OFST_DMA_CTL); + iowrite32(lower_32_bits(adap->desc_buf[0].b_addr), + pt3->regs[0] + base + OFST_DMA_DESC_L); + iowrite32(upper_32_bits(adap->desc_buf[0].b_addr), + pt3->regs[0] + base + OFST_DMA_DESC_H); + iowrite32(0x01, pt3->regs[0] + base + OFST_DMA_CTL); + return 0; +} + + +static u8 *next_unit(struct pt3_adapter *adap, int *idx, int *ofs) +{ + *ofs += PT3_ACCESS_UNIT; + if (*ofs >= DATA_BUF_SZ) { + *ofs -= DATA_BUF_SZ; + (*idx)++; + if (*idx == adap->num_bufs) + *idx = 0; + } + return &adap->buffer[*idx].data[*ofs]; +} + +int pt3_proc_dma(struct pt3_adapter *adap) +{ + int idx, ofs; + + idx = adap->buf_idx; + ofs = adap->buf_ofs; + + if (adap->buffer[idx].data[ofs] == PT3_BUF_CANARY) + return 0; + + while (*next_unit(adap, &idx, &ofs) != PT3_BUF_CANARY) { + u8 *p; + + p = &adap->buffer[adap->buf_idx].data[adap->buf_ofs]; + if (adap->num_discard > 0) + adap->num_discard--; + else if (adap->buf_ofs + PT3_ACCESS_UNIT > DATA_BUF_SZ) { + dvb_dmx_swfilter_packets(&adap->demux, p, + (DATA_BUF_SZ - adap->buf_ofs) / TS_PACKET_SZ); + dvb_dmx_swfilter_packets(&adap->demux, + adap->buffer[idx].data, ofs / TS_PACKET_SZ); + } else + dvb_dmx_swfilter_packets(&adap->demux, p, + PT3_ACCESS_UNIT / TS_PACKET_SZ); + + *p = PT3_BUF_CANARY; + adap->buf_idx = idx; + adap->buf_ofs = ofs; + } + return 0; +} + +void pt3_init_dmabuf(struct pt3_adapter *adap) +{ + int idx, ofs; + u8 *p; + + idx = 0; + ofs = 0; + p = adap->buffer[0].data; + /* mark the whole buffers as "not written yet" */ + while (idx < adap->num_bufs) { + p[ofs] = PT3_BUF_CANARY; + ofs += PT3_ACCESS_UNIT; + if (ofs >= DATA_BUF_SZ) { + ofs -= DATA_BUF_SZ; + idx++; + p = adap->buffer[idx].data; + } + } + adap->buf_idx = 0; + adap->buf_ofs = 0; +} + +void pt3_free_dmabuf(struct pt3_adapter *adap) +{ + struct pt3_board *pt3; + int i; + + pt3 = adap->dvb_adap.priv; + for (i = 0; i < adap->num_bufs; i++) + dma_free_coherent(&pt3->pdev->dev, DATA_BUF_SZ, + adap->buffer[i].data, adap->buffer[i].b_addr); + adap->num_bufs = 0; + + for (i = 0; i < adap->num_desc_bufs; i++) + dma_free_coherent(&pt3->pdev->dev, PAGE_SIZE, + adap->desc_buf[i].descs, adap->desc_buf[i].b_addr); + adap->num_desc_bufs = 0; +} + + +int pt3_alloc_dmabuf(struct pt3_adapter *adap) +{ + struct pt3_board *pt3; + void *p; + int i, j; + int idx, ofs; + int num_desc_bufs; + dma_addr_t data_addr, desc_addr; + struct xfer_desc *d; + + pt3 = adap->dvb_adap.priv; + adap->num_bufs = 0; + adap->num_desc_bufs = 0; + for (i = 0; i < pt3->num_bufs; i++) { + p = dma_alloc_coherent(&pt3->pdev->dev, DATA_BUF_SZ, + &adap->buffer[i].b_addr, GFP_KERNEL); + if (p == NULL) + goto failed; + adap->buffer[i].data = p; + adap->num_bufs++; + } + pt3_init_dmabuf(adap); + + /* build circular-linked pointers (xfer_desc) to the data buffers*/ + idx = 0; + ofs = 0; + num_desc_bufs = + DIV_ROUND_UP(adap->num_bufs * DATA_BUF_XFERS, DESCS_IN_PAGE); + for (i = 0; i < num_desc_bufs; i++) { + p = dma_alloc_coherent(&pt3->pdev->dev, PAGE_SIZE, + &desc_addr, GFP_KERNEL); + if (p == NULL) + goto failed; + adap->num_desc_bufs++; + adap->desc_buf[i].descs = p; + adap->desc_buf[i].b_addr = desc_addr; + + if (i > 0) { + d = &adap->desc_buf[i - 1].descs[DESCS_IN_PAGE - 1]; + d->next_l = lower_32_bits(desc_addr); + d->next_h = upper_32_bits(desc_addr); + } + for (j = 0; j < DESCS_IN_PAGE; j++) { + data_addr = adap->buffer[idx].b_addr + ofs; + d = &adap->desc_buf[i].descs[j]; + d->addr_l = lower_32_bits(data_addr); + d->addr_h = upper_32_bits(data_addr); + d->size = DATA_XFER_SZ; + + desc_addr += sizeof(struct xfer_desc); + d->next_l = lower_32_bits(desc_addr); + d->next_h = upper_32_bits(desc_addr); + + ofs += DATA_XFER_SZ; + if (ofs >= DATA_BUF_SZ) { + ofs -= DATA_BUF_SZ; + idx++; + if (idx >= adap->num_bufs) { + desc_addr = adap->desc_buf[0].b_addr; + d->next_l = lower_32_bits(desc_addr); + d->next_h = upper_32_bits(desc_addr); + return 0; + } + } + } + } + return 0; + +failed: + pt3_free_dmabuf(adap); + return -ENOMEM; +} diff --git a/drivers/media/pci/pt3/pt3_i2c.c b/drivers/media/pci/pt3/pt3_i2c.c new file mode 100644 index 000000000..b02be789a --- /dev/null +++ b/drivers/media/pci/pt3/pt3_i2c.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Earthsoft PT3 driver + * + * Copyright (C) 2014 Akihiro Tsukada + */ +#include +#include +#include +#include +#include + +#include "pt3.h" + +#define PT3_I2C_BASE 2048 +#define PT3_CMD_ADDR_NORMAL 0 +#define PT3_CMD_ADDR_INIT_DEMOD 4096 +#define PT3_CMD_ADDR_INIT_TUNER (4096 + 2042) + +/* masks for I2C status register */ +#define STAT_SEQ_RUNNING 0x1 +#define STAT_SEQ_ERROR 0x6 +#define STAT_NO_SEQ 0x8 + +#define PT3_I2C_RUN (1 << 16) +#define PT3_I2C_RESET (1 << 17) + +enum ctl_cmd { + I_END, + I_ADDRESS, + I_CLOCK_L, + I_CLOCK_H, + I_DATA_L, + I_DATA_H, + I_RESET, + I_SLEEP, + I_DATA_L_NOP = 0x08, + I_DATA_H_NOP = 0x0c, + I_DATA_H_READ = 0x0d, + I_DATA_H_ACK0 = 0x0e, + I_DATA_H_ACK1 = 0x0f, +}; + + +static void cmdbuf_add(struct pt3_i2cbuf *cbuf, enum ctl_cmd cmd) +{ + int buf_idx; + + if ((cbuf->num_cmds % 2) == 0) + cbuf->tmp = cmd; + else { + cbuf->tmp |= cmd << 4; + buf_idx = cbuf->num_cmds / 2; + if (buf_idx < ARRAY_SIZE(cbuf->data)) + cbuf->data[buf_idx] = cbuf->tmp; + } + cbuf->num_cmds++; +} + +static void put_end(struct pt3_i2cbuf *cbuf) +{ + cmdbuf_add(cbuf, I_END); + if (cbuf->num_cmds % 2) + cmdbuf_add(cbuf, I_END); +} + +static void put_start(struct pt3_i2cbuf *cbuf) +{ + cmdbuf_add(cbuf, I_DATA_H); + cmdbuf_add(cbuf, I_CLOCK_H); + cmdbuf_add(cbuf, I_DATA_L); + cmdbuf_add(cbuf, I_CLOCK_L); +} + +static void put_byte_write(struct pt3_i2cbuf *cbuf, u8 val) +{ + u8 mask; + + for (mask = 0x80; mask > 0; mask >>= 1) + cmdbuf_add(cbuf, (val & mask) ? I_DATA_H_NOP : I_DATA_L_NOP); + cmdbuf_add(cbuf, I_DATA_H_ACK0); +} + +static void put_byte_read(struct pt3_i2cbuf *cbuf, u32 size) +{ + int i, j; + + for (i = 0; i < size; i++) { + for (j = 0; j < 8; j++) + cmdbuf_add(cbuf, I_DATA_H_READ); + cmdbuf_add(cbuf, (i == size - 1) ? I_DATA_H_NOP : I_DATA_L_NOP); + } +} + +static void put_stop(struct pt3_i2cbuf *cbuf) +{ + cmdbuf_add(cbuf, I_DATA_L); + cmdbuf_add(cbuf, I_CLOCK_H); + cmdbuf_add(cbuf, I_DATA_H); +} + + +/* translates msgs to internal commands for bit-banging */ +static void translate(struct pt3_i2cbuf *cbuf, struct i2c_msg *msgs, int num) +{ + int i, j; + bool rd; + + cbuf->num_cmds = 0; + for (i = 0; i < num; i++) { + rd = !!(msgs[i].flags & I2C_M_RD); + put_start(cbuf); + put_byte_write(cbuf, msgs[i].addr << 1 | rd); + if (rd) + put_byte_read(cbuf, msgs[i].len); + else + for (j = 0; j < msgs[i].len; j++) + put_byte_write(cbuf, msgs[i].buf[j]); + } + if (num > 0) { + put_stop(cbuf); + put_end(cbuf); + } +} + +static int wait_i2c_result(struct pt3_board *pt3, u32 *result, int max_wait) +{ + int i; + u32 v; + + for (i = 0; i < max_wait; i++) { + v = ioread32(pt3->regs[0] + REG_I2C_R); + if (!(v & STAT_SEQ_RUNNING)) + break; + usleep_range(500, 750); + } + if (i >= max_wait) + return -EIO; + if (result) + *result = v; + return 0; +} + +/* send [pre-]translated i2c msgs stored at addr */ +static int send_i2c_cmd(struct pt3_board *pt3, u32 addr) +{ + u32 ret; + + /* make sure that previous transactions had finished */ + if (wait_i2c_result(pt3, NULL, 50)) { + dev_warn(&pt3->pdev->dev, "(%s) prev. transaction stalled\n", + __func__); + return -EIO; + } + + iowrite32(PT3_I2C_RUN | addr, pt3->regs[0] + REG_I2C_W); + usleep_range(200, 300); + /* wait for the current transaction to finish */ + if (wait_i2c_result(pt3, &ret, 500) || (ret & STAT_SEQ_ERROR)) { + dev_warn(&pt3->pdev->dev, "(%s) failed.\n", __func__); + return -EIO; + } + return 0; +} + + +/* init commands for each demod are combined into one transaction + * and hidden in ROM with the address PT3_CMD_ADDR_INIT_DEMOD. + */ +int pt3_init_all_demods(struct pt3_board *pt3) +{ + ioread32(pt3->regs[0] + REG_I2C_R); + return send_i2c_cmd(pt3, PT3_CMD_ADDR_INIT_DEMOD); +} + +/* init commands for two ISDB-T tuners are hidden in ROM. */ +int pt3_init_all_mxl301rf(struct pt3_board *pt3) +{ + usleep_range(1000, 2000); + return send_i2c_cmd(pt3, PT3_CMD_ADDR_INIT_TUNER); +} + +void pt3_i2c_reset(struct pt3_board *pt3) +{ + iowrite32(PT3_I2C_RESET, pt3->regs[0] + REG_I2C_W); +} + +/* + * I2C algorithm + */ +int +pt3_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + struct pt3_board *pt3; + struct pt3_i2cbuf *cbuf; + int i; + void __iomem *p; + + pt3 = i2c_get_adapdata(adap); + cbuf = pt3->i2c_buf; + + for (i = 0; i < num; i++) + if (msgs[i].flags & I2C_M_RECV_LEN) { + dev_warn(&pt3->pdev->dev, + "(%s) I2C_M_RECV_LEN not supported.\n", + __func__); + return -EINVAL; + } + + translate(cbuf, msgs, num); + memcpy_toio(pt3->regs[1] + PT3_I2C_BASE + PT3_CMD_ADDR_NORMAL / 2, + cbuf->data, cbuf->num_cmds); + + if (send_i2c_cmd(pt3, PT3_CMD_ADDR_NORMAL) < 0) + return -EIO; + + p = pt3->regs[1] + PT3_I2C_BASE; + for (i = 0; i < num; i++) + if ((msgs[i].flags & I2C_M_RD) && msgs[i].len > 0) { + memcpy_fromio(msgs[i].buf, p, msgs[i].len); + p += msgs[i].len; + } + + return num; +} + +u32 pt3_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C; +} diff --git a/drivers/media/pci/saa7134/Kconfig b/drivers/media/pci/saa7134/Kconfig new file mode 100644 index 000000000..30c175968 --- /dev/null +++ b/drivers/media/pci/saa7134/Kconfig @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_SAA7134 + tristate "Philips SAA7134 support" + depends on VIDEO_DEV && PCI && I2C + select VIDEOBUF2_DMA_SG + select VIDEO_TUNER + select VIDEO_TVEEPROM + select CRC32 + select VIDEO_SAA6588 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_SAA6752HS if MEDIA_SUBDRV_AUTOSELECT + help + This is a video4linux driver for Philips SAA713x based + TV cards. + + To compile this driver as a module, choose M here: the + module will be called saa7134. + +config VIDEO_SAA7134_ALSA + tristate "Philips SAA7134 DMA audio support" + depends on VIDEO_SAA7134 && SND + select SND_PCM + help + This is a video4linux driver for direct (DMA) audio in + Philips SAA713x based TV cards using ALSA + + To compile this driver as a module, choose M here: the + module will be called saa7134-alsa. + +config VIDEO_SAA7134_RC + bool "Philips SAA7134 Remote Controller support" + depends on RC_CORE + depends on VIDEO_SAA7134 + depends on !(RC_CORE=m && VIDEO_SAA7134=y) + default y + help + Enables Remote Controller support on saa7134 driver. + +config VIDEO_SAA7134_DVB + tristate "DVB/ATSC Support for saa7134 based TV cards" + depends on VIDEO_SAA7134 && DVB_CORE + select VIDEOBUF2_DVB + select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT + select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA1004X if MEDIA_SUBDRV_AUTOSELECT + select DVB_NXT200X if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10086 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA826X if MEDIA_SUBDRV_AUTOSELECT + select DVB_ISL6421 if MEDIA_SUBDRV_AUTOSELECT + select DVB_ISL6405 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA827X if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT + select DVB_ZL10036 if MEDIA_SUBDRV_AUTOSELECT + select DVB_MT312 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LGDT3305 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10048 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA8290 if MEDIA_SUBDRV_AUTOSELECT + select DVB_ZL10039 if MEDIA_SUBDRV_AUTOSELECT + help + This adds support for DVB cards based on the + Philips saa7134 chip. + + To compile this driver as a module, choose M here: the + module will be called saa7134-dvb. + +config VIDEO_SAA7134_GO7007 + tristate "go7007 support for saa7134 based TV cards" + depends on VIDEO_SAA7134 + depends on VIDEO_GO7007 + help + Enables saa7134 driver support for boards with go7007 + MPEG encoder (WIS Voyager or compatible). diff --git a/drivers/media/pci/saa7134/Makefile b/drivers/media/pci/saa7134/Makefile new file mode 100644 index 000000000..82ac7f312 --- /dev/null +++ b/drivers/media/pci/saa7134/Makefile @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 + +saa7134-y += saa7134-cards.o saa7134-core.o saa7134-i2c.o +saa7134-y += saa7134-ts.o saa7134-tvaudio.o saa7134-vbi.o +saa7134-y += saa7134-video.o +saa7134-$(CONFIG_VIDEO_SAA7134_RC) += saa7134-input.o + +obj-$(CONFIG_VIDEO_SAA7134) += saa7134.o saa7134-empress.o +obj-$(CONFIG_VIDEO_SAA7134_GO7007) += saa7134-go7007.o + +obj-$(CONFIG_VIDEO_SAA7134_ALSA) += saa7134-alsa.o + +obj-$(CONFIG_VIDEO_SAA7134_DVB) += saa7134-dvb.o + +ccflags-y += -I$(srctree)/drivers/media/tuners +ccflags-y += -I$(srctree)/drivers/media/dvb-frontends +ccflags-y += -I$(srctree)/drivers/media/usb/go7007 diff --git a/drivers/media/pci/saa7134/saa7134-alsa.c b/drivers/media/pci/saa7134/saa7134-alsa.c new file mode 100644 index 000000000..147985bb0 --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134-alsa.c @@ -0,0 +1,1259 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SAA713x ALSA support for V4L + */ + +#include "saa7134.h" +#include "saa7134-reg.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Configuration macros + */ + +/* defaults */ +#define MIXER_ADDR_UNSELECTED -1 +#define MIXER_ADDR_TVTUNER 0 +#define MIXER_ADDR_LINE1 1 +#define MIXER_ADDR_LINE2 2 +#define MIXER_ADDR_LAST 2 + + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static int enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 1}; + +module_param_array(index, int, NULL, 0444); +module_param_array(enable, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for SAA7134 capture interface(s)."); +MODULE_PARM_DESC(enable, "Enable (or not) the SAA7134 capture interface(s)."); + +/* + * Main chip structure + */ + +typedef struct snd_card_saa7134 { + struct snd_card *card; + spinlock_t mixer_lock; + int mixer_volume[MIXER_ADDR_LAST+1][2]; + int capture_source_addr; + int capture_source[2]; + struct snd_kcontrol *capture_ctl[MIXER_ADDR_LAST+1]; + struct pci_dev *pci; + struct saa7134_dev *dev; + + unsigned long iobase; + s16 irq; + u16 mute_was_on; + + spinlock_t lock; +} snd_card_saa7134_t; + + +/* + * PCM structure + */ + +typedef struct snd_card_saa7134_pcm { + struct saa7134_dev *dev; + + spinlock_t lock; + + struct snd_pcm_substream *substream; +} snd_card_saa7134_pcm_t; + +static struct snd_card *snd_saa7134_cards[SNDRV_CARDS]; + + +/* + * saa7134 DMA audio stop + * + * Called when the capture device is released or the buffer overflows + * + * - Copied verbatim from saa7134-oss's dsp_dma_stop. + * + */ + +static void saa7134_dma_stop(struct saa7134_dev *dev) +{ + dev->dmasound.dma_blk = -1; + dev->dmasound.dma_running = 0; + saa7134_set_dmabits(dev); +} + +/* + * saa7134 DMA audio start + * + * Called when preparing the capture device for use + * + * - Copied verbatim from saa7134-oss's dsp_dma_start. + * + */ + +static void saa7134_dma_start(struct saa7134_dev *dev) +{ + dev->dmasound.dma_blk = 0; + dev->dmasound.dma_running = 1; + saa7134_set_dmabits(dev); +} + +/* + * saa7134 audio DMA IRQ handler + * + * Called whenever we get an SAA7134_IRQ_REPORT_DONE_RA3 interrupt + * Handles shifting between the 2 buffers, manages the read counters, + * and notifies ALSA when periods elapse + * + * - Mostly copied from saa7134-oss's saa7134_irq_oss_done. + * + */ + +static void saa7134_irq_alsa_done(struct saa7134_dev *dev, + unsigned long status) +{ + int next_blk, reg = 0; + + spin_lock(&dev->slock); + if (UNSET == dev->dmasound.dma_blk) { + pr_debug("irq: recording stopped\n"); + goto done; + } + if (0 != (status & 0x0f000000)) + pr_debug("irq: lost %ld\n", (status >> 24) & 0x0f); + if (0 == (status & 0x10000000)) { + /* odd */ + if (0 == (dev->dmasound.dma_blk & 0x01)) + reg = SAA7134_RS_BA1(6); + } else { + /* even */ + if (1 == (dev->dmasound.dma_blk & 0x01)) + reg = SAA7134_RS_BA2(6); + } + if (0 == reg) { + pr_debug("irq: field oops [%s]\n", + (status & 0x10000000) ? "even" : "odd"); + goto done; + } + + if (dev->dmasound.read_count >= dev->dmasound.blksize * (dev->dmasound.blocks-2)) { + pr_debug("irq: overrun [full=%d/%d] - Blocks in %d\n", + dev->dmasound.read_count, + dev->dmasound.bufsize, dev->dmasound.blocks); + spin_unlock(&dev->slock); + snd_pcm_stop_xrun(dev->dmasound.substream); + return; + } + + /* next block addr */ + next_blk = (dev->dmasound.dma_blk + 2) % dev->dmasound.blocks; + saa_writel(reg,next_blk * dev->dmasound.blksize); + pr_debug("irq: ok, %s, next_blk=%d, addr=%x, blocks=%u, size=%u, read=%u\n", + (status & 0x10000000) ? "even" : "odd ", next_blk, + next_blk * dev->dmasound.blksize, dev->dmasound.blocks, + dev->dmasound.blksize, dev->dmasound.read_count); + + /* update status & wake waiting readers */ + dev->dmasound.dma_blk = (dev->dmasound.dma_blk + 1) % dev->dmasound.blocks; + dev->dmasound.read_count += dev->dmasound.blksize; + + dev->dmasound.recording_on = reg; + + if (dev->dmasound.read_count >= snd_pcm_lib_period_bytes(dev->dmasound.substream)) { + spin_unlock(&dev->slock); + snd_pcm_period_elapsed(dev->dmasound.substream); + spin_lock(&dev->slock); + } + + done: + spin_unlock(&dev->slock); + +} + +/* + * IRQ request handler + * + * Runs along with saa7134's IRQ handler, discards anything that isn't + * DMA sound + * + */ + +static irqreturn_t saa7134_alsa_irq(int irq, void *dev_id) +{ + struct saa7134_dmasound *dmasound = dev_id; + struct saa7134_dev *dev = dmasound->priv_data; + + unsigned long report, status; + int loop, handled = 0; + + for (loop = 0; loop < 10; loop++) { + report = saa_readl(SAA7134_IRQ_REPORT); + status = saa_readl(SAA7134_IRQ_STATUS); + + if (report & SAA7134_IRQ_REPORT_DONE_RA3) { + handled = 1; + saa_writel(SAA7134_IRQ_REPORT, + SAA7134_IRQ_REPORT_DONE_RA3); + saa7134_irq_alsa_done(dev, status); + } else { + goto out; + } + } + + if (loop == 10) { + pr_debug("error! looping IRQ!"); + } + +out: + return IRQ_RETVAL(handled); +} + +/* + * ALSA capture trigger + * + * - One of the ALSA capture callbacks. + * + * Called whenever a capture is started or stopped. Must be defined, + * but there's nothing we want to do here + * + */ + +static int snd_card_saa7134_capture_trigger(struct snd_pcm_substream * substream, + int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_card_saa7134_pcm_t *pcm = runtime->private_data; + struct saa7134_dev *dev=pcm->dev; + int err = 0; + + spin_lock(&dev->slock); + if (cmd == SNDRV_PCM_TRIGGER_START) { + /* start dma */ + saa7134_dma_start(dev); + } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + /* stop dma */ + saa7134_dma_stop(dev); + } else { + err = -EINVAL; + } + spin_unlock(&dev->slock); + + return err; +} + +static int saa7134_alsa_dma_init(struct saa7134_dev *dev, + unsigned long nr_pages) +{ + struct saa7134_dmasound *dma = &dev->dmasound; + struct page *pg; + int i; + + dma->vaddr = vmalloc_32(nr_pages << PAGE_SHIFT); + if (NULL == dma->vaddr) { + pr_debug("vmalloc_32(%lu pages) failed\n", nr_pages); + return -ENOMEM; + } + + pr_debug("vmalloc is at addr %p, size=%lu\n", + dma->vaddr, nr_pages << PAGE_SHIFT); + + memset(dma->vaddr, 0, nr_pages << PAGE_SHIFT); + dma->nr_pages = nr_pages; + + dma->sglist = vzalloc(array_size(sizeof(*dma->sglist), dma->nr_pages)); + if (NULL == dma->sglist) + goto vzalloc_err; + + sg_init_table(dma->sglist, dma->nr_pages); + for (i = 0; i < dma->nr_pages; i++) { + pg = vmalloc_to_page(dma->vaddr + i * PAGE_SIZE); + if (NULL == pg) + goto vmalloc_to_page_err; + sg_set_page(&dma->sglist[i], pg, PAGE_SIZE, 0); + } + return 0; + +vmalloc_to_page_err: + vfree(dma->sglist); + dma->sglist = NULL; +vzalloc_err: + vfree(dma->vaddr); + dma->vaddr = NULL; + return -ENOMEM; +} + +static int saa7134_alsa_dma_map(struct saa7134_dev *dev) +{ + struct saa7134_dmasound *dma = &dev->dmasound; + + dma->sglen = dma_map_sg(&dev->pci->dev, dma->sglist, + dma->nr_pages, DMA_FROM_DEVICE); + + if (0 == dma->sglen) { + pr_warn("%s: saa7134_alsa_map_sg failed\n", __func__); + return -ENOMEM; + } + return 0; +} + +static int saa7134_alsa_dma_unmap(struct saa7134_dev *dev) +{ + struct saa7134_dmasound *dma = &dev->dmasound; + + if (!dma->sglen) + return 0; + + dma_unmap_sg(&dev->pci->dev, dma->sglist, dma->nr_pages, DMA_FROM_DEVICE); + dma->sglen = 0; + return 0; +} + +static int saa7134_alsa_dma_free(struct saa7134_dmasound *dma) +{ + vfree(dma->sglist); + dma->sglist = NULL; + vfree(dma->vaddr); + dma->vaddr = NULL; + return 0; +} + +/* + * DMA buffer initialization + * + * Uses V4L functions to initialize the DMA. Shouldn't be necessary in + * ALSA, but I was unable to use ALSA's own DMA, and had to force the + * usage of V4L's + * + * - Copied verbatim from saa7134-oss. + * + */ + +static int dsp_buffer_init(struct saa7134_dev *dev) +{ + int err; + + BUG_ON(!dev->dmasound.bufsize); + + err = saa7134_alsa_dma_init(dev, + (dev->dmasound.bufsize + PAGE_SIZE) >> PAGE_SHIFT); + if (0 != err) + return err; + return 0; +} + +/* + * DMA buffer release + * + * Called after closing the device, during snd_card_saa7134_capture_close + * + */ + +static int dsp_buffer_free(struct saa7134_dev *dev) +{ + BUG_ON(!dev->dmasound.blksize); + + saa7134_alsa_dma_free(&dev->dmasound); + + dev->dmasound.blocks = 0; + dev->dmasound.blksize = 0; + dev->dmasound.bufsize = 0; + + return 0; +} + +/* + * Setting the capture source and updating the ALSA controls + */ +static int snd_saa7134_capsrc_set(struct snd_kcontrol *kcontrol, + int left, int right, bool force_notify) +{ + snd_card_saa7134_t *chip = snd_kcontrol_chip(kcontrol); + int change = 0, addr = kcontrol->private_value; + int active, old_addr; + u32 anabar, xbarin; + int analog_io, rate; + struct saa7134_dev *dev; + + dev = chip->dev; + + spin_lock_irq(&chip->mixer_lock); + + active = left != 0 || right != 0; + old_addr = chip->capture_source_addr; + + /* The active capture source cannot be deactivated */ + if (active) { + change = old_addr != addr || + chip->capture_source[0] != left || + chip->capture_source[1] != right; + + chip->capture_source[0] = left; + chip->capture_source[1] = right; + chip->capture_source_addr = addr; + dev->dmasound.input = addr; + } + spin_unlock_irq(&chip->mixer_lock); + + if (change) { + switch (dev->pci->device) { + + case PCI_DEVICE_ID_PHILIPS_SAA7134: + switch (addr) { + case MIXER_ADDR_TVTUNER: + saa_andorb(SAA7134_AUDIO_FORMAT_CTRL, + 0xc0, 0xc0); + saa_andorb(SAA7134_SIF_SAMPLE_FREQ, + 0x03, 0x00); + break; + case MIXER_ADDR_LINE1: + case MIXER_ADDR_LINE2: + analog_io = (MIXER_ADDR_LINE1 == addr) ? + 0x00 : 0x08; + rate = (32000 == dev->dmasound.rate) ? + 0x01 : 0x03; + saa_andorb(SAA7134_ANALOG_IO_SELECT, + 0x08, analog_io); + saa_andorb(SAA7134_AUDIO_FORMAT_CTRL, + 0xc0, 0x80); + saa_andorb(SAA7134_SIF_SAMPLE_FREQ, + 0x03, rate); + break; + } + + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + xbarin = 0x03; /* adc */ + anabar = 0; + switch (addr) { + case MIXER_ADDR_TVTUNER: + xbarin = 0; /* Demodulator */ + anabar = 2; /* DACs */ + break; + case MIXER_ADDR_LINE1: + anabar = 0; /* aux1, aux1 */ + break; + case MIXER_ADDR_LINE2: + anabar = 9; /* aux2, aux2 */ + break; + } + + /* output xbar always main channel */ + saa_dsp_writel(dev, SAA7133_DIGITAL_OUTPUT_SEL1, + 0xbbbb10); + + if (left || right) { + /* We've got data, turn the input on */ + saa_dsp_writel(dev, SAA7133_DIGITAL_INPUT_XBAR1, + xbarin); + saa_writel(SAA7133_ANALOG_IO_SELECT, anabar); + } else { + saa_dsp_writel(dev, SAA7133_DIGITAL_INPUT_XBAR1, + 0); + saa_writel(SAA7133_ANALOG_IO_SELECT, 0); + } + break; + } + } + + if (change) { + if (force_notify) + snd_ctl_notify(chip->card, + SNDRV_CTL_EVENT_MASK_VALUE, + &chip->capture_ctl[addr]->id); + + if (old_addr != MIXER_ADDR_UNSELECTED && old_addr != addr) + snd_ctl_notify(chip->card, + SNDRV_CTL_EVENT_MASK_VALUE, + &chip->capture_ctl[old_addr]->id); + } + + return change; +} + +/* + * ALSA PCM preparation + * + * - One of the ALSA capture callbacks. + * + * Called right after the capture device is opened, this function configures + * the buffer using the previously defined functions, allocates the memory, + * sets up the hardware registers, and then starts the DMA. When this function + * returns, the audio should be flowing. + * + */ + +static int snd_card_saa7134_capture_prepare(struct snd_pcm_substream * substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int bswap, sign; + u32 fmt, control; + snd_card_saa7134_t *saa7134 = snd_pcm_substream_chip(substream); + struct saa7134_dev *dev; + snd_card_saa7134_pcm_t *pcm = runtime->private_data; + + pcm->dev->dmasound.substream = substream; + + dev = saa7134->dev; + + if (snd_pcm_format_width(runtime->format) == 8) + fmt = 0x00; + else + fmt = 0x01; + + if (snd_pcm_format_signed(runtime->format)) + sign = 1; + else + sign = 0; + + if (snd_pcm_format_big_endian(runtime->format)) + bswap = 1; + else + bswap = 0; + + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + if (1 == runtime->channels) + fmt |= (1 << 3); + if (2 == runtime->channels) + fmt |= (3 << 3); + if (sign) + fmt |= 0x04; + + fmt |= (MIXER_ADDR_TVTUNER == dev->dmasound.input) ? 0xc0 : 0x80; + saa_writeb(SAA7134_NUM_SAMPLES0, ((dev->dmasound.blksize - 1) & 0x0000ff)); + saa_writeb(SAA7134_NUM_SAMPLES1, ((dev->dmasound.blksize - 1) & 0x00ff00) >> 8); + saa_writeb(SAA7134_NUM_SAMPLES2, ((dev->dmasound.blksize - 1) & 0xff0000) >> 16); + saa_writeb(SAA7134_AUDIO_FORMAT_CTRL, fmt); + + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + if (1 == runtime->channels) + fmt |= (1 << 4); + if (2 == runtime->channels) + fmt |= (2 << 4); + if (!sign) + fmt |= 0x04; + saa_writel(SAA7133_NUM_SAMPLES, dev->dmasound.blksize -1); + saa_writel(SAA7133_AUDIO_CHANNEL, 0x543210 | (fmt << 24)); + break; + } + + pr_debug("rec_start: afmt=%d ch=%d => fmt=0x%x swap=%c\n", + runtime->format, runtime->channels, fmt, + bswap ? 'b' : '-'); + /* dma: setup channel 6 (= AUDIO) */ + control = SAA7134_RS_CONTROL_BURST_16 | + SAA7134_RS_CONTROL_ME | + (dev->dmasound.pt.dma >> 12); + if (bswap) + control |= SAA7134_RS_CONTROL_BSWAP; + + saa_writel(SAA7134_RS_BA1(6),0); + saa_writel(SAA7134_RS_BA2(6),dev->dmasound.blksize); + saa_writel(SAA7134_RS_PITCH(6),0); + saa_writel(SAA7134_RS_CONTROL(6),control); + + dev->dmasound.rate = runtime->rate; + + /* Setup and update the card/ALSA controls */ + snd_saa7134_capsrc_set(saa7134->capture_ctl[dev->dmasound.input], 1, 1, + true); + + return 0; + +} + +/* + * ALSA pointer fetching + * + * - One of the ALSA capture callbacks. + * + * Called whenever a period elapses, it must return the current hardware + * position of the buffer. + * Also resets the read counter used to prevent overruns + * + */ + +static snd_pcm_uframes_t +snd_card_saa7134_capture_pointer(struct snd_pcm_substream * substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_card_saa7134_pcm_t *pcm = runtime->private_data; + struct saa7134_dev *dev=pcm->dev; + + if (dev->dmasound.read_count) { + dev->dmasound.read_count -= snd_pcm_lib_period_bytes(substream); + dev->dmasound.read_offset += snd_pcm_lib_period_bytes(substream); + if (dev->dmasound.read_offset == dev->dmasound.bufsize) + dev->dmasound.read_offset = 0; + } + + return bytes_to_frames(runtime, dev->dmasound.read_offset); +} + +/* + * ALSA hardware capabilities definition + * + * Report only 32kHz for ALSA: + * + * - SAA7133/35 uses DDEP (DemDec Easy Programming mode), which works in 32kHz + * only + * - SAA7134 for TV mode uses DemDec mode (32kHz) + * - Radio works in 32kHz only + * - When recording 48kHz from Line1/Line2, switching of capture source to TV + * means + * switching to 32kHz without any frequency translation + */ + +static const struct snd_pcm_hardware snd_card_saa7134_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_U16_BE, + .rates = SNDRV_PCM_RATE_32000, + .rate_min = 32000, + .rate_max = 32000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (256*1024), + .period_bytes_min = 64, + .period_bytes_max = (256*1024), + .periods_min = 4, + .periods_max = 1024, +}; + +static void snd_card_saa7134_runtime_free(struct snd_pcm_runtime *runtime) +{ + snd_card_saa7134_pcm_t *pcm = runtime->private_data; + + kfree(pcm); +} + + +/* + * ALSA hardware params + * + * - One of the ALSA capture callbacks. + * + * Called on initialization, right before the PCM preparation + * + */ + +static int snd_card_saa7134_hw_params(struct snd_pcm_substream * substream, + struct snd_pcm_hw_params * hw_params) +{ + snd_card_saa7134_t *saa7134 = snd_pcm_substream_chip(substream); + struct saa7134_dev *dev; + unsigned int period_size, periods; + int err; + + period_size = params_period_bytes(hw_params); + periods = params_periods(hw_params); + + if (period_size < 0x100 || period_size > 0x10000) + return -EINVAL; + if (periods < 4) + return -EINVAL; + if (period_size * periods > 1024 * 1024) + return -EINVAL; + + dev = saa7134->dev; + + if (dev->dmasound.blocks == periods && + dev->dmasound.blksize == period_size) + return 0; + + /* release the old buffer */ + if (substream->runtime->dma_area) { + saa7134_pgtable_free(dev->pci, &dev->dmasound.pt); + saa7134_alsa_dma_unmap(dev); + dsp_buffer_free(dev); + substream->runtime->dma_area = NULL; + } + dev->dmasound.blocks = periods; + dev->dmasound.blksize = period_size; + dev->dmasound.bufsize = period_size * periods; + + err = dsp_buffer_init(dev); + if (0 != err) { + dev->dmasound.blocks = 0; + dev->dmasound.blksize = 0; + dev->dmasound.bufsize = 0; + return err; + } + + err = saa7134_alsa_dma_map(dev); + if (err) { + dsp_buffer_free(dev); + return err; + } + err = saa7134_pgtable_alloc(dev->pci, &dev->dmasound.pt); + if (err) { + saa7134_alsa_dma_unmap(dev); + dsp_buffer_free(dev); + return err; + } + err = saa7134_pgtable_build(dev->pci, &dev->dmasound.pt, + dev->dmasound.sglist, dev->dmasound.sglen, 0); + if (err) { + saa7134_pgtable_free(dev->pci, &dev->dmasound.pt); + saa7134_alsa_dma_unmap(dev); + dsp_buffer_free(dev); + return err; + } + + /* I should be able to use runtime->dma_addr in the control + byte, but it doesn't work. So I allocate the DMA using the + V4L functions, and force ALSA to use that as the DMA area */ + + substream->runtime->dma_area = dev->dmasound.vaddr; + substream->runtime->dma_bytes = dev->dmasound.bufsize; + substream->runtime->dma_addr = 0; + + return 0; + +} + +/* + * ALSA hardware release + * + * - One of the ALSA capture callbacks. + * + * Called after closing the device, but before snd_card_saa7134_capture_close + * It stops the DMA audio and releases the buffers. + * + */ + +static int snd_card_saa7134_hw_free(struct snd_pcm_substream * substream) +{ + snd_card_saa7134_t *saa7134 = snd_pcm_substream_chip(substream); + struct saa7134_dev *dev; + + dev = saa7134->dev; + + if (substream->runtime->dma_area) { + saa7134_pgtable_free(dev->pci, &dev->dmasound.pt); + saa7134_alsa_dma_unmap(dev); + dsp_buffer_free(dev); + substream->runtime->dma_area = NULL; + } + + return 0; +} + +/* + * ALSA capture finish + * + * - One of the ALSA capture callbacks. + * + * Called after closing the device. + * + */ + +static int snd_card_saa7134_capture_close(struct snd_pcm_substream * substream) +{ + snd_card_saa7134_t *saa7134 = snd_pcm_substream_chip(substream); + struct saa7134_dev *dev = saa7134->dev; + + if (saa7134->mute_was_on) { + dev->ctl_mute = 1; + saa7134_tvaudio_setmute(dev); + } + return 0; +} + +/* + * ALSA capture start + * + * - One of the ALSA capture callbacks. + * + * Called when opening the device. It creates and populates the PCM + * structure + * + */ + +static int snd_card_saa7134_capture_open(struct snd_pcm_substream * substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_card_saa7134_pcm_t *pcm; + snd_card_saa7134_t *saa7134 = snd_pcm_substream_chip(substream); + struct saa7134_dev *dev; + int amux, err; + + if (!saa7134) { + pr_err("BUG: saa7134 can't find device struct. Can't proceed with open\n"); + return -ENODEV; + } + dev = saa7134->dev; + mutex_lock(&dev->dmasound.lock); + + dev->dmasound.read_count = 0; + dev->dmasound.read_offset = 0; + + amux = dev->input->amux; + if ((amux < 1) || (amux > 3)) + amux = 1; + dev->dmasound.input = amux - 1; + + mutex_unlock(&dev->dmasound.lock); + + pcm = kzalloc_obj(*pcm); + if (pcm == NULL) + return -ENOMEM; + + pcm->dev=saa7134->dev; + + spin_lock_init(&pcm->lock); + + pcm->substream = substream; + runtime->private_data = pcm; + runtime->private_free = snd_card_saa7134_runtime_free; + runtime->hw = snd_card_saa7134_capture; + + if (dev->ctl_mute != 0) { + saa7134->mute_was_on = 1; + dev->ctl_mute = 0; + saa7134_tvaudio_setmute(dev); + } + + err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIODS, 2); + if (err < 0) + return err; + + return 0; +} + +/* + * page callback (needed for mmap) + */ + +static struct page *snd_card_saa7134_page(struct snd_pcm_substream *substream, + unsigned long offset) +{ + void *pageptr = substream->runtime->dma_area + offset; + return vmalloc_to_page(pageptr); +} + +/* + * ALSA capture callbacks definition + */ + +static const struct snd_pcm_ops snd_card_saa7134_capture_ops = { + .open = snd_card_saa7134_capture_open, + .close = snd_card_saa7134_capture_close, + .hw_params = snd_card_saa7134_hw_params, + .hw_free = snd_card_saa7134_hw_free, + .prepare = snd_card_saa7134_capture_prepare, + .trigger = snd_card_saa7134_capture_trigger, + .pointer = snd_card_saa7134_capture_pointer, + .page = snd_card_saa7134_page, +}; + +/* + * ALSA PCM setup + * + * Called when initializing the board. Sets up the name and hooks up + * the callbacks + * + */ + +static int snd_card_saa7134_pcm(snd_card_saa7134_t *saa7134, int device) +{ + struct snd_pcm *pcm; + int err; + + if ((err = snd_pcm_new(saa7134->card, "SAA7134 PCM", device, 0, 1, &pcm)) < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_card_saa7134_capture_ops); + pcm->private_data = saa7134; + pcm->info_flags = 0; + strscpy(pcm->name, "SAA7134 PCM", sizeof(pcm->name)); + return 0; +} + +#define SAA713x_VOLUME(xname, xindex, addr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_saa7134_volume_info, \ + .get = snd_saa7134_volume_get, .put = snd_saa7134_volume_put, \ + .private_value = addr } + +static int snd_saa7134_volume_info(struct snd_kcontrol * kcontrol, + struct snd_ctl_elem_info * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 20; + return 0; +} + +static int snd_saa7134_volume_get(struct snd_kcontrol * kcontrol, + struct snd_ctl_elem_value * ucontrol) +{ + snd_card_saa7134_t *chip = snd_kcontrol_chip(kcontrol); + int addr = kcontrol->private_value; + + ucontrol->value.integer.value[0] = chip->mixer_volume[addr][0]; + ucontrol->value.integer.value[1] = chip->mixer_volume[addr][1]; + return 0; +} + +static int snd_saa7134_volume_put(struct snd_kcontrol * kcontrol, + struct snd_ctl_elem_value * ucontrol) +{ + snd_card_saa7134_t *chip = snd_kcontrol_chip(kcontrol); + struct saa7134_dev *dev = chip->dev; + + int change, addr = kcontrol->private_value; + int left, right; + + left = ucontrol->value.integer.value[0]; + if (left < 0) + left = 0; + if (left > 20) + left = 20; + right = ucontrol->value.integer.value[1]; + if (right < 0) + right = 0; + if (right > 20) + right = 20; + spin_lock_irq(&chip->mixer_lock); + change = 0; + if (chip->mixer_volume[addr][0] != left) { + change = 1; + right = left; + } + if (chip->mixer_volume[addr][1] != right) { + change = 1; + left = right; + } + if (change) { + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + switch (addr) { + case MIXER_ADDR_TVTUNER: + left = 20; + break; + case MIXER_ADDR_LINE1: + saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x10, + (left > 10) ? 0x00 : 0x10); + break; + case MIXER_ADDR_LINE2: + saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x20, + (left > 10) ? 0x00 : 0x20); + break; + } + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + switch (addr) { + case MIXER_ADDR_TVTUNER: + left = 20; + break; + case MIXER_ADDR_LINE1: + saa_andorb(0x0594, 0x10, + (left > 10) ? 0x00 : 0x10); + break; + case MIXER_ADDR_LINE2: + saa_andorb(0x0594, 0x20, + (left > 10) ? 0x00 : 0x20); + break; + } + break; + } + chip->mixer_volume[addr][0] = left; + chip->mixer_volume[addr][1] = right; + } + spin_unlock_irq(&chip->mixer_lock); + return change; +} + +#define SAA713x_CAPSRC(xname, xindex, addr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_saa7134_capsrc_info, \ + .get = snd_saa7134_capsrc_get, .put = snd_saa7134_capsrc_put, \ + .private_value = addr } + +static int snd_saa7134_capsrc_info(struct snd_kcontrol * kcontrol, + struct snd_ctl_elem_info * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int snd_saa7134_capsrc_get(struct snd_kcontrol * kcontrol, + struct snd_ctl_elem_value * ucontrol) +{ + snd_card_saa7134_t *chip = snd_kcontrol_chip(kcontrol); + int addr = kcontrol->private_value; + + spin_lock_irq(&chip->mixer_lock); + if (chip->capture_source_addr == addr) { + ucontrol->value.integer.value[0] = chip->capture_source[0]; + ucontrol->value.integer.value[1] = chip->capture_source[1]; + } else { + ucontrol->value.integer.value[0] = 0; + ucontrol->value.integer.value[1] = 0; + } + spin_unlock_irq(&chip->mixer_lock); + + return 0; +} + +static int snd_saa7134_capsrc_put(struct snd_kcontrol * kcontrol, + struct snd_ctl_elem_value * ucontrol) +{ + int left, right; + left = ucontrol->value.integer.value[0] & 1; + right = ucontrol->value.integer.value[1] & 1; + + return snd_saa7134_capsrc_set(kcontrol, left, right, false); +} + +static struct snd_kcontrol_new snd_saa7134_volume_controls[] = { +SAA713x_VOLUME("Video Volume", 0, MIXER_ADDR_TVTUNER), +SAA713x_VOLUME("Line Volume", 1, MIXER_ADDR_LINE1), +SAA713x_VOLUME("Line Volume", 2, MIXER_ADDR_LINE2), +}; + +static struct snd_kcontrol_new snd_saa7134_capture_controls[] = { +SAA713x_CAPSRC("Video Capture Switch", 0, MIXER_ADDR_TVTUNER), +SAA713x_CAPSRC("Line Capture Switch", 1, MIXER_ADDR_LINE1), +SAA713x_CAPSRC("Line Capture Switch", 2, MIXER_ADDR_LINE2), +}; + +/* + * ALSA mixer setup + * + * Called when initializing the board. Sets up the name and hooks up + * the callbacks + * + */ + +static int snd_card_saa7134_new_mixer(snd_card_saa7134_t * chip) +{ + struct snd_card *card = chip->card; + struct snd_kcontrol *kcontrol; + unsigned int idx; + int err, addr; + + strscpy(card->mixername, "SAA7134 Mixer", sizeof(card->mixername)); + + for (idx = 0; idx < ARRAY_SIZE(snd_saa7134_volume_controls); idx++) { + kcontrol = snd_ctl_new1(&snd_saa7134_volume_controls[idx], + chip); + err = snd_ctl_add(card, kcontrol); + if (err < 0) + return err; + } + + for (idx = 0; idx < ARRAY_SIZE(snd_saa7134_capture_controls); idx++) { + kcontrol = snd_ctl_new1(&snd_saa7134_capture_controls[idx], + chip); + addr = snd_saa7134_capture_controls[idx].private_value; + chip->capture_ctl[addr] = kcontrol; + err = snd_ctl_add(card, kcontrol); + if (err < 0) + return err; + } + + chip->capture_source_addr = MIXER_ADDR_UNSELECTED; + return 0; +} + +static void snd_saa7134_free(struct snd_card * card) +{ + snd_card_saa7134_t *chip = card->private_data; + + if (chip->dev->dmasound.priv_data == NULL) + return; + + chip->dev->dmasound.priv_data = NULL; + +} + +/* + * ALSA initialization + * + * Called by the init routine, once for each saa7134 device present, + * it creates the basic structures and registers the ALSA devices + * + */ + +static int alsa_card_saa7134_create(struct saa7134_dev *dev, int devnum) +{ + + struct snd_card *card; + snd_card_saa7134_t *chip; + int err; + + + if (devnum >= SNDRV_CARDS) + return -ENODEV; + if (!enable[devnum]) + return -ENODEV; + + err = snd_card_new(&dev->pci->dev, index[devnum], id[devnum], + THIS_MODULE, sizeof(snd_card_saa7134_t), &card); + if (err < 0) + return err; + + strscpy(card->driver, "SAA7134", sizeof(card->driver)); + + /* Card "creation" */ + + card->private_free = snd_saa7134_free; + chip = card->private_data; + + spin_lock_init(&chip->lock); + spin_lock_init(&chip->mixer_lock); + + chip->dev = dev; + + chip->card = card; + + chip->pci = dev->pci; + chip->iobase = pci_resource_start(dev->pci, 0); + + + err = devm_request_irq(&dev->pci->dev, dev->pci->irq, saa7134_alsa_irq, + IRQF_SHARED, dev->name, &dev->dmasound); + if (err < 0) { + pr_err("%s: can't get IRQ %d for ALSA\n", + dev->name, dev->pci->irq); + goto __nodev; + } + + chip->irq = dev->pci->irq; + + mutex_init(&dev->dmasound.lock); + + if ((err = snd_card_saa7134_new_mixer(chip)) < 0) + goto __nodev; + + if ((err = snd_card_saa7134_pcm(chip, 0)) < 0) + goto __nodev; + + /* End of "creation" */ + + strscpy(card->shortname, "SAA7134", sizeof(card->shortname)); + sprintf(card->longname, "%s at 0x%lx irq %d", + chip->dev->name, chip->iobase, chip->irq); + + pr_info("%s/alsa: %s registered as card %d\n", + dev->name, card->longname, index[devnum]); + + if ((err = snd_card_register(card)) == 0) { + snd_saa7134_cards[devnum] = card; + return 0; + } + +__nodev: + snd_card_free(card); + return err; +} + + +static int alsa_device_init(struct saa7134_dev *dev) +{ + dev->dmasound.priv_data = dev; + alsa_card_saa7134_create(dev,dev->nr); + return 1; +} + +static int alsa_device_exit(struct saa7134_dev *dev) +{ + if (!snd_saa7134_cards[dev->nr]) + return 1; + + snd_card_free(snd_saa7134_cards[dev->nr]); + snd_saa7134_cards[dev->nr] = NULL; + return 1; +} + +/* + * Module initializer + * + * Loops through present saa7134 cards, and assigns an ALSA device + * to each one + * + */ + +static int saa7134_alsa_init(void) +{ + struct saa7134_dev *dev; + + saa7134_dmasound_init = alsa_device_init; + saa7134_dmasound_exit = alsa_device_exit; + + pr_info("saa7134 ALSA driver for DMA sound loaded\n"); + + list_for_each_entry(dev, &saa7134_devlist, devlist) { + if (dev->pci->device == PCI_DEVICE_ID_PHILIPS_SAA7130) + pr_info("%s/alsa: %s doesn't support digital audio\n", + dev->name, saa7134_boards[dev->board].name); + else + alsa_device_init(dev); + } + + if (list_empty(&saa7134_devlist)) + pr_info("saa7134 ALSA: no saa7134 cards found\n"); + + return 0; + +} + +/* + * Module destructor + */ + +static void saa7134_alsa_exit(void) +{ + int idx; + + for (idx = 0; idx < SNDRV_CARDS; idx++) { + if (snd_saa7134_cards[idx]) + snd_card_free(snd_saa7134_cards[idx]); + } + + saa7134_dmasound_init = NULL; + saa7134_dmasound_exit = NULL; + pr_info("saa7134 ALSA driver for DMA sound unloaded\n"); + + return; +} + +/* We initialize this late, to make sure the sound system is up and running */ +late_initcall(saa7134_alsa_init); +module_exit(saa7134_alsa_exit); +MODULE_DESCRIPTION("Philips SAA7134 DMA audio support"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ricardo Cerqueira"); diff --git a/drivers/media/pci/saa7134/saa7134-cards.c b/drivers/media/pci/saa7134/saa7134-cards.c new file mode 100644 index 000000000..a7d665407 --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134-cards.c @@ -0,0 +1,7567 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * device driver for philips saa7134 based TV cards + * card-specific stuff. + * + * (c) 2001-04 Gerd Knorr [SuSE Labs] + */ + +#include "saa7134.h" +#include "saa7134-reg.h" + +#include +#include +#include +#include + +#include "xc2028.h" +#include +#include +#include "tea5767.h" +#include "tda18271.h" +#include "xc5000.h" +#include "s5h1411.h" + +/* Input names */ +const char * const saa7134_input_name[] = { + [SAA7134_INPUT_MUTE] = "mute", + [SAA7134_INPUT_RADIO] = "Radio", + [SAA7134_INPUT_TV] = "Television", + [SAA7134_INPUT_TV_MONO] = "TV (mono only)", + [SAA7134_INPUT_COMPOSITE] = "Composite", + [SAA7134_INPUT_COMPOSITE0] = "Composite0", + [SAA7134_INPUT_COMPOSITE1] = "Composite1", + [SAA7134_INPUT_COMPOSITE2] = "Composite2", + [SAA7134_INPUT_COMPOSITE3] = "Composite3", + [SAA7134_INPUT_COMPOSITE4] = "Composite4", + [SAA7134_INPUT_SVIDEO] = "S-Video", + [SAA7134_INPUT_SVIDEO0] = "S-Video0", + [SAA7134_INPUT_SVIDEO1] = "S-Video1", + [SAA7134_INPUT_COMPOSITE_OVER_SVIDEO] = "Composite over S-Video", +}; + +/* ------------------------------------------------------------------ */ +/* board config info */ + +static struct tda18271_std_map aver_a706_std_map = { + .fm_radio = { .if_freq = 5500, .fm_rfn = 0, .agc_mode = 3, .std = 0, + .if_lvl = 0, .rfagc_top = 0x2c, }, +}; + +/* If radio_type !=UNSET, radio_addr should be specified + */ + +struct saa7134_board saa7134_boards[] = { + [SAA7134_BOARD_UNKNOWN] = { + .name = "UNKNOWN/GENERIC", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 0, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_PROTEUS_PRO] = { + /* /me */ + .name = "Proteus Pro [philips reference design]", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_FLYVIDEO3000] = { + /* "Marco d'Itri" */ + .name = "LifeView FlyVIDEO3000", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + + .gpiomask = 0xe000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x8000, + },{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + .gpio = 0x0000, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + .gpio = 0x4000, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE2, + .gpio = 0x4000, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + .gpio = 0x4000, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0x2000, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + .gpio = 0x8000, + }, + }, + [SAA7134_BOARD_FLYVIDEO2000] = { + /* "TC Wan" */ + .name = "LifeView/Typhoon FlyVIDEO2000", + .audio_clock = 0x00200000, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + + .gpiomask = 0xe000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + .gpio = 0x0000, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + .gpio = 0x4000, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE2, + .gpio = 0x4000, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + .gpio = 0x4000, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0x2000, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE2, + .gpio = 0x8000, + }, + }, + [SAA7134_BOARD_FLYTVPLATINUM_MINI] = { + /* "Arnaud Quette" */ + .name = "LifeView FlyTV Platinum Mini", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 0, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_FLYTVPLATINUM_FM] = { + /* LifeView FlyTV Platinum FM (LR214WF) */ + /* "Peter Missel */ + .name = "LifeView FlyTV Platinum FM / Gold", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + + .gpiomask = 0x1E000, /* Set GP16 and unused 15,14,13 to Output */ + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x10000, /* GP16=1 selects TV input */ + },{ +/* .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + .gpio = 0x0000, + },{ +*/ .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 0, + .amux = LINE2, +/* .gpio = 0x4000, */ + },{ + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 3, + .amux = LINE2, +/* .gpio = 0x4000, */ + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, +/* .gpio = 0x4000, */ + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x00000, /* GP16=0 selects FM radio antenna */ + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + .gpio = 0x10000, + }, + }, + [SAA7134_BOARD_ROVERMEDIA_LINK_PRO_FM] = { + /* RoverMedia TV Link Pro FM (LR138 REV:I) */ + /* Eugene Yudin */ + .name = "RoverMedia TV Link Pro FM", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, /* TCL MFPE05 2 */ + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0xe000, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x8000, + }, { + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + .gpio = 0x0000, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + .gpio = 0x4000, + }, { + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE2, + .gpio = 0x4000, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + .gpio = 0x4000, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0x2000, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + .gpio = 0x8000, + }, + }, + [SAA7134_BOARD_EMPRESS] = { + /* "Gert Vervoort" */ + .name = "EMPRESS", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .empress_addr = 0x20, + + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + .mpeg = SAA7134_MPEG_EMPRESS, + .video_out = CCIR656, + }, + [SAA7134_BOARD_MONSTERTV] = { + /* "K.Ohta" */ + .name = "SKNet Monster TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_MD9717] = { + .name = "Tevion MD 9717", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + /* workaround for problems with normal TV sound */ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + }, + }, + [SAA7134_BOARD_TVSTATION_RDS] = { + /* Typhoon TV Tuner RDS: Art.Nr. 50694 */ + .name = "KNC One TV-Station RDS / Typhoon TV Tuner RDS", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + },{ + + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + + .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 0, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_TVSTATION_DVR] = { + .name = "KNC One TV-Station DVR", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .empress_addr = 0x20, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x820000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x20000, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + .gpio = 0x20000, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x20000, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0x20000, + }, + .mpeg = SAA7134_MPEG_EMPRESS, + .video_out = CCIR656, + }, + [SAA7134_BOARD_CINERGY400] = { + .name = "Terratec Cinergy 400 TV", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 4, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 0, + .amux = LINE1, + }} + }, + [SAA7134_BOARD_MD5044] = { + .name = "Medion 5044", + .audio_clock = 0x00187de7, /* was: 0x00200000, */ + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + /* workaround for problems with normal TV sound */ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_KWORLD] = { + .name = "Kworld/KuroutoShikou SAA7130-TVPCI", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_CINERGY600] = { + .name = "Terratec Cinergy 600 TV", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 4, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 0, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_MD7134] = { + .name = "Medion 7134", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + }, + }, + [SAA7134_BOARD_TYPHOON_90031] = { + /* aka Typhoon "TV+Radio", Art.Nr 90031 */ + /* Tom Zoerner */ + .name = "Typhoon TV+Radio 90031", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_ELSA] = { + .name = "ELSA EX-VISION 300TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_HITACHI_NTSC, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 4, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_ELSA_500TV] = { + .name = "ELSA EX-VISION 500TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_HITACHI_NTSC, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 7, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 8, + .amux = TV, + },{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_ELSA_700TV] = { + .name = "ELSA EX-VISION 700TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_HITACHI_NTSC, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 4, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 6, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 7, + .amux = LINE1, + }}, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + }, + }, + [SAA7134_BOARD_ASUSTeK_TVFM7134] = { + .name = "ASUS TV-FM 7134", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 4, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 6, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_ASUSTeK_TVFM7135] = { + .name = "ASUS TV-FM 7135", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x200000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x0000, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 4, + .amux = LINE2, + .gpio = 0x0000, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 6, + .amux = LINE2, + .gpio = 0x0000, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x200000, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .gpio = 0x0000, + }, + + }, + [SAA7134_BOARD_VA1000POWER] = { + .name = "AOPEN VA1000 POWER", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_10MOONSTVMASTER] = { + /* "lilicheng" */ + .name = "10MOONS PCI TV CAPTURE CARD", + .audio_clock = 0x00200000, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0xe000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + .gpio = 0x0000, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + .gpio = 0x4000, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE2, + .gpio = 0x4000, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + .gpio = 0x4000, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0x2000, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE2, + .gpio = 0x8000, + }, + }, + [SAA7134_BOARD_BMK_MPEX_NOTUNER] = { + /* "Andrew de Quincey" */ + .name = "BMK MPEX No Tuner", + .audio_clock = 0x200000, + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .empress_addr = 0x20, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 4, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE3, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE4, + .vmux = 1, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .mpeg = SAA7134_MPEG_EMPRESS, + .video_out = CCIR656, + }, + [SAA7134_BOARD_VIDEOMATE_TV] = { + .name = "Compro VideoMate TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_VIDEOMATE_TV_GOLD_PLUS] = { + .name = "Compro VideoMate TV Gold+", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .gpiomask = 0x800c0000, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + .gpio = 0x06c00012, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x0ac20012, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + .gpio = 0x08c20012, + }}, /* radio and probably mute is missing */ + }, + [SAA7134_BOARD_CRONOS_PLUS] = { + /* + gpio pins: + 0 .. 3 BASE_ID + 4 .. 7 PROTECT_ID + 8 .. 11 USER_OUT + 12 .. 13 USER_IN + 14 .. 15 VIDIN_SEL + */ + .name = "Matrox CronosPlus", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0xcf00, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .gpio = 2 << 14, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 0, + .gpio = 1 << 14, + },{ + .type = SAA7134_INPUT_COMPOSITE3, + .vmux = 0, + .gpio = 0 << 14, + },{ + .type = SAA7134_INPUT_COMPOSITE4, + .vmux = 0, + .gpio = 3 << 14, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .gpio = 2 << 14, + }}, + }, + [SAA7134_BOARD_MD2819] = { + .name = "AverMedia M156 / Medion 2819", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x03, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x00, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x02, + }, { + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 0, + .amux = LINE1, + .gpio = 0x02, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + .gpio = 0x02, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE1, + .gpio = 0x01, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + .gpio = 0x00, + }, + }, + [SAA7134_BOARD_BMK_MPEX_TUNER] = { + /* "Greg Wickham */ + .name = "BMK MPEX Tuner", + .audio_clock = 0x200000, + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .empress_addr = 0x20, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }}, + .mpeg = SAA7134_MPEG_EMPRESS, + .video_out = CCIR656, + }, + [SAA7134_BOARD_ASUSTEK_TVFM7133] = { + .name = "ASUS TV-FM 7133", + .audio_clock = 0x00187de7, + /* probably wrong, the 7133 one is the NTSC version ... + * .tuner_type = TUNER_PHILIPS_FM1236_MK3 */ + .tuner_type = TUNER_LG_NTSC_NEW_TAPC, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 4, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 6, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_PINNACLE_PCTV_STEREO] = { + .name = "Pinnacle PCTV Stereo (saa7134)", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_MT2032, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT | TDA9887_INTERCARRIER | TDA9887_PORT2_INACTIVE, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_MANLI_MTV002] = { + /* Ognjen Nastic */ + .name = "Manli MuchTV M-TV002", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_MANLI_MTV001] = { + /* Ognjen Nastic UNTESTED */ + .name = "Manli MuchTV M-TV001", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = LINE2, + }}, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_TG3000TV] = { + /* TransGear 3000TV */ + .name = "Nagase Sangyo TransGear 3000TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_ECS_TVP3XP] = { + .name = "Elitegroup ECS TVP3XP FM1216 Tuner Card(PAL-BG,FM) ", + .audio_clock = 0x187de7, /* xtal 32.1 MHz */ + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 0, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_ECS_TVP3XP_4CB5] = { + .name = "Elitegroup ECS TVP3XP FM1236 Tuner Card (NTSC,FM)", + .audio_clock = 0x187de7, + .tuner_type = TUNER_PHILIPS_NTSC, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 0, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_ECS_TVP3XP_4CB6] = { + /* Barry Scott */ + .name = "Elitegroup ECS TVP3XP FM1246 Tuner Card (PAL,FM)", + .audio_clock = 0x187de7, + .tuner_type = TUNER_PHILIPS_PAL_I, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 0, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_AVACSSMARTTV] = { + /* Roman Pszonczenko */ + .name = "AVACS SmartTV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0x200000, + }, + }, + [SAA7134_BOARD_AVERMEDIA_DVD_EZMAKER] = { + /* Michael Smith */ + .name = "AVerMedia DVD EZMaker", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + }}, + }, + [SAA7134_BOARD_AVERMEDIA_M103] = { + /* Massimo Piccioni */ + .name = "AVerMedia MiniPCI DVB-T Hybrid M103", + .audio_clock = 0x187de7, + .tuner_type = TUNER_XC2028, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + } }, + }, + [SAA7134_BOARD_NOVAC_PRIMETV7133] = { + /* toshii@netbsd.org */ + .name = "Noval Prime TV 7133", + .audio_clock = 0x00200000, + .tuner_type = TUNER_ALPS_TSBH1_NTSC, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + }}, + }, + [SAA7134_BOARD_AVERMEDIA_STUDIO_305] = { + .name = "AverMedia AverTV Studio 305", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1256_IH3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_AVERMEDIA_STUDIO_505] = { + /* Vasiliy Temnikov */ + .name = "AverMedia AverTV Studio 505", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_UPMOST_PURPLE_TV] = { + .name = "UPMOST PURPLE TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1236_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 7, + .amux = TV, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 7, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_ITEMS_MTV005] = { + /* Norman Jonas */ + .name = "Items MuchTV Plus / IT-005", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_CINERGY200] = { + .name = "Terratec Cinergy 200 TV", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 4, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 0, + .amux = LINE1, + }}, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_VIDEOMATE_TV_PVR] = { + /* Alain St-Denis */ + .name = "Compro VideoMate TV PVR/FM", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x808c0080, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + .gpio = 0x00080, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x00080, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2_LEFT, + .gpio = 0x00080, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0x80000, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE2, + .gpio = 0x40000, + }, + }, + [SAA7134_BOARD_SABRENT_SBTTVFM] = { + /* Michael Rodriguez-Torrent */ + .name = "Sabrent SBT-TVFM (saa7130)", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_ZOLID_XPERT_TV7134] = { + /* Helge Jensen */ + .name = ":Zolid Xpert TV7134", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_EMPIRE_PCI_TV_RADIO_LE] = { + /* "Matteo Az" ;-) */ + .name = "Empire PCI TV-Radio LE", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x4000, + .inputs = {{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + .gpio = 0x8000, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x8000, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 6, + .amux = LINE1, + .gpio = 0x8000, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE1, + .gpio = 0x8000, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + .gpio =0x8000, + } + }, + [SAA7134_BOARD_AVERMEDIA_STUDIO_307] = { + /* + Nickolay V. Shmyrev + Lots of thanks to Andrey Zolotarev + */ + .name = "Avermedia AVerTV Studio 307", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1256_IH3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x03, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x00, + },{ + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 3, + .amux = LINE1, + .gpio = 0x02, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + .gpio = 0x02, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE1, + .gpio = 0x01, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE1, + .gpio = 0x00, + }, + }, + [SAA7134_BOARD_AVERMEDIA_GO_007_FM] = { + .name = "Avermedia AVerTV GO 007 FM", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x00300003, + /* .gpiomask = 0x8c240003, */ + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x01, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + .gpio = 0x02, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 6, + .amux = LINE1, + .gpio = 0x02, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x00300001, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + .gpio = 0x01, + }, + }, + [SAA7134_BOARD_AVERMEDIA_CARDBUS] = { + /* Kees.Blom@cwi.nl */ + .name = "AVerMedia Cardbus TV/Radio (E500)", + .audio_clock = 0x187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_AVERMEDIA_CARDBUS_501] = { + /* Oldrich Jedlicka */ + .name = "AVerMedia Cardbus TV/Radio (E501R)", + .audio_clock = 0x187de7, + .tuner_type = TUNER_ALPS_TSBE5_PAL, + .radio_type = TUNER_TEA5767, + .tuner_addr = 0x61, + .radio_addr = 0x60, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x08000000, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x08000000, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x08000000, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + .gpio = 0x08000000, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0x00000000, + }, + }, + [SAA7134_BOARD_CINERGY400_CARDBUS] = { + .name = "Terratec Cinergy 400 mobile", + .audio_clock = 0x187de7, + .tuner_type = TUNER_ALPS_TSBE5_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_CINERGY600_MK3] = { + .name = "Terratec Cinergy 600 TV MK3", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .rds_addr = 0x10, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 4, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 0, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_VIDEOMATE_GOLD_PLUS] = { + /* Dylan Walkden */ + .name = "Compro VideoMate Gold+ Pal", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x1ce780, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 0, + .amux = LINE1, + .gpio = 0x008080, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x008080, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x008080, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0x80000, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE2, + .gpio = 0x0c8000, + }, + }, + [SAA7134_BOARD_PINNACLE_300I_DVBT_PAL] = { + .name = "Pinnacle PCTV 300i DVB-T + PAL", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_MT2032, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT | TDA9887_INTERCARRIER | TDA9887_PORT2_INACTIVE, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_PROVIDEO_PV952] = { + /* andreas.kretschmer@web.de */ + .name = "ProVideo PV952", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_AVERMEDIA_305] = { + /* much like the "studio" version but without radio + * and another tuner (sirspiritus@yandex.ru) */ + .name = "AverMedia AverTV/305", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FQ1216ME, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_FLYDVBTDUO] = { + /* LifeView FlyDVB-T DUO */ + /* "Nico Sabbi Hartmut Hackmann hartmut.hackmann@t-online.de*/ + .name = "LifeView FlyDVB-T DUO / MSI TV@nywhere Duo", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x00200000, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x200000, /* GPIO21=High for TV input */ + },{ + .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 0, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x000000, /* GPIO21=Low for FM radio antenna */ + }, + }, + [SAA7134_BOARD_PHILIPS_TOUGH] = { + .name = "Philips TOUGH DVB-T reference design", + .tuner_type = TUNER_ABSENT, + .audio_clock = 0x00187de7, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_AVERMEDIA_307] = { + /* + Davydov Vladimir + */ + .name = "Avermedia AVerTV 307", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FQ1216ME, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_ADS_INSTANT_TV] = { + .name = "ADS Tech Instant TV (saa7135)", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_KWORLD_VSTREAM_XPERT] = { + .name = "Kworld/Tevion V-Stream Xpert TV PVR7134", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_PAL_I, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x0700, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x000, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x200, /* gpio by DScaler */ + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 0, + .amux = LINE1, + .gpio = 0x200, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE1, + .gpio = 0x100, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + .gpio = 0x000, + }, + }, + [SAA7134_BOARD_FLYDVBT_DUO_CARDBUS] = { + .name = "LifeView/Typhoon/Genius FlyDVB-T Duo Cardbus", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .gpiomask = 0x00200000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x200000, /* GPIO21=High for TV input */ + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 0, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x000000, /* GPIO21=Low for FM radio antenna */ + }, + }, + [SAA7134_BOARD_VIDEOMATE_TV_GOLD_PLUSII] = { + .name = "Compro VideoMate TV Gold+II", + .audio_clock = 0x002187de7, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .radio_type = TUNER_TEA5767, + .tuner_addr = 0x63, + .radio_addr = 0x60, + .gpiomask = 0x8c1880, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 0, + .amux = LINE1, + .gpio = 0x800800, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x801000, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x800000, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x880000, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE2, + .gpio = 0x840000, + }, + }, + [SAA7134_BOARD_KWORLD_XPERT] = { + /* + FIXME: + - Remote control doesn't initialize properly. + - Audio volume starts muted, + then gradually increases after channel change. + - Composite S-Video untested. + From: Konrad Rzepecki + */ + .name = "Kworld Xpert TV PVR7134", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_TENA_9533_DI, + .radio_type = TUNER_TEA5767, + .tuner_addr = 0x61, + .radio_addr = 0x60, + .gpiomask = 0x0700, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x000, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x200, /* gpio by DScaler */ + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 0, + .amux = LINE1, + .gpio = 0x200, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE1, + .gpio = 0x100, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + .gpio = 0x000, + }, + }, + [SAA7134_BOARD_FLYTV_DIGIMATRIX] = { + .name = "FlyTV mini Asus Digimatrix", + .audio_clock = 0x00200000, + .tuner_type = TUNER_LG_TALN, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, /* radio unconfirmed */ + .amux = LINE2, + }, + }, + [SAA7134_BOARD_KWORLD_TERMINATOR] = { + /* Kworld V-Stream Studio TV Terminator */ + /* "James Webb */ + .name = "V-Stream Studio TV Terminator", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 1 << 21, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x0000000, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE2, + .gpio = 0x0000000, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + .gpio = 0x0000000, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_YUAN_TUN900] = { + /* FIXME: + * S-Video and composite sources untested. + * Radio not working. + * Remote control not yet implemented. + * From : codemaster@webgeeks.be */ + .name = "Yuan TUN-900 (saa7135)", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr= ADDR_UNSET, + .radio_addr= ADDR_UNSET, + .gpiomask = 0x00010003, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x01, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + .gpio = 0x02, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 6, + .amux = LINE2, + .gpio = 0x02, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE1, + .gpio = 0x00010003, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + .gpio = 0x01, + }, + }, + [SAA7134_BOARD_BEHOLD_409FM] = { + /* , Sergey */ + /* Beholder Intl. Ltd. 2008 */ + /*Dmitry Belimov */ + .name = "Beholder BeholdTV 409 FM", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x00008000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_GOTVIEW_7135] = { + /* Mike Baikov */ + /* Andrey Cvetcov */ + .name = "GoTView 7135 PCI", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x00200003, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x00200003, + },{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + .gpio = 0x00200003, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x00200003, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + .gpio = 0x00200003, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0x00200003, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + .gpio = 0x00200003, + }, + }, + [SAA7134_BOARD_PHILIPS_EUROPA] = { + .name = "Philips EUROPA V3 reference design", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TD1316, + .radio_type = UNSET, + .tuner_addr = 0x61, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT | TDA9887_PORT1_ACTIVE, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_VIDEOMATE_DVBT_300] = { + .name = "Compro Videomate DVB-T300", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TD1316, + .radio_type = UNSET, + .tuner_addr = 0x61, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT | TDA9887_PORT1_ACTIVE, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_VIDEOMATE_DVBT_200] = { + .name = "Compro Videomate DVB-T200", + .tuner_type = TUNER_ABSENT, + .audio_clock = 0x00187de7, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_RTD_VFG7350] = { + .name = "RTD Embedded Technologies VFG7350", + .audio_clock = 0x00200000, + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .empress_addr = 0x21, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE0, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 2, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE3, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO0, + + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO1, + .vmux = 9, + .amux = LINE2, + }}, + .mpeg = SAA7134_MPEG_EMPRESS, + .video_out = CCIR656, + .vid_port_opts = ( SET_T_CODE_POLARITY_NON_INVERTED | + SET_CLOCK_NOT_DELAYED | + SET_CLOCK_INVERTED | + SET_VSYNC_OFF ), + }, + [SAA7134_BOARD_RTD_VFG7330] = { + .name = "RTD Embedded Technologies VFG7330", + .audio_clock = 0x00200000, + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE0, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 2, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE3, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO0, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO1, + .vmux = 9, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_FLYTVPLATINUM_MINI2] = { + .name = "LifeView FlyTV Platinum Mini2", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 0, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_AVERMEDIA_AVERTVHD_A180] = { + /* Michael Krufky + * Uses Alps Electric TDHU2, containing NXT2004 ATSC Decoder + * AFAIK, there is no analog demod, thus, + * no support for analog television. + */ + .name = "AVerMedia AVerTVHD MCE A180", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_MONSTERTV_MOBILE] = { + .name = "SKNet MonsterTV Mobile", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 6, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_PINNACLE_PCTV_110i] = { + .name = "Pinnacle PCTV 40i/50i/110i (saa7133)", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x080200000, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 4, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 0, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_ASUSTeK_P7131_DUAL] = { + .name = "ASUSTeK P7131 Dual", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 1 << 21, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x0000000, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE2, + .gpio = 0x0200000, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 0, + .amux = LINE2, + .gpio = 0x0200000, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + .gpio = 0x0200000, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_SEDNA_PC_TV_CARDBUS] = { + /* Paul Tom Zalac */ + /* Pavel Mihaylov */ + .name = "Sedna/MuchTV PC TV Cardbus TV/Radio (ITO25 Rev:2B)", + /* Sedna/MuchTV (OEM) Cardbus TV Tuner */ + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0xe880c0, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 6, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_ASUSTEK_DIGIMATRIX_TV] = { + /* "Cyril Lacoux (Yack)" */ + .name = "ASUS Digimatrix TV", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_FQ1216ME, + .tda9887_conf = TDA9887_PRESENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_PHILIPS_TIGER] = { + .name = "Philips Tiger reference design", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_OFF }, + .mpeg = SAA7134_MPEG_DVB, + .gpiomask = 0x0200000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_MSI_TVATANYWHERE_PLUS] = { + .name = "MSI TV@Anywhere plus", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 1 << 21, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 3, + .amux = LINE2, /* unconfirmed, taken from Philips driver */ + },{ + .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 0, /* untested */ + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_CINERGY250PCI] = { + /* remote-control does not work. The signal about a + key press comes in via gpio, but the key code + doesn't. Neither does it have an i2c remote control + interface. */ + .name = "Terratec Cinergy 250 PCI TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x80200000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_SVIDEO, /* NOT tested */ + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_FLYDVB_TRIO] = { + /* LifeView LR319 FlyDVB Trio */ + /* Peter Missel */ + .name = "LifeView FlyDVB Trio", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x00200000, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, /* Analog broadcast/cable TV */ + .vmux = 1, + .amux = TV, + .gpio = 0x200000, /* GPIO21=High for TV input */ + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 0, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x000000, /* GPIO21=Low for FM radio antenna */ + }, + }, + [SAA7134_BOARD_AVERMEDIA_777] = { + .name = "AverTV DVB-T 777", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_FLYDVBT_LR301] = { + /* LifeView FlyDVB-T */ + /* Giampiero Giancipoli */ + .name = "LifeView FlyDVB-T / Genius VideoWonder DVB-T", + .audio_clock = 0x00200000, + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_ADS_DUO_CARDBUS_PTV331] = { + .name = "ADS Instant TV Duo Cardbus PTV331", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .gpiomask = 0x00600000, /* Bit 21 0=Radio, Bit 22 0=TV */ + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x00200000, + }}, + }, + [SAA7134_BOARD_TEVION_DVBT_220RF] = { + .name = "Tevion/KWorld DVB-T 220RF", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .gpiomask = 1 << 21, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_KWORLD_DVBT_210] = { + .name = "KWorld DVB-T 210", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .gpiomask = 1 << 21, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_KWORLD_ATSC110] = { + .name = "Kworld ATSC110/115", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TUV1236D, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_AVERMEDIA_A169_B] = { + /* AVerMedia A169 */ + /* Rickard Osser */ + /* This card has two saa7134 chips on it, + but only one of them is currently working. */ + .name = "AVerMedia A169 B", + .audio_clock = 0x02187de7, + .tuner_type = TUNER_LG_TALN, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x0a60000, + }, + [SAA7134_BOARD_AVERMEDIA_A169_B1] = { + /* AVerMedia A169 */ + /* Rickard Osser */ + .name = "AVerMedia A169 B1", + .audio_clock = 0x02187de7, + .tuner_type = TUNER_LG_TALN, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0xca60000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 4, + .amux = TV, + .gpio = 0x04a61000, + },{ + .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 9, /* 9 is correct as S-VIDEO1 according to a169.inf! */ + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_MD7134_BRIDGE_2] = { + /* The second saa7134 on this card only serves as DVB-S host bridge */ + .name = "Medion 7134 Bridge #2", + .audio_clock = 0x00187de7, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + }, + [SAA7134_BOARD_FLYDVBT_HYBRID_CARDBUS] = { + .name = "LifeView FlyDVB-T Hybrid Cardbus/MSI TV @nywhere A/D NB", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .gpiomask = 0x00600000, /* Bit 21 0=Radio, Bit 22 0=TV */ + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x200000, /* GPIO21=High for TV input */ + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, + .vmux = 0, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 3, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x000000, /* GPIO21=Low for FM radio antenna */ + }, + }, + [SAA7134_BOARD_FLYVIDEO3000_NTSC] = { + /* "Zac Bowling" */ + .name = "LifeView FlyVIDEO3000 (NTSC)", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_NTSC, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + + .gpiomask = 0xe000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x8000, + },{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + .gpio = 0x0000, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + .gpio = 0x4000, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE2, + .gpio = 0x4000, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + .gpio = 0x4000, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0x2000, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + .gpio = 0x8000, + }, + }, + [SAA7134_BOARD_MEDION_MD8800_QUADRO] = { + .name = "Medion Md8800 Quadro", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_FLYDVBS_LR300] = { + /* LifeView FlyDVB-s */ + /* Igor M. Liplianin */ + .name = "LifeView FlyDVB-S /Acorp TV134DS", + .audio_clock = 0x00200000, + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_PROTEUS_2309] = { + .name = "Proteus Pro 2309", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_AVERMEDIA_A16AR] = { + /* Petr Baudis */ + .name = "AVerMedia TV Hybrid A16AR", + .audio_clock = 0x187de7, + .tuner_type = TUNER_PHILIPS_TD1316, /* untested */ + .radio_type = TUNER_TEA5767, /* untested */ + .tuner_addr = ADDR_UNSET, + .radio_addr = 0x60, + .tda9887_conf = TDA9887_PRESENT, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_ASUS_EUROPA2_HYBRID] = { + .name = "Asus Europa2 OEM", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT| TDA9887_PORT1_ACTIVE | TDA9887_PORT2_ACTIVE, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 4, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_PINNACLE_PCTV_310i] = { + .name = "Pinnacle PCTV 310i", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_GP0_HIGH_ON }, + .mpeg = SAA7134_MPEG_DVB, + .gpiomask = 0x000200000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 4, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 0, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_AVERMEDIA_STUDIO_507] = { + /* Mikhail Fedotov */ + .name = "Avermedia AVerTV Studio 507", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1256_IH3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x03, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x00, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + .gpio = 0x00, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE2, + .gpio = 0x00, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + .gpio = 0x00, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0x01, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE1, + .gpio = 0x00, + }, + }, + [SAA7134_BOARD_VIDEOMATE_DVBT_200A] = { + /* Francis Barber */ + .name = "Compro Videomate DVB-T200A", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT | TDA9887_PORT1_ACTIVE, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_HAUPPAUGE_HVR1110] = { + /* Thomas Genty */ + /* David Bentham */ + .name = "Hauppauge WinTV-HVR1110 DVB-T/Hybrid", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_GP0_HIGH_ON }, + .mpeg = SAA7134_MPEG_DVB, + .gpiomask = 0x0200100, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x0000100, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200100, + }, + }, + [SAA7134_BOARD_HAUPPAUGE_HVR1150] = { + .name = "Hauppauge WinTV-HVR1150 ATSC/QAM-Hybrid", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_ON_BRIDGE }, + .mpeg = SAA7134_MPEG_DVB, + .ts_type = SAA7134_MPEG_TS_SERIAL, + .ts_force_val = 1, + .gpiomask = 0x0800100, /* GPIO 21 is an INPUT */ + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x0000100, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0800100, /* GPIO 23 HI for FM */ + }, + }, + [SAA7134_BOARD_HAUPPAUGE_HVR1120] = { + .name = "Hauppauge WinTV-HVR1120 DVB-T/Hybrid", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_ON_BRIDGE }, + .mpeg = SAA7134_MPEG_DVB, + .ts_type = SAA7134_MPEG_TS_SERIAL, + .gpiomask = 0x0800100, /* GPIO 21 is an INPUT */ + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x0000100, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0800100, /* GPIO 23 HI for FM */ + }, + }, + [SAA7134_BOARD_CINERGY_HT_PCMCIA] = { + .name = "Terratec Cinergy HT PCMCIA", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 6, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_ENCORE_ENLTV] = { + /* Steven Walter + Juan Pablo Sormani */ + .name = "Encore ENLTV", + .audio_clock = 0x00200000, + .tuner_type = TUNER_TNF_5335MF, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = 3, + },{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 7, + .amux = 4, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = 2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 0, + .amux = 2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, +/* .gpio = 0x00300001,*/ + .gpio = 0x20000, + + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = 0, + }, + }, + [SAA7134_BOARD_ENCORE_ENLTV_FM] = { + /* Juan Pablo Sormani */ + .name = "Encore ENLTV-FM", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_FCV1236D, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = 3, + },{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 7, + .amux = 4, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = 2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 0, + .amux = 2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0x20000, + + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = 0, + }, + }, + [SAA7134_BOARD_ENCORE_ENLTV_FM53] = { + .name = "Encore ENLTV-FM v5.3", + .audio_clock = 0x00200000, + .tuner_type = TUNER_TNF_5335MF, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x7000, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = 1, + .gpio = 0x50000, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = 2, + .gpio = 0x2000, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = 2, + .gpio = 0x2000, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .vmux = 1, + .amux = 1, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .gpio = 0xf000, + .amux = 0, + }, + }, + [SAA7134_BOARD_ENCORE_ENLTV_FM3] = { + .name = "Encore ENLTV-FM 3", + .audio_clock = 0x02187de7, + .tuner_type = TUNER_TENA_TNF_5337, + .radio_type = TUNER_TEA5767, + .tuner_addr = 0x61, + .radio_addr = 0x60, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .vmux = 1, + .amux = LINE1, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE1, + .gpio = 0x43000, + }, + }, + [SAA7134_BOARD_CINERGY_HT_PCI] = { + .name = "Terratec Cinergy HT PCI", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 6, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_PHILIPS_TIGER_S] = { + .name = "Philips Tiger - S Reference design", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_GP0_HIGH_OFF }, + .mpeg = SAA7134_MPEG_DVB, + .gpiomask = 0x0200000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_AVERMEDIA_M102] = { + .name = "Avermedia M102", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 1<<21, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 6, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_ASUS_P7131_4871] = { + .name = "ASUS P7131 4871", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_GP0_HIGH_OFF }, + .mpeg = SAA7134_MPEG_DVB, + .gpiomask = 0x0200000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x0200000, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE2, + .gpio = 0x0200000, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 0, + .amux = LINE2, + .gpio = 0x0200000, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + .gpio = 0x0200000, + }}, + }, + [SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA] = { + .name = "ASUSTeK P7131 Hybrid", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_GP0_HIGH_OFF }, + .gpiomask = 1 << 21, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x0000000, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE2, + .gpio = 0x0200000, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 0, + .amux = LINE2, + .gpio = 0x0200000, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + .gpio = 0x0200000, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_ASUSTeK_P7131_ANALOG] = { + .name = "ASUSTeK P7131 Analog", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 1 << 21, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x0000000, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 0, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_SABRENT_TV_PCB05] = { + .name = "Sabrent PCMCIA TV-PCB05", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 0, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + }, + }, + [SAA7134_BOARD_10MOONSTVMASTER3] = { + /* Tony Wan */ + .name = "10MOONS TM300 TV Card", + .audio_clock = 0x00200000, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x7000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + .gpio = 0x0000, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x2000, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + .gpio = 0x2000, + }}, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE2, + .gpio = 0x3000, + }, + }, + [SAA7134_BOARD_AVERMEDIA_SUPER_007] = { + .name = "Avermedia Super 007", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_OFF }, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, /* FIXME: analog tv untested */ + .vmux = 1, + .amux = TV, + }}, + }, + [SAA7134_BOARD_AVERMEDIA_M135A] = { + .name = "Avermedia PCI pure analog (M135A)", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_GP0_HIGH_OFF }, + .gpiomask = 0x020200000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x00200000, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + .gpio = 0x01, + }, + }, + [SAA7134_BOARD_AVERMEDIA_M733A] = { + .name = "Avermedia PCI M733A", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_OFF }, + .gpiomask = 0x020200000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x00200000, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + .gpio = 0x01, + }, + }, + [SAA7134_BOARD_BEHOLD_401] = { + /* Beholder Intl. Ltd. 2008 */ + /*Dmitry Belimov */ + .name = "Beholder BeholdTV 401", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FQ1216ME, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x00008000, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = LINE2, + }}, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_BEHOLD_403] = { + /* Beholder Intl. Ltd. 2008 */ + /*Dmitry Belimov */ + .name = "Beholder BeholdTV 403", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FQ1216ME, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x00008000, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_BEHOLD_403FM] = { + /* Beholder Intl. Ltd. 2008 */ + /*Dmitry Belimov */ + .name = "Beholder BeholdTV 403 FM", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FQ1216ME, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x00008000, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_BEHOLD_405] = { + /* Beholder Intl. Ltd. 2008 */ + /*Dmitry Belimov */ + .name = "Beholder BeholdTV 405", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x00008000, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_BEHOLD_405FM] = { + /* Sergey */ + /* Beholder Intl. Ltd. 2008 */ + /*Dmitry Belimov */ + .name = "Beholder BeholdTV 405 FM", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x00008000, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = LINE2, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_BEHOLD_407] = { + /* Beholder Intl. Ltd. 2008 */ + /*Dmitry Belimov */ + .name = "Beholder BeholdTV 407", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x00008000, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + .gpio = 0xc0c000, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + .gpio = 0xc0c000, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + .gpio = 0xc0c000, + }}, + }, + [SAA7134_BOARD_BEHOLD_407FM] = { + /* Beholder Intl. Ltd. 2008 */ + /*Dmitry Belimov */ + .name = "Beholder BeholdTV 407 FM", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x00008000, + .inputs = {{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + .gpio = 0xc0c000, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + .gpio = 0xc0c000, + },{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + .gpio = 0xc0c000, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0xc0c000, + }, + }, + [SAA7134_BOARD_BEHOLD_409] = { + /* Beholder Intl. Ltd. 2008 */ + /*Dmitry Belimov */ + .name = "Beholder BeholdTV 409", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x00008000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_BEHOLD_505FM] = { + /* Beholder Intl. Ltd. 2008 */ + /*Dmitry Belimov */ + .name = "Beholder BeholdTV 505 FM", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x00008000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE1, + }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_BEHOLD_505RDS_MK5] = { + /* Beholder Intl. Ltd. 2008 */ + /*Dmitry Belimov */ + .name = "Beholder BeholdTV 505 RDS", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_FM1216MK5, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .rds_addr = 0x10, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x00008000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = LINE2, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE1, + }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_BEHOLD_507_9FM] = { + /* Beholder Intl. Ltd. 2008 */ + /*Dmitry Belimov */ + .name = "Beholder BeholdTV 507 FM / BeholdTV 509 FM", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x00008000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_BEHOLD_507RDS_MK5] = { + /* Beholder Intl. Ltd. 2008 */ + /*Dmitry Belimov */ + .name = "Beholder BeholdTV 507 RDS", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216MK5, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .rds_addr = 0x10, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x00008000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_BEHOLD_507RDS_MK3] = { + /* Beholder Intl. Ltd. 2008 */ + /*Dmitry Belimov */ + .name = "Beholder BeholdTV 507 RDS", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .rds_addr = 0x10, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x00008000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_BEHOLD_COLUMBUS_TVFM] = { + /* Beholder Intl. Ltd. 2008 */ + /* Dmitry Belimov */ + .name = "Beholder BeholdTV Columbus TV/FM", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_ALPS_TSBE5_PAL, + .radio_type = TUNER_TEA5767, + .tuner_addr = 0xc2 >> 1, + .radio_addr = 0xc0 >> 1, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x000A8004, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + .gpio = 0x000A8004, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + .gpio = 0x000A8000, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + .gpio = 0x000A8000, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0x000A8000, + }, + }, + [SAA7134_BOARD_BEHOLD_607FM_MK3] = { + /* Andrey Melnikoff */ + .name = "Beholder BeholdTV 607 FM", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_BEHOLD_609FM_MK3] = { + /* Andrey Melnikoff */ + .name = "Beholder BeholdTV 609 FM", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_BEHOLD_607FM_MK5] = { + /* Andrey Melnikoff */ + .name = "Beholder BeholdTV 607 FM", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216MK5, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_BEHOLD_609FM_MK5] = { + /* Andrey Melnikoff */ + .name = "Beholder BeholdTV 609 FM", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216MK5, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_BEHOLD_607RDS_MK3] = { + /* Andrey Melnikoff */ + .name = "Beholder BeholdTV 607 RDS", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .rds_addr = 0x10, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_BEHOLD_609RDS_MK3] = { + /* Andrey Melnikoff */ + .name = "Beholder BeholdTV 609 RDS", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .rds_addr = 0x10, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_BEHOLD_607RDS_MK5] = { + /* Andrey Melnikoff */ + .name = "Beholder BeholdTV 607 RDS", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216MK5, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .rds_addr = 0x10, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_BEHOLD_609RDS_MK5] = { + /* Andrey Melnikoff */ + .name = "Beholder BeholdTV 609 RDS", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216MK5, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .rds_addr = 0x10, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + },{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + },{ + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_BEHOLD_M6] = { + /* Igor Kuznetsov */ + /* Andrey Melnikoff */ + /* Beholder Intl. Ltd. Dmitry Belimov */ + /* Alexey Osipov */ + .name = "Beholder BeholdTV M6", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .empress_addr = 0x20, + .tda9887_conf = TDA9887_PRESENT, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + .mpeg = SAA7134_MPEG_EMPRESS, + .video_out = CCIR656, + .vid_port_opts = (SET_T_CODE_POLARITY_NON_INVERTED | + SET_CLOCK_NOT_DELAYED | + SET_CLOCK_INVERTED | + SET_VSYNC_OFF), + }, + [SAA7134_BOARD_BEHOLD_M63] = { + /* Igor Kuznetsov */ + /* Andrey Melnikoff */ + /* Beholder Intl. Ltd. Dmitry Belimov */ + .name = "Beholder BeholdTV M63", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .empress_addr = 0x20, + .tda9887_conf = TDA9887_PRESENT, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + .mpeg = SAA7134_MPEG_EMPRESS, + .video_out = CCIR656, + .vid_port_opts = (SET_T_CODE_POLARITY_NON_INVERTED | + SET_CLOCK_NOT_DELAYED | + SET_CLOCK_INVERTED | + SET_VSYNC_OFF), + }, + [SAA7134_BOARD_BEHOLD_M6_EXTRA] = { + /* Igor Kuznetsov */ + /* Andrey Melnikoff */ + /* Beholder Intl. Ltd. Dmitry Belimov */ + /* Alexey Osipov */ + .name = "Beholder BeholdTV M6 Extra", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216MK5, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .rds_addr = 0x10, + .empress_addr = 0x20, + .tda9887_conf = TDA9887_PRESENT, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + .mpeg = SAA7134_MPEG_EMPRESS, + .video_out = CCIR656, + .vid_port_opts = (SET_T_CODE_POLARITY_NON_INVERTED | + SET_CLOCK_NOT_DELAYED | + SET_CLOCK_INVERTED | + SET_VSYNC_OFF), + }, + [SAA7134_BOARD_TWINHAN_DTV_DVB_3056] = { + .name = "Twinhan Hybrid DTV-DVB 3056 PCI", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_GP0_HIGH_OFF }, + .mpeg = SAA7134_MPEG_DVB, + .gpiomask = 0x0200000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, /* untested */ + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_GENIUS_TVGO_A11MCE] = { + /* Adrian Pardini */ + .name = "Genius TVGO AM11MCE", + .audio_clock = 0x00200000, + .tuner_type = TUNER_TNF_5335MF, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0xf000, + .inputs = {{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE2, + .gpio = 0x0000, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x2000, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + .gpio = 0x2000, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0x1000, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE2, + .gpio = 0x6000, + }, + }, + [SAA7134_BOARD_PHILIPS_SNAKE] = { + .name = "NXP Snake DVB-S reference design", + .audio_clock = 0x00200000, + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + }, + [SAA7134_BOARD_CREATIX_CTX953] = { + .name = "Medion/Creatix CTX953 Hybrid", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_OFF }, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + }, + [SAA7134_BOARD_MSI_TVANYWHERE_AD11] = { + .name = "MSI TV@nywhere A/D v1.1", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_GP0_HIGH_OFF }, + .mpeg = SAA7134_MPEG_DVB, + .gpiomask = 0x0200000, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_AVERMEDIA_CARDBUS_506] = { + .name = "AVerMedia Cardbus TV/Radio (E506R)", + .audio_clock = 0x187de7, + .tuner_type = TUNER_XC2028, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + }, + }, + [SAA7134_BOARD_AVERMEDIA_A16D] = { + .name = "AVerMedia Hybrid TV/Radio (A16D)", + .audio_clock = 0x187de7, + .tuner_type = TUNER_XC2028, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 0, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + }, + }, + [SAA7134_BOARD_AVERMEDIA_M115] = { + .name = "Avermedia M115", + .audio_clock = 0x187de7, + .tuner_type = TUNER_XC2028, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + } }, + }, + [SAA7134_BOARD_VIDEOMATE_T750] = { + /* John Newbigin */ + .name = "Compro VideoMate T750", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_XC2028, + .radio_type = UNSET, + .tuner_addr = 0x61, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + } + }, + [SAA7134_BOARD_AVERMEDIA_A700_PRO] = { + /* Matthias Schwarzott */ + .name = "Avermedia DVB-S Pro A700", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = { { + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 6, + .amux = LINE1, + } }, + }, + [SAA7134_BOARD_AVERMEDIA_A700_HYBRID] = { + /* Matthias Schwarzott */ + .name = "Avermedia DVB-S Hybrid+FM A700", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_XC2028, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 4, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 6, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + }, + }, + [SAA7134_BOARD_BEHOLD_H6] = { + /* Igor Kuznetsov */ + .name = "Beholder BeholdTV H6", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FMD1216MEX_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_ASUSTeK_TIGER_3IN1] = { + .name = "Asus Tiger 3in1", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_GP0_HIGH_OFF }, + .gpiomask = 1 << 21, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 0, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_ASUSTeK_PS3_100] = { + .name = "Asus My Cinema PS3-100", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_GP0_HIGH_OFF }, + .gpiomask = 1 << 21, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 0, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_REAL_ANGEL_220] = { + .name = "Zogis Real Angel 220", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_TNF_5335MF, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x801a8087, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = LINE2, + .gpio = 0x624000, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + .gpio = 0x624000, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 1, + .amux = LINE1, + .gpio = 0x624000, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0x624001, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + }, + }, + [SAA7134_BOARD_ADS_INSTANT_HDTV_PCI] = { + .name = "ADS Tech Instant HDTV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TUV1236D, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .mpeg = SAA7134_MPEG_DVB, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 4, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + }, + [SAA7134_BOARD_ASUSTeK_TIGER] = { + .name = "Asus Tiger Rev:1.00", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_OFF }, + .mpeg = SAA7134_MPEG_DVB, + .gpiomask = 0x0200000, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 0, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_KWORLD_PLUS_TV_ANALOG] = { + .name = "Kworld Plus TV Analog Lite PCI", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_YMEC_TVF_5533MF, + .radio_type = TUNER_TEA5767, + .tuner_addr = ADDR_UNSET, + .radio_addr = 0x60, + .gpiomask = 0x80000700, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + .gpio = 0x100, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x200, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + .gpio = 0x200, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .vmux = 1, + .amux = LINE1, + .gpio = 0x100, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .vmux = 8, + .amux = 2, + }, + }, + [SAA7134_BOARD_KWORLD_PCI_SBTVD_FULLSEG] = { + .name = "Kworld PCI SBTVD/ISDB-T Full-Seg Hybrid", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .tuner_addr = ADDR_UNSET, + .radio_type = UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x8e054000, + .mpeg = SAA7134_MPEG_DVB, + .ts_type = SAA7134_MPEG_TS_PARALLEL, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, +#if 0 /* FIXME */ + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x200, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + .gpio = 0x200, +#endif + } }, +#if 0 + .radio = { + .type = SAA7134_INPUT_RADIO, + .vmux = 1, + .amux = LINE1, + .gpio = 0x100, + }, +#endif + .mute = { + .type = SAA7134_INPUT_MUTE, + .vmux = 0, + .amux = TV, + }, + }, + [SAA7134_BOARD_AVERMEDIA_GO_007_FM_PLUS] = { + .name = "Avermedia AVerTV GO 007 FM Plus", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x00300003, + /* .gpiomask = 0x8c240003, */ + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x01, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 6, + .amux = LINE1, + .gpio = 0x02, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x00300001, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + .gpio = 0x01, + }, + }, + [SAA7134_BOARD_AVERMEDIA_STUDIO_507UA] = { + /* Andy Shevchenko */ + .name = "Avermedia AVerTV Studio 507UA", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, /* Should be MK5 */ + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x03, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x00, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x00, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + .gpio = 0x00, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + .gpio = 0x01, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE1, + .gpio = 0x00, + }, + }, + [SAA7134_BOARD_VIDEOMATE_S350] = { + /* Jan D. Louw */ + .name = "Beholder BeholdTV X7", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_XC5000, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 2, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 9, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + }, + }, + [SAA7134_BOARD_ZOLID_HYBRID_PCI] = { + .name = "Zolid Hybrid TV Tuner PCI", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_OFF }, + .mpeg = SAA7134_MPEG_DVB, + .ts_type = SAA7134_MPEG_TS_PARALLEL, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + } }, + .radio = { /* untested */ + .type = SAA7134_INPUT_RADIO, + .amux = TV, + }, + }, + [SAA7134_BOARD_ASUS_EUROPA_HYBRID] = { + .name = "Asus Europa Hybrid OEM", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TD1316, + .radio_type = UNSET, + .tuner_addr = 0x61, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT | TDA9887_PORT1_ACTIVE, + .mpeg = SAA7134_MPEG_DVB, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 4, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + } }, + }, + [SAA7134_BOARD_LEADTEK_WINFAST_DTV1000S] = { + .name = "Leadtek Winfast DTV1000S", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .inputs = { { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + } }, + }, + [SAA7134_BOARD_BEHOLD_505RDS_MK3] = { + /* Beholder Intl. Ltd. 2008 */ + /*Dmitry Belimov */ + .name = "Beholder BeholdTV 505 RDS", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .rds_addr = 0x10, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x00008000, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE1, + }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_HAWELL_HW_404M7] = { + /* Hawell HW-404M7 & Hawell HW-808M7 */ + /* Bogoslovskiy Viktor */ + .name = "Hawell HW-404M7", + .audio_clock = 0x00200000, + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x389c00, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x01fc00, + } }, + }, + [SAA7134_BOARD_BEHOLD_H7] = { + /* Beholder Intl. Ltd. Dmitry Belimov */ + .name = "Beholder BeholdTV H7", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_XC5000, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .ts_type = SAA7134_MPEG_TS_PARALLEL, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 2, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 9, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + }, + }, + [SAA7134_BOARD_BEHOLD_A7] = { + /* Beholder Intl. Ltd. Dmitry Belimov */ + .name = "Beholder BeholdTV A7", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_XC5000, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 2, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 9, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + }, + }, + [SAA7134_BOARD_TECHNOTREND_BUDGET_T3000] = { + .name = "TechoTrend TT-budget T-3000", + .tuner_type = TUNER_PHILIPS_TD1316, + .audio_clock = 0x00187de7, + .radio_type = UNSET, + .tuner_addr = 0x63, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT | TDA9887_PORT1_ACTIVE, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + } }, + }, + [SAA7134_BOARD_VIDEOMATE_M1F] = { + /* Pavel Osnova */ + .name = "Compro VideoMate Vista M1F", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .radio_type = TUNER_TEA5767, + .tuner_addr = ADDR_UNSET, + .radio_addr = 0x60, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE1, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = TV, + }, + }, + [SAA7134_BOARD_MAGICPRO_PROHDTV_PRO2] = { + /* Timothy Lee */ + .name = "MagicPro ProHDTV Pro2 DMB-TH/Hybrid", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_ON_BRIDGE }, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x02050000, + .mpeg = SAA7134_MPEG_DVB, + .ts_type = SAA7134_MPEG_TS_PARALLEL, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x00050000, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x00050000, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + .gpio = 0x00050000, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x00050000, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .vmux = 0, + .amux = TV, + .gpio = 0x00050000, + }, + }, + [SAA7134_BOARD_BEHOLD_501] = { + /* Beholder Intl. Ltd. 2010 */ + /* Dmitry Belimov */ + .name = "Beholder BeholdTV 501", + .audio_clock = 0x00200000, + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x00008000, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_BEHOLD_503FM] = { + /* Beholder Intl. Ltd. 2010 */ + /* Dmitry Belimov */ + .name = "Beholder BeholdTV 503 FM", + .audio_clock = 0x00200000, + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x00008000, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 1, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_SENSORAY811_911] = { + .name = "Sensoray 811/911", + .audio_clock = 0x00200000, + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_COMPOSITE3, + .vmux = 2, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + }, + [SAA7134_BOARD_KWORLD_PC150U] = { + .name = "Kworld PC150-U", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .gpiomask = 1 << 21, + .ts_type = SAA7134_MPEG_TS_PARALLEL, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 3, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0000000, + }, + }, + [SAA7134_BOARD_HAWELL_HW_9004V1] = { + /* Hawell HW-9004V1 */ + /* Vadim Frolov */ + .name = "Hawell HW-9004V1", + .audio_clock = 0x00200000, + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x618E700, + .inputs = {{ + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x6010000, + } }, + }, + [SAA7134_BOARD_AVERMEDIA_A706] = { + .name = "AverMedia AverTV Satellite Hybrid+FM A706", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda829x_conf = { .lna_cfg = TDA8290_LNA_OFF, + .no_i2c_gate = 1, + .tda18271_std_map = &aver_a706_std_map }, + .gpiomask = 1 << 11, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + }, { + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 4, + .amux = LINE1, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0000800, + }, + }, + [SAA7134_BOARD_WIS_VOYAGER] = { + .name = "WIS Voyager or compatible", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_GO7007, + .inputs = { { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_TV, + .vmux = 3, + .amux = TV, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 6, + .amux = LINE1, + } }, + }, + [SAA7134_BOARD_AVERMEDIA_505] = { + /* much like the "studio" version but without radio + * and another tuner (dbaryshkov@gmail.com) */ + .name = "AverMedia AverTV/505", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FQ1216ME, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 0, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_COMPOSITE2, + .vmux = 3, + .amux = LINE2, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + } }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_LEADTEK_WINFAST_TV2100_FM] = { + .name = "Leadtek Winfast TV2100 FM", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_TNF_5335MF, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 0x0d, + .inputs = {{ + .type = SAA7134_INPUT_TV_MONO, + .vmux = 1, + .amux = LINE1, + .gpio = 0x00, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE2, + .gpio = 0x08, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + .gpio = 0x08, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = LINE1, + .gpio = 0x04, + }, + .mute = { + .type = SAA7134_INPUT_MUTE, + .amux = LINE1, + .gpio = 0x08, + }, + }, + [SAA7134_BOARD_SNAZIO_TVPVR_PRO] = { + .name = "SnaZio* TVPVR PRO", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 1 << 21, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x0000000, + }, { + .type = SAA7134_INPUT_COMPOSITE1, + .vmux = 3, + .amux = LINE2, + .gpio = 0x0000000, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE2, + .gpio = 0x0000000, + } }, + .radio = { + .type = SAA7134_INPUT_RADIO, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_LEADTEK_WINFAST_HDTV200_H] = { + .name = "Leadtek Winfast HDTV200 H", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .ts_type = SAA7134_MPEG_TS_PARALLEL, + .gpiomask = 0x00200700, + .inputs = { { + .type = SAA7134_INPUT_TV, + .vmux = 1, + .amux = TV, + .gpio = 0x00000300, + }, { + .type = SAA7134_INPUT_COMPOSITE, + .vmux = 3, + .amux = LINE1, + .gpio = 0x00200300, + }, { + .type = SAA7134_INPUT_SVIDEO, + .vmux = 8, + .amux = LINE1, + .gpio = 0x00200300, + } }, + }, +}; + +const unsigned int saa7134_bcount = ARRAY_SIZE(saa7134_boards); + +/* ------------------------------------------------------------------ */ +/* PCI ids + subsystem IDs */ + +struct pci_device_id saa7134_pci_tbl[] = { + { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, + PCI_VENDOR_ID_PHILIPS, 0x2001), + .driver_data = SAA7134_BOARD_PROTEUS_PRO, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, + PCI_VENDOR_ID_PHILIPS, 0x2001), + .driver_data = SAA7134_BOARD_PROTEUS_PRO, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, + PCI_VENDOR_ID_PHILIPS, 0x6752), + .driver_data = SAA7134_BOARD_EMPRESS, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1131, 0x4e85), + .driver_data = SAA7134_BOARD_MONSTERTV, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x153b, 0x1142), + .driver_data = SAA7134_BOARD_CINERGY400, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x153b, 0x1143), + .driver_data = SAA7134_BOARD_CINERGY600, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x153b, 0x1158), + .driver_data = SAA7134_BOARD_CINERGY600_MK3, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x153b, 0x1162), + .driver_data = SAA7134_BOARD_CINERGY400_CARDBUS, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x5169, 0x0138), + .driver_data = SAA7134_BOARD_FLYVIDEO3000_NTSC, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x5168, 0x0138), + .driver_data = SAA7134_BOARD_FLYVIDEO3000, + }, { + /* "Typhoon PCI Capture TV Card" Art.No. 50673 */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x4e42, 0x0138), + .driver_data = SAA7134_BOARD_FLYVIDEO3000, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x5168, 0x0138), + .driver_data = SAA7134_BOARD_FLYVIDEO2000, + }, { + /* Typhoon LifeView FlyTV Prime30 OEM */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x4e42, 0x0138), + .driver_data = SAA7134_BOARD_FLYVIDEO2000, + }, { + /* minipci, LR212 */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5168, 0x0212), + .driver_data = SAA7134_BOARD_FLYTVPLATINUM_MINI, + }, { + /* minipci, LR1212 */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x14c0, 0x1212), + .driver_data = SAA7134_BOARD_FLYTVPLATINUM_MINI2, + }, { + /* OEM minipci, LR212 */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x4e42, 0x0212), + .driver_data = SAA7134_BOARD_FLYTVPLATINUM_MINI, + }, { + /* + * Animation Technologies (LifeView) + * Standard PCI, LR214 Rev E and earlier (SAA7135) + */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5168, 0x0214), + .driver_data = SAA7134_BOARD_FLYTVPLATINUM_FM, + }, { + /* + * Animation Technologies (LifeView) + * Standard PCI, LR214 Rev F onwards (SAA7131) + */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5168, 0x5214), + .driver_data = SAA7134_BOARD_FLYTVPLATINUM_FM, + }, { + /* KYE Genius VideoWonder ProTV */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1489, 0x0214), + .driver_data = SAA7134_BOARD_FLYTVPLATINUM_FM, /* is an LR214WF actually */ + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x16be, 0x0003), + .driver_data = SAA7134_BOARD_MD7134, + }, { + /* CTX946 analog TV, HW mpeg, DVB-T; only analog TV and DVB-T for now */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x16be, 0x5000), + .driver_data = SAA7134_BOARD_MD7134, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x1048, 0x226b), + .driver_data = SAA7134_BOARD_ELSA, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x1048, 0x226a), + .driver_data = SAA7134_BOARD_ELSA_500TV, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x1048, 0x226c), + .driver_data = SAA7134_BOARD_ELSA_700TV, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, + PCI_VENDOR_ID_ASUSTEK, 0x4842), + .driver_data = SAA7134_BOARD_ASUSTeK_TVFM7134, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, + PCI_VENDOR_ID_ASUSTEK, 0x4845), + .driver_data = SAA7134_BOARD_ASUSTeK_TVFM7135, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, + PCI_VENDOR_ID_ASUSTEK, 0x4830), + .driver_data = SAA7134_BOARD_ASUSTeK_TVFM7134, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, + PCI_VENDOR_ID_ASUSTEK, 0x4843), + .driver_data = SAA7134_BOARD_ASUSTEK_TVFM7133, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, + PCI_VENDOR_ID_ASUSTEK, 0x4840), + .driver_data = SAA7134_BOARD_ASUSTeK_TVFM7134, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, + PCI_VENDOR_ID_PHILIPS, 0xfe01), + .driver_data = SAA7134_BOARD_TVSTATION_RDS, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1894, 0xfe01), + .driver_data = SAA7134_BOARD_TVSTATION_RDS, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1894, 0xa006), + .driver_data = SAA7134_BOARD_TVSTATION_DVR, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1131, 0x7133), + .driver_data = SAA7134_BOARD_VA1000POWER, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, + PCI_VENDOR_ID_PHILIPS, 0x2001), + .driver_data = SAA7134_BOARD_10MOONSTVMASTER, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x185b, 0xc100), + .driver_data = SAA7134_BOARD_VIDEOMATE_TV, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x185b, 0xc100), + .driver_data = SAA7134_BOARD_VIDEOMATE_TV_GOLD_PLUS, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, + PCI_VENDOR_ID_MATROX, 0x48d0), + .driver_data = SAA7134_BOARD_CRONOS_PLUS, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1461, 0xa70b), + .driver_data = SAA7134_BOARD_MD2819, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0xa7a1), + .driver_data = SAA7134_BOARD_AVERMEDIA_A700_PRO, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0xa7a2), + .driver_data = SAA7134_BOARD_AVERMEDIA_A700_HYBRID, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x1461, 0x2115), + .driver_data = SAA7134_BOARD_AVERMEDIA_STUDIO_305, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x1461, 0xa115), + .driver_data = SAA7134_BOARD_AVERMEDIA_STUDIO_505, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x1461, 0x2108), + .driver_data = SAA7134_BOARD_AVERMEDIA_305, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x1461, 0x10ff), + .driver_data = SAA7134_BOARD_AVERMEDIA_DVD_EZMAKER, + }, { + /* AVerMedia CardBus */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1461, 0xd6ee), + .driver_data = SAA7134_BOARD_AVERMEDIA_CARDBUS, + }, { + /* AVerMedia CardBus */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1461, 0xb7e9), + .driver_data = SAA7134_BOARD_AVERMEDIA_CARDBUS_501, + }, { + /* AVerMedia TransGear 3000TV */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x1461, 0x050c), + .driver_data = SAA7134_BOARD_TG3000TV, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x11bd, 0x002b), + .driver_data = SAA7134_BOARD_PINNACLE_PCTV_STEREO, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x11bd, 0x002d), + .driver_data = SAA7134_BOARD_PINNACLE_300I_DVBT_PAL, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1019, 0x4cb4), + .driver_data = SAA7134_BOARD_ECS_TVP3XP, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1019, 0x4cb5), + .driver_data = SAA7134_BOARD_ECS_TVP3XP_4CB5, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1019, 0x4cb6), + .driver_data = SAA7134_BOARD_ECS_TVP3XP_4CB6, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x12ab, 0x0800), + .driver_data = SAA7134_BOARD_UPMOST_PURPLE_TV, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x153b, 0x1152), + .driver_data = SAA7134_BOARD_CINERGY200, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x185b, 0xc100), + .driver_data = SAA7134_BOARD_VIDEOMATE_TV_PVR, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1461, 0x9715), + .driver_data = SAA7134_BOARD_AVERMEDIA_STUDIO_307, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1461, 0xa70a), + .driver_data = SAA7134_BOARD_AVERMEDIA_307, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x185b, 0xc200), + .driver_data = SAA7134_BOARD_VIDEOMATE_GOLD_PLUS, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1540, 0x9524), + .driver_data = SAA7134_BOARD_PROVIDEO_PV952, + }, { + /* Cardbus version */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5168, 0x0502), + .driver_data = SAA7134_BOARD_FLYDVBT_DUO_CARDBUS, + }, { + /* PCI version */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5168, 0x0306), + .driver_data = SAA7134_BOARD_FLYDVBTDUO, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0xf31f), + .driver_data = SAA7134_BOARD_AVERMEDIA_GO_007_FM, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0xf11d), + .driver_data = SAA7134_BOARD_AVERMEDIA_M135A, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0x4155), + .driver_data = SAA7134_BOARD_AVERMEDIA_M733A, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0x4255), + .driver_data = SAA7134_BOARD_AVERMEDIA_M733A, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, + PCI_VENDOR_ID_PHILIPS, 0x2004), + .driver_data = SAA7134_BOARD_PHILIPS_TOUGH, + }, { + /* PCI version */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1421, 0x0350), + .driver_data = SAA7134_BOARD_ADS_INSTANT_TV, + }, { + /* PCI version, new revision */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1421, 0x0351), + .driver_data = SAA7134_BOARD_ADS_INSTANT_TV, + }, { + /* cardbus version */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1421, 0x0370), + .driver_data = SAA7134_BOARD_ADS_INSTANT_TV, + }, { + /* cardbus version */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1421, 0x1370), + .driver_data = SAA7134_BOARD_ADS_INSTANT_TV, + }, { + /* Typhoon LifeView LR502 OEM */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x4e42, 0x0502), + .driver_data = SAA7134_BOARD_FLYDVBT_DUO_CARDBUS, + }, { + /* mini pci NTSC version */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1043, 0x0210), + .driver_data = SAA7134_BOARD_FLYTV_DIGIMATRIX, + }, { + /* mini pci PAL/SECAM version */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1043, 0x0210), + .driver_data = SAA7134_BOARD_ASUSTEK_DIGIMATRIX_TV, + }, { + /* subvendor == 0 shouldn't break anything, since subdevice id seems unique */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x0000, 0x4091), + .driver_data = SAA7134_BOARD_BEHOLD_409FM, + }, { + /* GoTView */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5456, 0x7135), + .driver_data = SAA7134_BOARD_GOTVIEW_7135, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, + PCI_VENDOR_ID_PHILIPS, 0x2004), + .driver_data = SAA7134_BOARD_PHILIPS_EUROPA, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x185b, 0xc900), + .driver_data = SAA7134_BOARD_VIDEOMATE_DVBT_300, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x185b, 0xc901), + .driver_data = SAA7134_BOARD_VIDEOMATE_DVBT_200, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1435, 0x7350), + .driver_data = SAA7134_BOARD_RTD_VFG7350, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1435, 0x7330), + .driver_data = SAA7134_BOARD_RTD_VFG7330, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0x1044), + .driver_data = SAA7134_BOARD_AVERMEDIA_AVERTVHD_A180, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1131, 0x4ee9), + .driver_data = SAA7134_BOARD_MONSTERTV_MOBILE, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x11bd, 0x002e), + .driver_data = SAA7134_BOARD_PINNACLE_PCTV_110i, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1043, 0x4862), + .driver_data = SAA7134_BOARD_ASUSTeK_P7131_DUAL, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, + PCI_VENDOR_ID_PHILIPS, 0x2018), + .driver_data = SAA7134_BOARD_PHILIPS_TIGER, + }, { + /* tda8275a, ks003 IR */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1462, 0x6231), + .driver_data = SAA7134_BOARD_MSI_TVATANYWHERE_PLUS, + }, { + /* tda8275, ks003 IR */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1462, 0x8624), + .driver_data = SAA7134_BOARD_MSI_TVATANYWHERE_PLUS, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x153b, 0x1160), + .driver_data = SAA7134_BOARD_CINERGY250PCI, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5168, 0x0319), + .driver_data = SAA7134_BOARD_FLYDVB_TRIO, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1461, 0x2c05), + .driver_data = SAA7134_BOARD_AVERMEDIA_777, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x5168, 0x0301), + .driver_data = SAA7134_BOARD_FLYDVBT_LR301, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x0331, 0x1421), + .driver_data = SAA7134_BOARD_ADS_DUO_CARDBUS_PTV331, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x17de, 0x7201), + .driver_data = SAA7134_BOARD_TEVION_DVBT_220RF, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x17de, 0x7250), + .driver_data = SAA7134_BOARD_KWORLD_DVBT_210, + }, { + /* SAA7135HL */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x17de, 0x7350), + .driver_data = SAA7134_BOARD_KWORLD_ATSC110, + }, { + /* SAA7135HL */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x17de, 0x7352), + .driver_data = SAA7134_BOARD_KWORLD_ATSC110, /* ATSC 115 */ + }, { + /* SAA7135HL */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x17de, 0xa134), + .driver_data = SAA7134_BOARD_KWORLD_PC150U, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1461, 0x7360), + .driver_data = SAA7134_BOARD_AVERMEDIA_A169_B, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1461, 0x6360), + .driver_data = SAA7134_BOARD_AVERMEDIA_A169_B1, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x16be, 0x0005), + .driver_data = SAA7134_BOARD_MD7134_BRIDGE_2, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x5168, 0x0300), + .driver_data = SAA7134_BOARD_FLYDVBS_LR300, + }, { + /* LR300 */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x4e42, 0x0300), + .driver_data = SAA7134_BOARD_FLYDVBS_LR300, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1489, 0x0301), + .driver_data = SAA7134_BOARD_FLYDVBT_LR301, + }, { + /* Animation Technologies (LifeView) */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5168, 0x0304), + .driver_data = SAA7134_BOARD_FLYTVPLATINUM_FM, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5168, 0x3306), + .driver_data = SAA7134_BOARD_FLYDVBT_HYBRID_CARDBUS, + }, { + /* what's the difference to .subdevice = 0x3306 above? */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5168, 0x3502), + .driver_data = SAA7134_BOARD_FLYDVBT_HYBRID_CARDBUS, + }, { + /* FlyDVB-T Hybrid Mini PCI */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5168, 0x3307), + .driver_data = SAA7134_BOARD_FLYDVBT_HYBRID_CARDBUS, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x16be, 0x0007), + .driver_data = SAA7134_BOARD_MEDION_MD8800_QUADRO, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x16be, 0x0008), + .driver_data = SAA7134_BOARD_MEDION_MD8800_QUADRO, + }, { + /* triple CTX948_V1.1.1 */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x16be, 0x000d), + .driver_data = SAA7134_BOARD_MEDION_MD8800_QUADRO, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0x2c05), + .driver_data = SAA7134_BOARD_AVERMEDIA_777, + }, { + /* Cardbus version */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1489, 0x0502), + .driver_data = SAA7134_BOARD_FLYDVBT_DUO_CARDBUS, + }, { + /* Philips Proteus PRO 2309 */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x0919, 0x2003), + .driver_data = SAA7134_BOARD_PROTEUS_2309, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1461, 0x2c00), + .driver_data = SAA7134_BOARD_AVERMEDIA_A16AR, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1043, 0x4860), + .driver_data = SAA7134_BOARD_ASUS_EUROPA2_HYBRID, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x11bd, 0x002f), + .driver_data = SAA7134_BOARD_PINNACLE_PCTV_310i, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0x9715), + .driver_data = SAA7134_BOARD_AVERMEDIA_STUDIO_507, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1461, 0xa11b), + .driver_data = SAA7134_BOARD_AVERMEDIA_STUDIO_507UA, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1043, 0x4876), + .driver_data = SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x0070, 0x6700), + .driver_data = SAA7134_BOARD_HAUPPAUGE_HVR1110, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x0070, 0x6701), + .driver_data = SAA7134_BOARD_HAUPPAUGE_HVR1110, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x0070, 0x6702), + .driver_data = SAA7134_BOARD_HAUPPAUGE_HVR1110, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x0070, 0x6703), + .driver_data = SAA7134_BOARD_HAUPPAUGE_HVR1110, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x0070, 0x6704), + .driver_data = SAA7134_BOARD_HAUPPAUGE_HVR1110, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x0070, 0x6705), + .driver_data = SAA7134_BOARD_HAUPPAUGE_HVR1110, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x0070, 0x6706), + .driver_data = SAA7134_BOARD_HAUPPAUGE_HVR1150, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x0070, 0x6707), + .driver_data = SAA7134_BOARD_HAUPPAUGE_HVR1120, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x0070, 0x6708), + .driver_data = SAA7134_BOARD_HAUPPAUGE_HVR1150, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x0070, 0x6709), + .driver_data = SAA7134_BOARD_HAUPPAUGE_HVR1120, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x0070, 0x670a), + .driver_data = SAA7134_BOARD_HAUPPAUGE_HVR1120, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x153b, 0x1172), + .driver_data = SAA7134_BOARD_CINERGY_HT_PCMCIA, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, + PCI_VENDOR_ID_PHILIPS, 0x2342), + .driver_data = SAA7134_BOARD_ENCORE_ENLTV, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x1131, 0x2341), + .driver_data = SAA7134_BOARD_ENCORE_ENLTV, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x3016, 0x2344), + .driver_data = SAA7134_BOARD_ENCORE_ENLTV, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x1131, 0x230f), + .driver_data = SAA7134_BOARD_ENCORE_ENLTV_FM, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x1a7f, 0x2008), + .driver_data = SAA7134_BOARD_ENCORE_ENLTV_FM53, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1a7f, 0x2108), + .driver_data = SAA7134_BOARD_ENCORE_ENLTV_FM3, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x153b, 0x1175), + .driver_data = SAA7134_BOARD_CINERGY_HT_PCI, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0xf31e), + .driver_data = SAA7134_BOARD_AVERMEDIA_M102, + }, { + /* MSI TV@nywhere DUO */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x4E42, 0x0306), + .driver_data = SAA7134_BOARD_FLYDVBTDUO, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1043, 0x4871), + .driver_data = SAA7134_BOARD_ASUS_P7131_4871, + }, { + /* REV:1.00 */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1043, 0x4857), + .driver_data = SAA7134_BOARD_ASUSTeK_TIGER, + }, { + /* SinoVideo PCI 2309 Proteus (7134) OEM cardbus */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x0919, 0x2003), + .driver_data = SAA7134_BOARD_SABRENT_TV_PCB05, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, + PCI_VENDOR_ID_PHILIPS, 0x2304), + .driver_data = SAA7134_BOARD_10MOONSTVMASTER3, + }, { + /* Avermedia Technologies Inc AVerTV DVB-T Super 007 */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0xf01d), + .driver_data = SAA7134_BOARD_AVERMEDIA_SUPER_007, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x0000, 0x4016), + .driver_data = SAA7134_BOARD_BEHOLD_401, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x0000, 0x4036), + .driver_data = SAA7134_BOARD_BEHOLD_403, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x0000, 0x4037), + .driver_data = SAA7134_BOARD_BEHOLD_403FM, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x0000, 0x4050), + .driver_data = SAA7134_BOARD_BEHOLD_405, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x0000, 0x4051), + .driver_data = SAA7134_BOARD_BEHOLD_405FM, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x0000, 0x4070), + .driver_data = SAA7134_BOARD_BEHOLD_407, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x0000, 0x4071), + .driver_data = SAA7134_BOARD_BEHOLD_407FM, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x0000, 0x4090), + .driver_data = SAA7134_BOARD_BEHOLD_409, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x0000, 0x505B), + .driver_data = SAA7134_BOARD_BEHOLD_505RDS_MK5, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x0000, 0x5051), + .driver_data = SAA7134_BOARD_BEHOLD_505RDS_MK3, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x5ace, 0x5050), + .driver_data = SAA7134_BOARD_BEHOLD_505FM, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x0000, 0x5071), + .driver_data = SAA7134_BOARD_BEHOLD_507RDS_MK3, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x0000, 0x507B), + .driver_data = SAA7134_BOARD_BEHOLD_507RDS_MK5, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x5ace, 0x5070), + .driver_data = SAA7134_BOARD_BEHOLD_507_9FM, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5ace, 0x5090), + .driver_data = SAA7134_BOARD_BEHOLD_507_9FM, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x0000, 0x5201), + .driver_data = SAA7134_BOARD_BEHOLD_COLUMBUS_TVFM, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x5ace, 0x6070), + .driver_data = SAA7134_BOARD_BEHOLD_607FM_MK3, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x5ace, 0x6071), + .driver_data = SAA7134_BOARD_BEHOLD_607FM_MK5, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x5ace, 0x6072), + .driver_data = SAA7134_BOARD_BEHOLD_607RDS_MK3, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x5ace, 0x6073), + .driver_data = SAA7134_BOARD_BEHOLD_607RDS_MK5, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5ace, 0x6090), + .driver_data = SAA7134_BOARD_BEHOLD_609FM_MK3, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5ace, 0x6091), + .driver_data = SAA7134_BOARD_BEHOLD_609FM_MK5, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5ace, 0x6092), + .driver_data = SAA7134_BOARD_BEHOLD_609RDS_MK3, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5ace, 0x6093), + .driver_data = SAA7134_BOARD_BEHOLD_609RDS_MK5, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5ace, 0x6190), + .driver_data = SAA7134_BOARD_BEHOLD_M6, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5ace, 0x6193), + .driver_data = SAA7134_BOARD_BEHOLD_M6_EXTRA, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5ace, 0x6191), + .driver_data = SAA7134_BOARD_BEHOLD_M63, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x4e42, 0x3502), + .driver_data = SAA7134_BOARD_FLYDVBT_HYBRID_CARDBUS, + }, { + /*Twinhan Technology Co. Ltd*/ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1822, 0x0022), + .driver_data = SAA7134_BOARD_TWINHAN_DTV_DVB_3056, + }, { + /* Medion version CTX953_V.1.4.3 */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x16be, 0x0010), + .driver_data = SAA7134_BOARD_CREATIX_CTX953, + }, { + /* MSI TV@nywhere A/D v1.1 */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1462, 0x8625), + .driver_data = SAA7134_BOARD_MSI_TVANYWHERE_AD11, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0xf436), + .driver_data = SAA7134_BOARD_AVERMEDIA_CARDBUS_506, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0xf936), + .driver_data = SAA7134_BOARD_AVERMEDIA_A16D, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0xa836), + .driver_data = SAA7134_BOARD_AVERMEDIA_M115, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x185b, 0xc900), + .driver_data = SAA7134_BOARD_VIDEOMATE_T750, + }, { + /* SAA7135HL */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1421, 0x0380), + .driver_data = SAA7134_BOARD_ADS_INSTANT_HDTV_PCI, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5169, 0x1502), + .driver_data = SAA7134_BOARD_FLYTVPLATINUM_MINI, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5ace, 0x6290), + .driver_data = SAA7134_BOARD_BEHOLD_H6, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0xf636), + .driver_data = SAA7134_BOARD_AVERMEDIA_M103, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0xf736), + .driver_data = SAA7134_BOARD_AVERMEDIA_M103, + }, { + /* REV:1.02G */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1043, 0x4878), + .driver_data = SAA7134_BOARD_ASUSTeK_TIGER_3IN1, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1043, 0x48cd), + .driver_data = SAA7134_BOARD_ASUSTeK_PS3_100, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x17de, 0x7128), + .driver_data = SAA7134_BOARD_KWORLD_PLUS_TV_ANALOG, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x17de, 0xb136), + .driver_data = SAA7134_BOARD_KWORLD_PCI_SBTVD_FULLSEG, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0xf31d), + .driver_data = SAA7134_BOARD_AVERMEDIA_GO_007_FM_PLUS, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x185b, 0xc900), + .driver_data = SAA7134_BOARD_VIDEOMATE_S350, + }, { + /* Beholder Intl. Ltd. */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5ace, 0x7595), + .driver_data = SAA7134_BOARD_BEHOLD_X7, + }, { + /* RoverMedia LifeView FlyTV Prime30 OEM */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x19d1, 0x0138), + .driver_data = SAA7134_BOARD_ROVERMEDIA_LINK_PRO_FM, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, + PCI_VENDOR_ID_PHILIPS, 0x2004), + .driver_data = SAA7134_BOARD_ZOLID_HYBRID_PCI, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x1043, 0x4847), + .driver_data = SAA7134_BOARD_ASUS_EUROPA_HYBRID, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x107d, 0x6655), + .driver_data = SAA7134_BOARD_LEADTEK_WINFAST_DTV1000S, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x13c2, 0x2804), + .driver_data = SAA7134_BOARD_TECHNOTREND_BUDGET_T3000, + }, { + /* Beholder Intl. Ltd. */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5ace, 0x7190), + .driver_data = SAA7134_BOARD_BEHOLD_H7, + }, { + /* Beholder Intl. Ltd. */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5ace, 0x7090), + .driver_data = SAA7134_BOARD_BEHOLD_A7, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7135, 0x185b, 0xc900), + .driver_data = SAA7134_BOARD_VIDEOMATE_M1F, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x5ace, 0x5030), + .driver_data = SAA7134_BOARD_BEHOLD_503FM, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x5ace, 0x5010), + .driver_data = SAA7134_BOARD_BEHOLD_501, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, 0x17de, 0xd136), + .driver_data = SAA7134_BOARD_MAGICPRO_PROHDTV_PRO2, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x6000, 0x0811), + .driver_data = SAA7134_BOARD_SENSORAY811_911, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x6000, 0x0911), + .driver_data = SAA7134_BOARD_SENSORAY811_911, + }, { + /* Avermedia Technologies Inc AverTV Satellite Hybrid+FM A706 */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1461, 0x2055), + .driver_data = SAA7134_BOARD_AVERMEDIA_A706, + }, { + /* WIS */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1905, 0x7007), + .driver_data = SAA7134_BOARD_WIS_VOYAGER, + }, { + /* Avermedia Technologies Inc */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x1461, 0xa10a), + .driver_data = SAA7134_BOARD_AVERMEDIA_505, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, 0x107d, 0x6f3a), + .driver_data = SAA7134_BOARD_LEADTEK_WINFAST_TV2100_FM, + }, { + /* V One Multimedia PTE Ltd */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x1779, 0x13cf), + .driver_data = SAA7134_BOARD_SNAZIO_TVPVR_PRO, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133, 0x107d, 0x6f2e), + .driver_data = SAA7134_BOARD_LEADTEK_WINFAST_HDTV200_H, + }, { + /* --- boards without eeprom + subsystem ID --- */ + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134, + PCI_VENDOR_ID_PHILIPS, 0), + .driver_data = SAA7134_BOARD_NOAUTO, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130, + PCI_VENDOR_ID_PHILIPS, 0), + .driver_data = SAA7134_BOARD_NOAUTO, + }, { + /* --- default catch --- */ + PCI_DEVICE(PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7130), + .driver_data = SAA7134_BOARD_UNKNOWN, + }, { + PCI_DEVICE(PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7133), + .driver_data = SAA7134_BOARD_UNKNOWN, + }, { + PCI_DEVICE(PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7134), + .driver_data = SAA7134_BOARD_UNKNOWN, + }, { + PCI_DEVICE(PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7135), + .driver_data = SAA7134_BOARD_UNKNOWN, + }, { + /* --- end of list --- */ + } +}; +MODULE_DEVICE_TABLE(pci, saa7134_pci_tbl); + +/* ----------------------------------------------------------- */ +/* flyvideo tweaks */ + + +static void board_flyvideo(struct saa7134_dev *dev) +{ + pr_warn("%s: there are different flyvideo cards with different tuners\n" + "%s: out there, you might have to use the tuner= insmod\n" + "%s: option to override the default value.\n", + dev->name, dev->name, dev->name); +} + +static int saa7134_xc2028_callback(struct saa7134_dev *dev, + int command, int arg) +{ + switch (command) { + case XC2028_TUNER_RESET: + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x00008000, 0x00000000); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x00008000, 0x00008000); + switch (dev->board) { + case SAA7134_BOARD_AVERMEDIA_CARDBUS_506: + case SAA7134_BOARD_AVERMEDIA_M103: + saa7134_set_gpio(dev, 23, 0); + msleep(10); + saa7134_set_gpio(dev, 23, 1); + break; + case SAA7134_BOARD_AVERMEDIA_A16D: + saa7134_set_gpio(dev, 21, 0); + msleep(10); + saa7134_set_gpio(dev, 21, 1); + break; + case SAA7134_BOARD_AVERMEDIA_A700_HYBRID: + saa7134_set_gpio(dev, 18, 0); + msleep(10); + saa7134_set_gpio(dev, 18, 1); + break; + case SAA7134_BOARD_VIDEOMATE_T750: + saa7134_set_gpio(dev, 20, 0); + msleep(10); + saa7134_set_gpio(dev, 20, 1); + break; + } + return 0; + } + return -EINVAL; +} + +static int saa7134_xc5000_callback(struct saa7134_dev *dev, + int command, int arg) +{ + switch (dev->board) { + case SAA7134_BOARD_BEHOLD_X7: + case SAA7134_BOARD_BEHOLD_H7: + case SAA7134_BOARD_BEHOLD_A7: + if (command == XC5000_TUNER_RESET) { + /* Down and UP pheripherial RESET pin for reset all chips */ + saa_writeb(SAA7134_SPECIAL_MODE, 0x00); + msleep(10); + saa_writeb(SAA7134_SPECIAL_MODE, 0x01); + msleep(10); + } + break; + default: + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0x06e20000, 0x06e20000); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x06a20000, 0x06a20000); + saa_andorl(SAA7133_ANALOG_IO_SELECT >> 2, 0x02, 0x02); + saa_andorl(SAA7134_ANALOG_IN_CTRL1 >> 2, 0x81, 0x81); + saa_andorl(SAA7134_AUDIO_CLOCK0 >> 2, 0x03187de7, 0x03187de7); + saa_andorl(SAA7134_AUDIO_PLL_CTRL >> 2, 0x03, 0x03); + saa_andorl(SAA7134_AUDIO_CLOCKS_PER_FIELD0 >> 2, + 0x0001e000, 0x0001e000); + break; + } + return 0; +} + +static int saa7134_tda8290_827x_callback(struct saa7134_dev *dev, + int command, int arg) +{ + u8 sync_control; + + switch (command) { + case 0: /* switch LNA gain through GPIO 22*/ + saa7134_set_gpio(dev, 22, arg) ; + break; + case 1: /* vsync output at GPIO22. 50 / 60Hz */ + saa_andorb(SAA7134_VIDEO_PORT_CTRL3, 0x80, 0x80); + saa_andorb(SAA7134_VIDEO_PORT_CTRL6, 0x0f, 0x03); + if (arg == 1) + sync_control = 11; + else + sync_control = 17; + saa_writeb(SAA7134_VGATE_START, sync_control); + saa_writeb(SAA7134_VGATE_STOP, sync_control + 1); + saa_andorb(SAA7134_MISC_VGATE_MSB, 0x03, 0x00); + break; + default: + return -EINVAL; + } + + return 0; +} + +static inline int saa7134_tda18271_hvr11x0_toggle_agc(struct saa7134_dev *dev, + enum tda18271_mode mode) +{ + /* toggle AGC switch through GPIO 26 */ + switch (mode) { + case TDA18271_ANALOG: + saa7134_set_gpio(dev, 26, 0); + break; + case TDA18271_DIGITAL: + saa7134_set_gpio(dev, 26, 1); + break; + default: + return -EINVAL; + } + return 0; +} + +static inline int saa7134_kworld_sbtvd_toggle_agc(struct saa7134_dev *dev, + enum tda18271_mode mode) +{ + /* toggle AGC switch through GPIO 27 */ + switch (mode) { + case TDA18271_ANALOG: + saa_writel(SAA7134_GPIO_GPMODE0 >> 2, 0x4000); + saa_writel(SAA7134_GPIO_GPSTATUS0 >> 2, 0x4000); + msleep(20); + break; + case TDA18271_DIGITAL: + saa_writel(SAA7134_GPIO_GPMODE0 >> 2, 0x14000); + saa_writel(SAA7134_GPIO_GPSTATUS0 >> 2, 0x14000); + msleep(20); + saa_writel(SAA7134_GPIO_GPMODE0 >> 2, 0x54000); + saa_writel(SAA7134_GPIO_GPSTATUS0 >> 2, 0x54000); + msleep(30); + break; + default: + return -EINVAL; + } + return 0; +} + +static int saa7134_kworld_pc150u_toggle_agc(struct saa7134_dev *dev, + enum tda18271_mode mode) +{ + switch (mode) { + case TDA18271_ANALOG: + saa7134_set_gpio(dev, 18, 0); + break; + case TDA18271_DIGITAL: + saa7134_set_gpio(dev, 18, 1); + msleep(30); + break; + default: + return -EINVAL; + } + return 0; +} + +static int saa7134_leadtek_hdtv200h_toggle_agc(struct saa7134_dev *dev, + enum tda18271_mode mode) +{ + switch (mode) { + case TDA18271_ANALOG: + saa7134_set_gpio(dev, 10, 0); + break; + case TDA18271_DIGITAL: + saa7134_set_gpio(dev, 10, 1); + break; + default: + return -EINVAL; + } + return 0; +} + +static int saa7134_tda8290_18271_callback(struct saa7134_dev *dev, + int command, int arg) +{ + int ret = 0; + + switch (command) { + case TDA18271_CALLBACK_CMD_AGC_ENABLE: /* 0 */ + switch (dev->board) { + case SAA7134_BOARD_HAUPPAUGE_HVR1150: + case SAA7134_BOARD_HAUPPAUGE_HVR1120: + case SAA7134_BOARD_MAGICPRO_PROHDTV_PRO2: + ret = saa7134_tda18271_hvr11x0_toggle_agc(dev, arg); + break; + case SAA7134_BOARD_KWORLD_PCI_SBTVD_FULLSEG: + ret = saa7134_kworld_sbtvd_toggle_agc(dev, arg); + break; + case SAA7134_BOARD_KWORLD_PC150U: + ret = saa7134_kworld_pc150u_toggle_agc(dev, arg); + break; + case SAA7134_BOARD_LEADTEK_WINFAST_HDTV200_H: + ret = saa7134_leadtek_hdtv200h_toggle_agc(dev, arg); + break; + default: + break; + } + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int saa7134_tda8290_callback(struct saa7134_dev *dev, + int command, int arg) +{ + int ret; + + switch (dev->board) { + case SAA7134_BOARD_HAUPPAUGE_HVR1150: + case SAA7134_BOARD_HAUPPAUGE_HVR1120: + case SAA7134_BOARD_AVERMEDIA_M733A: + case SAA7134_BOARD_KWORLD_PCI_SBTVD_FULLSEG: + case SAA7134_BOARD_KWORLD_PC150U: + case SAA7134_BOARD_MAGICPRO_PROHDTV_PRO2: + case SAA7134_BOARD_LEADTEK_WINFAST_HDTV200_H: + /* tda8290 + tda18271 */ + ret = saa7134_tda8290_18271_callback(dev, command, arg); + break; + default: + /* tda8290 + tda827x */ + ret = saa7134_tda8290_827x_callback(dev, command, arg); + break; + } + return ret; +} + +int saa7134_tuner_callback(void *priv, int component, int command, int arg) +{ + struct saa7134_dev *dev = priv; + + if (dev != NULL) { + switch (dev->tuner_type) { + case TUNER_PHILIPS_TDA8290: + return saa7134_tda8290_callback(dev, command, arg); + case TUNER_XC2028: + return saa7134_xc2028_callback(dev, command, arg); + case TUNER_XC5000: + return saa7134_xc5000_callback(dev, command, arg); + } + } else { + pr_err("saa7134: Error - device struct undefined.\n"); + return -EINVAL; + } + return -EINVAL; +} +EXPORT_SYMBOL(saa7134_tuner_callback); + +/* ----------------------------------------------------------- */ + +static void hauppauge_eeprom(struct saa7134_dev *dev, u8 *eeprom_data) +{ + struct tveeprom tv; + + tveeprom_hauppauge_analog(&tv, eeprom_data); + + /* Make sure we support the board model */ + switch (tv.model) { + case 67019: /* WinTV-HVR1110 (Retail, IR Blaster, hybrid, FM, SVid/Comp, 3.5mm audio in) */ + case 67109: /* WinTV-HVR1000 (Retail, IR Receive, analog, no FM, SVid/Comp, 3.5mm audio in) */ + case 67201: /* WinTV-HVR1150 (Retail, IR Receive, hybrid, FM, SVid/Comp, 3.5mm audio in) */ + case 67301: /* WinTV-HVR1000 (Retail, IR Receive, analog, no FM, SVid/Comp, 3.5mm audio in) */ + case 67209: /* WinTV-HVR1110 (Retail, IR Receive, hybrid, FM, SVid/Comp, 3.5mm audio in) */ + case 67559: /* WinTV-HVR1110 (OEM, no IR, hybrid, FM, SVid/Comp, RCA aud) */ + case 67569: /* WinTV-HVR1110 (OEM, no IR, hybrid, FM) */ + case 67579: /* WinTV-HVR1110 (OEM, no IR, hybrid, no FM) */ + case 67589: /* WinTV-HVR1110 (OEM, no IR, hybrid, no FM, SVid/Comp, RCA aud) */ + case 67599: /* WinTV-HVR1110 (OEM, no IR, hybrid, no FM, SVid/Comp, RCA aud) */ + case 67651: /* WinTV-HVR1150 (OEM, no IR, hybrid, FM, SVid/Comp, RCA aud) */ + case 67659: /* WinTV-HVR1110 (OEM, no IR, hybrid, FM, SVid/Comp, RCA aud) */ + break; + default: + pr_warn("%s: warning: unknown hauppauge model #%d\n", + dev->name, tv.model); + break; + } + + pr_info("%s: hauppauge eeprom: model=%d\n", + dev->name, tv.model); +} + +/* ----------------------------------------------------------- */ + +int saa7134_board_init1(struct saa7134_dev *dev) +{ + /* Always print gpio, often manufacturers encode tuner type and other info. */ + saa_writel(SAA7134_GPIO_GPMODE0 >> 2, 0); + dev->gpio_value = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2); + pr_info("%s: board init: gpio is %x\n", dev->name, dev->gpio_value); + + switch (dev->board) { + case SAA7134_BOARD_FLYVIDEO2000: + case SAA7134_BOARD_FLYVIDEO3000: + case SAA7134_BOARD_FLYVIDEO3000_NTSC: + dev->has_remote = SAA7134_REMOTE_GPIO; + board_flyvideo(dev); + break; + case SAA7134_BOARD_FLYTVPLATINUM_MINI2: + case SAA7134_BOARD_FLYTVPLATINUM_FM: + case SAA7134_BOARD_CINERGY400: + case SAA7134_BOARD_CINERGY600: + case SAA7134_BOARD_CINERGY600_MK3: + case SAA7134_BOARD_ECS_TVP3XP: + case SAA7134_BOARD_ECS_TVP3XP_4CB5: + case SAA7134_BOARD_ECS_TVP3XP_4CB6: + case SAA7134_BOARD_MD2819: + case SAA7134_BOARD_KWORLD_VSTREAM_XPERT: + case SAA7134_BOARD_KWORLD_XPERT: + case SAA7134_BOARD_AVERMEDIA_STUDIO_305: + case SAA7134_BOARD_AVERMEDIA_305: + case SAA7134_BOARD_AVERMEDIA_STUDIO_505: + case SAA7134_BOARD_AVERMEDIA_505: + case SAA7134_BOARD_AVERMEDIA_STUDIO_307: + case SAA7134_BOARD_AVERMEDIA_307: + case SAA7134_BOARD_AVERMEDIA_STUDIO_507: + case SAA7134_BOARD_AVERMEDIA_GO_007_FM: + case SAA7134_BOARD_AVERMEDIA_777: + case SAA7134_BOARD_AVERMEDIA_M135A: +/* case SAA7134_BOARD_SABRENT_SBTTVFM: */ /* not finished yet */ + case SAA7134_BOARD_VIDEOMATE_TV_PVR: + case SAA7134_BOARD_VIDEOMATE_GOLD_PLUS: + case SAA7134_BOARD_VIDEOMATE_TV_GOLD_PLUSII: + case SAA7134_BOARD_VIDEOMATE_M1F: + case SAA7134_BOARD_VIDEOMATE_DVBT_300: + case SAA7134_BOARD_VIDEOMATE_DVBT_200: + case SAA7134_BOARD_VIDEOMATE_DVBT_200A: + case SAA7134_BOARD_MANLI_MTV001: + case SAA7134_BOARD_MANLI_MTV002: + case SAA7134_BOARD_BEHOLD_409FM: + case SAA7134_BOARD_AVACSSMARTTV: + case SAA7134_BOARD_GOTVIEW_7135: + case SAA7134_BOARD_KWORLD_TERMINATOR: + case SAA7134_BOARD_SEDNA_PC_TV_CARDBUS: + case SAA7134_BOARD_FLYDVBT_LR301: + case SAA7134_BOARD_ASUSTeK_PS3_100: + case SAA7134_BOARD_ASUSTeK_P7131_DUAL: + case SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA: + case SAA7134_BOARD_ASUSTeK_P7131_ANALOG: + case SAA7134_BOARD_FLYDVBTDUO: + case SAA7134_BOARD_PROTEUS_2309: + case SAA7134_BOARD_AVERMEDIA_A16AR: + case SAA7134_BOARD_ENCORE_ENLTV: + case SAA7134_BOARD_ENCORE_ENLTV_FM: + case SAA7134_BOARD_ENCORE_ENLTV_FM53: + case SAA7134_BOARD_ENCORE_ENLTV_FM3: + case SAA7134_BOARD_10MOONSTVMASTER3: + case SAA7134_BOARD_BEHOLD_401: + case SAA7134_BOARD_BEHOLD_403: + case SAA7134_BOARD_BEHOLD_403FM: + case SAA7134_BOARD_BEHOLD_405: + case SAA7134_BOARD_BEHOLD_405FM: + case SAA7134_BOARD_BEHOLD_407: + case SAA7134_BOARD_BEHOLD_407FM: + case SAA7134_BOARD_BEHOLD_409: + case SAA7134_BOARD_BEHOLD_505FM: + case SAA7134_BOARD_BEHOLD_505RDS_MK5: + case SAA7134_BOARD_BEHOLD_505RDS_MK3: + case SAA7134_BOARD_BEHOLD_507_9FM: + case SAA7134_BOARD_BEHOLD_507RDS_MK3: + case SAA7134_BOARD_BEHOLD_507RDS_MK5: + case SAA7134_BOARD_GENIUS_TVGO_A11MCE: + case SAA7134_BOARD_REAL_ANGEL_220: + case SAA7134_BOARD_KWORLD_PLUS_TV_ANALOG: + case SAA7134_BOARD_AVERMEDIA_GO_007_FM_PLUS: + case SAA7134_BOARD_ROVERMEDIA_LINK_PRO_FM: + case SAA7134_BOARD_LEADTEK_WINFAST_DTV1000S: + case SAA7134_BOARD_LEADTEK_WINFAST_TV2100_FM: + dev->has_remote = SAA7134_REMOTE_GPIO; + break; + case SAA7134_BOARD_FLYDVBS_LR300: + saa_writeb(SAA7134_GPIO_GPMODE3, 0x80); + saa_writeb(SAA7134_GPIO_GPSTATUS2, 0x40); + dev->has_remote = SAA7134_REMOTE_GPIO; + break; + case SAA7134_BOARD_MD5044: + pr_warn("%s: seems there are two different versions of the MD5044\n" + "%s: (with the same ID) out there. If sound doesn't work for\n" + "%s: you try the audio_clock_override=0x200000 insmod option.\n", + dev->name, dev->name, dev->name); + break; + case SAA7134_BOARD_CINERGY400_CARDBUS: + /* power-up tuner chip */ + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0x00040000, 0x00040000); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x00040000, 0x00000000); + break; + case SAA7134_BOARD_PINNACLE_300I_DVBT_PAL: + /* this turns the remote control chip off to work around a bug in it */ + saa_writeb(SAA7134_GPIO_GPMODE1, 0x80); + saa_writeb(SAA7134_GPIO_GPSTATUS1, 0x80); + break; + case SAA7134_BOARD_MONSTERTV_MOBILE: + /* power-up tuner chip */ + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0x00040000, 0x00040000); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x00040000, 0x00000004); + break; + case SAA7134_BOARD_FLYDVBT_DUO_CARDBUS: + /* turn the fan on */ + saa_writeb(SAA7134_GPIO_GPMODE3, 0x08); + saa_writeb(SAA7134_GPIO_GPSTATUS3, 0x06); + break; + case SAA7134_BOARD_ADS_DUO_CARDBUS_PTV331: + case SAA7134_BOARD_FLYDVBT_HYBRID_CARDBUS: + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0x08000000, 0x08000000); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x08000000, 0x00000000); + break; + case SAA7134_BOARD_AVERMEDIA_CARDBUS: + case SAA7134_BOARD_AVERMEDIA_M115: + /* power-down tuner chip */ + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0xffffffff, 0); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0xffffffff, 0); + msleep(10); + /* power-up tuner chip */ + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0xffffffff, 0xffffffff); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0xffffffff, 0xffffffff); + msleep(10); + break; + case SAA7134_BOARD_AVERMEDIA_CARDBUS_501: + /* power-down tuner chip */ + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0x08400000, 0x08400000); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x08400000, 0); + msleep(10); + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0x08400000, 0x08400000); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x08400000, 0x08400000); + msleep(10); + dev->has_remote = SAA7134_REMOTE_I2C; + break; + case SAA7134_BOARD_AVERMEDIA_CARDBUS_506: + saa7134_set_gpio(dev, 23, 0); + msleep(10); + saa7134_set_gpio(dev, 23, 1); + dev->has_remote = SAA7134_REMOTE_I2C; + break; + case SAA7134_BOARD_AVERMEDIA_M103: + saa7134_set_gpio(dev, 23, 0); + msleep(10); + saa7134_set_gpio(dev, 23, 1); + break; + case SAA7134_BOARD_AVERMEDIA_A16D: + saa7134_set_gpio(dev, 21, 0); + msleep(10); + saa7134_set_gpio(dev, 21, 1); + msleep(1); + dev->has_remote = SAA7134_REMOTE_GPIO; + break; + case SAA7134_BOARD_BEHOLD_COLUMBUS_TVFM: + /* power-down tuner chip */ + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0x000A8004, 0x000A8004); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x000A8004, 0); + msleep(10); + /* power-up tuner chip */ + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0x000A8004, 0x000A8004); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x000A8004, 0x000A8004); + msleep(10); + /* remote via GPIO */ + dev->has_remote = SAA7134_REMOTE_GPIO; + break; + case SAA7134_BOARD_RTD_VFG7350: + + /* + * Make sure Production Test Register at offset 0x1D1 is cleared + * to take chip out of test mode. Clearing bit 4 (TST_EN_AOUT) + * prevents pin 105 from remaining low; keeping pin 105 low + * continually resets the SAA6752 chip. + */ + + saa_writeb (SAA7134_PRODUCTION_TEST_MODE, 0x00); + break; + case SAA7134_BOARD_HAUPPAUGE_HVR1150: + case SAA7134_BOARD_HAUPPAUGE_HVR1120: + dev->has_remote = SAA7134_REMOTE_GPIO; + /* GPIO 26 high for digital, low for analog */ + saa7134_set_gpio(dev, 26, 0); + msleep(1); + + saa7134_set_gpio(dev, 22, 0); + msleep(10); + saa7134_set_gpio(dev, 22, 1); + break; + /* i2c remotes */ + case SAA7134_BOARD_PINNACLE_PCTV_110i: + case SAA7134_BOARD_PINNACLE_PCTV_310i: + case SAA7134_BOARD_UPMOST_PURPLE_TV: + case SAA7134_BOARD_MSI_TVATANYWHERE_PLUS: + case SAA7134_BOARD_HAUPPAUGE_HVR1110: + case SAA7134_BOARD_BEHOLD_607FM_MK3: + case SAA7134_BOARD_BEHOLD_607FM_MK5: + case SAA7134_BOARD_BEHOLD_609FM_MK3: + case SAA7134_BOARD_BEHOLD_609FM_MK5: + case SAA7134_BOARD_BEHOLD_607RDS_MK3: + case SAA7134_BOARD_BEHOLD_607RDS_MK5: + case SAA7134_BOARD_BEHOLD_609RDS_MK3: + case SAA7134_BOARD_BEHOLD_609RDS_MK5: + case SAA7134_BOARD_BEHOLD_M6: + case SAA7134_BOARD_BEHOLD_M63: + case SAA7134_BOARD_BEHOLD_M6_EXTRA: + case SAA7134_BOARD_BEHOLD_H6: + case SAA7134_BOARD_BEHOLD_X7: + case SAA7134_BOARD_BEHOLD_H7: + case SAA7134_BOARD_BEHOLD_A7: + case SAA7134_BOARD_KWORLD_PC150U: + case SAA7134_BOARD_SNAZIO_TVPVR_PRO: + dev->has_remote = SAA7134_REMOTE_I2C; + break; + case SAA7134_BOARD_AVERMEDIA_A169_B: + pr_warn("%s: %s: dual saa713x broadcast decoders\n" + "%s: Sorry, none of the inputs to this chip are supported yet.\n" + "%s: Dual decoder functionality is disabled for now, use the other chip.\n", + dev->name, card(dev).name, dev->name, dev->name); + break; + case SAA7134_BOARD_AVERMEDIA_M102: + /* enable tuner */ + dev->has_remote = SAA7134_REMOTE_GPIO; + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0x8c040007, 0x8c040007); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x0c0007cd, 0x0c0007cd); + break; + case SAA7134_BOARD_AVERMEDIA_A700_HYBRID: + case SAA7134_BOARD_AVERMEDIA_A700_PRO: + /* write windows gpio values */ + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0x80040100, 0x80040100); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x80040100, 0x00040100); + break; + case SAA7134_BOARD_AVERMEDIA_A706: + /* radio antenna select: tristate both as in Windows driver */ + saa7134_set_gpio(dev, 12, 3); /* TV antenna */ + saa7134_set_gpio(dev, 13, 3); /* FM antenna */ + dev->has_remote = SAA7134_REMOTE_I2C; + /* + * Disable CE5039 DVB-S tuner now (SLEEP pin high) to prevent + * it from interfering with analog tuner detection + */ + saa7134_set_gpio(dev, 23, 1); + break; + case SAA7134_BOARD_VIDEOMATE_S350: + dev->has_remote = SAA7134_REMOTE_GPIO; + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0x0000C000, 0x0000C000); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x0000C000, 0x0000C000); + break; + case SAA7134_BOARD_AVERMEDIA_M733A: + saa7134_set_gpio(dev, 1, 1); + msleep(10); + saa7134_set_gpio(dev, 1, 0); + msleep(10); + saa7134_set_gpio(dev, 1, 1); + dev->has_remote = SAA7134_REMOTE_GPIO; + break; + case SAA7134_BOARD_MAGICPRO_PROHDTV_PRO2: + /* enable LGS-8G75 */ + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0x0e050000, 0x0c050000); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x0e050000, 0x0c050000); + break; + case SAA7134_BOARD_VIDEOMATE_T750: + /* enable the analog tuner */ + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0x00008000, 0x00008000); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x00008000, 0x00008000); + break; + } + return 0; +} + +static void saa7134_tuner_setup(struct saa7134_dev *dev) +{ + struct tuner_setup tun_setup; + unsigned int mode_mask = T_RADIO | T_ANALOG_TV; + + memset(&tun_setup, 0, sizeof(tun_setup)); + tun_setup.tuner_callback = saa7134_tuner_callback; + + if (saa7134_boards[dev->board].radio_type != UNSET) { + tun_setup.type = saa7134_boards[dev->board].radio_type; + tun_setup.addr = saa7134_boards[dev->board].radio_addr; + + tun_setup.mode_mask = T_RADIO; + + saa_call_all(dev, tuner, s_type_addr, &tun_setup); + mode_mask &= ~T_RADIO; + } + + if ((dev->tuner_type != TUNER_ABSENT) && (dev->tuner_type != UNSET)) { + tun_setup.type = dev->tuner_type; + tun_setup.addr = dev->tuner_addr; + tun_setup.config = &saa7134_boards[dev->board].tda829x_conf; + tun_setup.tuner_callback = saa7134_tuner_callback; + + tun_setup.mode_mask = mode_mask; + + saa_call_all(dev, tuner, s_type_addr, &tun_setup); + } + + if (dev->tda9887_conf) { + struct v4l2_priv_tun_config tda9887_cfg; + + tda9887_cfg.tuner = TUNER_TDA9887; + tda9887_cfg.priv = &dev->tda9887_conf; + + saa_call_all(dev, tuner, s_config, &tda9887_cfg); + } + + if (dev->tuner_type == TUNER_XC2028) { + struct v4l2_priv_tun_config xc2028_cfg; + struct xc2028_ctrl ctl; + + memset(&xc2028_cfg, 0, sizeof(xc2028_cfg)); + memset(&ctl, 0, sizeof(ctl)); + + ctl.fname = XC2028_DEFAULT_FIRMWARE; + ctl.max_len = 64; + + switch (dev->board) { + case SAA7134_BOARD_AVERMEDIA_A16D: + case SAA7134_BOARD_AVERMEDIA_CARDBUS_506: + case SAA7134_BOARD_AVERMEDIA_M103: + case SAA7134_BOARD_AVERMEDIA_A700_HYBRID: + ctl.demod = XC3028_FE_ZARLINK456; + break; + default: + ctl.demod = XC3028_FE_OREN538; + ctl.mts = 1; + } + + xc2028_cfg.tuner = TUNER_XC2028; + xc2028_cfg.priv = &ctl; + + saa_call_all(dev, tuner, s_config, &xc2028_cfg); + } +} + +/* stuff which needs working i2c */ +int saa7134_board_init2(struct saa7134_dev *dev) +{ + unsigned char buf; + int board; + + /* Put here the code that enables the chips that are needed + for analog mode and doesn't depend on the tuner attachment. + It is also a good idea to get tuner type from eeprom, etc before + initializing tuner, since we can avoid loading tuner driver + on devices that has TUNER_ABSENT + */ + switch (dev->board) { + case SAA7134_BOARD_BMK_MPEX_NOTUNER: + case SAA7134_BOARD_BMK_MPEX_TUNER: + /* Checks if the device has a tuner at 0x60 addr + If the device doesn't have a tuner, TUNER_ABSENT + will be used at tuner_type, avoiding loading tuner + without needing it + */ + dev->i2c_client.addr = 0x60; + board = (i2c_master_recv(&dev->i2c_client, &buf, 0) < 0) + ? SAA7134_BOARD_BMK_MPEX_NOTUNER + : SAA7134_BOARD_BMK_MPEX_TUNER; + if (board == dev->board) + break; + dev->board = board; + pr_warn("%s: board type fixup: %s\n", dev->name, + saa7134_boards[dev->board].name); + dev->tuner_type = saa7134_boards[dev->board].tuner_type; + + break; + case SAA7134_BOARD_MD7134: + { + u8 subaddr; + u8 data[3], data1[] = { 0x09, 0x9f, 0x86, 0x11}; + int ret, tuner_t; + struct i2c_msg msg[] = {{.addr = 0x50, .flags = 0, .buf = &subaddr, .len = 1}, + {.addr = 0x50, .flags = I2C_M_RD, .buf = data, .len = 3}}, + msg1 = {.addr = 0x61, .flags = 0, .buf = data1, .len = sizeof(data1)}; + + subaddr= 0x14; + tuner_t = 0; + + /* Retrieve device data from eeprom, checking for the + proper tuner_type. + */ + ret = i2c_transfer(&dev->i2c_adap, msg, 2); + if (ret != 2) { + pr_err("EEPROM read failure\n"); + } else if ((data[0] != 0) && (data[0] != 0xff)) { + /* old config structure */ + subaddr = data[0] + 2; + msg[1].len = 2; + i2c_transfer(&dev->i2c_adap, msg, 2); + tuner_t = (data[0] << 8) + data[1]; + switch (tuner_t){ + case 0x0103: + dev->tuner_type = TUNER_PHILIPS_PAL; + break; + case 0x010C: + dev->tuner_type = TUNER_PHILIPS_FM1216ME_MK3; + break; + default: + pr_err("%s Can't determine tuner type %x from EEPROM\n", + dev->name, tuner_t); + } + } else if ((data[1] != 0) && (data[1] != 0xff)) { + /* new config structure */ + subaddr = data[1] + 1; + msg[1].len = 1; + i2c_transfer(&dev->i2c_adap, msg, 2); + subaddr = data[0] + 1; + msg[1].len = 2; + i2c_transfer(&dev->i2c_adap, msg, 2); + tuner_t = (data[1] << 8) + data[0]; + switch (tuner_t) { + case 0x0005: + dev->tuner_type = TUNER_PHILIPS_FM1216ME_MK3; + break; + case 0x001d: + dev->tuner_type = TUNER_PHILIPS_FMD1216ME_MK3; + pr_info("%s Board has DVB-T\n", + dev->name); + break; + default: + pr_err("%s Can't determine tuner type %x from EEPROM\n", + dev->name, tuner_t); + } + } else { + pr_err("%s unexpected config structure\n", dev->name); + } + + pr_info("%s Tuner type is %d\n", dev->name, dev->tuner_type); + + /* The tuner TUNER_PHILIPS_FMD1216ME_MK3 after hardware */ + /* start has disabled IF and enabled DVB-T. When saa7134 */ + /* scan I2C devices it will not detect IF tda9887 and can`t*/ + /* watch TV without software reboot. To solve this problem */ + /* switch the tuner to analog TV mode manually. */ + if (dev->tuner_type == TUNER_PHILIPS_FMD1216ME_MK3) { + if (i2c_transfer(&dev->i2c_adap, &msg1, 1) != 1) + printk(KERN_WARNING "%s: Unable to enable IF of the tuner.\n", dev->name); + } + break; + } + case SAA7134_BOARD_PHILIPS_EUROPA: + if (dev->autodetected && (dev->eedata[0x41] == 0x1c)) { + /* Reconfigure board as Snake reference design */ + dev->board = SAA7134_BOARD_PHILIPS_SNAKE; + dev->tuner_type = saa7134_boards[dev->board].tuner_type; + pr_info("%s: Reconfigured board as %s\n", + dev->name, saa7134_boards[dev->board].name); + break; + } + fallthrough; + case SAA7134_BOARD_VIDEOMATE_DVBT_300: + case SAA7134_BOARD_ASUS_EUROPA2_HYBRID: + case SAA7134_BOARD_ASUS_EUROPA_HYBRID: + case SAA7134_BOARD_TECHNOTREND_BUDGET_T3000: + { + + /* The Philips EUROPA based hybrid boards have the tuner + connected through the channel decoder. We have to make it + transparent to find it + */ + u8 data[] = { 0x07, 0x02}; + struct i2c_msg msg = {.addr=0x08, .flags=0, .buf=data, .len = sizeof(data)}; + i2c_transfer(&dev->i2c_adap, &msg, 1); + + break; + } + case SAA7134_BOARD_PHILIPS_TIGER: + case SAA7134_BOARD_PHILIPS_TIGER_S: + { + u8 data[] = { 0x3c, 0x33, 0x60}; + struct i2c_msg msg = {.addr=0x08, .flags=0, .buf=data, .len = sizeof(data)}; + if (dev->autodetected && (dev->eedata[0x49] == 0x50)) { + dev->board = SAA7134_BOARD_PHILIPS_TIGER_S; + pr_info("%s: Reconfigured board as %s\n", + dev->name, saa7134_boards[dev->board].name); + } + if (dev->board == SAA7134_BOARD_PHILIPS_TIGER_S) { + dev->tuner_type = TUNER_PHILIPS_TDA8290; + + data[2] = 0x68; + i2c_transfer(&dev->i2c_adap, &msg, 1); + break; + } + i2c_transfer(&dev->i2c_adap, &msg, 1); + break; + } + case SAA7134_BOARD_ASUSTeK_TVFM7135: + /* The card below is detected as card=53, but is different */ + if (dev->autodetected && (dev->eedata[0x27] == 0x03)) { + dev->board = SAA7134_BOARD_ASUSTeK_P7131_ANALOG; + pr_info("%s: P7131 analog only, using entry of %s\n", + dev->name, saa7134_boards[dev->board].name); + + /* + * IR init has already happened for other cards, so + * we have to catch up. + */ + dev->has_remote = SAA7134_REMOTE_GPIO; + saa7134_input_init1(dev); + } + break; + case SAA7134_BOARD_HAUPPAUGE_HVR1150: + case SAA7134_BOARD_HAUPPAUGE_HVR1120: + hauppauge_eeprom(dev, dev->eedata+0x80); + break; + case SAA7134_BOARD_HAUPPAUGE_HVR1110: + hauppauge_eeprom(dev, dev->eedata+0x80); + fallthrough; + case SAA7134_BOARD_PINNACLE_PCTV_310i: + case SAA7134_BOARD_KWORLD_DVBT_210: + case SAA7134_BOARD_TEVION_DVBT_220RF: + case SAA7134_BOARD_ASUSTeK_TIGER: + case SAA7134_BOARD_ASUSTeK_P7131_DUAL: + case SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA: + case SAA7134_BOARD_MEDION_MD8800_QUADRO: + case SAA7134_BOARD_AVERMEDIA_SUPER_007: + case SAA7134_BOARD_TWINHAN_DTV_DVB_3056: + case SAA7134_BOARD_CREATIX_CTX953: + { + /* this is a hybrid board, initialize to analog mode + * and configure firmware eeprom address + */ + u8 data[] = { 0x3c, 0x33, 0x60}; + struct i2c_msg msg = {.addr=0x08, .flags=0, .buf=data, .len = sizeof(data)}; + i2c_transfer(&dev->i2c_adap, &msg, 1); + break; + } + case SAA7134_BOARD_ASUSTeK_TIGER_3IN1: + { + u8 data[] = { 0x3c, 0x33, 0x60}; + struct i2c_msg msg = {.addr = 0x0b, .flags = 0, .buf = data, + .len = sizeof(data)}; + i2c_transfer(&dev->i2c_adap, &msg, 1); + break; + } + case SAA7134_BOARD_ASUSTeK_PS3_100: + { + u8 data[] = { 0x3c, 0x33, 0x60}; + struct i2c_msg msg = {.addr = 0x0b, .flags = 0, .buf = data, + .len = sizeof(data)}; + i2c_transfer(&dev->i2c_adap, &msg, 1); + break; + } + case SAA7134_BOARD_FLYDVB_TRIO: + { + u8 temp = 0; + int rc; + u8 data[] = { 0x3c, 0x33, 0x62}; + struct i2c_msg msg = {.addr=0x09, .flags=0, .buf=data, .len = sizeof(data)}; + i2c_transfer(&dev->i2c_adap, &msg, 1); + + /* + * send weak up message to pic16C505 chip + * @ LifeView FlyDVB Trio + */ + msg.buf = &temp; + msg.addr = 0x0b; + msg.len = 1; + if (1 != i2c_transfer(&dev->i2c_adap, &msg, 1)) { + pr_warn("%s: send wake up byte to pic16C505(IR chip) failed\n", + dev->name); + } else { + msg.flags = I2C_M_RD; + rc = i2c_transfer(&dev->i2c_adap, &msg, 1); + pr_info("%s: probe IR chip @ i2c 0x%02x: %s\n", + dev->name, msg.addr, + (1 == rc) ? "yes" : "no"); + if (rc == 1) + dev->has_remote = SAA7134_REMOTE_I2C; + } + break; + } + case SAA7134_BOARD_ADS_DUO_CARDBUS_PTV331: + case SAA7134_BOARD_FLYDVBT_HYBRID_CARDBUS: + { + /* initialize analog mode */ + u8 data[] = { 0x3c, 0x33, 0x6a}; + struct i2c_msg msg = {.addr=0x08, .flags=0, .buf=data, .len = sizeof(data)}; + i2c_transfer(&dev->i2c_adap, &msg, 1); + break; + } + case SAA7134_BOARD_CINERGY_HT_PCMCIA: + case SAA7134_BOARD_CINERGY_HT_PCI: + { + /* initialize analog mode */ + u8 data[] = { 0x3c, 0x33, 0x68}; + struct i2c_msg msg = {.addr=0x08, .flags=0, .buf=data, .len = sizeof(data)}; + i2c_transfer(&dev->i2c_adap, &msg, 1); + break; + } + case SAA7134_BOARD_VIDEOMATE_DVBT_200: + case SAA7134_BOARD_VIDEOMATE_DVBT_200A: + /* The T200 and the T200A share the same pci id. Consequently, + * we are going to query eeprom to try to find out which one we + * are actually looking at. */ + + /* Don't do this if the board was specifically selected with an + * insmod option or if we have the default configuration T200*/ + if (!dev->autodetected || (dev->eedata[0x41] == 0xd0)) + break; + if (dev->eedata[0x41] == 0x02) { + /* Reconfigure board as T200A */ + dev->board = SAA7134_BOARD_VIDEOMATE_DVBT_200A; + dev->tuner_type = saa7134_boards[dev->board].tuner_type; + dev->tda9887_conf = saa7134_boards[dev->board].tda9887_conf; + pr_info("%s: Reconfigured board as %s\n", + dev->name, saa7134_boards[dev->board].name); + } else { + pr_warn("%s: Unexpected tuner type info: %x in eeprom\n", + dev->name, dev->eedata[0x41]); + break; + } + break; + case SAA7134_BOARD_ADS_INSTANT_HDTV_PCI: + case SAA7134_BOARD_KWORLD_ATSC110: + { + struct i2c_msg msg = { .addr = 0x0a, .flags = 0 }; + int i; + static u8 buffer[][2] = { + { 0x10, 0x12 }, + { 0x13, 0x04 }, + { 0x16, 0x00 }, + { 0x14, 0x04 }, + { 0x17, 0x00 }, + }; + + for (i = 0; i < ARRAY_SIZE(buffer); i++) { + msg.buf = &buffer[i][0]; + msg.len = ARRAY_SIZE(buffer[0]); + if (i2c_transfer(&dev->i2c_adap, &msg, 1) != 1) + pr_warn("%s: Unable to enable tuner(%i).\n", + dev->name, i); + } + break; + } + case SAA7134_BOARD_BEHOLD_H6: + { + u8 data[] = { 0x09, 0x9f, 0x86, 0x11}; + struct i2c_msg msg = {.addr = 0x61, .flags = 0, .buf = data, + .len = sizeof(data)}; + + /* The tuner TUNER_PHILIPS_FMD1216MEX_MK3 after hardware */ + /* start has disabled IF and enabled DVB-T. When saa7134 */ + /* scan I2C devices it not detect IF tda9887 and can`t */ + /* watch TV without software reboot. For solve this problem */ + /* switch the tuner to analog TV mode manually. */ + if (i2c_transfer(&dev->i2c_adap, &msg, 1) != 1) + pr_warn("%s: Unable to enable IF of the tuner.\n", + dev->name); + break; + } + case SAA7134_BOARD_KWORLD_PCI_SBTVD_FULLSEG: + saa_writel(SAA7134_GPIO_GPMODE0 >> 2, 0x4000); + saa_writel(SAA7134_GPIO_GPSTATUS0 >> 2, 0x4000); + + saa7134_set_gpio(dev, 27, 0); + break; + } /* switch() */ + + /* initialize tuner (don't do this when resuming) */ + if (!dev->insuspend && TUNER_ABSENT != dev->tuner_type) { + int has_demod = (dev->tda9887_conf & TDA9887_PRESENT); + + /* Note: radio tuner address is always filled in, + so we do not need to probe for a radio tuner device. */ + if (dev->radio_type != UNSET) + v4l2_i2c_new_subdev(&dev->v4l2_dev, + &dev->i2c_adap, "tuner", + dev->radio_addr, NULL); + if (has_demod) + v4l2_i2c_new_subdev(&dev->v4l2_dev, + &dev->i2c_adap, "tuner", + 0, v4l2_i2c_tuner_addrs(ADDRS_DEMOD)); + if (dev->tuner_addr == ADDR_UNSET) { + enum v4l2_i2c_tuner_type type = + has_demod ? ADDRS_TV_WITH_DEMOD : ADDRS_TV; + + v4l2_i2c_new_subdev(&dev->v4l2_dev, + &dev->i2c_adap, "tuner", + 0, v4l2_i2c_tuner_addrs(type)); + } else { + v4l2_i2c_new_subdev(&dev->v4l2_dev, + &dev->i2c_adap, "tuner", + dev->tuner_addr, NULL); + } + } + + saa7134_tuner_setup(dev); + + switch (dev->board) { + case SAA7134_BOARD_BEHOLD_COLUMBUS_TVFM: + case SAA7134_BOARD_AVERMEDIA_CARDBUS_501: + { + struct v4l2_priv_tun_config tea5767_cfg; + struct tea5767_ctrl ctl; + + dev->i2c_client.addr = 0xC0; + /* set TEA5767(analog FM) defines */ + memset(&ctl, 0, sizeof(ctl)); + ctl.xtal_freq = TEA5767_HIGH_LO_13MHz; + tea5767_cfg.tuner = TUNER_TEA5767; + tea5767_cfg.priv = &ctl; + saa_call_all(dev, tuner, s_config, &tea5767_cfg); + break; + } + } /* switch() */ + + return 0; +} diff --git a/drivers/media/pci/saa7134/saa7134-core.c b/drivers/media/pci/saa7134/saa7134-core.c new file mode 100644 index 000000000..2f5b258d6 --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134-core.c @@ -0,0 +1,1524 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * device driver for philips saa7134 based TV cards + * driver core + * + * (c) 2001-03 Gerd Knorr [SuSE Labs] + */ + +#include "saa7134.h" +#include "saa7134-reg.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("v4l2 driver module for saa7130/34 based TV cards"); +MODULE_AUTHOR("Gerd Knorr [SuSE Labs]"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(SAA7134_VERSION); + + +/* ------------------------------------------------------------------ */ + +static unsigned int irq_debug; +module_param(irq_debug, int, 0644); +MODULE_PARM_DESC(irq_debug,"enable debug messages [IRQ handler]"); + +static unsigned int core_debug; +module_param(core_debug, int, 0644); +MODULE_PARM_DESC(core_debug,"enable debug messages [core]"); + +static unsigned int gpio_tracking; +module_param(gpio_tracking, int, 0644); +MODULE_PARM_DESC(gpio_tracking,"enable debug messages [gpio]"); + +static unsigned int alsa = 1; +module_param(alsa, int, 0644); +MODULE_PARM_DESC(alsa,"enable/disable ALSA DMA sound [dmasound]"); + +static unsigned int latency = UNSET; +module_param(latency, int, 0444); +MODULE_PARM_DESC(latency,"pci latency timer"); + +bool saa7134_userptr; +module_param(saa7134_userptr, bool, 0644); +MODULE_PARM_DESC(saa7134_userptr, "enable page-aligned userptr support"); + +static unsigned int video_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +static unsigned int vbi_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +static unsigned int radio_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +static unsigned int tuner[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +static unsigned int card[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; + + +module_param_array(video_nr, int, NULL, 0444); +module_param_array(vbi_nr, int, NULL, 0444); +module_param_array(radio_nr, int, NULL, 0444); +module_param_array(tuner, int, NULL, 0444); +module_param_array(card, int, NULL, 0444); + +MODULE_PARM_DESC(video_nr, "video device number"); +MODULE_PARM_DESC(vbi_nr, "vbi device number"); +MODULE_PARM_DESC(radio_nr, "radio device number"); +MODULE_PARM_DESC(tuner, "tuner type"); +MODULE_PARM_DESC(card, "card type"); + +DEFINE_MUTEX(saa7134_devlist_lock); +EXPORT_SYMBOL(saa7134_devlist_lock); +LIST_HEAD(saa7134_devlist); +EXPORT_SYMBOL(saa7134_devlist); +static LIST_HEAD(mops_list); +static unsigned int saa7134_devcount; + +int (*saa7134_dmasound_init)(struct saa7134_dev *dev); +int (*saa7134_dmasound_exit)(struct saa7134_dev *dev); + +#define core_dbg(fmt, arg...) do { \ + if (core_debug) \ + printk(KERN_DEBUG pr_fmt("core: " fmt), ## arg); \ + } while (0) + +#define irq_dbg(level, fmt, arg...) do {\ + if (irq_debug > level) \ + printk(KERN_DEBUG pr_fmt("irq: " fmt), ## arg); \ + } while (0) + +void saa7134_track_gpio(struct saa7134_dev *dev, const char *msg) +{ + unsigned long mode,status; + + if (!gpio_tracking) + return; + /* rising SAA7134_GPIO_GPRESCAN reads the status */ + saa_andorb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN,0); + saa_andorb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN,SAA7134_GPIO_GPRESCAN); + mode = saa_readl(SAA7134_GPIO_GPMODE0 >> 2) & 0xfffffff; + status = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2) & 0xfffffff; + core_dbg("%s: gpio: mode=0x%07lx in=0x%07lx out=0x%07lx [%s]\n", + dev->name, mode, (~mode) & status, mode & status, msg); +} + +void saa7134_set_gpio(struct saa7134_dev *dev, int bit_no, int value) +{ + u32 index, bitval; + + index = 1 << bit_no; + switch (value) { + case 0: /* static value */ + case 1: + core_dbg("setting GPIO%d to static %d\n", bit_no, value); + /* turn sync mode off if necessary */ + if (index & 0x00c00000) + saa_andorb(SAA7134_VIDEO_PORT_CTRL6, 0x0f, 0x00); + if (value) + bitval = index; + else + bitval = 0; + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, index, index); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, index, bitval); + break; + case 3: /* tristate */ + core_dbg("setting GPIO%d to tristate\n", bit_no); + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, index, 0); + break; + } +} + +/* ------------------------------------------------------------------ */ + + +/* ----------------------------------------------------------- */ +/* delayed request_module */ + +#if defined(CONFIG_MODULES) && defined(MODULE) + +static void request_module_async(struct work_struct *work){ + struct saa7134_dev* dev = container_of(work, struct saa7134_dev, request_module_wk); + if (card_is_empress(dev)) + request_module("saa7134-empress"); + if (card_is_dvb(dev)) + request_module("saa7134-dvb"); + if (card_is_go7007(dev)) + request_module("saa7134-go7007"); + if (alsa) { + if (dev->pci->device != PCI_DEVICE_ID_PHILIPS_SAA7130) + request_module("saa7134-alsa"); + } +} + +static void request_submodules(struct saa7134_dev *dev) +{ + INIT_WORK(&dev->request_module_wk, request_module_async); + schedule_work(&dev->request_module_wk); +} + +static void flush_request_submodules(struct saa7134_dev *dev) +{ + flush_work(&dev->request_module_wk); +} + +#else +#define request_submodules(dev) +#define flush_request_submodules(dev) +#endif /* CONFIG_MODULES */ + +/* ------------------------------------------------------------------ */ + +/* nr of (saa7134-)pages for the given buffer size */ +static int saa7134_buffer_pages(int size) +{ + size = PAGE_ALIGN(size); + size += PAGE_SIZE; /* for non-page-aligned buffers */ + size /= 4096; + return size; +} + +/* calc max # of buffers from size (must not exceed the 4MB virtual + * address space per DMA channel) */ +int saa7134_buffer_count(unsigned int size, unsigned int count) +{ + unsigned int maxcount; + + maxcount = 1024 / saa7134_buffer_pages(size); + if (count > maxcount) + count = maxcount; + return count; +} + +int saa7134_buffer_startpage(struct saa7134_buf *buf) +{ + return saa7134_buffer_pages(vb2_plane_size(&buf->vb2.vb2_buf, 0)) + * buf->vb2.vb2_buf.index; +} + +unsigned long saa7134_buffer_base(struct saa7134_buf *buf) +{ + unsigned long base; + struct sg_table *dma = vb2_dma_sg_plane_desc(&buf->vb2.vb2_buf, 0); + + base = saa7134_buffer_startpage(buf) * 4096; + base += dma->sgl[0].offset; + return base; +} + +/* ------------------------------------------------------------------ */ + +int saa7134_pgtable_alloc(struct pci_dev *pci, struct saa7134_pgtable *pt) +{ + __le32 *cpu; + dma_addr_t dma_addr = 0; + + cpu = dma_alloc_coherent(&pci->dev, SAA7134_PGTABLE_SIZE, &dma_addr, + GFP_KERNEL); + if (NULL == cpu) + return -ENOMEM; + pt->size = SAA7134_PGTABLE_SIZE; + pt->cpu = cpu; + pt->dma = dma_addr; + return 0; +} + +int saa7134_pgtable_build(struct pci_dev *pci, struct saa7134_pgtable *pt, + struct scatterlist *list, unsigned int length, + unsigned int startpage) +{ + __le32 *ptr; + unsigned int i, p; + + BUG_ON(NULL == pt || NULL == pt->cpu); + + ptr = pt->cpu + startpage; + for (i = 0; i < length; i++, list = sg_next(list)) { + for (p = 0; p * 4096 < sg_dma_len(list); p++, ptr++) + *ptr = cpu_to_le32(sg_dma_address(list) + + list->offset + p * 4096); + } + return 0; +} + +void saa7134_pgtable_free(struct pci_dev *pci, struct saa7134_pgtable *pt) +{ + if (NULL == pt->cpu) + return; + dma_free_coherent(&pci->dev, pt->size, pt->cpu, pt->dma); + pt->cpu = NULL; +} + +/* ------------------------------------------------------------------ */ + +int saa7134_buffer_queue(struct saa7134_dev *dev, + struct saa7134_dmaqueue *q, + struct saa7134_buf *buf) +{ + struct saa7134_buf *next = NULL; + unsigned long flags; + + spin_lock_irqsave(&dev->slock, flags); + core_dbg("buffer_queue %p\n", buf); + if (NULL == q->curr) { + if (!q->need_two) { + q->curr = buf; + buf->activate(dev, buf, NULL); + } else if (list_empty(&q->queue)) { + list_add_tail(&buf->entry, &q->queue); + } else { + next = list_entry(q->queue.next, struct saa7134_buf, + entry); + q->curr = buf; + buf->activate(dev, buf, next); + } + } else { + list_add_tail(&buf->entry, &q->queue); + } + spin_unlock_irqrestore(&dev->slock, flags); + return 0; +} + +void saa7134_buffer_finish(struct saa7134_dev *dev, + struct saa7134_dmaqueue *q, + unsigned int state) +{ + core_dbg("buffer_finish %p\n", q->curr); + + /* finish current buffer */ + q->curr->vb2.vb2_buf.timestamp = ktime_get_ns(); + q->curr->vb2.sequence = q->seq_nr++; + vb2_buffer_done(&q->curr->vb2.vb2_buf, state); + q->curr = NULL; +} + +void saa7134_buffer_next(struct saa7134_dev *dev, + struct saa7134_dmaqueue *q) +{ + struct saa7134_buf *buf,*next = NULL; + + assert_spin_locked(&dev->slock); + BUG_ON(NULL != q->curr); + + if (!list_empty(&q->queue)) { + /* activate next one from queue */ + buf = list_entry(q->queue.next, struct saa7134_buf, entry); + core_dbg("buffer_next %p [prev=%p/next=%p]\n", + buf, q->queue.prev, q->queue.next); + list_del(&buf->entry); + if (!list_empty(&q->queue)) + next = list_entry(q->queue.next, struct saa7134_buf, entry); + q->curr = buf; + buf->activate(dev, buf, next); + core_dbg("buffer_next #2 prev=%p/next=%p\n", + q->queue.prev, q->queue.next); + } else { + /* nothing to do -- just stop DMA */ + core_dbg("buffer_next %p\n", NULL); + saa7134_set_dmabits(dev); + timer_delete(&q->timeout); + } +} + +void saa7134_buffer_timeout(struct timer_list *t) +{ + struct saa7134_dmaqueue *q = timer_container_of(q, t, timeout); + struct saa7134_dev *dev = q->dev; + unsigned long flags; + + spin_lock_irqsave(&dev->slock, flags); + + /* try to reset the hardware (SWRST) */ + saa_writeb(SAA7134_REGION_ENABLE, 0x00); + saa_writeb(SAA7134_REGION_ENABLE, 0x80); + saa_writeb(SAA7134_REGION_ENABLE, 0x00); + + /* flag current buffer as failed, + try to start over with the next one. */ + if (q->curr) { + core_dbg("timeout on %p\n", q->curr); + saa7134_buffer_finish(dev, q, VB2_BUF_STATE_ERROR); + } + saa7134_buffer_next(dev, q); + spin_unlock_irqrestore(&dev->slock, flags); +} + +void saa7134_stop_streaming(struct saa7134_dev *dev, struct saa7134_dmaqueue *q) +{ + unsigned long flags; + struct list_head *pos, *n; + struct saa7134_buf *tmp; + + spin_lock_irqsave(&dev->slock, flags); + list_for_each_safe(pos, n, &q->queue) { + tmp = list_entry(pos, struct saa7134_buf, entry); + vb2_buffer_done(&tmp->vb2.vb2_buf, + VB2_BUF_STATE_ERROR); + list_del(pos); + tmp = NULL; + } + spin_unlock_irqrestore(&dev->slock, flags); + saa7134_buffer_timeout(&q->timeout); /* also calls timer_delete(&q->timeout) */ +} +EXPORT_SYMBOL_GPL(saa7134_stop_streaming); + +/* ------------------------------------------------------------------ */ + +int saa7134_set_dmabits(struct saa7134_dev *dev) +{ + u32 split, task=0, ctrl=0, irq=0; + enum v4l2_field cap = V4L2_FIELD_ANY; + enum v4l2_field ov = V4L2_FIELD_ANY; + + assert_spin_locked(&dev->slock); + + if (dev->insuspend) + return 0; + + /* video capture -- dma 0 + video task A */ + if (dev->video_q.curr) { + task |= 0x01; + ctrl |= SAA7134_MAIN_CTRL_TE0; + irq |= SAA7134_IRQ1_INTE_RA0_1 | + SAA7134_IRQ1_INTE_RA0_0; + cap = dev->field; + } + + /* video capture -- dma 1+2 (planar modes) */ + if (dev->video_q.curr && dev->fmt->planar) { + ctrl |= SAA7134_MAIN_CTRL_TE4 | + SAA7134_MAIN_CTRL_TE5; + } + + /* vbi capture -- dma 0 + vbi task A+B */ + if (dev->vbi_q.curr) { + task |= 0x22; + ctrl |= SAA7134_MAIN_CTRL_TE2 | + SAA7134_MAIN_CTRL_TE3; + irq |= SAA7134_IRQ1_INTE_RA0_7 | + SAA7134_IRQ1_INTE_RA0_6 | + SAA7134_IRQ1_INTE_RA0_5 | + SAA7134_IRQ1_INTE_RA0_4; + } + + /* audio capture -- dma 3 */ + if (dev->dmasound.dma_running) { + ctrl |= SAA7134_MAIN_CTRL_TE6; + irq |= SAA7134_IRQ1_INTE_RA3_1 | + SAA7134_IRQ1_INTE_RA3_0; + } + + /* TS capture -- dma 5 */ + if (dev->ts_q.curr) { + ctrl |= SAA7134_MAIN_CTRL_TE5; + irq |= SAA7134_IRQ1_INTE_RA2_1 | + SAA7134_IRQ1_INTE_RA2_0; + } + + /* set task conditions + field handling */ + if (V4L2_FIELD_HAS_BOTH(cap) || V4L2_FIELD_HAS_BOTH(ov) || cap == ov) { + /* default config -- use full frames */ + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_A), 0x0d); + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_B), 0x0d); + saa_writeb(SAA7134_FIELD_HANDLING(TASK_A), 0x02); + saa_writeb(SAA7134_FIELD_HANDLING(TASK_B), 0x02); + split = 0; + } else { + /* split fields between tasks */ + if (V4L2_FIELD_TOP == cap) { + /* odd A, even B, repeat */ + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_A), 0x0d); + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_B), 0x0e); + } else { + /* odd B, even A, repeat */ + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_A), 0x0e); + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_B), 0x0d); + } + saa_writeb(SAA7134_FIELD_HANDLING(TASK_A), 0x01); + saa_writeb(SAA7134_FIELD_HANDLING(TASK_B), 0x01); + split = 1; + } + + /* irqs */ + saa_writeb(SAA7134_REGION_ENABLE, task); + saa_writel(SAA7134_IRQ1, irq); + saa_andorl(SAA7134_MAIN_CTRL, + SAA7134_MAIN_CTRL_TE0 | + SAA7134_MAIN_CTRL_TE1 | + SAA7134_MAIN_CTRL_TE2 | + SAA7134_MAIN_CTRL_TE3 | + SAA7134_MAIN_CTRL_TE4 | + SAA7134_MAIN_CTRL_TE5 | + SAA7134_MAIN_CTRL_TE6, + ctrl); + core_dbg("dmabits: task=0x%02x ctrl=0x%02x irq=0x%x split=%s\n", + task, ctrl, irq, split ? "no" : "yes"); + + return 0; +} + +/* ------------------------------------------------------------------ */ +/* IRQ handler + helpers */ + +static char *irqbits[] = { + "DONE_RA0", "DONE_RA1", "DONE_RA2", "DONE_RA3", + "AR", "PE", "PWR_ON", "RDCAP", "INTL", "FIDT", "MMC", + "TRIG_ERR", "CONF_ERR", "LOAD_ERR", + "GPIO16", "GPIO18", "GPIO22", "GPIO23" +}; +#define IRQBITS ARRAY_SIZE(irqbits) + +static void print_irqstatus(struct saa7134_dev *dev, int loop, + unsigned long report, unsigned long status) +{ + unsigned int i; + + irq_dbg(1, "[%d,%ld]: r=0x%lx s=0x%02lx", + loop, jiffies, report, status); + for (i = 0; i < IRQBITS; i++) { + if (!(report & (1 << i))) + continue; + pr_cont(" %s", irqbits[i]); + } + if (report & SAA7134_IRQ_REPORT_DONE_RA0) { + pr_cont(" | RA0=%s,%s,%s,%ld", + (status & 0x40) ? "vbi" : "video", + (status & 0x20) ? "b" : "a", + (status & 0x10) ? "odd" : "even", + (status & 0x0f)); + } + pr_cont("\n"); +} + +static irqreturn_t saa7134_irq(int irq, void *dev_id) +{ + struct saa7134_dev *dev = (struct saa7134_dev*) dev_id; + unsigned long report,status; + int loop, handled = 0; + + if (dev->insuspend) + goto out; + + for (loop = 0; loop < 10; loop++) { + report = saa_readl(SAA7134_IRQ_REPORT); + status = saa_readl(SAA7134_IRQ_STATUS); + + /* If dmasound support is active and we get a sound report, + * mask out the report and let the saa7134-alsa module deal + * with it */ + if ((report & SAA7134_IRQ_REPORT_DONE_RA3) && + (dev->dmasound.priv_data != NULL) ) + { + irq_dbg(2, "preserving DMA sound interrupt\n"); + report &= ~SAA7134_IRQ_REPORT_DONE_RA3; + } + + if (0 == report) { + irq_dbg(2, "no (more) work\n"); + goto out; + } + + handled = 1; + saa_writel(SAA7134_IRQ_REPORT,report); + if (irq_debug) + print_irqstatus(dev,loop,report,status); + + + if ((report & SAA7134_IRQ_REPORT_RDCAP) || + (report & SAA7134_IRQ_REPORT_INTL)) + saa7134_irq_video_signalchange(dev); + + + if ((report & SAA7134_IRQ_REPORT_DONE_RA0) && + (status & 0x60) == 0) + saa7134_irq_video_done(dev,status); + + if ((report & SAA7134_IRQ_REPORT_DONE_RA0) && + (status & 0x40) == 0x40) + saa7134_irq_vbi_done(dev,status); + + if ((report & SAA7134_IRQ_REPORT_DONE_RA2) && + card_has_mpeg(dev)) { + if (dev->mops->irq_ts_done != NULL) + dev->mops->irq_ts_done(dev, status); + else + saa7134_irq_ts_done(dev, status); + } + + if (report & SAA7134_IRQ_REPORT_GPIO16) { + switch (dev->has_remote) { + case SAA7134_REMOTE_GPIO: + if (!dev->remote) + break; + if (dev->remote->mask_keydown & 0x10000) { + saa7134_input_irq(dev); + } + break; + + case SAA7134_REMOTE_I2C: + break; /* FIXME: invoke I2C get_key() */ + + default: /* GPIO16 not used by IR remote */ + break; + } + } + + if (report & SAA7134_IRQ_REPORT_GPIO18) { + switch (dev->has_remote) { + case SAA7134_REMOTE_GPIO: + if (!dev->remote) + break; + if ((dev->remote->mask_keydown & 0x40000) || + (dev->remote->mask_keyup & 0x40000)) { + saa7134_input_irq(dev); + } + break; + + case SAA7134_REMOTE_I2C: + break; /* FIXME: invoke I2C get_key() */ + + default: /* GPIO18 not used by IR remote */ + break; + } + } + } + + if (10 == loop) { + print_irqstatus(dev,loop,report,status); + if (report & SAA7134_IRQ_REPORT_PE) { + /* disable all parity error */ + pr_warn("%s/irq: looping -- clearing PE (parity error!) enable bit\n", + dev->name); + saa_clearl(SAA7134_IRQ2,SAA7134_IRQ2_INTE_PE); + } else if (report & SAA7134_IRQ_REPORT_GPIO16) { + /* disable gpio16 IRQ */ + pr_warn("%s/irq: looping -- clearing GPIO16 enable bit\n", + dev->name); + saa_clearl(SAA7134_IRQ2, SAA7134_IRQ2_INTE_GPIO16_P); + saa_clearl(SAA7134_IRQ2, SAA7134_IRQ2_INTE_GPIO16_N); + } else if (report & SAA7134_IRQ_REPORT_GPIO18) { + /* disable gpio18 IRQs */ + pr_warn("%s/irq: looping -- clearing GPIO18 enable bit\n", + dev->name); + saa_clearl(SAA7134_IRQ2, SAA7134_IRQ2_INTE_GPIO18_P); + saa_clearl(SAA7134_IRQ2, SAA7134_IRQ2_INTE_GPIO18_N); + } else { + /* disable all irqs */ + pr_warn("%s/irq: looping -- clearing all enable bits\n", + dev->name); + saa_writel(SAA7134_IRQ1,0); + saa_writel(SAA7134_IRQ2,0); + } + } + + out: + return IRQ_RETVAL(handled); +} + +/* ------------------------------------------------------------------ */ + +/* early init (no i2c, no irq) */ + +static int saa7134_hw_enable1(struct saa7134_dev *dev) +{ + /* RAM FIFO config */ + saa_writel(SAA7134_FIFO_SIZE, 0x08070503); + saa_writel(SAA7134_THRESHOULD, 0x02020202); + + /* enable audio + video processing */ + saa_writel(SAA7134_MAIN_CTRL, + SAA7134_MAIN_CTRL_VPLLE | + SAA7134_MAIN_CTRL_APLLE | + SAA7134_MAIN_CTRL_EXOSC | + SAA7134_MAIN_CTRL_EVFE1 | + SAA7134_MAIN_CTRL_EVFE2 | + SAA7134_MAIN_CTRL_ESFE | + SAA7134_MAIN_CTRL_EBDAC); + + /* + * Initialize OSS _after_ enabling audio clock PLL and audio processing. + * OSS initialization writes to registers via the audio DSP; these + * writes will fail unless the audio clock has been started. At worst, + * audio will not work. + */ + + /* enable peripheral devices */ + saa_writeb(SAA7134_SPECIAL_MODE, 0x01); + + /* set vertical line numbering start (vbi needs this) */ + saa_writeb(SAA7134_SOURCE_TIMING2, 0x20); + + return 0; +} + +static int saa7134_hwinit1(struct saa7134_dev *dev) +{ + core_dbg("hwinit1\n"); + + saa_writel(SAA7134_IRQ1, 0); + saa_writel(SAA7134_IRQ2, 0); + + /* Clear any stale IRQ reports */ + saa_writel(SAA7134_IRQ_REPORT, saa_readl(SAA7134_IRQ_REPORT)); + + mutex_init(&dev->lock); + spin_lock_init(&dev->slock); + + saa7134_track_gpio(dev,"pre-init"); + saa7134_video_init1(dev); + saa7134_vbi_init1(dev); + if (card_has_mpeg(dev)) + saa7134_ts_init1(dev); + saa7134_input_init1(dev); + + saa7134_hw_enable1(dev); + + return 0; +} + +/* late init (with i2c + irq) */ +static int saa7134_hw_enable2(struct saa7134_dev *dev) +{ + + unsigned int irq2_mask; + + /* enable IRQ's */ + irq2_mask = + SAA7134_IRQ2_INTE_DEC3 | + SAA7134_IRQ2_INTE_DEC2 | + SAA7134_IRQ2_INTE_DEC1 | + SAA7134_IRQ2_INTE_DEC0 | + SAA7134_IRQ2_INTE_PE | + SAA7134_IRQ2_INTE_AR; + + if (dev->has_remote == SAA7134_REMOTE_GPIO && dev->remote) { + if (dev->remote->mask_keydown & 0x10000) + irq2_mask |= SAA7134_IRQ2_INTE_GPIO16_N; + else { /* Allow enabling both IRQ edge triggers */ + if (dev->remote->mask_keydown & 0x40000) + irq2_mask |= SAA7134_IRQ2_INTE_GPIO18_P; + if (dev->remote->mask_keyup & 0x40000) + irq2_mask |= SAA7134_IRQ2_INTE_GPIO18_N; + } + } + + if (dev->has_remote == SAA7134_REMOTE_I2C) { + request_module("ir-kbd-i2c"); + } + + saa_writel(SAA7134_IRQ1, 0); + saa_writel(SAA7134_IRQ2, irq2_mask); + + return 0; +} + +static int saa7134_hwinit2(struct saa7134_dev *dev) +{ + + core_dbg("hwinit2\n"); + + saa7134_video_init2(dev); + saa7134_tvaudio_init2(dev); + + saa7134_hw_enable2(dev); + + return 0; +} + + +/* shutdown */ +static int saa7134_hwfini(struct saa7134_dev *dev) +{ + core_dbg("hwfini\n"); + + if (card_has_mpeg(dev)) + saa7134_ts_fini(dev); + saa7134_input_fini(dev); + saa7134_vbi_fini(dev); + saa7134_tvaudio_fini(dev); + saa7134_video_fini(dev); + return 0; +} + +static void must_configure_manually(int has_eeprom) +{ + unsigned int i,p; + + if (!has_eeprom) + pr_warn("saa7134: \n" + "saa7134: Congratulations! Your TV card vendor saved a few\n" + "saa7134: cents for a eeprom, thus your pci board has no\n" + "saa7134: subsystem ID and I can't identify it automatically\n" + "saa7134: \n" + "saa7134: I feel better now. Ok, here are the good news:\n" + "saa7134: You can use the card= insmod option to specify\n" + "saa7134: which board do you have. The list:\n"); + else + pr_warn("saa7134: Board is currently unknown. You might try to use the card=\n" + "saa7134: insmod option to specify which board do you have, but this is\n" + "saa7134: somewhat risky, as might damage your card. It is better to ask\n" + "saa7134: for support at linux-media@vger.kernel.org.\n" + "saa7134: The supported cards are:\n"); + + for (i = 0; i < saa7134_bcount; i++) { + pr_warn("saa7134: card=%d -> %-40.40s", + i,saa7134_boards[i].name); + for (p = 0; saa7134_pci_tbl[p].driver_data; p++) { + if (saa7134_pci_tbl[p].driver_data != i) + continue; + pr_cont(" %04x:%04x", + saa7134_pci_tbl[p].subvendor, + saa7134_pci_tbl[p].subdevice); + } + pr_cont("\n"); + } +} + +static void saa7134_unregister_media_device(struct saa7134_dev *dev) +{ + +#ifdef CONFIG_MEDIA_CONTROLLER + if (!dev->media_dev) + return; + media_device_unregister(dev->media_dev); + media_device_cleanup(dev->media_dev); + kfree(dev->media_dev); + dev->media_dev = NULL; +#endif +} + +static void saa7134_media_release(struct saa7134_dev *dev) +{ +#ifdef CONFIG_MEDIA_CONTROLLER + int i; + + for (i = 0; i < SAA7134_INPUT_MAX + 1; i++) + media_device_unregister_entity(&dev->input_ent[i]); +#endif +} + +#if defined(CONFIG_MEDIA_CONTROLLER) +static void saa7134_create_entities(struct saa7134_dev *dev) +{ + int ret, i; + struct media_entity *entity; + struct media_entity *decoder = NULL; + + /* Check if it is using an external analog TV demod */ + media_device_for_each_entity(entity, dev->media_dev) { + if (entity->function == MEDIA_ENT_F_ATV_DECODER) { + decoder = entity; + break; + } + } + + /* + * saa713x is not using an external ATV demod. + * Register the internal one + */ + if (!decoder) { + dev->demod.name = "saa713x"; + dev->demod_pad[SAA7134_PAD_IF_INPUT].flags = MEDIA_PAD_FL_SINK; + dev->demod_pad[SAA7134_PAD_IF_INPUT].sig_type = PAD_SIGNAL_ANALOG; + dev->demod_pad[SAA7134_PAD_VID_OUT].flags = MEDIA_PAD_FL_SOURCE; + dev->demod_pad[SAA7134_PAD_VID_OUT].sig_type = PAD_SIGNAL_DV; + dev->demod.function = MEDIA_ENT_F_ATV_DECODER; + + ret = media_entity_pads_init(&dev->demod, SAA7134_NUM_PADS, + dev->demod_pad); + if (ret < 0) + pr_err("failed to initialize demod pad!\n"); + + ret = media_device_register_entity(dev->media_dev, &dev->demod); + if (ret < 0) + pr_err("failed to register demod entity!\n"); + + dev->decoder = &dev->demod; + } else { + dev->decoder = decoder; + } + + /* Initialize Video, VBI and Radio pads */ + dev->video_pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&dev->video_dev->entity, 1, + &dev->video_pad); + if (ret < 0) + pr_err("failed to initialize video media entity!\n"); + + dev->vbi_pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&dev->vbi_dev->entity, 1, + &dev->vbi_pad); + if (ret < 0) + pr_err("failed to initialize vbi media entity!\n"); + + /* Create entities for each input connector */ + for (i = 0; i < SAA7134_INPUT_MAX; i++) { + struct media_entity *ent = &dev->input_ent[i]; + struct saa7134_input *in = &card_in(dev, i); + + if (in->type == SAA7134_NO_INPUT) + break; + + /* This input uses the S-Video connector */ + if (in->type == SAA7134_INPUT_COMPOSITE_OVER_SVIDEO) + continue; + + ent->name = saa7134_input_name[in->type]; + ent->flags = MEDIA_ENT_FL_CONNECTOR; + dev->input_pad[i].flags = MEDIA_PAD_FL_SOURCE; + + switch (in->type) { + case SAA7134_INPUT_COMPOSITE: + case SAA7134_INPUT_COMPOSITE0: + case SAA7134_INPUT_COMPOSITE1: + case SAA7134_INPUT_COMPOSITE2: + case SAA7134_INPUT_COMPOSITE3: + case SAA7134_INPUT_COMPOSITE4: + ent->function = MEDIA_ENT_F_CONN_COMPOSITE; + break; + case SAA7134_INPUT_SVIDEO: + case SAA7134_INPUT_SVIDEO0: + case SAA7134_INPUT_SVIDEO1: + ent->function = MEDIA_ENT_F_CONN_SVIDEO; + break; + default: + /* + * SAA7134_INPUT_TV and SAA7134_INPUT_TV_MONO. + * + * Please notice that neither SAA7134_INPUT_MUTE or + * SAA7134_INPUT_RADIO are defined at + * saa7134_board.input. + */ + ent->function = MEDIA_ENT_F_CONN_RF; + break; + } + + ret = media_entity_pads_init(ent, 1, &dev->input_pad[i]); + if (ret < 0) + pr_err("failed to initialize input pad[%d]!\n", i); + + ret = media_device_register_entity(dev->media_dev, ent); + if (ret < 0) + pr_err("failed to register input entity %d!\n", i); + } + + /* Create input for Radio RF connector */ + if (card_has_radio(dev)) { + struct saa7134_input *in = &saa7134_boards[dev->board].radio; + struct media_entity *ent = &dev->input_ent[i]; + + ent->name = saa7134_input_name[in->type]; + ent->flags = MEDIA_ENT_FL_CONNECTOR; + dev->input_pad[i].flags = MEDIA_PAD_FL_SOURCE; + ent->function = MEDIA_ENT_F_CONN_RF; + + ret = media_entity_pads_init(ent, 1, &dev->input_pad[i]); + if (ret < 0) + pr_err("failed to initialize input pad[%d]!\n", i); + + ret = media_device_register_entity(dev->media_dev, ent); + if (ret < 0) + pr_err("failed to register input entity %d!\n", i); + } +} +#endif + +static struct video_device *vdev_init(struct saa7134_dev *dev, + struct video_device *template, + char *type) +{ + struct video_device *vfd; + + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + *vfd = *template; + vfd->v4l2_dev = &dev->v4l2_dev; + vfd->release = video_device_release; + snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)", + dev->name, type, saa7134_boards[dev->board].name); + video_set_drvdata(vfd, dev); + return vfd; +} + +static void saa7134_unregister_video(struct saa7134_dev *dev) +{ + saa7134_media_release(dev); + + if (dev->video_dev) { + if (video_is_registered(dev->video_dev)) + vb2_video_unregister_device(dev->video_dev); + else + video_device_release(dev->video_dev); + dev->video_dev = NULL; + } + if (dev->vbi_dev) { + if (video_is_registered(dev->vbi_dev)) + vb2_video_unregister_device(dev->vbi_dev); + else + video_device_release(dev->vbi_dev); + dev->vbi_dev = NULL; + } + if (dev->radio_dev) { + if (video_is_registered(dev->radio_dev)) + video_unregister_device(dev->radio_dev); + else + video_device_release(dev->radio_dev); + dev->radio_dev = NULL; + } +} + +static void mpeg_ops_attach(struct saa7134_mpeg_ops *ops, + struct saa7134_dev *dev) +{ + int err; + + if (NULL != dev->mops) + return; + if (saa7134_boards[dev->board].mpeg != ops->type) + return; + err = ops->init(dev); + if (0 != err) + return; + dev->mops = ops; +} + +static void mpeg_ops_detach(struct saa7134_mpeg_ops *ops, + struct saa7134_dev *dev) +{ + if (NULL == dev->mops) + return; + if (dev->mops != ops) + return; + dev->mops->fini(dev); + dev->mops = NULL; +} + +static int saa7134_initdev(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct saa7134_dev *dev; + struct saa7134_mpeg_ops *mops; + int err; + + if (saa7134_devcount == SAA7134_MAXBOARDS) + return -ENOMEM; + + dev = kzalloc_obj(*dev); + if (NULL == dev) + return -ENOMEM; + + dev->nr = saa7134_devcount; + sprintf(dev->name, "saa%x[%d]", pci_dev->device, dev->nr); + +#ifdef CONFIG_MEDIA_CONTROLLER + dev->media_dev = kzalloc_obj(*dev->media_dev); + if (!dev->media_dev) { + err = -ENOMEM; + goto err_free_dev; + } + media_device_pci_init(dev->media_dev, pci_dev, dev->name); + dev->v4l2_dev.mdev = dev->media_dev; +#endif + + err = v4l2_device_register(&pci_dev->dev, &dev->v4l2_dev); + if (err) + goto err_free_dev; + + /* pci init */ + dev->pci = pci_dev; + if (pci_enable_device(pci_dev)) { + err = -EIO; + goto err_v4l2_unregister; + } + + /* pci quirks */ + if (pci_pci_problems) { + if (pci_pci_problems & PCIPCI_TRITON) + pr_info("%s: quirk: PCIPCI_TRITON\n", dev->name); + if (pci_pci_problems & PCIPCI_NATOMA) + pr_info("%s: quirk: PCIPCI_NATOMA\n", dev->name); + if (pci_pci_problems & PCIPCI_VIAETBF) + pr_info("%s: quirk: PCIPCI_VIAETBF\n", dev->name); + if (pci_pci_problems & PCIPCI_VSFX) + pr_info("%s: quirk: PCIPCI_VSFX\n", dev->name); +#ifdef PCIPCI_ALIMAGIK + if (pci_pci_problems & PCIPCI_ALIMAGIK) { + pr_info("%s: quirk: PCIPCI_ALIMAGIK -- latency fixup\n", + dev->name); + latency = 0x0A; + } +#endif + } + if (UNSET != latency) { + pr_info("%s: setting pci latency timer to %d\n", + dev->name,latency); + pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, latency); + } + + /* print pci info */ + dev->pci_rev = pci_dev->revision; + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat); + pr_info("%s: found at %s, rev: %d, irq: %d, latency: %d, mmio: 0x%llx\n", + dev->name, pci_name(pci_dev), dev->pci_rev, pci_dev->irq, + dev->pci_lat, + (unsigned long long)pci_resource_start(pci_dev, 0)); + pci_set_master(pci_dev); + err = dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(32)); + if (err) { + pr_warn("%s: Oops: no 32bit PCI DMA ???\n", dev->name); + goto err_v4l2_unregister; + } + + /* board config */ + dev->board = pci_id->driver_data; + if ((unsigned)card[dev->nr] < saa7134_bcount) + dev->board = card[dev->nr]; + if (SAA7134_BOARD_UNKNOWN == dev->board) + must_configure_manually(0); + else if (SAA7134_BOARD_NOAUTO == dev->board) { + must_configure_manually(1); + dev->board = SAA7134_BOARD_UNKNOWN; + } + dev->autodetected = card[dev->nr] != dev->board; + dev->tuner_type = saa7134_boards[dev->board].tuner_type; + dev->tuner_addr = saa7134_boards[dev->board].tuner_addr; + dev->radio_type = saa7134_boards[dev->board].radio_type; + dev->radio_addr = saa7134_boards[dev->board].radio_addr; + dev->tda9887_conf = saa7134_boards[dev->board].tda9887_conf; + if (UNSET != tuner[dev->nr]) + dev->tuner_type = tuner[dev->nr]; + pr_info("%s: subsystem: %04x:%04x, board: %s [card=%d,%s]\n", + dev->name,pci_dev->subsystem_vendor, + pci_dev->subsystem_device,saa7134_boards[dev->board].name, + dev->board, dev->autodetected ? + "autodetected" : "insmod option"); + + /* get mmio */ + if (!request_mem_region(pci_resource_start(pci_dev,0), + pci_resource_len(pci_dev,0), + dev->name)) { + err = -EBUSY; + pr_err("%s: can't get MMIO memory @ 0x%llx\n", + dev->name,(unsigned long long)pci_resource_start(pci_dev,0)); + goto err_v4l2_unregister; + } + dev->lmmio = ioremap(pci_resource_start(pci_dev, 0), + pci_resource_len(pci_dev, 0)); + dev->bmmio = (__u8 __iomem *)dev->lmmio; + if (NULL == dev->lmmio) { + err = -EIO; + pr_err("%s: can't ioremap() MMIO memory\n", + dev->name); + goto err_release_mem_reg; + } + + /* initialize hardware #1 */ + saa7134_board_init1(dev); + saa7134_hwinit1(dev); + + /* get irq */ + err = request_irq(pci_dev->irq, saa7134_irq, + IRQF_SHARED, dev->name, dev); + if (err < 0) { + pr_err("%s: can't get IRQ %d\n", + dev->name,pci_dev->irq); + goto err_iounmap; + } + + /* wait a bit, register i2c bus */ + msleep(100); + saa7134_i2c_register(dev); + saa7134_board_init2(dev); + + saa7134_hwinit2(dev); + + /* load i2c helpers */ + if (card_is_empress(dev)) { + dev->empress_sd = + v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap, + "saa6752hs", + saa7134_boards[dev->board].empress_addr, NULL); + + if (dev->empress_sd) + dev->empress_sd->grp_id = GRP_EMPRESS; + } + + if (saa7134_boards[dev->board].rds_addr) { + struct v4l2_subdev *sd; + + sd = v4l2_i2c_new_subdev(&dev->v4l2_dev, + &dev->i2c_adap, "saa6588", + 0, I2C_ADDRS(saa7134_boards[dev->board].rds_addr)); + if (sd) { + pr_info("%s: found RDS decoder\n", dev->name); + dev->has_rds = 1; + } + } + + mutex_lock(&saa7134_devlist_lock); + list_for_each_entry(mops, &mops_list, next) + mpeg_ops_attach(mops, dev); + list_add_tail(&dev->devlist, &saa7134_devlist); + mutex_unlock(&saa7134_devlist_lock); + + /* check for signal */ + saa7134_irq_video_signalchange(dev); + + if (TUNER_ABSENT != dev->tuner_type) + saa_call_all(dev, core, s_power, 0); + + /* register v4l devices */ + dev->video_dev = vdev_init(dev,&saa7134_video_template,"video"); + dev->video_dev->ctrl_handler = &dev->ctrl_handler; + dev->video_dev->lock = &dev->lock; + dev->video_dev->queue = &dev->video_vbq; + dev->video_dev->device_caps = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | + V4L2_CAP_VIDEO_CAPTURE; + if (dev->tuner_type != TUNER_ABSENT && dev->tuner_type != UNSET) + dev->video_dev->device_caps |= V4L2_CAP_TUNER; + + err = video_register_device(dev->video_dev,VFL_TYPE_VIDEO, + video_nr[dev->nr]); + if (err < 0) { + pr_info("%s: can't register video device\n", + dev->name); + goto err_unregister_video; + } + pr_info("%s: registered device %s [v4l2]\n", + dev->name, video_device_node_name(dev->video_dev)); + + dev->vbi_dev = vdev_init(dev, &saa7134_video_template, "vbi"); + dev->vbi_dev->ctrl_handler = &dev->ctrl_handler; + dev->vbi_dev->lock = &dev->lock; + dev->vbi_dev->queue = &dev->vbi_vbq; + dev->vbi_dev->device_caps = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | + V4L2_CAP_VBI_CAPTURE; + if (dev->tuner_type != TUNER_ABSENT && dev->tuner_type != UNSET) + dev->vbi_dev->device_caps |= V4L2_CAP_TUNER; + + err = video_register_device(dev->vbi_dev,VFL_TYPE_VBI, + vbi_nr[dev->nr]); + if (err < 0) + goto err_unregister_video; + pr_info("%s: registered device %s\n", + dev->name, video_device_node_name(dev->vbi_dev)); + + if (card_has_radio(dev)) { + dev->radio_dev = vdev_init(dev,&saa7134_radio_template,"radio"); + dev->radio_dev->ctrl_handler = &dev->radio_ctrl_handler; + dev->radio_dev->lock = &dev->lock; + dev->radio_dev->device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER; + if (dev->has_rds) + dev->radio_dev->device_caps |= V4L2_CAP_RDS_CAPTURE; + err = video_register_device(dev->radio_dev,VFL_TYPE_RADIO, + radio_nr[dev->nr]); + if (err < 0) + goto err_unregister_video; + pr_info("%s: registered device %s\n", + dev->name, video_device_node_name(dev->radio_dev)); + } + +#ifdef CONFIG_MEDIA_CONTROLLER + saa7134_create_entities(dev); + + err = v4l2_mc_create_media_graph(dev->media_dev); + if (err) { + pr_err("failed to create media graph\n"); + goto err_unregister_video; + } +#endif + /* everything worked */ + saa7134_devcount++; + + if (saa7134_dmasound_init && !dev->dmasound.priv_data) + saa7134_dmasound_init(dev); + + request_submodules(dev); + + /* + * Do it at the end, to reduce dynamic configuration changes during + * the device init. Yet, as request_modules() can be async, the + * topology will likely change after load the saa7134 subdrivers. + */ +#ifdef CONFIG_MEDIA_CONTROLLER + err = media_device_register(dev->media_dev); + if (err) { + media_device_cleanup(dev->media_dev); + goto err_unregister_video; + } +#endif + + return 0; + +err_unregister_video: + saa7134_unregister_video(dev); + list_del(&dev->devlist); + saa7134_i2c_unregister(dev); + free_irq(pci_dev->irq, dev); +err_iounmap: + saa7134_hwfini(dev); + iounmap(dev->lmmio); +err_release_mem_reg: + release_mem_region(pci_resource_start(pci_dev,0), + pci_resource_len(pci_dev,0)); +err_v4l2_unregister: + v4l2_device_unregister(&dev->v4l2_dev); +err_free_dev: +#ifdef CONFIG_MEDIA_CONTROLLER + kfree(dev->media_dev); +#endif + kfree(dev); + return err; +} + +static void saa7134_finidev(struct pci_dev *pci_dev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct saa7134_dev *dev = container_of(v4l2_dev, struct saa7134_dev, v4l2_dev); + struct saa7134_mpeg_ops *mops; + + flush_request_submodules(dev); + + /* Release DMA sound modules if present */ + if (saa7134_dmasound_exit && dev->dmasound.priv_data) { + saa7134_dmasound_exit(dev); + } + + /* debugging ... */ + if (irq_debug) { + u32 report = saa_readl(SAA7134_IRQ_REPORT); + u32 status = saa_readl(SAA7134_IRQ_STATUS); + print_irqstatus(dev,42,report,status); + } + + /* disable peripheral devices */ + saa_writeb(SAA7134_SPECIAL_MODE,0); + + /* shutdown hardware */ + saa_writel(SAA7134_IRQ1,0); + saa_writel(SAA7134_IRQ2,0); + saa_writel(SAA7134_MAIN_CTRL,0); + + /* shutdown subsystems */ + saa7134_hwfini(dev); + + /* unregister */ + mutex_lock(&saa7134_devlist_lock); + list_del(&dev->devlist); + list_for_each_entry(mops, &mops_list, next) + mpeg_ops_detach(mops, dev); + mutex_unlock(&saa7134_devlist_lock); + saa7134_devcount--; + + saa7134_i2c_unregister(dev); + saa7134_unregister_video(dev); + + + /* the DMA sound modules should be unloaded before reaching + this, but just in case they are still present... */ + if (dev->dmasound.priv_data != NULL) { + free_irq(pci_dev->irq, &dev->dmasound); + dev->dmasound.priv_data = NULL; + } + + + /* release resources */ + free_irq(pci_dev->irq, dev); + iounmap(dev->lmmio); + release_mem_region(pci_resource_start(pci_dev,0), + pci_resource_len(pci_dev,0)); + + v4l2_device_unregister(&dev->v4l2_dev); + + saa7134_unregister_media_device(dev); + + /* free memory */ + kfree(dev); +} + +/* resends a current buffer in queue after resume */ +static int __maybe_unused saa7134_buffer_requeue(struct saa7134_dev *dev, + struct saa7134_dmaqueue *q) +{ + struct saa7134_buf *buf, *next; + + assert_spin_locked(&dev->slock); + + buf = q->curr; + next = buf; + core_dbg("buffer_requeue\n"); + + if (!buf) + return 0; + + core_dbg("buffer_requeue : resending active buffer\n"); + + if (!list_empty(&q->queue)) + next = list_entry(q->queue.next, struct saa7134_buf, + entry); + buf->activate(dev, buf, next); + + return 0; +} + +static int __maybe_unused saa7134_suspend(struct device *dev_d) +{ + struct pci_dev *pci_dev = to_pci_dev(dev_d); + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct saa7134_dev *dev = container_of(v4l2_dev, struct saa7134_dev, v4l2_dev); + + /* Disable interrupts, DMA, and rest of the chip*/ + saa_writel(SAA7134_IRQ1, 0); + saa_writel(SAA7134_IRQ2, 0); + saa_writel(SAA7134_MAIN_CTRL, 0); + + dev->insuspend = 1; + synchronize_irq(pci_dev->irq); + + /* ACK interrupts once more, just in case, + since the IRQ handler won't ack them anymore*/ + + saa_writel(SAA7134_IRQ_REPORT, saa_readl(SAA7134_IRQ_REPORT)); + + /* Disable timeout timers - if we have active buffers, we will + fill them on resume*/ + + timer_delete(&dev->video_q.timeout); + timer_delete(&dev->vbi_q.timeout); + timer_delete(&dev->ts_q.timeout); + + if (dev->remote && dev->remote->dev->users) + saa7134_ir_close(dev->remote->dev); + + return 0; +} + +static int __maybe_unused saa7134_resume(struct device *dev_d) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev_d); + struct saa7134_dev *dev = container_of(v4l2_dev, struct saa7134_dev, v4l2_dev); + unsigned long flags; + + /* Do things that are done in saa7134_initdev , + except of initializing memory structures.*/ + + saa7134_board_init1(dev); + + /* saa7134_hwinit1 */ + if (saa7134_boards[dev->board].video_out) + saa7134_videoport_init(dev); + if (card_has_mpeg(dev)) + saa7134_ts_init_hw(dev); + if (dev->remote && dev->remote->dev->users) + saa7134_ir_open(dev->remote->dev); + saa7134_hw_enable1(dev); + + msleep(100); + + saa7134_board_init2(dev); + + /*saa7134_hwinit2*/ + saa7134_set_tvnorm_hw(dev); + saa7134_tvaudio_setmute(dev); + saa7134_tvaudio_setvolume(dev, dev->ctl_volume); + saa7134_tvaudio_init(dev); + saa7134_enable_i2s(dev); + saa7134_hw_enable2(dev); + + saa7134_irq_video_signalchange(dev); + + /*resume unfinished buffer(s)*/ + spin_lock_irqsave(&dev->slock, flags); + saa7134_buffer_requeue(dev, &dev->video_q); + saa7134_buffer_requeue(dev, &dev->vbi_q); + saa7134_buffer_requeue(dev, &dev->ts_q); + + /* FIXME: Disable DMA audio sound - temporary till proper support + is implemented*/ + + dev->dmasound.dma_running = 0; + + /* start DMA now*/ + dev->insuspend = 0; + smp_wmb(); + saa7134_set_dmabits(dev); + spin_unlock_irqrestore(&dev->slock, flags); + + return 0; +} + +/* ----------------------------------------------------------- */ + +int saa7134_ts_register(struct saa7134_mpeg_ops *ops) +{ + struct saa7134_dev *dev; + + mutex_lock(&saa7134_devlist_lock); + list_for_each_entry(dev, &saa7134_devlist, devlist) + mpeg_ops_attach(ops, dev); + list_add_tail(&ops->next,&mops_list); + mutex_unlock(&saa7134_devlist_lock); + return 0; +} + +void saa7134_ts_unregister(struct saa7134_mpeg_ops *ops) +{ + struct saa7134_dev *dev; + + mutex_lock(&saa7134_devlist_lock); + list_del(&ops->next); + list_for_each_entry(dev, &saa7134_devlist, devlist) + mpeg_ops_detach(ops, dev); + mutex_unlock(&saa7134_devlist_lock); +} + +EXPORT_SYMBOL(saa7134_ts_register); +EXPORT_SYMBOL(saa7134_ts_unregister); + +/* ----------------------------------------------------------- */ + +static SIMPLE_DEV_PM_OPS(saa7134_pm_ops, saa7134_suspend, saa7134_resume); + +static struct pci_driver saa7134_pci_driver = { + .name = "saa7134", + .id_table = saa7134_pci_tbl, + .probe = saa7134_initdev, + .remove = saa7134_finidev, + .driver.pm = &saa7134_pm_ops, +}; + +static int __init saa7134_init(void) +{ + pr_info("saa7130/34: v4l2 driver version %s loaded\n", + SAA7134_VERSION); + return pci_register_driver(&saa7134_pci_driver); +} + +static void __exit saa7134_fini(void) +{ + pci_unregister_driver(&saa7134_pci_driver); +} + +module_init(saa7134_init); +module_exit(saa7134_fini); + +/* ----------------------------------------------------------- */ + +EXPORT_SYMBOL(saa7134_set_gpio); +EXPORT_SYMBOL(saa7134_boards); + +/* ----------------- for the DMA sound modules --------------- */ + +EXPORT_SYMBOL(saa7134_dmasound_init); +EXPORT_SYMBOL(saa7134_dmasound_exit); +EXPORT_SYMBOL(saa7134_pgtable_free); +EXPORT_SYMBOL(saa7134_pgtable_build); +EXPORT_SYMBOL(saa7134_pgtable_alloc); +EXPORT_SYMBOL(saa7134_set_dmabits); diff --git a/drivers/media/pci/saa7134/saa7134-dvb.c b/drivers/media/pci/saa7134/saa7134-dvb.c new file mode 100644 index 000000000..6de8a0231 --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134-dvb.c @@ -0,0 +1,2009 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * (c) 2004 Gerd Knorr [SuSE Labs] + * + * Extended 3 / 2005 by Hartmut Hackmann to support various + * cards with the tda10046 DVB-T channel decoder + */ + +#include "saa7134.h" +#include "saa7134-reg.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "dvb-pll.h" +#include + +#include "mt352.h" +#include "mt352_priv.h" /* FIXME */ +#include "tda1004x.h" +#include "nxt200x.h" +#include "xc2028.h" +#include "xc5000.h" + +#include "tda10086.h" +#include "tda826x.h" +#include "tda827x.h" +#include "isl6421.h" +#include "isl6405.h" +#include "lnbp21.h" +#include "tuner-simple.h" +#include "tda10048.h" +#include "tda18271.h" +#include "lgdt3305.h" +#include "tda8290.h" +#include "mb86a20s.h" +#include "lgs8gxx.h" + +#include "zl10353.h" +#include "qt1010.h" + +#include "zl10036.h" +#include "zl10039.h" +#include "mt312.h" +#include "s5h1411.h" + +MODULE_AUTHOR("Gerd Knorr [SuSE Labs]"); +MODULE_DESCRIPTION("DVB/ATSC Support for saa7134 based TV cards"); +MODULE_LICENSE("GPL"); + +static unsigned int antenna_pwr; + +module_param(antenna_pwr, int, 0444); +MODULE_PARM_DESC(antenna_pwr,"enable antenna power (Pinnacle 300i)"); + +static int use_frontend; +module_param(use_frontend, int, 0644); +MODULE_PARM_DESC(use_frontend,"for cards with multiple frontends (0: terrestrial, 1: satellite)"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +/* ------------------------------------------------------------------ + * mt352 based DVB-T cards + */ + +static int pinnacle_antenna_pwr(struct saa7134_dev *dev, int on) +{ + u32 ok; + + if (!on) { + saa_setl(SAA7134_GPIO_GPMODE0 >> 2, (1 << 26)); + saa_clearl(SAA7134_GPIO_GPSTATUS0 >> 2, (1 << 26)); + return 0; + } + + saa_setl(SAA7134_GPIO_GPMODE0 >> 2, (1 << 26)); + saa_setl(SAA7134_GPIO_GPSTATUS0 >> 2, (1 << 26)); + udelay(10); + + saa_setl(SAA7134_GPIO_GPMODE0 >> 2, (1 << 28)); + saa_clearl(SAA7134_GPIO_GPSTATUS0 >> 2, (1 << 28)); + udelay(10); + saa_setl(SAA7134_GPIO_GPSTATUS0 >> 2, (1 << 28)); + udelay(10); + ok = saa_readl(SAA7134_GPIO_GPSTATUS0) & (1 << 27); + pr_debug("%s %s\n", __func__, ok ? "on" : "off"); + + if (!ok) + saa_clearl(SAA7134_GPIO_GPSTATUS0 >> 2, (1 << 26)); + return ok; +} + +static int mt352_pinnacle_init(struct dvb_frontend* fe) +{ + static u8 clock_config [] = { CLOCK_CTL, 0x3d, 0x28 }; + static u8 reset [] = { RESET, 0x80 }; + static u8 adc_ctl_1_cfg [] = { ADC_CTL_1, 0x40 }; + static u8 agc_cfg [] = { AGC_TARGET, 0x28, 0xa0 }; + static u8 capt_range_cfg[] = { CAPT_RANGE, 0x31 }; + static u8 fsm_ctl_cfg[] = { 0x7b, 0x04 }; + static u8 gpp_ctl_cfg [] = { GPP_CTL, 0x0f }; + static u8 scan_ctl_cfg [] = { SCAN_CTL, 0x0d }; + static u8 irq_cfg [] = { INTERRUPT_EN_0, 0x00, 0x00, 0x00, 0x00 }; + + pr_debug("%s called\n", __func__); + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(200); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + mt352_write(fe, gpp_ctl_cfg, sizeof(gpp_ctl_cfg)); + + mt352_write(fe, fsm_ctl_cfg, sizeof(fsm_ctl_cfg)); + mt352_write(fe, scan_ctl_cfg, sizeof(scan_ctl_cfg)); + mt352_write(fe, irq_cfg, sizeof(irq_cfg)); + + return 0; +} + +static int mt352_aver777_init(struct dvb_frontend* fe) +{ + static u8 clock_config [] = { CLOCK_CTL, 0x38, 0x2d }; + static u8 reset [] = { RESET, 0x80 }; + static u8 adc_ctl_1_cfg [] = { ADC_CTL_1, 0x40 }; + static u8 agc_cfg [] = { AGC_TARGET, 0x28, 0xa0 }; + static u8 capt_range_cfg[] = { CAPT_RANGE, 0x33 }; + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(200); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + + return 0; +} + +static int mt352_avermedia_xc3028_init(struct dvb_frontend *fe) +{ + static u8 clock_config [] = { CLOCK_CTL, 0x38, 0x2d }; + static u8 reset [] = { RESET, 0x80 }; + static u8 adc_ctl_1_cfg [] = { ADC_CTL_1, 0x40 }; + static u8 agc_cfg [] = { AGC_TARGET, 0xe }; + static u8 capt_range_cfg[] = { CAPT_RANGE, 0x33 }; + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(200); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + return 0; +} + +static int mt352_pinnacle_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u8 off[] = { 0x00, 0xf1}; + u8 on[] = { 0x00, 0x71}; + struct i2c_msg msg = {.addr=0x43, .flags=0, .buf=off, .len = sizeof(off)}; + + struct saa7134_dev *dev = fe->dvb->priv; + struct v4l2_frequency f; + + /* set frequency (mt2050) */ + f.tuner = 0; + f.type = V4L2_TUNER_DIGITAL_TV; + f.frequency = c->frequency / 1000 * 16 / 1000; + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + i2c_transfer(&dev->i2c_adap, &msg, 1); + saa_call_all(dev, tuner, s_frequency, &f); + msg.buf = on; + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + i2c_transfer(&dev->i2c_adap, &msg, 1); + + pinnacle_antenna_pwr(dev, antenna_pwr); + + /* mt352 setup */ + return mt352_pinnacle_init(fe); +} + +static struct mt352_config pinnacle_300i = { + .demod_address = 0x3c >> 1, + .adc_clock = 20333, + .if2 = 36150, + .no_tuner = 1, + .demod_init = mt352_pinnacle_init, +}; + +static struct mt352_config avermedia_777 = { + .demod_address = 0xf, + .demod_init = mt352_aver777_init, +}; + +static struct mt352_config avermedia_xc3028_mt352_dev = { + .demod_address = (0x1e >> 1), + .no_tuner = 1, + .demod_init = mt352_avermedia_xc3028_init, +}; + +static struct tda18271_std_map mb86a20s_tda18271_std_map = { + .dvbt_6 = { .if_freq = 3300, .agc_mode = 3, .std = 4, + .if_lvl = 7, .rfagc_top = 0x37, }, +}; + +static struct tda18271_config kworld_tda18271_config = { + .std_map = &mb86a20s_tda18271_std_map, + .gate = TDA18271_GATE_DIGITAL, + .config = 3, /* Use tuner callback for AGC */ + +}; + +static const struct mb86a20s_config kworld_mb86a20s_config = { + .demod_address = 0x10, +}; + +static int kworld_sbtvd_gate_ctrl(struct dvb_frontend* fe, int enable) +{ + struct saa7134_dev *dev = fe->dvb->priv; + + unsigned char initmsg[] = {0x45, 0x97}; + unsigned char msg_enable[] = {0x45, 0xc1}; + unsigned char msg_disable[] = {0x45, 0x81}; + struct i2c_msg msg = {.addr = 0x4b, .flags = 0, .buf = initmsg, .len = 2}; + + if (i2c_transfer(&dev->i2c_adap, &msg, 1) != 1) { + pr_warn("could not access the I2C gate\n"); + return -EIO; + } + if (enable) + msg.buf = msg_enable; + else + msg.buf = msg_disable; + if (i2c_transfer(&dev->i2c_adap, &msg, 1) != 1) { + pr_warn("could not access the I2C gate\n"); + return -EIO; + } + msleep(20); + return 0; +} + +/* ================================================================== + * tda1004x based DVB-T cards, helper functions + */ + +static int philips_tda1004x_request_firmware(struct dvb_frontend *fe, + const struct firmware **fw, char *name) +{ + struct saa7134_dev *dev = fe->dvb->priv; + return request_firmware(fw, name, &dev->pci->dev); +} + +/* ------------------------------------------------------------------ + * these tuners are tu1216, td1316(a) + */ + +static int philips_tda6651_pll_set(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + u8 addr = state->config->tuner_address; + u8 tuner_buf[4]; + struct i2c_msg tuner_msg = {.addr = addr,.flags = 0,.buf = tuner_buf,.len = + sizeof(tuner_buf) }; + int tuner_frequency = 0; + u8 band, cp, filter; + + /* determine charge pump */ + tuner_frequency = c->frequency + 36166000; + if (tuner_frequency < 87000000) + return -EINVAL; + else if (tuner_frequency < 130000000) + cp = 3; + else if (tuner_frequency < 160000000) + cp = 5; + else if (tuner_frequency < 200000000) + cp = 6; + else if (tuner_frequency < 290000000) + cp = 3; + else if (tuner_frequency < 420000000) + cp = 5; + else if (tuner_frequency < 480000000) + cp = 6; + else if (tuner_frequency < 620000000) + cp = 3; + else if (tuner_frequency < 830000000) + cp = 5; + else if (tuner_frequency < 895000000) + cp = 7; + else + return -EINVAL; + + /* determine band */ + if (c->frequency < 49000000) + return -EINVAL; + else if (c->frequency < 161000000) + band = 1; + else if (c->frequency < 444000000) + band = 2; + else if (c->frequency < 861000000) + band = 4; + else + return -EINVAL; + + /* setup PLL filter */ + switch (c->bandwidth_hz) { + case 6000000: + filter = 0; + break; + + case 7000000: + filter = 0; + break; + + case 8000000: + filter = 1; + break; + + default: + return -EINVAL; + } + + /* calculate divisor + * ((36166000+((1000000/6)/2)) + Finput)/(1000000/6) + */ + tuner_frequency = (((c->frequency / 1000) * 6) + 217496) / 1000; + + /* setup tuner buffer */ + tuner_buf[0] = (tuner_frequency >> 8) & 0x7f; + tuner_buf[1] = tuner_frequency & 0xff; + tuner_buf[2] = 0xca; + tuner_buf[3] = (cp << 5) | (filter << 3) | band; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&dev->i2c_adap, &tuner_msg, 1) != 1) { + pr_warn("could not write to tuner at addr: 0x%02x\n", + addr << 1); + return -EIO; + } + msleep(1); + return 0; +} + +static int philips_tu1216_init(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + u8 addr = state->config->tuner_address; + static u8 tu1216_init[] = { 0x0b, 0xf5, 0x85, 0xab }; + struct i2c_msg tuner_msg = {.addr = addr,.flags = 0,.buf = tu1216_init,.len = sizeof(tu1216_init) }; + + /* setup PLL configuration */ + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&dev->i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + msleep(1); + + return 0; +} + +/* ------------------------------------------------------------------ */ + +static struct tda1004x_config philips_tu1216_60_config = { + .demod_address = 0x8, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_4M, + .agc_config = TDA10046_AGC_DEFAULT, + .if_freq = TDA10046_FREQ_3617, + .tuner_address = 0x60, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config philips_tu1216_61_config = { + + .demod_address = 0x8, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_4M, + .agc_config = TDA10046_AGC_DEFAULT, + .if_freq = TDA10046_FREQ_3617, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware +}; + +/* ------------------------------------------------------------------ */ + +static int philips_td1316_tuner_init(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + u8 addr = state->config->tuner_address; + static u8 msg[] = { 0x0b, 0xf5, 0x86, 0xab }; + struct i2c_msg init_msg = {.addr = addr,.flags = 0,.buf = msg,.len = sizeof(msg) }; + + /* setup PLL configuration */ + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&dev->i2c_adap, &init_msg, 1) != 1) + return -EIO; + return 0; +} + +static int philips_td1316_tuner_set_params(struct dvb_frontend *fe) +{ + return philips_tda6651_pll_set(fe); +} + +static int philips_td1316_tuner_sleep(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + u8 addr = state->config->tuner_address; + static u8 msg[] = { 0x0b, 0xdc, 0x86, 0xa4 }; + struct i2c_msg analog_msg = {.addr = addr,.flags = 0,.buf = msg,.len = sizeof(msg) }; + + /* switch the tuner to analog mode */ + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&dev->i2c_adap, &analog_msg, 1) != 1) + return -EIO; + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int philips_europa_tuner_init(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + static u8 msg[] = { 0x00, 0x40}; + struct i2c_msg init_msg = {.addr = 0x43,.flags = 0,.buf = msg,.len = sizeof(msg) }; + + + if (philips_td1316_tuner_init(fe)) + return -EIO; + msleep(1); + if (i2c_transfer(&dev->i2c_adap, &init_msg, 1) != 1) + return -EIO; + + return 0; +} + +static int philips_europa_tuner_sleep(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + + static u8 msg[] = { 0x00, 0x14 }; + struct i2c_msg analog_msg = {.addr = 0x43,.flags = 0,.buf = msg,.len = sizeof(msg) }; + + if (philips_td1316_tuner_sleep(fe)) + return -EIO; + + /* switch the board to analog mode */ + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&dev->i2c_adap, &analog_msg, 1) != 1) + return -EIO; + + return 0; +} + +static int philips_europa_demod_sleep(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + + if (dev->original_demod_sleep) + dev->original_demod_sleep(fe); + fe->ops.i2c_gate_ctrl(fe, 1); + return 0; +} + +static struct tda1004x_config philips_europa_config = { + + .demod_address = 0x8, + .invert = 0, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_4M, + .agc_config = TDA10046_AGC_IFO_AUTO_POS, + .if_freq = TDA10046_FREQ_052, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config medion_cardbus = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_IFO_AUTO_NEG, + .if_freq = TDA10046_FREQ_3613, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config technotrend_budget_t3000_config = { + .demod_address = 0x8, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_4M, + .agc_config = TDA10046_AGC_DEFAULT, + .if_freq = TDA10046_FREQ_3617, + .tuner_address = 0x63, + .request_firmware = philips_tda1004x_request_firmware +}; + +/* ------------------------------------------------------------------ + * tda 1004x based cards with philips silicon tuner + */ + +static int tda8290_i2c_gate_ctrl( struct dvb_frontend* fe, int enable) +{ + struct tda1004x_state *state = fe->demodulator_priv; + + u8 addr = state->config->i2c_gate; + static u8 tda8290_close[] = { 0x21, 0xc0}; + static u8 tda8290_open[] = { 0x21, 0x80}; + struct i2c_msg tda8290_msg = {.addr = addr,.flags = 0, .len = 2}; + if (enable) { + tda8290_msg.buf = tda8290_close; + } else { + tda8290_msg.buf = tda8290_open; + } + if (i2c_transfer(state->i2c, &tda8290_msg, 1) != 1) { + pr_warn("could not access tda8290 I2C gate\n"); + return -EIO; + } + msleep(20); + return 0; +} + +static int philips_tda827x_tuner_init(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + + switch (state->config->antenna_switch) { + case 0: + break; + case 1: + pr_debug("setting GPIO21 to 0 (TV antenna?)\n"); + saa7134_set_gpio(dev, 21, 0); + break; + case 2: + pr_debug("setting GPIO21 to 1 (Radio antenna?)\n"); + saa7134_set_gpio(dev, 21, 1); + break; + } + return 0; +} + +static int philips_tda827x_tuner_sleep(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + + switch (state->config->antenna_switch) { + case 0: + break; + case 1: + pr_debug("setting GPIO21 to 1 (Radio antenna?)\n"); + saa7134_set_gpio(dev, 21, 1); + break; + case 2: + pr_debug("setting GPIO21 to 0 (TV antenna?)\n"); + saa7134_set_gpio(dev, 21, 0); + break; + } + return 0; +} + +static int configure_tda827x_fe(struct saa7134_dev *dev, + struct tda1004x_config *cdec_conf, + struct tda827x_config *tuner_conf) +{ + struct vb2_dvb_frontend *fe0; + + /* Get the first frontend */ + fe0 = vb2_dvb_get_frontend(&dev->frontends, 1); + + if (!fe0) + return -EINVAL; + + fe0->dvb.frontend = dvb_attach(tda10046_attach, cdec_conf, &dev->i2c_adap); + if (fe0->dvb.frontend) { + if (cdec_conf->i2c_gate) + fe0->dvb.frontend->ops.i2c_gate_ctrl = tda8290_i2c_gate_ctrl; + if (dvb_attach(tda827x_attach, fe0->dvb.frontend, + cdec_conf->tuner_address, + &dev->i2c_adap, tuner_conf)) + return 0; + + pr_warn("no tda827x tuner found at addr: %02x\n", + cdec_conf->tuner_address); + } + return -EINVAL; +} + +/* ------------------------------------------------------------------ */ + +static struct tda827x_config tda827x_cfg_0 = { + .init = philips_tda827x_tuner_init, + .sleep = philips_tda827x_tuner_sleep, + .config = 0, + .switch_addr = 0 +}; + +static struct tda827x_config tda827x_cfg_1 = { + .init = philips_tda827x_tuner_init, + .sleep = philips_tda827x_tuner_sleep, + .config = 1, + .switch_addr = 0x4b +}; + +static struct tda827x_config tda827x_cfg_2 = { + .init = philips_tda827x_tuner_init, + .sleep = philips_tda827x_tuner_sleep, + .config = 2, + .switch_addr = 0x4b +}; + +static struct tda827x_config tda827x_cfg_2_sw42 = { + .init = philips_tda827x_tuner_init, + .sleep = philips_tda827x_tuner_sleep, + .config = 2, + .switch_addr = 0x42 +}; + +/* ------------------------------------------------------------------ */ + +static struct tda1004x_config tda827x_lifeview_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .tuner_address = 0x60, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config philips_tiger_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .antenna_switch= 1, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config cinergy_ht_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config cinergy_ht_pci_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x60, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config philips_tiger_s_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .antenna_switch= 1, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config pinnacle_pctv_310i_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config hauppauge_hvr_1110_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config asus_p7131_dual_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .antenna_switch= 2, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config lifeview_trio_config = { + .demod_address = 0x09, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP00_I, + .if_freq = TDA10046_FREQ_045, + .tuner_address = 0x60, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config tevion_dvbt220rf_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .tuner_address = 0x60, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config md8800_dvbt_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x60, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config asus_p7131_4871_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .antenna_switch= 2, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config asus_p7131_hybrid_lna_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .antenna_switch= 2, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config kworld_dvb_t_210_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .antenna_switch= 1, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config avermedia_super_007_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x60, + .antenna_switch= 1, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config twinhan_dtv_dvb_3056_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x42, + .tuner_address = 0x61, + .antenna_switch = 1, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config asus_tiger_3in1_config = { + .demod_address = 0x0b, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .antenna_switch = 1, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config asus_ps3_100_config = { + .demod_address = 0x0b, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .antenna_switch = 1, + .request_firmware = philips_tda1004x_request_firmware +}; + +/* ------------------------------------------------------------------ + * special case: this card uses saa713x GPIO22 for the mode switch + */ + +static int ads_duo_tuner_init(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + philips_tda827x_tuner_init(fe); + /* route TDA8275a AGC input to the channel decoder */ + saa7134_set_gpio(dev, 22, 1); + return 0; +} + +static int ads_duo_tuner_sleep(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + /* route TDA8275a AGC input to the analog IF chip*/ + saa7134_set_gpio(dev, 22, 0); + philips_tda827x_tuner_sleep(fe); + return 0; +} + +static struct tda827x_config ads_duo_cfg = { + .init = ads_duo_tuner_init, + .sleep = ads_duo_tuner_sleep, + .config = 0 +}; + +static struct tda1004x_config ads_tech_duo_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP00_I, + .if_freq = TDA10046_FREQ_045, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct zl10353_config behold_h6_config = { + .demod_address = 0x1e>>1, + .no_tuner = 1, + .parallel_ts = 1, + .disable_i2c_gate_ctrl = 1, +}; + +static struct xc5000_config behold_x7_tunerconfig = { + .i2c_address = 0xc2>>1, + .if_khz = 4560, + .radio_input = XC5000_RADIO_FM1, +}; + +static struct zl10353_config behold_x7_config = { + .demod_address = 0x1e>>1, + .if2 = 45600, + .no_tuner = 1, + .parallel_ts = 1, + .disable_i2c_gate_ctrl = 1, +}; + +static struct zl10353_config videomate_t750_zl10353_config = { + .demod_address = 0x0f, + .no_tuner = 1, + .parallel_ts = 1, + .disable_i2c_gate_ctrl = 1, +}; + +static struct qt1010_config videomate_t750_qt1010_config = { + .i2c_address = 0x62 +}; + + +/* ================================================================== + * tda10086 based DVB-S cards, helper functions + */ + +static struct tda10086_config flydvbs = { + .demod_address = 0x0e, + .invert = 0, + .diseqc_tone = 0, + .xtal_freq = TDA10086_XTAL_16M, +}; + +static struct tda10086_config sd1878_4m = { + .demod_address = 0x0e, + .invert = 0, + .diseqc_tone = 0, + .xtal_freq = TDA10086_XTAL_4M, +}; + +/* ------------------------------------------------------------------ + * special case: lnb supply is connected to the gated i2c + */ + +static int md8800_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage voltage) +{ + int res = -EIO; + struct saa7134_dev *dev = fe->dvb->priv; + if (fe->ops.i2c_gate_ctrl) { + fe->ops.i2c_gate_ctrl(fe, 1); + if (dev->original_set_voltage) + res = dev->original_set_voltage(fe, voltage); + fe->ops.i2c_gate_ctrl(fe, 0); + } + return res; +}; + +static int md8800_set_high_voltage(struct dvb_frontend *fe, long arg) +{ + int res = -EIO; + struct saa7134_dev *dev = fe->dvb->priv; + if (fe->ops.i2c_gate_ctrl) { + fe->ops.i2c_gate_ctrl(fe, 1); + if (dev->original_set_high_voltage) + res = dev->original_set_high_voltage(fe, arg); + fe->ops.i2c_gate_ctrl(fe, 0); + } + return res; +}; + +static int md8800_set_voltage2(struct dvb_frontend *fe, + enum fe_sec_voltage voltage) +{ + struct saa7134_dev *dev = fe->dvb->priv; + u8 wbuf[2] = { 0x1f, 00 }; + u8 rbuf; + struct i2c_msg msg[] = { { .addr = 0x08, .flags = 0, .buf = wbuf, .len = 1 }, + { .addr = 0x08, .flags = I2C_M_RD, .buf = &rbuf, .len = 1 } }; + + if (i2c_transfer(&dev->i2c_adap, msg, 2) != 2) + return -EIO; + /* NOTE: this assumes that gpo1 is used, it might be bit 5 (gpo2) */ + if (voltage == SEC_VOLTAGE_18) + wbuf[1] = rbuf | 0x10; + else + wbuf[1] = rbuf & 0xef; + msg[0].len = 2; + if (i2c_transfer(&dev->i2c_adap, msg, 1) != 1) + return -EIO; + + return 0; +} + +static int md8800_set_high_voltage2(struct dvb_frontend *fe, long arg) +{ + pr_warn("%s: sorry can't set high LNB supply voltage from here\n", + __func__); + return -EIO; +} + +/* ================================================================== + * nxt200x based ATSC cards, helper functions + */ + +static const struct nxt200x_config avertvhda180 = { + .demod_address = 0x0a, +}; + +static const struct nxt200x_config kworldatsc110 = { + .demod_address = 0x0a, +}; + +/* ------------------------------------------------------------------ */ + +static struct mt312_config avertv_a700_mt312 = { + .demod_address = 0x0e, + .voltage_inverted = 1, +}; + +static struct zl10036_config avertv_a700_tuner = { + .tuner_address = 0x60, +}; + +static struct mt312_config zl10313_compro_s350_config = { + .demod_address = 0x0e, +}; + +static struct mt312_config zl10313_avermedia_a706_config = { + .demod_address = 0x0e, +}; + +static struct lgdt3305_config hcw_lgdt3305_config = { + .i2c_addr = 0x0e, + .mpeg_mode = LGDT3305_MPEG_SERIAL, + .tpclk_edge = LGDT3305_TPCLK_RISING_EDGE, + .tpvalid_polarity = LGDT3305_TP_VALID_HIGH, + .deny_i2c_rptr = 1, + .spectral_inversion = 1, + .qam_if_khz = 4000, + .vsb_if_khz = 3250, +}; + +static struct tda10048_config hcw_tda10048_config = { + .demod_address = 0x10 >> 1, + .output_mode = TDA10048_SERIAL_OUTPUT, + .fwbulkwritelen = TDA10048_BULKWRITE_200, + .inversion = TDA10048_INVERSION_ON, + .dtv6_if_freq_khz = TDA10048_IF_3300, + .dtv7_if_freq_khz = TDA10048_IF_3500, + .dtv8_if_freq_khz = TDA10048_IF_4000, + .clk_freq_khz = TDA10048_CLK_16000, + .disable_gate_access = 1, +}; + +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_ANALOG, + .config = 3, + .output_opt = TDA18271_OUTPUT_LT_OFF, +}; + +static struct tda829x_config tda829x_no_probe = { + .probe_tuner = TDA829X_DONT_PROBE, +}; + +static struct tda10048_config zolid_tda10048_config = { + .demod_address = 0x10 >> 1, + .output_mode = TDA10048_PARALLEL_OUTPUT, + .fwbulkwritelen = TDA10048_BULKWRITE_200, + .inversion = TDA10048_INVERSION_ON, + .dtv6_if_freq_khz = TDA10048_IF_3300, + .dtv7_if_freq_khz = TDA10048_IF_3500, + .dtv8_if_freq_khz = TDA10048_IF_4000, + .clk_freq_khz = TDA10048_CLK_16000, + .disable_gate_access = 1, +}; + +static struct tda18271_config zolid_tda18271_config = { + .gate = TDA18271_GATE_ANALOG, +}; + +static struct tda10048_config dtv1000s_tda10048_config = { + .demod_address = 0x10 >> 1, + .output_mode = TDA10048_PARALLEL_OUTPUT, + .fwbulkwritelen = TDA10048_BULKWRITE_200, + .inversion = TDA10048_INVERSION_ON, + .dtv6_if_freq_khz = TDA10048_IF_3300, + .dtv7_if_freq_khz = TDA10048_IF_3800, + .dtv8_if_freq_khz = TDA10048_IF_4300, + .clk_freq_khz = TDA10048_CLK_16000, + .disable_gate_access = 1, +}; + +static struct tda18271_std_map dtv1000s_tda18271_std_map = { + .dvbt_6 = { .if_freq = 3300, .agc_mode = 3, .std = 4, + .if_lvl = 1, .rfagc_top = 0x37, }, + .dvbt_7 = { .if_freq = 3800, .agc_mode = 3, .std = 5, + .if_lvl = 1, .rfagc_top = 0x37, }, + .dvbt_8 = { .if_freq = 4300, .agc_mode = 3, .std = 6, + .if_lvl = 1, .rfagc_top = 0x37, }, +}; + +static struct tda18271_config dtv1000s_tda18271_config = { + .std_map = &dtv1000s_tda18271_std_map, + .gate = TDA18271_GATE_ANALOG, +}; + +static struct lgs8gxx_config prohdtv_pro2_lgs8g75_config = { + .prod = LGS8GXX_PROD_LGS8G75, + .demod_address = 0x1d, + .serial_ts = 0, + .ts_clk_pol = 1, + .ts_clk_gated = 0, + .if_clk_freq = 30400, /* 30.4 MHz */ + .if_freq = 4000, /* 4.00 MHz */ + .if_neg_center = 0, + .ext_adc = 0, + .adc_signed = 1, + .adc_vpp = 3, /* 2.0 Vpp */ + .if_neg_edge = 1, +}; + +static struct tda18271_config prohdtv_pro2_tda18271_config = { + .gate = TDA18271_GATE_ANALOG, + .output_opt = TDA18271_OUTPUT_LT_OFF, +}; + +static struct tda18271_std_map kworld_tda18271_std_map = { + .atsc_6 = { .if_freq = 3250, .agc_mode = 3, .std = 3, + .if_lvl = 6, .rfagc_top = 0x37 }, + .qam_6 = { .if_freq = 4000, .agc_mode = 3, .std = 0, + .if_lvl = 6, .rfagc_top = 0x37 }, +}; + +static struct tda18271_config kworld_pc150u_tda18271_config = { + .std_map = &kworld_tda18271_std_map, + .gate = TDA18271_GATE_ANALOG, + .output_opt = TDA18271_OUTPUT_LT_OFF, + .config = 3, /* Use tuner callback for AGC */ + .rf_cal_on_startup = 1 +}; + +static struct s5h1411_config kworld_s5h1411_config = { + .output_mode = S5H1411_PARALLEL_OUTPUT, + .gpio = S5H1411_GPIO_OFF, + .qam_if = S5H1411_IF_4000, + .vsb_if = S5H1411_IF_3250, + .inversion = S5H1411_INVERSION_ON, + .status_mode = S5H1411_DEMODLOCKING, + .mpeg_timing = + S5H1411_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, +}; + +static struct tda18271_config hdtv200h_tda18271_config = { + .gate = TDA18271_GATE_ANALOG, + .config = 3 /* Use tuner callback for AGC */ +}; + +static struct s5h1411_config hdtv200h_s5h1411_config = { + .output_mode = S5H1411_PARALLEL_OUTPUT, + .gpio = S5H1411_GPIO_OFF, + .qam_if = S5H1411_IF_4000, + .vsb_if = S5H1411_IF_3250, + .inversion = S5H1411_INVERSION_ON, + .status_mode = S5H1411_DEMODLOCKING, + .mpeg_timing = + S5H1411_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, +}; + + +/* ================================================================== + * Core code + */ + +static int dvb_init(struct saa7134_dev *dev) +{ + int ret; + int attach_xc3028 = 0; + struct vb2_dvb_frontend *fe0; + struct vb2_queue *q; + + /* FIXME: add support for multi-frontend */ + mutex_init(&dev->frontends.lock); + INIT_LIST_HEAD(&dev->frontends.felist); + + pr_info("%s() allocating 1 frontend\n", __func__); + fe0 = vb2_dvb_alloc_frontend(&dev->frontends, 1); + if (!fe0) { + pr_err("%s() failed to alloc\n", __func__); + return -ENOMEM; + } + + /* init struct vb2_dvb */ + dev->ts.nr_bufs = 32; + dev->ts.nr_packets = 32*4; + fe0->dvb.name = dev->name; + q = &fe0->dvb.dvbq; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_READ; + q->drv_priv = &dev->ts_q; + q->ops = &saa7134_ts_qops; + q->mem_ops = &vb2_dma_sg_memops; + q->buf_struct_size = sizeof(struct saa7134_buf); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &dev->lock; + q->dev = &dev->pci->dev; + ret = vb2_queue_init(q); + if (ret) { + vb2_dvb_dealloc_frontends(&dev->frontends); + return ret; + } + + switch (dev->board) { + case SAA7134_BOARD_PINNACLE_300I_DVBT_PAL: + pr_debug("pinnacle 300i dvb setup\n"); + fe0->dvb.frontend = dvb_attach(mt352_attach, &pinnacle_300i, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + fe0->dvb.frontend->ops.tuner_ops.set_params = mt352_pinnacle_tuner_set_params; + } + break; + case SAA7134_BOARD_AVERMEDIA_777: + case SAA7134_BOARD_AVERMEDIA_A16AR: + pr_debug("avertv 777 dvb setup\n"); + fe0->dvb.frontend = dvb_attach(mt352_attach, &avermedia_777, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x61, + TUNER_PHILIPS_TD1316); + } + break; + case SAA7134_BOARD_AVERMEDIA_A16D: + pr_debug("AverMedia A16D dvb setup\n"); + fe0->dvb.frontend = dvb_attach(mt352_attach, + &avermedia_xc3028_mt352_dev, + &dev->i2c_adap); + attach_xc3028 = 1; + break; + case SAA7134_BOARD_MD7134: + fe0->dvb.frontend = dvb_attach(tda10046_attach, + &medion_cardbus, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + /* + * The TV tuner on this board is actually NOT + * behind the demod i2c gate. + * However, the demod EEPROM is indeed there and it + * conflicts with the SAA7134 chip config EEPROM + * if the i2c gate is open (since they have same + * bus addresses) resulting in card PCI SVID / SSID + * being garbage after a reboot from time to time. + * + * Let's just leave the gate permanently closed - + * saa7134_i2c_eeprom_md7134_gate() will close it for + * us at probe time if it was open for some reason. + */ + fe0->dvb.frontend->ops.i2c_gate_ctrl = NULL; + dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &dev->i2c_adap, medion_cardbus.tuner_address, + TUNER_PHILIPS_FMD1216ME_MK3); + } + break; + case SAA7134_BOARD_PHILIPS_TOUGH: + fe0->dvb.frontend = dvb_attach(tda10046_attach, + &philips_tu1216_60_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + fe0->dvb.frontend->ops.tuner_ops.init = philips_tu1216_init; + fe0->dvb.frontend->ops.tuner_ops.set_params = philips_tda6651_pll_set; + } + break; + case SAA7134_BOARD_FLYDVBTDUO: + case SAA7134_BOARD_FLYDVBT_DUO_CARDBUS: + if (configure_tda827x_fe(dev, &tda827x_lifeview_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_PHILIPS_EUROPA: + case SAA7134_BOARD_VIDEOMATE_DVBT_300: + case SAA7134_BOARD_ASUS_EUROPA_HYBRID: + fe0->dvb.frontend = dvb_attach(tda10046_attach, + &philips_europa_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dev->original_demod_sleep = fe0->dvb.frontend->ops.sleep; + fe0->dvb.frontend->ops.sleep = philips_europa_demod_sleep; + fe0->dvb.frontend->ops.tuner_ops.init = philips_europa_tuner_init; + fe0->dvb.frontend->ops.tuner_ops.sleep = philips_europa_tuner_sleep; + fe0->dvb.frontend->ops.tuner_ops.set_params = philips_td1316_tuner_set_params; + } + break; + case SAA7134_BOARD_TECHNOTREND_BUDGET_T3000: + fe0->dvb.frontend = dvb_attach(tda10046_attach, + &technotrend_budget_t3000_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dev->original_demod_sleep = fe0->dvb.frontend->ops.sleep; + fe0->dvb.frontend->ops.sleep = philips_europa_demod_sleep; + fe0->dvb.frontend->ops.tuner_ops.init = philips_europa_tuner_init; + fe0->dvb.frontend->ops.tuner_ops.sleep = philips_europa_tuner_sleep; + fe0->dvb.frontend->ops.tuner_ops.set_params = philips_td1316_tuner_set_params; + } + break; + case SAA7134_BOARD_VIDEOMATE_DVBT_200: + fe0->dvb.frontend = dvb_attach(tda10046_attach, + &philips_tu1216_61_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + fe0->dvb.frontend->ops.tuner_ops.init = philips_tu1216_init; + fe0->dvb.frontend->ops.tuner_ops.set_params = philips_tda6651_pll_set; + } + break; + case SAA7134_BOARD_KWORLD_DVBT_210: + if (configure_tda827x_fe(dev, &kworld_dvb_t_210_config, + &tda827x_cfg_2) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_HAUPPAUGE_HVR1120: + fe0->dvb.frontend = dvb_attach(tda10048_attach, + &hcw_tda10048_config, + &dev->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x4b, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, + &hcw_tda18271_config); + } + break; + case SAA7134_BOARD_PHILIPS_TIGER: + if (configure_tda827x_fe(dev, &philips_tiger_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_PINNACLE_PCTV_310i: + if (configure_tda827x_fe(dev, &pinnacle_pctv_310i_config, + &tda827x_cfg_1) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_HAUPPAUGE_HVR1110: + if (configure_tda827x_fe(dev, &hauppauge_hvr_1110_config, + &tda827x_cfg_1) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_HAUPPAUGE_HVR1150: + fe0->dvb.frontend = dvb_attach(lgdt3305_attach, + &hcw_lgdt3305_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x4b, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, + &hcw_tda18271_config); + } + break; + case SAA7134_BOARD_ASUSTeK_P7131_DUAL: + if (configure_tda827x_fe(dev, &asus_p7131_dual_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_FLYDVBT_LR301: + if (configure_tda827x_fe(dev, &tda827x_lifeview_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_FLYDVB_TRIO: + if (!use_frontend) { /* terrestrial */ + if (configure_tda827x_fe(dev, &lifeview_trio_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + } else { /* satellite */ + fe0->dvb.frontend = dvb_attach(tda10086_attach, &flydvbs, &dev->i2c_adap); + if (fe0->dvb.frontend) { + if (dvb_attach(tda826x_attach, fe0->dvb.frontend, 0x63, + &dev->i2c_adap, 0) == NULL) { + pr_warn("%s: Lifeview Trio, No tda826x found!\n", + __func__); + goto detach_frontend; + } + if (dvb_attach(isl6421_attach, fe0->dvb.frontend, + &dev->i2c_adap, + 0x08, 0, 0, false) == NULL) { + pr_warn("%s: Lifeview Trio, No ISL6421 found!\n", + __func__); + goto detach_frontend; + } + } + } + break; + case SAA7134_BOARD_ADS_DUO_CARDBUS_PTV331: + case SAA7134_BOARD_FLYDVBT_HYBRID_CARDBUS: + fe0->dvb.frontend = dvb_attach(tda10046_attach, + &ads_tech_duo_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + if (dvb_attach(tda827x_attach,fe0->dvb.frontend, + ads_tech_duo_config.tuner_address, &dev->i2c_adap, + &ads_duo_cfg) == NULL) { + pr_warn("no tda827x tuner found at addr: %02x\n", + ads_tech_duo_config.tuner_address); + goto detach_frontend; + } + } else + pr_warn("failed to attach tda10046\n"); + break; + case SAA7134_BOARD_TEVION_DVBT_220RF: + if (configure_tda827x_fe(dev, &tevion_dvbt220rf_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_MEDION_MD8800_QUADRO: + if (!use_frontend) { /* terrestrial */ + if (configure_tda827x_fe(dev, &md8800_dvbt_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + } else { /* satellite */ + fe0->dvb.frontend = dvb_attach(tda10086_attach, + &flydvbs, &dev->i2c_adap); + if (fe0->dvb.frontend) { + struct dvb_frontend *fe = fe0->dvb.frontend; + u8 dev_id = dev->eedata[2]; + u8 data = 0xc4; + struct i2c_msg msg = {.addr = 0x08, .flags = 0, .len = 1}; + + if (dvb_attach(tda826x_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, 0) == NULL) { + pr_warn("%s: Medion Quadro, no tda826x found !\n", + __func__); + goto detach_frontend; + } + if (dev_id != 0x08) { + /* we need to open the i2c gate (we know it exists) */ + fe->ops.i2c_gate_ctrl(fe, 1); + if (dvb_attach(isl6405_attach, fe, + &dev->i2c_adap, 0x08, 0, 0) == NULL) { + pr_warn("%s: Medion Quadro, no ISL6405 found !\n", + __func__); + goto detach_frontend; + } + if (dev_id == 0x07) { + /* fire up the 2nd section of the LNB supply since + we can't do this from the other section */ + msg.buf = &data; + i2c_transfer(&dev->i2c_adap, &msg, 1); + } + fe->ops.i2c_gate_ctrl(fe, 0); + dev->original_set_voltage = fe->ops.set_voltage; + fe->ops.set_voltage = md8800_set_voltage; + dev->original_set_high_voltage = fe->ops.enable_high_lnb_voltage; + fe->ops.enable_high_lnb_voltage = md8800_set_high_voltage; + } else { + fe->ops.set_voltage = md8800_set_voltage2; + fe->ops.enable_high_lnb_voltage = md8800_set_high_voltage2; + } + } + } + break; + case SAA7134_BOARD_AVERMEDIA_AVERTVHD_A180: + fe0->dvb.frontend = dvb_attach(nxt200x_attach, &avertvhda180, + &dev->i2c_adap); + if (fe0->dvb.frontend) + dvb_attach(dvb_pll_attach, fe0->dvb.frontend, 0x61, + NULL, DVB_PLL_TDHU2); + break; + case SAA7134_BOARD_ADS_INSTANT_HDTV_PCI: + case SAA7134_BOARD_KWORLD_ATSC110: + fe0->dvb.frontend = dvb_attach(nxt200x_attach, &kworldatsc110, + &dev->i2c_adap); + if (fe0->dvb.frontend) + dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x61, + TUNER_PHILIPS_TUV1236D); + break; + case SAA7134_BOARD_KWORLD_PC150U: + saa7134_set_gpio(dev, 18, 1); /* Switch to digital mode */ + saa7134_tuner_callback(dev, 0, + TDA18271_CALLBACK_CMD_AGC_ENABLE, 1); + fe0->dvb.frontend = dvb_attach(s5h1411_attach, + &kworld_s5h1411_config, + &dev->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x4b, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, + &kworld_pc150u_tda18271_config); + } + break; + case SAA7134_BOARD_FLYDVBS_LR300: + fe0->dvb.frontend = dvb_attach(tda10086_attach, &flydvbs, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + if (dvb_attach(tda826x_attach, fe0->dvb.frontend, 0x60, + &dev->i2c_adap, 0) == NULL) { + pr_warn("%s: No tda826x found!\n", __func__); + goto detach_frontend; + } + if (dvb_attach(isl6421_attach, fe0->dvb.frontend, + &dev->i2c_adap, + 0x08, 0, 0, false) == NULL) { + pr_warn("%s: No ISL6421 found!\n", __func__); + goto detach_frontend; + } + } + break; + case SAA7134_BOARD_ASUS_EUROPA2_HYBRID: + fe0->dvb.frontend = dvb_attach(tda10046_attach, + &medion_cardbus, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dev->original_demod_sleep = fe0->dvb.frontend->ops.sleep; + fe0->dvb.frontend->ops.sleep = philips_europa_demod_sleep; + + dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &dev->i2c_adap, medion_cardbus.tuner_address, + TUNER_PHILIPS_FMD1216ME_MK3); + } + break; + case SAA7134_BOARD_VIDEOMATE_DVBT_200A: + fe0->dvb.frontend = dvb_attach(tda10046_attach, + &philips_europa_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + fe0->dvb.frontend->ops.tuner_ops.init = philips_td1316_tuner_init; + fe0->dvb.frontend->ops.tuner_ops.set_params = philips_td1316_tuner_set_params; + } + break; + case SAA7134_BOARD_CINERGY_HT_PCMCIA: + if (configure_tda827x_fe(dev, &cinergy_ht_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_CINERGY_HT_PCI: + if (configure_tda827x_fe(dev, &cinergy_ht_pci_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_PHILIPS_TIGER_S: + if (configure_tda827x_fe(dev, &philips_tiger_s_config, + &tda827x_cfg_2) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_ASUS_P7131_4871: + if (configure_tda827x_fe(dev, &asus_p7131_4871_config, + &tda827x_cfg_2) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA: + if (configure_tda827x_fe(dev, &asus_p7131_hybrid_lna_config, + &tda827x_cfg_2) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_AVERMEDIA_SUPER_007: + if (configure_tda827x_fe(dev, &avermedia_super_007_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_TWINHAN_DTV_DVB_3056: + if (configure_tda827x_fe(dev, &twinhan_dtv_dvb_3056_config, + &tda827x_cfg_2_sw42) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_PHILIPS_SNAKE: + fe0->dvb.frontend = dvb_attach(tda10086_attach, &flydvbs, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + if (dvb_attach(tda826x_attach, fe0->dvb.frontend, 0x60, + &dev->i2c_adap, 0) == NULL) { + pr_warn("%s: No tda826x found!\n", __func__); + goto detach_frontend; + } + if (dvb_attach(lnbp21_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0, 0) == NULL) { + pr_warn("%s: No lnbp21 found!\n", __func__); + goto detach_frontend; + } + } + break; + case SAA7134_BOARD_CREATIX_CTX953: + if (configure_tda827x_fe(dev, &md8800_dvbt_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_MSI_TVANYWHERE_AD11: + if (configure_tda827x_fe(dev, &philips_tiger_s_config, + &tda827x_cfg_2) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_AVERMEDIA_CARDBUS_506: + pr_debug("AverMedia E506R dvb setup\n"); + saa7134_set_gpio(dev, 25, 0); + msleep(10); + saa7134_set_gpio(dev, 25, 1); + fe0->dvb.frontend = dvb_attach(mt352_attach, + &avermedia_xc3028_mt352_dev, + &dev->i2c_adap); + attach_xc3028 = 1; + break; + case SAA7134_BOARD_MD7134_BRIDGE_2: + fe0->dvb.frontend = dvb_attach(tda10086_attach, + &sd1878_4m, &dev->i2c_adap); + if (fe0->dvb.frontend) { + struct dvb_frontend *fe; + if (dvb_attach(dvb_pll_attach, fe0->dvb.frontend, 0x60, + &dev->i2c_adap, DVB_PLL_PHILIPS_SD1878_TDA8261) == NULL) { + pr_warn("%s: MD7134 DVB-S, no SD1878 found !\n", + __func__); + goto detach_frontend; + } + /* we need to open the i2c gate (we know it exists) */ + fe = fe0->dvb.frontend; + fe->ops.i2c_gate_ctrl(fe, 1); + if (dvb_attach(isl6405_attach, fe, + &dev->i2c_adap, 0x08, 0, 0) == NULL) { + pr_warn("%s: MD7134 DVB-S, no ISL6405 found !\n", + __func__); + goto detach_frontend; + } + fe->ops.i2c_gate_ctrl(fe, 0); + dev->original_set_voltage = fe->ops.set_voltage; + fe->ops.set_voltage = md8800_set_voltage; + dev->original_set_high_voltage = fe->ops.enable_high_lnb_voltage; + fe->ops.enable_high_lnb_voltage = md8800_set_high_voltage; + } + break; + case SAA7134_BOARD_AVERMEDIA_M103: + saa7134_set_gpio(dev, 25, 0); + msleep(10); + saa7134_set_gpio(dev, 25, 1); + fe0->dvb.frontend = dvb_attach(mt352_attach, + &avermedia_xc3028_mt352_dev, + &dev->i2c_adap); + attach_xc3028 = 1; + break; + case SAA7134_BOARD_ASUSTeK_TIGER_3IN1: + if (!use_frontend) { /* terrestrial */ + if (configure_tda827x_fe(dev, &asus_tiger_3in1_config, + &tda827x_cfg_2) < 0) + goto detach_frontend; + } else { /* satellite */ + fe0->dvb.frontend = dvb_attach(tda10086_attach, + &flydvbs, &dev->i2c_adap); + if (fe0->dvb.frontend) { + if (dvb_attach(tda826x_attach, + fe0->dvb.frontend, 0x60, + &dev->i2c_adap, 0) == NULL) { + pr_warn("%s: Asus Tiger 3in1, no tda826x found!\n", + __func__); + goto detach_frontend; + } + if (dvb_attach(lnbp21_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0, 0) == NULL) { + pr_warn("%s: Asus Tiger 3in1, no lnbp21 found!\n", + __func__); + goto detach_frontend; + } + } + } + break; + case SAA7134_BOARD_ASUSTeK_PS3_100: + if (!use_frontend) { /* terrestrial */ + if (configure_tda827x_fe(dev, &asus_ps3_100_config, + &tda827x_cfg_2) < 0) + goto detach_frontend; + } else { /* satellite */ + fe0->dvb.frontend = dvb_attach(tda10086_attach, + &flydvbs, &dev->i2c_adap); + if (fe0->dvb.frontend) { + if (dvb_attach(tda826x_attach, + fe0->dvb.frontend, 0x60, + &dev->i2c_adap, 0) == NULL) { + pr_warn("%s: Asus My Cinema PS3-100, no tda826x found!\n", + __func__); + goto detach_frontend; + } + if (dvb_attach(lnbp21_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0, 0) == NULL) { + pr_warn("%s: Asus My Cinema PS3-100, no lnbp21 found!\n", + __func__); + goto detach_frontend; + } + } + } + break; + case SAA7134_BOARD_ASUSTeK_TIGER: + if (configure_tda827x_fe(dev, &philips_tiger_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_BEHOLD_H6: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &behold_h6_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x61, + TUNER_PHILIPS_FMD1216MEX_MK3); + } + break; + case SAA7134_BOARD_BEHOLD_X7: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &behold_x7_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dvb_attach(xc5000_attach, fe0->dvb.frontend, + &dev->i2c_adap, &behold_x7_tunerconfig); + } + break; + case SAA7134_BOARD_BEHOLD_H7: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &behold_x7_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dvb_attach(xc5000_attach, fe0->dvb.frontend, + &dev->i2c_adap, &behold_x7_tunerconfig); + } + break; + case SAA7134_BOARD_AVERMEDIA_A700_PRO: + case SAA7134_BOARD_AVERMEDIA_A700_HYBRID: + /* Zarlink ZL10313 */ + fe0->dvb.frontend = dvb_attach(mt312_attach, + &avertv_a700_mt312, &dev->i2c_adap); + if (fe0->dvb.frontend) { + if (dvb_attach(zl10036_attach, fe0->dvb.frontend, + &avertv_a700_tuner, &dev->i2c_adap) == NULL) { + pr_warn("%s: No zl10036 found!\n", + __func__); + } + } + break; + case SAA7134_BOARD_VIDEOMATE_S350: + fe0->dvb.frontend = dvb_attach(mt312_attach, + &zl10313_compro_s350_config, &dev->i2c_adap); + if (fe0->dvb.frontend) + if (dvb_attach(zl10039_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap) == NULL) + pr_warn("%s: No zl10039 found!\n", + __func__); + + break; + case SAA7134_BOARD_VIDEOMATE_T750: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &videomate_t750_zl10353_config, + &dev->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (dvb_attach(qt1010_attach, + fe0->dvb.frontend, + &dev->i2c_adap, + &videomate_t750_qt1010_config) == NULL) + pr_warn("error attaching QT1010\n"); + } + break; + case SAA7134_BOARD_ZOLID_HYBRID_PCI: + fe0->dvb.frontend = dvb_attach(tda10048_attach, + &zolid_tda10048_config, + &dev->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x4b, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, + &zolid_tda18271_config); + } + break; + case SAA7134_BOARD_LEADTEK_WINFAST_DTV1000S: + fe0->dvb.frontend = dvb_attach(tda10048_attach, + &dtv1000s_tda10048_config, + &dev->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x4b, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, + &dtv1000s_tda18271_config); + } + break; + case SAA7134_BOARD_KWORLD_PCI_SBTVD_FULLSEG: + /* Switch to digital mode */ + saa7134_tuner_callback(dev, 0, + TDA18271_CALLBACK_CMD_AGC_ENABLE, 1); + fe0->dvb.frontend = dvb_attach(mb86a20s_attach, + &kworld_mb86a20s_config, + &dev->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x4b, + &tda829x_no_probe); + fe0->dvb.frontend->ops.i2c_gate_ctrl = kworld_sbtvd_gate_ctrl; + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, + &kworld_tda18271_config); + } + + /* mb86a20s need to use the I2C gateway */ + break; + case SAA7134_BOARD_MAGICPRO_PROHDTV_PRO2: + fe0->dvb.frontend = dvb_attach(lgs8gxx_attach, + &prohdtv_pro2_lgs8g75_config, + &dev->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x4b, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, + &prohdtv_pro2_tda18271_config); + } + break; + case SAA7134_BOARD_AVERMEDIA_A706: + /* Enable all DVB-S devices now */ + /* CE5039 DVB-S tuner SLEEP pin low */ + saa7134_set_gpio(dev, 23, 0); + /* CE6313 DVB-S demod SLEEP pin low */ + saa7134_set_gpio(dev, 9, 0); + /* CE6313 DVB-S demod RESET# pin high */ + saa7134_set_gpio(dev, 25, 1); + msleep(1); + fe0->dvb.frontend = dvb_attach(mt312_attach, + &zl10313_avermedia_a706_config, &dev->i2c_adap); + if (fe0->dvb.frontend) { + fe0->dvb.frontend->ops.i2c_gate_ctrl = NULL; + if (dvb_attach(zl10039_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap) == NULL) + pr_warn("%s: No zl10039 found!\n", + __func__); + } + break; + case SAA7134_BOARD_LEADTEK_WINFAST_HDTV200_H: + fe0->dvb.frontend = dvb_attach(s5h1411_attach, + &hdtv200h_s5h1411_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x4b, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, + &hdtv200h_tda18271_config); + } + break; + default: + pr_warn("Huh? unknown DVB card?\n"); + break; + } + + if (attach_xc3028) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &dev->i2c_adap, + .i2c_addr = 0x61, + }; + + if (!fe0->dvb.frontend) + goto detach_frontend; + + fe = dvb_attach(xc2028_attach, fe0->dvb.frontend, &cfg); + if (!fe) { + pr_err("%s/2: xc3028 attach failed\n", + dev->name); + goto detach_frontend; + } + } + + if (NULL == fe0->dvb.frontend) { + pr_err("%s/dvb: frontend initialization failed\n", dev->name); + goto detach_frontend; + } + /* define general-purpose callback pointer */ + fe0->dvb.frontend->callback = saa7134_tuner_callback; + + /* register everything else */ +#ifndef CONFIG_MEDIA_CONTROLLER_DVB + ret = vb2_dvb_register_bus(&dev->frontends, THIS_MODULE, dev, + &dev->pci->dev, NULL, + adapter_nr, 0); +#else + ret = vb2_dvb_register_bus(&dev->frontends, THIS_MODULE, dev, + &dev->pci->dev, dev->media_dev, + adapter_nr, 0); +#endif + + /* this sequence is necessary to make the tda1004x load its firmware + * and to enter analog mode of hybrid boards + */ + if (!ret) { + if (fe0->dvb.frontend->ops.init) + fe0->dvb.frontend->ops.init(fe0->dvb.frontend); + if (fe0->dvb.frontend->ops.sleep) + fe0->dvb.frontend->ops.sleep(fe0->dvb.frontend); + if (fe0->dvb.frontend->ops.tuner_ops.sleep) + fe0->dvb.frontend->ops.tuner_ops.sleep(fe0->dvb.frontend); + } + return ret; + +detach_frontend: + vb2_dvb_dealloc_frontends(&dev->frontends); + vb2_queue_release(&fe0->dvb.dvbq); + return -EINVAL; +} + +static int dvb_fini(struct saa7134_dev *dev) +{ + struct vb2_dvb_frontend *fe0; + + /* Get the first frontend */ + fe0 = vb2_dvb_get_frontend(&dev->frontends, 1); + if (!fe0) + return -EINVAL; + + /* FIXME: I suspect that this code is bogus, since the entry for + Pinnacle 300I DVB-T PAL already defines the proper init to allow + the detection of mt2032 (TDA9887_PORT2_INACTIVE) + */ + if (dev->board == SAA7134_BOARD_PINNACLE_300I_DVBT_PAL) { + struct v4l2_priv_tun_config tda9887_cfg; + static int on = TDA9887_PRESENT | TDA9887_PORT2_INACTIVE; + + tda9887_cfg.tuner = TUNER_TDA9887; + tda9887_cfg.priv = &on; + + /* otherwise we don't detect the tuner on next insmod */ + saa_call_all(dev, tuner, s_config, &tda9887_cfg); + } else if (dev->board == SAA7134_BOARD_MEDION_MD8800_QUADRO) { + if ((dev->eedata[2] == 0x07) && use_frontend) { + /* turn off the 2nd lnb supply */ + u8 data = 0x80; + struct i2c_msg msg = {.addr = 0x08, .buf = &data, .flags = 0, .len = 1}; + struct dvb_frontend *fe; + fe = fe0->dvb.frontend; + if (fe->ops.i2c_gate_ctrl) { + fe->ops.i2c_gate_ctrl(fe, 1); + i2c_transfer(&dev->i2c_adap, &msg, 1); + fe->ops.i2c_gate_ctrl(fe, 0); + } + } + } + vb2_dvb_unregister_bus(&dev->frontends); + vb2_queue_release(&fe0->dvb.dvbq); + return 0; +} + +static struct saa7134_mpeg_ops dvb_ops = { + .type = SAA7134_MPEG_DVB, + .init = dvb_init, + .fini = dvb_fini, +}; + +static int __init dvb_register(void) +{ + return saa7134_ts_register(&dvb_ops); +} + +static void __exit dvb_unregister(void) +{ + saa7134_ts_unregister(&dvb_ops); +} + +module_init(dvb_register); +module_exit(dvb_unregister); diff --git a/drivers/media/pci/saa7134/saa7134-empress.c b/drivers/media/pci/saa7134/saa7134-empress.c new file mode 100644 index 000000000..8c4f70e41 --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134-empress.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * (c) 2004 Gerd Knorr [SuSE Labs] + */ + +#include "saa7134.h" +#include "saa7134-reg.h" + +#include +#include +#include +#include +#include + +#include +#include + +/* ------------------------------------------------------------------ */ + +MODULE_AUTHOR("Gerd Knorr [SuSE Labs]"); +MODULE_DESCRIPTION("Philips SAA7134 empress support"); +MODULE_LICENSE("GPL"); + +static unsigned int empress_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; + +module_param_array(empress_nr, int, NULL, 0444); +MODULE_PARM_DESC(empress_nr,"ts device number"); + +/* ------------------------------------------------------------------ */ + +static int start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct saa7134_dmaqueue *dmaq = vq->drv_priv; + struct saa7134_dev *dev = dmaq->dev; + u32 leading_null_bytes = 0; + int err; + + err = saa7134_ts_start_streaming(vq, count); + if (err) + return err; + + /* If more cards start to need this, then this + should probably be added to the card definitions. */ + switch (dev->board) { + case SAA7134_BOARD_BEHOLD_M6: + case SAA7134_BOARD_BEHOLD_M63: + case SAA7134_BOARD_BEHOLD_M6_EXTRA: + leading_null_bytes = 1; + break; + } + saa_call_all(dev, core, init, leading_null_bytes); + /* Unmute audio */ + saa_writeb(SAA7134_AUDIO_MUTE_CTRL, + saa_readb(SAA7134_AUDIO_MUTE_CTRL) & ~(1 << 6)); + dev->empress_started = 1; + return 0; +} + +static void stop_streaming(struct vb2_queue *vq) +{ + struct saa7134_dmaqueue *dmaq = vq->drv_priv; + struct saa7134_dev *dev = dmaq->dev; + + saa7134_ts_stop_streaming(vq); + saa_writeb(SAA7134_SPECIAL_MODE, 0x00); + msleep(20); + saa_writeb(SAA7134_SPECIAL_MODE, 0x01); + msleep(100); + /* Mute audio */ + saa_writeb(SAA7134_AUDIO_MUTE_CTRL, + saa_readb(SAA7134_AUDIO_MUTE_CTRL) | (1 << 6)); + dev->empress_started = 0; +} + +static const struct vb2_ops saa7134_empress_qops = { + .queue_setup = saa7134_ts_queue_setup, + .buf_init = saa7134_ts_buffer_init, + .buf_prepare = saa7134_ts_buffer_prepare, + .buf_queue = saa7134_vb2_buffer_queue, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, +}; + +/* ------------------------------------------------------------------ */ + +static int empress_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index != 0) + return -EINVAL; + + f->pixelformat = V4L2_PIX_FMT_MPEG; + return 0; +} + +static int empress_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct saa7134_dev *dev = video_drvdata(file); + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_mbus_framefmt *mbus_fmt = &fmt.format; + + saa_call_all(dev, pad, get_fmt, NULL, &fmt); + + v4l2_fill_pix_format(&f->fmt.pix, mbus_fmt); + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.sizeimage = TS_PACKET_SIZE * dev->ts.nr_packets; + f->fmt.pix.bytesperline = 0; + + return 0; +} + +static int empress_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct saa7134_dev *dev = video_drvdata(file); + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + v4l2_fill_mbus_format(&format.format, &f->fmt.pix, MEDIA_BUS_FMT_FIXED); + saa_call_all(dev, pad, set_fmt, NULL, &format); + v4l2_fill_pix_format(&f->fmt.pix, &format.format); + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.sizeimage = TS_PACKET_SIZE * dev->ts.nr_packets; + f->fmt.pix.bytesperline = 0; + + return 0; +} + +static int empress_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct saa7134_dev *dev = video_drvdata(file); + struct v4l2_subdev_pad_config pad_cfg; + struct v4l2_subdev_state pad_state = { + .pads = &pad_cfg, + }; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_TRY, + }; + + v4l2_fill_mbus_format(&format.format, &f->fmt.pix, MEDIA_BUS_FMT_FIXED); + saa_call_all(dev, pad, set_fmt, &pad_state, &format); + v4l2_fill_pix_format(&f->fmt.pix, &format.format); + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.sizeimage = TS_PACKET_SIZE * dev->ts.nr_packets; + f->fmt.pix.bytesperline = 0; + + return 0; +} + +static const struct v4l2_file_operations ts_fops = +{ + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops ts_ioctl_ops = { + .vidioc_querycap = saa7134_querycap, + .vidioc_enum_fmt_vid_cap = empress_enum_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = empress_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = empress_s_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = empress_g_fmt_vid_cap, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_g_frequency = saa7134_g_frequency, + .vidioc_s_frequency = saa7134_s_frequency, + .vidioc_g_tuner = saa7134_g_tuner, + .vidioc_s_tuner = saa7134_s_tuner, + .vidioc_enum_input = saa7134_enum_input, + .vidioc_g_input = saa7134_g_input, + .vidioc_s_input = saa7134_s_input, + .vidioc_s_std = saa7134_s_std, + .vidioc_g_std = saa7134_g_std, + .vidioc_querystd = saa7134_querystd, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* ----------------------------------------------------------- */ + +static const struct video_device saa7134_empress_template = { + .name = "saa7134-empress", + .fops = &ts_fops, + .ioctl_ops = &ts_ioctl_ops, + + .tvnorms = SAA7134_NORMS, +}; + +static void empress_signal_update(struct work_struct *work) +{ + struct saa7134_dev* dev = + container_of(work, struct saa7134_dev, empress_workqueue); + + if (dev->nosignal) { + pr_debug("no video signal\n"); + } else { + pr_debug("video signal acquired\n"); + } +} + +static void empress_signal_change(struct saa7134_dev *dev) +{ + schedule_work(&dev->empress_workqueue); +} + +static bool empress_ctrl_filter(const struct v4l2_ctrl *ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_HUE: + case V4L2_CID_CONTRAST: + case V4L2_CID_SATURATION: + case V4L2_CID_AUDIO_MUTE: + case V4L2_CID_AUDIO_VOLUME: + case V4L2_CID_PRIVATE_INVERT: + case V4L2_CID_PRIVATE_AUTOMUTE: + return true; + default: + return false; + } +} + +static int empress_init(struct saa7134_dev *dev) +{ + struct v4l2_ctrl_handler *hdl = &dev->empress_ctrl_handler; + struct vb2_queue *q; + int err; + + pr_debug("%s: %s\n", dev->name, __func__); + dev->empress_dev = video_device_alloc(); + if (NULL == dev->empress_dev) + return -ENOMEM; + *(dev->empress_dev) = saa7134_empress_template; + dev->empress_dev->v4l2_dev = &dev->v4l2_dev; + dev->empress_dev->release = video_device_release; + dev->empress_dev->lock = &dev->lock; + snprintf(dev->empress_dev->name, sizeof(dev->empress_dev->name), + "%s empress (%s)", dev->name, + saa7134_boards[dev->board].name); + v4l2_ctrl_handler_init(hdl, 21); + v4l2_ctrl_add_handler(hdl, &dev->ctrl_handler, empress_ctrl_filter, false); + if (dev->empress_sd) + v4l2_ctrl_add_handler(hdl, dev->empress_sd->ctrl_handler, NULL, true); + if (hdl->error) { + video_device_release(dev->empress_dev); + return hdl->error; + } + dev->empress_dev->ctrl_handler = hdl; + + INIT_WORK(&dev->empress_workqueue, empress_signal_update); + + q = &dev->empress_vbq; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + /* + * Do not add VB2_USERPTR: the saa7134 DMA engine cannot handle + * transfers that do not start at the beginning of a page. A USERPTR + * can start anywhere in a page, so USERPTR support is a no-go. + */ + q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ; + q->drv_priv = &dev->ts_q; + q->ops = &saa7134_empress_qops; + q->gfp_flags = GFP_DMA32; + q->mem_ops = &vb2_dma_sg_memops; + q->buf_struct_size = sizeof(struct saa7134_buf); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &dev->lock; + q->dev = &dev->pci->dev; + err = vb2_queue_init(q); + if (err) { + video_device_release(dev->empress_dev); + dev->empress_dev = NULL; + return err; + } + dev->empress_dev->queue = q; + dev->empress_dev->device_caps = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | + V4L2_CAP_VIDEO_CAPTURE; + if (dev->tuner_type != TUNER_ABSENT && dev->tuner_type != UNSET) + dev->empress_dev->device_caps |= V4L2_CAP_TUNER; + + video_set_drvdata(dev->empress_dev, dev); + err = video_register_device(dev->empress_dev,VFL_TYPE_VIDEO, + empress_nr[dev->nr]); + if (err < 0) { + pr_info("%s: can't register video device\n", + dev->name); + video_device_release(dev->empress_dev); + dev->empress_dev = NULL; + return err; + } + pr_info("%s: registered device %s [mpeg]\n", + dev->name, video_device_node_name(dev->empress_dev)); + + empress_signal_update(&dev->empress_workqueue); + return 0; +} + +static int empress_fini(struct saa7134_dev *dev) +{ + pr_debug("%s: %s\n", dev->name, __func__); + + if (NULL == dev->empress_dev) + return 0; + flush_work(&dev->empress_workqueue); + vb2_video_unregister_device(dev->empress_dev); + v4l2_ctrl_handler_free(&dev->empress_ctrl_handler); + dev->empress_dev = NULL; + return 0; +} + +static struct saa7134_mpeg_ops empress_ops = { + .type = SAA7134_MPEG_EMPRESS, + .init = empress_init, + .fini = empress_fini, + .signal_change = empress_signal_change, +}; + +static int __init empress_register(void) +{ + return saa7134_ts_register(&empress_ops); +} + +static void __exit empress_unregister(void) +{ + saa7134_ts_unregister(&empress_ops); +} + +module_init(empress_register); +module_exit(empress_unregister); diff --git a/drivers/media/pci/saa7134/saa7134-go7007.c b/drivers/media/pci/saa7134/saa7134-go7007.c new file mode 100644 index 000000000..1f4d77179 --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134-go7007.c @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + */ + +#include "saa7134.h" +#include "saa7134-reg.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "go7007-priv.h" + +/*#define GO7007_HPI_DEBUG*/ + +enum hpi_address { + HPI_ADDR_VIDEO_BUFFER = 0xe4, + HPI_ADDR_INIT_BUFFER = 0xea, + HPI_ADDR_INTR_RET_VALUE = 0xee, + HPI_ADDR_INTR_RET_DATA = 0xec, + HPI_ADDR_INTR_STATUS = 0xf4, + HPI_ADDR_INTR_WR_PARAM = 0xf6, + HPI_ADDR_INTR_WR_INDEX = 0xf8, +}; + +enum gpio_command { + GPIO_COMMAND_RESET = 0x00, /* 000b */ + GPIO_COMMAND_REQ1 = 0x04, /* 001b */ + GPIO_COMMAND_WRITE = 0x20, /* 010b */ + GPIO_COMMAND_REQ2 = 0x24, /* 011b */ + GPIO_COMMAND_READ = 0x80, /* 100b */ + GPIO_COMMAND_VIDEO = 0x84, /* 101b */ + GPIO_COMMAND_IDLE = 0xA0, /* 110b */ + GPIO_COMMAND_ADDR = 0xA4, /* 111b */ +}; + +struct saa7134_go7007 { + struct v4l2_subdev sd; + struct saa7134_dev *dev; + u8 *top; + u8 *bottom; + dma_addr_t top_dma; + dma_addr_t bottom_dma; +}; + +static const struct go7007_board_info board_voyager = { + .flags = 0, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_VALID_ENABLE | + GO7007_SENSOR_TV | + GO7007_SENSOR_VBI, + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_WORD_16, + .audio_rate = 48000, + .audio_bclk_div = 8, + .audio_main_div = 2, + .hpi_buffer_cap = 7, + .num_inputs = 1, + .inputs = { + { + .name = "SAA7134", + }, + }, +}; + +/********************* Driver for GPIO HPI interface *********************/ + +static int gpio_write(struct saa7134_dev *dev, u8 addr, u16 data) +{ + saa_writeb(SAA7134_GPIO_GPMODE0, 0xff); + + /* Write HPI address */ + saa_writeb(SAA7134_GPIO_GPSTATUS0, addr); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_ADDR); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE); + + /* Write low byte */ + saa_writeb(SAA7134_GPIO_GPSTATUS0, data & 0xff); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_WRITE); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE); + + /* Write high byte */ + saa_writeb(SAA7134_GPIO_GPSTATUS0, data >> 8); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_WRITE); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE); + + return 0; +} + +static int gpio_read(struct saa7134_dev *dev, u8 addr, u16 *data) +{ + saa_writeb(SAA7134_GPIO_GPMODE0, 0xff); + + /* Write HPI address */ + saa_writeb(SAA7134_GPIO_GPSTATUS0, addr); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_ADDR); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE); + + saa_writeb(SAA7134_GPIO_GPMODE0, 0x00); + + /* Read low byte */ + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_READ); + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + *data = saa_readb(SAA7134_GPIO_GPSTATUS0); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE); + + /* Read high byte */ + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_READ); + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + *data |= saa_readb(SAA7134_GPIO_GPSTATUS0) << 8; + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE); + + return 0; +} + +static int saa7134_go7007_interface_reset(struct go7007 *go) +{ + struct saa7134_go7007 *saa = go->hpi_context; + struct saa7134_dev *dev = saa->dev; + u16 intr_val, intr_data; + int count = 20; + + saa_clearb(SAA7134_TS_PARALLEL, 0x80); /* Disable TS interface */ + saa_writeb(SAA7134_GPIO_GPMODE2, 0xa4); + saa_writeb(SAA7134_GPIO_GPMODE0, 0xff); + + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_REQ1); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_RESET); + msleep(1); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_REQ1); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_REQ2); + msleep(10); + + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + + saa_readb(SAA7134_GPIO_GPSTATUS2); + /*pr_debug("status is %s\n", saa_readb(SAA7134_GPIO_GPSTATUS2) & 0x40 ? "OK" : "not OK"); */ + + /* enter command mode...(?) */ + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_REQ1); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_REQ2); + + do { + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_readb(SAA7134_GPIO_GPSTATUS2); + /*pr_info("gpio is %08x\n", saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2)); */ + } while (--count > 0); + + /* Wait for an interrupt to indicate successful hardware reset */ + if (go7007_read_interrupt(go, &intr_val, &intr_data) < 0 || + (intr_val & ~0x1) != 0x55aa) { + pr_err("saa7134-go7007: unable to reset the GO7007\n"); + return -1; + } + return 0; +} + +static int saa7134_go7007_write_interrupt(struct go7007 *go, int addr, int data) +{ + struct saa7134_go7007 *saa = go->hpi_context; + struct saa7134_dev *dev = saa->dev; + int i; + u16 status_reg; + +#ifdef GO7007_HPI_DEBUG + pr_debug("saa7134-go7007: WriteInterrupt: %04x %04x\n", addr, data); +#endif + + for (i = 0; i < 100; ++i) { + gpio_read(dev, HPI_ADDR_INTR_STATUS, &status_reg); + if (!(status_reg & 0x0010)) + break; + msleep(10); + } + if (i == 100) { + pr_err("saa7134-go7007: device is hung, status reg = 0x%04x\n", + status_reg); + return -1; + } + gpio_write(dev, HPI_ADDR_INTR_WR_PARAM, data); + gpio_write(dev, HPI_ADDR_INTR_WR_INDEX, addr); + + return 0; +} + +static int saa7134_go7007_read_interrupt(struct go7007 *go) +{ + struct saa7134_go7007 *saa = go->hpi_context; + struct saa7134_dev *dev = saa->dev; + + /* XXX we need to wait if there is no interrupt available */ + go->interrupt_available = 1; + gpio_read(dev, HPI_ADDR_INTR_RET_VALUE, &go->interrupt_value); + gpio_read(dev, HPI_ADDR_INTR_RET_DATA, &go->interrupt_data); +#ifdef GO7007_HPI_DEBUG + pr_debug("saa7134-go7007: ReadInterrupt: %04x %04x\n", + go->interrupt_value, go->interrupt_data); +#endif + return 0; +} + +static void saa7134_go7007_irq_ts_done(struct saa7134_dev *dev, + unsigned long status) +{ + struct go7007 *go = video_get_drvdata(dev->empress_dev); + struct saa7134_go7007 *saa = go->hpi_context; + + if (!vb2_is_streaming(&go->vidq)) + return; + if (0 != (status & 0x000f0000)) + pr_debug("saa7134-go7007: irq: lost %ld\n", + (status >> 16) & 0x0f); + if (status & 0x100000) { + dma_sync_single_for_cpu(&dev->pci->dev, + saa->bottom_dma, PAGE_SIZE, DMA_FROM_DEVICE); + go7007_parse_video_stream(go, saa->bottom, PAGE_SIZE); + saa_writel(SAA7134_RS_BA2(5), saa->bottom_dma); + } else { + dma_sync_single_for_cpu(&dev->pci->dev, + saa->top_dma, PAGE_SIZE, DMA_FROM_DEVICE); + go7007_parse_video_stream(go, saa->top, PAGE_SIZE); + saa_writel(SAA7134_RS_BA1(5), saa->top_dma); + } +} + +static int saa7134_go7007_stream_start(struct go7007 *go) +{ + struct saa7134_go7007 *saa = go->hpi_context; + struct saa7134_dev *dev = saa->dev; + + saa->top_dma = dma_map_page(&dev->pci->dev, virt_to_page(saa->top), + 0, PAGE_SIZE, DMA_FROM_DEVICE); + if (dma_mapping_error(&dev->pci->dev, saa->top_dma)) + return -ENOMEM; + saa->bottom_dma = dma_map_page(&dev->pci->dev, + virt_to_page(saa->bottom), + 0, PAGE_SIZE, DMA_FROM_DEVICE); + if (dma_mapping_error(&dev->pci->dev, saa->bottom_dma)) { + dma_unmap_page(&dev->pci->dev, saa->top_dma, PAGE_SIZE, + DMA_FROM_DEVICE); + return -ENOMEM; + } + + saa_writel(SAA7134_VIDEO_PORT_CTRL0 >> 2, 0xA300B000); + saa_writel(SAA7134_VIDEO_PORT_CTRL4 >> 2, 0x40000200); + + /* Set HPI interface for video */ + saa_writeb(SAA7134_GPIO_GPMODE0, 0xff); + saa_writeb(SAA7134_GPIO_GPSTATUS0, HPI_ADDR_VIDEO_BUFFER); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_ADDR); + saa_writeb(SAA7134_GPIO_GPMODE0, 0x00); + + /* Enable TS interface */ + saa_writeb(SAA7134_TS_PARALLEL, 0xe6); + + /* Reset TS interface */ + saa_setb(SAA7134_TS_SERIAL1, 0x01); + saa_clearb(SAA7134_TS_SERIAL1, 0x01); + + /* Set up transfer block size */ + saa_writeb(SAA7134_TS_PARALLEL_SERIAL, 128 - 1); + saa_writeb(SAA7134_TS_DMA0, ((PAGE_SIZE >> 7) - 1) & 0xff); + saa_writeb(SAA7134_TS_DMA1, (PAGE_SIZE >> 15) & 0xff); + saa_writeb(SAA7134_TS_DMA2, (PAGE_SIZE >> 31) & 0x3f); + + /* Enable video streaming mode */ + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_VIDEO); + + saa_writel(SAA7134_RS_BA1(5), saa->top_dma); + saa_writel(SAA7134_RS_BA2(5), saa->bottom_dma); + saa_writel(SAA7134_RS_PITCH(5), 128); + saa_writel(SAA7134_RS_CONTROL(5), SAA7134_RS_CONTROL_BURST_MAX); + + /* Enable TS FIFO */ + saa_setl(SAA7134_MAIN_CTRL, SAA7134_MAIN_CTRL_TE5); + + /* Enable DMA IRQ */ + saa_setl(SAA7134_IRQ1, + SAA7134_IRQ1_INTE_RA2_1 | SAA7134_IRQ1_INTE_RA2_0); + + return 0; +} + +static int saa7134_go7007_stream_stop(struct go7007 *go) +{ + struct saa7134_go7007 *saa = go->hpi_context; + struct saa7134_dev *dev; + + if (!saa) + return -EINVAL; + dev = saa->dev; + if (!dev) + return -EINVAL; + + /* Shut down TS FIFO */ + saa_clearl(SAA7134_MAIN_CTRL, SAA7134_MAIN_CTRL_TE5); + + /* Disable DMA IRQ */ + saa_clearl(SAA7134_IRQ1, + SAA7134_IRQ1_INTE_RA2_1 | SAA7134_IRQ1_INTE_RA2_0); + + /* Disable TS interface */ + saa_clearb(SAA7134_TS_PARALLEL, 0x80); + + dma_unmap_page(&dev->pci->dev, saa->top_dma, PAGE_SIZE, + DMA_FROM_DEVICE); + dma_unmap_page(&dev->pci->dev, saa->bottom_dma, PAGE_SIZE, + DMA_FROM_DEVICE); + + return 0; +} + +static int saa7134_go7007_send_firmware(struct go7007 *go, u8 *data, int len) +{ + struct saa7134_go7007 *saa = go->hpi_context; + struct saa7134_dev *dev = saa->dev; + u16 status_reg; + int i; + +#ifdef GO7007_HPI_DEBUG + pr_debug("saa7134-go7007: DownloadBuffer sending %d bytes\n", len); +#endif + + while (len > 0) { + i = len > 64 ? 64 : len; + saa_writeb(SAA7134_GPIO_GPMODE0, 0xff); + saa_writeb(SAA7134_GPIO_GPSTATUS0, HPI_ADDR_INIT_BUFFER); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_ADDR); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE); + while (i-- > 0) { + saa_writeb(SAA7134_GPIO_GPSTATUS0, *data); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_WRITE); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE); + ++data; + --len; + } + for (i = 0; i < 100; ++i) { + gpio_read(dev, HPI_ADDR_INTR_STATUS, &status_reg); + if (!(status_reg & 0x0002)) + break; + } + if (i == 100) { + pr_err("saa7134-go7007: device is hung, status reg = 0x%04x\n", + status_reg); + return -1; + } + } + return 0; +} + +static const struct go7007_hpi_ops saa7134_go7007_hpi_ops = { + .interface_reset = saa7134_go7007_interface_reset, + .write_interrupt = saa7134_go7007_write_interrupt, + .read_interrupt = saa7134_go7007_read_interrupt, + .stream_start = saa7134_go7007_stream_start, + .stream_stop = saa7134_go7007_stream_stop, + .send_firmware = saa7134_go7007_send_firmware, +}; +MODULE_FIRMWARE("go7007/go7007tv.bin"); + +/* --------------------------------------------------------------------------*/ + +static int saa7134_go7007_s_std(struct v4l2_subdev *sd, v4l2_std_id norm) +{ +#if 0 + struct saa7134_go7007 *saa = container_of(sd, struct saa7134_go7007, sd); + struct saa7134_dev *dev = saa->dev; + + return saa7134_s_std_internal(dev, NULL, norm); +#else + return 0; +#endif +} + +static const struct v4l2_subdev_video_ops saa7134_go7007_video_ops = { + .s_std = saa7134_go7007_s_std, +}; + +static const struct v4l2_subdev_ops saa7134_go7007_sd_ops = { + .video = &saa7134_go7007_video_ops, +}; + +/* --------------------------------------------------------------------------*/ + + +/********************* Add/remove functions *********************/ + +static int saa7134_go7007_init(struct saa7134_dev *dev) +{ + struct go7007 *go; + struct saa7134_go7007 *saa; + struct v4l2_subdev *sd; + + pr_debug("saa7134-go7007: probing new SAA713X board\n"); + + go = go7007_alloc(&board_voyager, &dev->pci->dev); + if (go == NULL) + return -ENOMEM; + + saa = kzalloc_obj(struct saa7134_go7007); + if (saa == NULL) { + kfree(go); + return -ENOMEM; + } + + go->board_id = GO7007_BOARDID_PCI_VOYAGER; + snprintf(go->bus_info, sizeof(go->bus_info), "PCI:%s", pci_name(dev->pci)); + strscpy(go->name, saa7134_boards[dev->board].name, sizeof(go->name)); + go->hpi_ops = &saa7134_go7007_hpi_ops; + go->hpi_context = saa; + saa->dev = dev; + + /* Init the subdevice interface */ + sd = &saa->sd; + v4l2_subdev_init(sd, &saa7134_go7007_sd_ops); + v4l2_set_subdevdata(sd, saa); + strscpy(sd->name, "saa7134-go7007", sizeof(sd->name)); + + /* Allocate a couple pages for receiving the compressed stream */ + saa->top = (u8 *)get_zeroed_page(GFP_KERNEL); + if (!saa->top) + goto allocfail; + saa->bottom = (u8 *)get_zeroed_page(GFP_KERNEL); + if (!saa->bottom) + goto allocfail; + + /* Boot the GO7007 */ + if (go7007_boot_encoder(go, go->board_info->flags & + GO7007_BOARD_USE_ONBOARD_I2C) < 0) + goto allocfail; + + /* Do any final GO7007 initialization, then register the + * V4L2 and ALSA interfaces */ + if (go7007_register_encoder(go, go->board_info->num_i2c_devs) < 0) + goto allocfail; + + /* Register the subdevice interface with the go7007 device */ + if (v4l2_device_register_subdev(&go->v4l2_dev, sd) < 0) + pr_info("saa7134-go7007: register subdev failed\n"); + + dev->empress_dev = &go->vdev; + + go->status = STATUS_ONLINE; + return 0; + +allocfail: + if (saa->top) + free_page((unsigned long)saa->top); + if (saa->bottom) + free_page((unsigned long)saa->bottom); + kfree(saa); + kfree(go); + return -ENOMEM; +} + +static int saa7134_go7007_fini(struct saa7134_dev *dev) +{ + struct go7007 *go; + struct saa7134_go7007 *saa; + + if (NULL == dev->empress_dev) + return 0; + + go = video_get_drvdata(dev->empress_dev); + if (go->audio_enabled) + go7007_snd_remove(go); + + saa = go->hpi_context; + go->status = STATUS_SHUTDOWN; + free_page((unsigned long)saa->top); + free_page((unsigned long)saa->bottom); + v4l2_device_unregister_subdev(&saa->sd); + kfree(saa); + vb2_video_unregister_device(&go->vdev); + + v4l2_device_put(&go->v4l2_dev); + dev->empress_dev = NULL; + + return 0; +} + +static struct saa7134_mpeg_ops saa7134_go7007_ops = { + .type = SAA7134_MPEG_GO7007, + .init = saa7134_go7007_init, + .fini = saa7134_go7007_fini, + .irq_ts_done = saa7134_go7007_irq_ts_done, +}; + +static int __init saa7134_go7007_mod_init(void) +{ + return saa7134_ts_register(&saa7134_go7007_ops); +} + +static void __exit saa7134_go7007_mod_cleanup(void) +{ + saa7134_ts_unregister(&saa7134_go7007_ops); +} + +module_init(saa7134_go7007_mod_init); +module_exit(saa7134_go7007_mod_cleanup); + +MODULE_DESCRIPTION("go7007 support for saa7134 based TV cards"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/pci/saa7134/saa7134-i2c.c b/drivers/media/pci/saa7134/saa7134-i2c.c new file mode 100644 index 000000000..1164e91cb --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134-i2c.c @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * device driver for philips saa7134 based TV cards + * i2c interface support + * + * (c) 2001,02 Gerd Knorr [SuSE Labs] + */ + +#include "saa7134.h" +#include "saa7134-reg.h" + +#include +#include +#include +#include +#include + +#include + +/* ----------------------------------------------------------- */ + +static unsigned int i2c_debug; +module_param(i2c_debug, int, 0644); +MODULE_PARM_DESC(i2c_debug,"enable debug messages [i2c]"); + +static unsigned int i2c_scan; +module_param(i2c_scan, int, 0444); +MODULE_PARM_DESC(i2c_scan,"scan i2c bus at insmod time"); + +#define saa7134_i2c_dbg(level, fmt, arg...) do { \ + if (i2c_debug == level) \ + printk(KERN_DEBUG pr_fmt("i2c: " fmt), ## arg); \ + } while (0) + +#define i2c_cont(level, fmt, arg...) do { \ + if (i2c_debug == level) \ + pr_cont(fmt, ## arg); \ + } while (0) + +#define I2C_WAIT_DELAY 32 +#define I2C_WAIT_RETRY 16 + +/* ----------------------------------------------------------- */ + +static char *str_i2c_status[] = { + "IDLE", "DONE_STOP", "BUSY", "TO_SCL", "TO_ARB", "DONE_WRITE", + "DONE_READ", "DONE_WRITE_TO", "DONE_READ_TO", "NO_DEVICE", + "NO_ACKN", "BUS_ERR", "ARB_LOST", "SEQ_ERR", "ST_ERR", "SW_ERR" +}; + +enum i2c_status { + IDLE = 0, // no I2C command pending + DONE_STOP = 1, // I2C command done and STOP executed + BUSY = 2, // executing I2C command + TO_SCL = 3, // executing I2C command, time out on clock stretching + TO_ARB = 4, // time out on arbitration trial, still trying + DONE_WRITE = 5, // I2C command done and awaiting next write command + DONE_READ = 6, // I2C command done and awaiting next read command + DONE_WRITE_TO = 7, // see 5, and time out on status echo + DONE_READ_TO = 8, // see 6, and time out on status echo + NO_DEVICE = 9, // no acknowledge on device slave address + NO_ACKN = 10, // no acknowledge after data byte transfer + BUS_ERR = 11, // bus error + ARB_LOST = 12, // arbitration lost during transfer + SEQ_ERR = 13, // erroneous programming sequence + ST_ERR = 14, // wrong status echoing + SW_ERR = 15 // software error +}; + +static char *str_i2c_attr[] = { + "NOP", "STOP", "CONTINUE", "START" +}; + +enum i2c_attr { + NOP = 0, // no operation on I2C bus + STOP = 1, // stop condition, no associated byte transfer + CONTINUE = 2, // continue with byte transfer + START = 3 // start condition with byte transfer +}; + +static inline enum i2c_status i2c_get_status(struct saa7134_dev *dev) +{ + enum i2c_status status; + + status = saa_readb(SAA7134_I2C_ATTR_STATUS) & 0x0f; + saa7134_i2c_dbg(2, "i2c stat <= %s\n", str_i2c_status[status]); + return status; +} + +static inline void i2c_set_status(struct saa7134_dev *dev, + enum i2c_status status) +{ + saa7134_i2c_dbg(2, "i2c stat => %s\n", str_i2c_status[status]); + saa_andorb(SAA7134_I2C_ATTR_STATUS,0x0f,status); +} + +static inline void i2c_set_attr(struct saa7134_dev *dev, enum i2c_attr attr) +{ + saa7134_i2c_dbg(2, "i2c attr => %s\n", str_i2c_attr[attr]); + saa_andorb(SAA7134_I2C_ATTR_STATUS,0xc0,attr << 6); +} + +static inline int i2c_is_error(enum i2c_status status) +{ + switch (status) { + case NO_DEVICE: + case NO_ACKN: + case BUS_ERR: + case ARB_LOST: + case SEQ_ERR: + case ST_ERR: + return true; + default: + return false; + } +} + +static inline int i2c_is_idle(enum i2c_status status) +{ + switch (status) { + case IDLE: + case DONE_STOP: + return true; + default: + return false; + } +} + +static inline int i2c_is_busy(enum i2c_status status) +{ + switch (status) { + case BUSY: + case TO_SCL: + case TO_ARB: + return true; + default: + return false; + } +} + +static int i2c_is_busy_wait(struct saa7134_dev *dev) +{ + enum i2c_status status; + int count; + + for (count = 0; count < I2C_WAIT_RETRY; count++) { + status = i2c_get_status(dev); + if (!i2c_is_busy(status)) + break; + saa_wait(I2C_WAIT_DELAY); + } + if (I2C_WAIT_RETRY == count) + return false; + return true; +} + +static int i2c_reset(struct saa7134_dev *dev) +{ + enum i2c_status status; + int count; + + saa7134_i2c_dbg(2, "i2c reset\n"); + status = i2c_get_status(dev); + if (!i2c_is_error(status)) + return true; + i2c_set_status(dev,status); + + for (count = 0; count < I2C_WAIT_RETRY; count++) { + status = i2c_get_status(dev); + if (!i2c_is_error(status)) + break; + udelay(I2C_WAIT_DELAY); + } + if (I2C_WAIT_RETRY == count) + return false; + + if (!i2c_is_idle(status)) + return false; + + i2c_set_attr(dev,NOP); + return true; +} + +static inline int i2c_send_byte(struct saa7134_dev *dev, + enum i2c_attr attr, + unsigned char data) +{ + enum i2c_status status; + __u32 dword; + + /* have to write both attr + data in one 32bit word */ + dword = saa_readl(SAA7134_I2C_ATTR_STATUS >> 2); + dword &= 0x0f; + dword |= (attr << 6); + dword |= ((__u32)data << 8); + dword |= 0x00 << 16; /* 100 kHz */ +// dword |= 0x40 << 16; /* 400 kHz */ + dword |= 0xf0 << 24; + saa_writel(SAA7134_I2C_ATTR_STATUS >> 2, dword); + saa7134_i2c_dbg(2, "i2c data => 0x%x\n", data); + + if (!i2c_is_busy_wait(dev)) + return -EIO; + status = i2c_get_status(dev); + if (i2c_is_error(status)) + return -EIO; + return 0; +} + +static inline int i2c_recv_byte(struct saa7134_dev *dev) +{ + enum i2c_status status; + unsigned char data; + + i2c_set_attr(dev,CONTINUE); + if (!i2c_is_busy_wait(dev)) + return -EIO; + status = i2c_get_status(dev); + if (i2c_is_error(status)) + return -EIO; + data = saa_readb(SAA7134_I2C_DATA); + saa7134_i2c_dbg(2, "i2c data <= 0x%x\n", data); + return data; +} + +static int saa7134_i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg *msgs, int num) +{ + struct saa7134_dev *dev = i2c_adap->algo_data; + enum i2c_status status; + unsigned char data; + int addr,rc,i,byte; + + status = i2c_get_status(dev); + if (!i2c_is_idle(status)) + if (!i2c_reset(dev)) + return -EIO; + + saa7134_i2c_dbg(2, "start xfer\n"); + saa7134_i2c_dbg(1, "i2c xfer:"); + for (i = 0; i < num; i++) { + if (!(msgs[i].flags & I2C_M_NOSTART) || 0 == i) { + /* send address */ + saa7134_i2c_dbg(2, "send address\n"); + addr = msgs[i].addr << 1; + if (msgs[i].flags & I2C_M_RD) + addr |= 1; + if (i > 0 && msgs[i].flags & + I2C_M_RD && msgs[i].addr != 0x40 && + msgs[i].addr != 0x41 && + msgs[i].addr != 0x19) { + /* workaround for a saa7134 i2c bug + * needed to talk to the mt352 demux + * thanks to pinnacle for the hint */ + int quirk = 0xfe; + i2c_cont(1, " [%02x quirk]", quirk); + i2c_send_byte(dev,START,quirk); + i2c_recv_byte(dev); + } + i2c_cont(1, " < %02x", addr); + rc = i2c_send_byte(dev,START,addr); + if (rc < 0) + goto err; + } + if (msgs[i].flags & I2C_M_RD) { + /* read bytes */ + saa7134_i2c_dbg(2, "read bytes\n"); + for (byte = 0; byte < msgs[i].len; byte++) { + i2c_cont(1, " ="); + rc = i2c_recv_byte(dev); + if (rc < 0) + goto err; + i2c_cont(1, "%02x", rc); + msgs[i].buf[byte] = rc; + } + /* discard mysterious extra byte when reading + from Samsung S5H1411. i2c bus gets error + if we do not. */ + if (0x19 == msgs[i].addr) { + i2c_cont(1, " ?"); + rc = i2c_recv_byte(dev); + if (rc < 0) + goto err; + i2c_cont(1, "%02x", rc); + } + } else { + /* write bytes */ + saa7134_i2c_dbg(2, "write bytes\n"); + for (byte = 0; byte < msgs[i].len; byte++) { + data = msgs[i].buf[byte]; + i2c_cont(1, " %02x", data); + rc = i2c_send_byte(dev,CONTINUE,data); + if (rc < 0) + goto err; + } + } + } + saa7134_i2c_dbg(2, "xfer done\n"); + i2c_cont(1, " >"); + i2c_set_attr(dev,STOP); + rc = -EIO; + if (!i2c_is_busy_wait(dev)) + goto err; + status = i2c_get_status(dev); + if (i2c_is_error(status)) + goto err; + /* ensure that the bus is idle for at least one bit slot */ + msleep(1); + + i2c_cont(1, "\n"); + return num; + err: + if (1 == i2c_debug) { + status = i2c_get_status(dev); + i2c_cont(1, " ERROR: %s\n", str_i2c_status[status]); + } + return rc; +} + +/* ----------------------------------------------------------- */ + +static u32 functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm saa7134_algo = { + .master_xfer = saa7134_i2c_xfer, + .functionality = functionality, +}; + +static const struct i2c_adapter saa7134_adap_template = { + .owner = THIS_MODULE, + .name = "saa7134", + .algo = &saa7134_algo, +}; + +static const struct i2c_client saa7134_client_template = { + .name = "saa7134 internal", +}; + +/* ----------------------------------------------------------- */ + +/* + * On Medion 7134 reading the SAA7134 chip config EEPROM needs DVB-T + * demod i2c gate closed due to an address clash between this EEPROM + * and the demod one. + */ +static void saa7134_i2c_eeprom_md7134_gate(struct saa7134_dev *dev) +{ + u8 subaddr = 0x7, dmdregval; + u8 data[2]; + int ret; + struct i2c_msg i2cgatemsg_r[] = { {.addr = 0x08, .flags = 0, + .buf = &subaddr, .len = 1}, + {.addr = 0x08, + .flags = I2C_M_RD, + .buf = &dmdregval, .len = 1} + }; + struct i2c_msg i2cgatemsg_w[] = { {.addr = 0x08, .flags = 0, + .buf = data, .len = 2} }; + + ret = i2c_transfer(&dev->i2c_adap, i2cgatemsg_r, 2); + if ((ret == 2) && (dmdregval & 0x2)) { + pr_debug("%s: DVB-T demod i2c gate was left open\n", + dev->name); + + data[0] = subaddr; + data[1] = (dmdregval & ~0x2); + if (i2c_transfer(&dev->i2c_adap, i2cgatemsg_w, 1) != 1) + pr_err("%s: EEPROM i2c gate close failure\n", + dev->name); + } +} + +static int +saa7134_i2c_eeprom(struct saa7134_dev *dev, unsigned char *eedata, int len) +{ + unsigned char buf; + int i,err; + + if (dev->board == SAA7134_BOARD_MD7134) + saa7134_i2c_eeprom_md7134_gate(dev); + + dev->i2c_client.addr = 0xa0 >> 1; + buf = 0; + if (1 != (err = i2c_master_send(&dev->i2c_client,&buf,1))) { + pr_info("%s: Huh, no eeprom present (err=%d)?\n", + dev->name,err); + return -1; + } + if (len != (err = i2c_master_recv(&dev->i2c_client,eedata,len))) { + pr_warn("%s: i2c eeprom read error (err=%d)\n", + dev->name,err); + return -1; + } + + for (i = 0; i < len; i += 16) { + int size = (len - i) > 16 ? 16 : len - i; + + pr_info("i2c eeprom %02x: %*ph\n", i, size, &eedata[i]); + } + + return 0; +} + +static char *i2c_devs[128] = { + [ 0x20 ] = "mpeg encoder (saa6752hs)", + [ 0xa0 >> 1 ] = "eeprom", + [ 0xc0 >> 1 ] = "tuner (analog)", + [ 0x86 >> 1 ] = "tda9887", + [ 0x5a >> 1 ] = "remote control", +}; + +static void do_i2c_scan(struct i2c_client *c) +{ + unsigned char buf; + int i,rc; + + for (i = 0; i < ARRAY_SIZE(i2c_devs); i++) { + c->addr = i; + rc = i2c_master_recv(c,&buf,0); + if (rc < 0) + continue; + pr_info("i2c scan: found device @ 0x%x [%s]\n", + i << 1, i2c_devs[i] ? i2c_devs[i] : "???"); + } +} + +int saa7134_i2c_register(struct saa7134_dev *dev) +{ + dev->i2c_adap = saa7134_adap_template; + dev->i2c_adap.dev.parent = &dev->pci->dev; + strscpy(dev->i2c_adap.name, dev->name, sizeof(dev->i2c_adap.name)); + dev->i2c_adap.algo_data = dev; + i2c_set_adapdata(&dev->i2c_adap, &dev->v4l2_dev); + i2c_add_adapter(&dev->i2c_adap); + + dev->i2c_client = saa7134_client_template; + dev->i2c_client.adapter = &dev->i2c_adap; + + saa7134_i2c_eeprom(dev,dev->eedata,sizeof(dev->eedata)); + if (i2c_scan) + do_i2c_scan(&dev->i2c_client); + + /* Instantiate the IR receiver device, if present */ + saa7134_probe_i2c_ir(dev); + return 0; +} + +int saa7134_i2c_unregister(struct saa7134_dev *dev) +{ + i2c_del_adapter(&dev->i2c_adap); + return 0; +} diff --git a/drivers/media/pci/saa7134/saa7134-input.c b/drivers/media/pci/saa7134/saa7134-input.c new file mode 100644 index 000000000..7f6680de3 --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134-input.c @@ -0,0 +1,1001 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * handle saa7134 IR remotes via linux kernel input layer. + */ + +#include "saa7134.h" +#include "saa7134-reg.h" + +#include +#include +#include +#include +#include + +#define MODULE_NAME "saa7134" + +static unsigned int disable_ir; +module_param(disable_ir, int, 0444); +MODULE_PARM_DESC(disable_ir,"disable infrared remote support"); + +static unsigned int ir_debug; +module_param(ir_debug, int, 0644); +MODULE_PARM_DESC(ir_debug,"enable debug messages [IR]"); + +static int pinnacle_remote; +module_param(pinnacle_remote, int, 0644); /* Choose Pinnacle PCTV remote */ +MODULE_PARM_DESC(pinnacle_remote, "Specify Pinnacle PCTV remote: 0=coloured, 1=grey (defaults to 0)"); + +#define input_dbg(fmt, arg...) do { \ + if (ir_debug) \ + printk(KERN_DEBUG pr_fmt("input: " fmt), ## arg); \ + } while (0) +#define ir_dbg(ir, fmt, arg...) do { \ + if (ir_debug) \ + printk(KERN_DEBUG pr_fmt("ir %s: " fmt), ir->rc->device_name, \ + ## arg); \ + } while (0) + +/* Helper function for raw decoding at GPIO16 or GPIO18 */ +static int saa7134_raw_decode_irq(struct saa7134_dev *dev); + +/* -------------------- GPIO generic keycode builder -------------------- */ + +static int build_key(struct saa7134_dev *dev) +{ + struct saa7134_card_ir *ir = dev->remote; + u32 gpio, data; + + /* here comes the additional handshake steps for some cards */ + switch (dev->board) { + case SAA7134_BOARD_GOTVIEW_7135: + saa_setb(SAA7134_GPIO_GPSTATUS1, 0x80); + saa_clearb(SAA7134_GPIO_GPSTATUS1, 0x80); + break; + } + /* rising SAA7134_GPIO_GPRESCAN reads the status */ + saa_clearb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN); + + gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2); + if (ir->polling) { + if (ir->last_gpio == gpio) + return 0; + ir->last_gpio = gpio; + } + + data = ir_extract_bits(gpio, ir->mask_keycode); + input_dbg("build_key gpio=0x%x mask=0x%x data=%d\n", + gpio, ir->mask_keycode, data); + + switch (dev->board) { + case SAA7134_BOARD_KWORLD_PLUS_TV_ANALOG: + if (data == ir->mask_keycode) + rc_keyup(ir->dev); + else + rc_keydown_notimeout(ir->dev, RC_PROTO_UNKNOWN, data, + 0); + return 0; + } + + if (ir->polling) { + if ((ir->mask_keydown && (0 != (gpio & ir->mask_keydown))) || + (ir->mask_keyup && (0 == (gpio & ir->mask_keyup)))) { + rc_keydown_notimeout(ir->dev, RC_PROTO_UNKNOWN, data, + 0); + } else { + rc_keyup(ir->dev); + } + } + else { /* IRQ driven mode - handle key press and release in one go */ + if ((ir->mask_keydown && (0 != (gpio & ir->mask_keydown))) || + (ir->mask_keyup && (0 == (gpio & ir->mask_keyup)))) { + rc_keydown_notimeout(ir->dev, RC_PROTO_UNKNOWN, data, + 0); + rc_keyup(ir->dev); + } + } + + return 0; +} + +/* --------------------- Chip specific I2C key builders ----------------- */ + +static int get_key_flydvb_trio(struct IR_i2c *ir, enum rc_proto *protocol, + u32 *scancode, u8 *toggle) +{ + int gpio, rc; + int attempt = 0; + unsigned char b; + + /* We need this to access GPI Used by the saa_readl macro. */ + struct saa7134_dev *dev = ir->c->adapter->algo_data; + + if (dev == NULL) { + ir_dbg(ir, "get_key_flydvb_trio: ir->c->adapter->algo_data is NULL!\n"); + return -EIO; + } + + /* rising SAA7134_GPIGPRESCAN reads the status */ + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + + gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2); + + if (0x40000 & ~gpio) + return 0; /* No button press */ + + /* poll IR chip */ + /* weak up the IR chip */ + b = 0; + + while (1 != i2c_master_send(ir->c, &b, 1)) { + if ((attempt++) < 10) { + /* + * wait a bit for next attempt - + * I don't know how make it better + */ + msleep(10); + continue; + } + ir_dbg(ir, "send wake up byte to pic16C505 (IR chip)failed %dx\n", + attempt); + return -EIO; + } + rc = i2c_master_recv(ir->c, &b, 1); + if (rc != 1) { + ir_dbg(ir, "read error\n"); + if (rc < 0) + return rc; + return -EIO; + } + + *protocol = RC_PROTO_UNKNOWN; + *scancode = b; + *toggle = 0; + return 1; +} + +static int get_key_msi_tvanywhere_plus(struct IR_i2c *ir, + enum rc_proto *protocol, + u32 *scancode, u8 *toggle) +{ + unsigned char b; + int gpio, rc; + + /* is needed to access GPIO. Used by the saa_readl macro. */ + struct saa7134_dev *dev = ir->c->adapter->algo_data; + if (dev == NULL) { + ir_dbg(ir, "get_key_msi_tvanywhere_plus: ir->c->adapter->algo_data is NULL!\n"); + return -EIO; + } + + /* rising SAA7134_GPIO_GPRESCAN reads the status */ + + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + + gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2); + + /* GPIO&0x40 is pulsed low when a button is pressed. Don't do + I2C receive if gpio&0x40 is not low. */ + + if (gpio & 0x40) + return 0; /* No button press */ + + /* GPIO says there is a button press. Get it. */ + + rc = i2c_master_recv(ir->c, &b, 1); + if (rc != 1) { + ir_dbg(ir, "read error\n"); + if (rc < 0) + return rc; + return -EIO; + } + + /* No button press */ + + if (b == 0xff) + return 0; + + /* Button pressed */ + + input_dbg("get_key_msi_tvanywhere_plus: Key = 0x%02X\n", b); + *protocol = RC_PROTO_UNKNOWN; + *scancode = b; + *toggle = 0; + return 1; +} + +/* copied and modified from get_key_msi_tvanywhere_plus() */ +static int get_key_kworld_pc150u(struct IR_i2c *ir, enum rc_proto *protocol, + u32 *scancode, u8 *toggle) +{ + unsigned char b; + unsigned int gpio; + int rc; + + /* is needed to access GPIO. Used by the saa_readl macro. */ + struct saa7134_dev *dev = ir->c->adapter->algo_data; + if (dev == NULL) { + ir_dbg(ir, "get_key_kworld_pc150u: ir->c->adapter->algo_data is NULL!\n"); + return -EIO; + } + + /* rising SAA7134_GPIO_GPRESCAN reads the status */ + + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + + gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2); + + /* GPIO&0x100 is pulsed low when a button is pressed. Don't do + I2C receive if gpio&0x100 is not low. */ + + if (gpio & 0x100) + return 0; /* No button press */ + + /* GPIO says there is a button press. Get it. */ + + rc = i2c_master_recv(ir->c, &b, 1); + if (rc != 1) { + ir_dbg(ir, "read error\n"); + if (rc < 0) + return rc; + return -EIO; + } + + /* No button press */ + + if (b == 0xff) + return 0; + + /* Button pressed */ + + input_dbg("get_key_kworld_pc150u: Key = 0x%02X\n", b); + *protocol = RC_PROTO_UNKNOWN; + *scancode = b; + *toggle = 0; + return 1; +} + +static int get_key_purpletv(struct IR_i2c *ir, enum rc_proto *protocol, + u32 *scancode, u8 *toggle) +{ + int rc; + unsigned char b; + + /* poll IR chip */ + rc = i2c_master_recv(ir->c, &b, 1); + if (rc != 1) { + ir_dbg(ir, "read error\n"); + if (rc < 0) + return rc; + return -EIO; + } + + /* no button press */ + if (b==0) + return 0; + + /* repeating */ + if (b & 0x80) + return 1; + + *protocol = RC_PROTO_UNKNOWN; + *scancode = b; + *toggle = 0; + return 1; +} + +static int get_key_beholdm6xx(struct IR_i2c *ir, enum rc_proto *protocol, + u32 *scancode, u8 *toggle) +{ + int rc; + unsigned char data[12]; + u32 gpio; + + struct saa7134_dev *dev = ir->c->adapter->algo_data; + + /* rising SAA7134_GPIO_GPRESCAN reads the status */ + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + + gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2); + + if (0x400000 & ~gpio) + return 0; /* No button press */ + + ir->c->addr = 0x5a >> 1; + + rc = i2c_master_recv(ir->c, data, 12); + if (rc != 12) { + ir_dbg(ir, "read error\n"); + if (rc < 0) + return rc; + return -EIO; + } + + if (data[9] != (unsigned char)(~data[8])) + return 0; + + *protocol = RC_PROTO_NECX; + *scancode = RC_SCANCODE_NECX(data[11] << 8 | data[10], data[9]); + *toggle = 0; + return 1; +} + +/* Common (grey or coloured) pinnacle PCTV remote handling + * + */ +static int get_key_pinnacle(struct IR_i2c *ir, enum rc_proto *protocol, + u32 *scancode, u8 *toggle, int parity_offset, + int marker, int code_modulo) +{ + int rc; + unsigned char b[4]; + unsigned int start = 0,parity = 0,code = 0; + + /* poll IR chip */ + rc = i2c_master_recv(ir->c, b, 4); + if (rc != 4) { + ir_dbg(ir, "read error\n"); + if (rc < 0) + return rc; + return -EIO; + } + + for (start = 0; start < ARRAY_SIZE(b); start++) { + if (b[start] == marker) { + code=b[(start+parity_offset + 1) % 4]; + parity=b[(start+parity_offset) % 4]; + } + } + + /* Empty Request */ + if (parity == 0) + return 0; + + /* Repeating... */ + if (ir->old == parity) + return 0; + + ir->old = parity; + + /* drop special codes when a key is held down a long time for the grey controller + In this case, the second bit of the code is asserted */ + if (marker == 0xfe && (code & 0x40)) + return 0; + + code %= code_modulo; + + *protocol = RC_PROTO_UNKNOWN; + *scancode = code; + *toggle = 0; + + ir_dbg(ir, "Pinnacle PCTV key %02x\n", code); + return 1; +} + +/* The grey pinnacle PCTV remote + * + * There are one issue with this remote: + * - I2c packet does not change when the same key is pressed quickly. The workaround + * is to hold down each key for about half a second, so that another code is generated + * in the i2c packet, and the function can distinguish key presses. + * + * Sylvain Pasche + */ +static int get_key_pinnacle_grey(struct IR_i2c *ir, enum rc_proto *protocol, + u32 *scancode, u8 *toggle) +{ + + return get_key_pinnacle(ir, protocol, scancode, toggle, 1, 0xfe, 0xff); +} + + +/* The new pinnacle PCTV remote (with the colored buttons) + * + * Ricardo Cerqueira + */ +static int get_key_pinnacle_color(struct IR_i2c *ir, enum rc_proto *protocol, + u32 *scancode, u8 *toggle) +{ + /* code_modulo parameter (0x88) is used to reduce code value to fit inside IR_KEYTAB_SIZE + * + * this is the only value that results in 42 unique + * codes < 128 + */ + + return get_key_pinnacle(ir, protocol, scancode, toggle, 2, 0x80, 0x88); +} + +void saa7134_input_irq(struct saa7134_dev *dev) +{ + struct saa7134_card_ir *ir; + + if (!dev || !dev->remote) + return; + + ir = dev->remote; + if (!ir->running) + return; + + if (!ir->polling && !ir->raw_decode) { + build_key(dev); + } else if (ir->raw_decode) { + saa7134_raw_decode_irq(dev); + } +} + +static void saa7134_input_timer(struct timer_list *t) +{ + struct saa7134_card_ir *ir = timer_container_of(ir, t, timer); + struct saa7134_dev *dev = ir->dev->priv; + + build_key(dev); + mod_timer(&ir->timer, jiffies + msecs_to_jiffies(ir->polling)); +} + +int saa7134_ir_open(struct rc_dev *rc) +{ + struct saa7134_dev *dev = rc->priv; + struct saa7134_card_ir *ir = dev->remote; + + /* Moved here from saa7134_input_init1() because the latter + * is not called on device resume */ + switch (dev->board) { + case SAA7134_BOARD_MD2819: + case SAA7134_BOARD_KWORLD_VSTREAM_XPERT: + case SAA7134_BOARD_AVERMEDIA_305: + case SAA7134_BOARD_AVERMEDIA_307: + case SAA7134_BOARD_AVERMEDIA_505: + case SAA7134_BOARD_AVERMEDIA_STUDIO_305: + case SAA7134_BOARD_AVERMEDIA_STUDIO_505: + case SAA7134_BOARD_AVERMEDIA_STUDIO_307: + case SAA7134_BOARD_AVERMEDIA_STUDIO_507: + case SAA7134_BOARD_AVERMEDIA_STUDIO_507UA: + case SAA7134_BOARD_AVERMEDIA_GO_007_FM: + case SAA7134_BOARD_AVERMEDIA_M102: + case SAA7134_BOARD_AVERMEDIA_GO_007_FM_PLUS: + /* Without this we won't receive key up events */ + saa_setb(SAA7134_GPIO_GPMODE0, 0x4); + saa_setb(SAA7134_GPIO_GPSTATUS0, 0x4); + break; + case SAA7134_BOARD_AVERMEDIA_777: + case SAA7134_BOARD_AVERMEDIA_A16AR: + /* Without this we won't receive key up events */ + saa_setb(SAA7134_GPIO_GPMODE1, 0x1); + saa_setb(SAA7134_GPIO_GPSTATUS1, 0x1); + break; + case SAA7134_BOARD_AVERMEDIA_A16D: + /* Without this we won't receive key up events */ + saa_setb(SAA7134_GPIO_GPMODE1, 0x1); + saa_setb(SAA7134_GPIO_GPSTATUS1, 0x1); + break; + case SAA7134_BOARD_GOTVIEW_7135: + saa_setb(SAA7134_GPIO_GPMODE1, 0x80); + break; + } + + ir->running = true; + + if (ir->polling) { + timer_setup(&ir->timer, saa7134_input_timer, 0); + ir->timer.expires = jiffies + HZ; + add_timer(&ir->timer); + } + + return 0; +} + +void saa7134_ir_close(struct rc_dev *rc) +{ + struct saa7134_dev *dev = rc->priv; + struct saa7134_card_ir *ir = dev->remote; + + if (ir->polling) + timer_delete_sync(&ir->timer); + + ir->running = false; +} + +int saa7134_input_init1(struct saa7134_dev *dev) +{ + struct saa7134_card_ir *ir; + struct rc_dev *rc; + char *ir_codes = NULL; + u32 mask_keycode = 0; + u32 mask_keydown = 0; + u32 mask_keyup = 0; + unsigned polling = 0; + bool raw_decode = false; + int err; + + if (dev->has_remote != SAA7134_REMOTE_GPIO) + return -ENODEV; + if (disable_ir) + return -ENODEV; + + /* detect & configure */ + switch (dev->board) { + case SAA7134_BOARD_FLYVIDEO2000: + case SAA7134_BOARD_FLYVIDEO3000: + case SAA7134_BOARD_FLYTVPLATINUM_FM: + case SAA7134_BOARD_FLYTVPLATINUM_MINI2: + case SAA7134_BOARD_ROVERMEDIA_LINK_PRO_FM: + ir_codes = RC_MAP_FLYVIDEO; + mask_keycode = 0xEC00000; + mask_keydown = 0x0040000; + break; + case SAA7134_BOARD_CINERGY400: + case SAA7134_BOARD_CINERGY600: + case SAA7134_BOARD_CINERGY600_MK3: + ir_codes = RC_MAP_CINERGY; + mask_keycode = 0x00003f; + mask_keyup = 0x040000; + break; + case SAA7134_BOARD_ECS_TVP3XP: + case SAA7134_BOARD_ECS_TVP3XP_4CB5: + ir_codes = RC_MAP_EZTV; + mask_keycode = 0x00017c; + mask_keyup = 0x000002; + polling = 50; // ms + break; + case SAA7134_BOARD_KWORLD_XPERT: + case SAA7134_BOARD_AVACSSMARTTV: + ir_codes = RC_MAP_PIXELVIEW; + mask_keycode = 0x00001F; + mask_keyup = 0x000020; + polling = 50; // ms + break; + case SAA7134_BOARD_MD2819: + case SAA7134_BOARD_KWORLD_VSTREAM_XPERT: + case SAA7134_BOARD_AVERMEDIA_305: + case SAA7134_BOARD_AVERMEDIA_307: + case SAA7134_BOARD_AVERMEDIA_505: + case SAA7134_BOARD_AVERMEDIA_STUDIO_305: + case SAA7134_BOARD_AVERMEDIA_STUDIO_505: + case SAA7134_BOARD_AVERMEDIA_STUDIO_307: + case SAA7134_BOARD_AVERMEDIA_STUDIO_507: + case SAA7134_BOARD_AVERMEDIA_STUDIO_507UA: + case SAA7134_BOARD_AVERMEDIA_GO_007_FM: + case SAA7134_BOARD_AVERMEDIA_M102: + case SAA7134_BOARD_AVERMEDIA_GO_007_FM_PLUS: + ir_codes = RC_MAP_AVERMEDIA; + mask_keycode = 0x0007C8; + mask_keydown = 0x000010; + polling = 50; // ms + /* GPIO stuff moved to saa7134_ir_open() */ + break; + case SAA7134_BOARD_AVERMEDIA_M135A: + ir_codes = RC_MAP_AVERMEDIA_M135A; + mask_keydown = 0x0040000; /* Enable GPIO18 line on both edges */ + mask_keyup = 0x0040000; + mask_keycode = 0xffff; + raw_decode = true; + break; + case SAA7134_BOARD_AVERMEDIA_M733A: + ir_codes = RC_MAP_AVERMEDIA_M733A_RM_K6; + mask_keydown = 0x0040000; + mask_keyup = 0x0040000; + mask_keycode = 0xffff; + raw_decode = true; + break; + case SAA7134_BOARD_AVERMEDIA_777: + case SAA7134_BOARD_AVERMEDIA_A16AR: + ir_codes = RC_MAP_AVERMEDIA; + mask_keycode = 0x02F200; + mask_keydown = 0x000400; + polling = 50; // ms + /* GPIO stuff moved to saa7134_ir_open() */ + break; + case SAA7134_BOARD_AVERMEDIA_A16D: + ir_codes = RC_MAP_AVERMEDIA_A16D; + mask_keycode = 0x02F200; + mask_keydown = 0x000400; + polling = 50; /* ms */ + /* GPIO stuff moved to saa7134_ir_open() */ + break; + case SAA7134_BOARD_KWORLD_TERMINATOR: + ir_codes = RC_MAP_PIXELVIEW; + mask_keycode = 0x00001f; + mask_keyup = 0x000060; + polling = 50; // ms + break; + case SAA7134_BOARD_MANLI_MTV001: + case SAA7134_BOARD_MANLI_MTV002: + ir_codes = RC_MAP_MANLI; + mask_keycode = 0x001f00; + mask_keyup = 0x004000; + polling = 50; /* ms */ + break; + case SAA7134_BOARD_BEHOLD_409FM: + case SAA7134_BOARD_BEHOLD_401: + case SAA7134_BOARD_BEHOLD_403: + case SAA7134_BOARD_BEHOLD_403FM: + case SAA7134_BOARD_BEHOLD_405: + case SAA7134_BOARD_BEHOLD_405FM: + case SAA7134_BOARD_BEHOLD_407: + case SAA7134_BOARD_BEHOLD_407FM: + case SAA7134_BOARD_BEHOLD_409: + case SAA7134_BOARD_BEHOLD_505FM: + case SAA7134_BOARD_BEHOLD_505RDS_MK5: + case SAA7134_BOARD_BEHOLD_505RDS_MK3: + case SAA7134_BOARD_BEHOLD_507_9FM: + case SAA7134_BOARD_BEHOLD_507RDS_MK3: + case SAA7134_BOARD_BEHOLD_507RDS_MK5: + ir_codes = RC_MAP_MANLI; + mask_keycode = 0x003f00; + mask_keyup = 0x004000; + polling = 50; /* ms */ + break; + case SAA7134_BOARD_BEHOLD_COLUMBUS_TVFM: + ir_codes = RC_MAP_BEHOLD_COLUMBUS; + mask_keycode = 0x003f00; + mask_keyup = 0x004000; + polling = 50; // ms + break; + case SAA7134_BOARD_SEDNA_PC_TV_CARDBUS: + ir_codes = RC_MAP_PCTV_SEDNA; + mask_keycode = 0x001f00; + mask_keyup = 0x004000; + polling = 50; // ms + break; + case SAA7134_BOARD_GOTVIEW_7135: + ir_codes = RC_MAP_GOTVIEW7135; + mask_keycode = 0x0003CC; + mask_keydown = 0x000010; + polling = 5; /* ms */ + /* GPIO stuff moved to saa7134_ir_open() */ + break; + case SAA7134_BOARD_VIDEOMATE_TV_PVR: + case SAA7134_BOARD_VIDEOMATE_GOLD_PLUS: + case SAA7134_BOARD_VIDEOMATE_TV_GOLD_PLUSII: + ir_codes = RC_MAP_VIDEOMATE_TV_PVR; + mask_keycode = 0x00003F; + mask_keyup = 0x400000; + polling = 50; // ms + break; + case SAA7134_BOARD_PROTEUS_2309: + ir_codes = RC_MAP_PROTEUS_2309; + mask_keycode = 0x00007F; + mask_keyup = 0x000080; + polling = 50; // ms + break; + case SAA7134_BOARD_VIDEOMATE_DVBT_300: + case SAA7134_BOARD_VIDEOMATE_DVBT_200: + ir_codes = RC_MAP_VIDEOMATE_TV_PVR; + mask_keycode = 0x003F00; + mask_keyup = 0x040000; + break; + case SAA7134_BOARD_FLYDVBS_LR300: + case SAA7134_BOARD_FLYDVBT_LR301: + case SAA7134_BOARD_FLYDVBTDUO: + ir_codes = RC_MAP_FLYDVB; + mask_keycode = 0x0001F00; + mask_keydown = 0x0040000; + break; + case SAA7134_BOARD_ASUSTeK_P7131_DUAL: + case SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA: + case SAA7134_BOARD_ASUSTeK_P7131_ANALOG: + ir_codes = RC_MAP_ASUS_PC39; + mask_keydown = 0x0040000; /* Enable GPIO18 line on both edges */ + mask_keyup = 0x0040000; + mask_keycode = 0xffff; + raw_decode = true; + break; + case SAA7134_BOARD_ASUSTeK_PS3_100: + ir_codes = RC_MAP_ASUS_PS3_100; + mask_keydown = 0x0040000; + mask_keyup = 0x0040000; + mask_keycode = 0xffff; + raw_decode = true; + break; + case SAA7134_BOARD_ENCORE_ENLTV: + case SAA7134_BOARD_ENCORE_ENLTV_FM: + ir_codes = RC_MAP_ENCORE_ENLTV; + mask_keycode = 0x00007f; + mask_keyup = 0x040000; + polling = 50; // ms + break; + case SAA7134_BOARD_ENCORE_ENLTV_FM53: + case SAA7134_BOARD_ENCORE_ENLTV_FM3: + ir_codes = RC_MAP_ENCORE_ENLTV_FM53; + mask_keydown = 0x0040000; /* Enable GPIO18 line on both edges */ + mask_keyup = 0x0040000; + mask_keycode = 0xffff; + raw_decode = true; + break; + case SAA7134_BOARD_10MOONSTVMASTER3: + ir_codes = RC_MAP_ENCORE_ENLTV; + mask_keycode = 0x5f80000; + mask_keyup = 0x8000000; + polling = 50; //ms + break; + case SAA7134_BOARD_GENIUS_TVGO_A11MCE: + ir_codes = RC_MAP_GENIUS_TVGO_A11MCE; + mask_keycode = 0xff; + mask_keydown = 0xf00000; + polling = 50; /* ms */ + break; + case SAA7134_BOARD_REAL_ANGEL_220: + ir_codes = RC_MAP_REAL_AUDIO_220_32_KEYS; + mask_keycode = 0x3f00; + mask_keyup = 0x4000; + polling = 50; /* ms */ + break; + case SAA7134_BOARD_KWORLD_PLUS_TV_ANALOG: + ir_codes = RC_MAP_KWORLD_PLUS_TV_ANALOG; + mask_keycode = 0x7f; + polling = 40; /* ms */ + break; + case SAA7134_BOARD_VIDEOMATE_S350: + ir_codes = RC_MAP_VIDEOMATE_S350; + mask_keycode = 0x003f00; + mask_keydown = 0x040000; + break; + case SAA7134_BOARD_LEADTEK_WINFAST_DTV1000S: + ir_codes = RC_MAP_WINFAST; + mask_keycode = 0x5f00; + mask_keyup = 0x020000; + polling = 50; /* ms */ + break; + case SAA7134_BOARD_VIDEOMATE_M1F: + ir_codes = RC_MAP_VIDEOMATE_K100; + mask_keycode = 0x0ff00; + mask_keyup = 0x040000; + break; + case SAA7134_BOARD_HAUPPAUGE_HVR1150: + case SAA7134_BOARD_HAUPPAUGE_HVR1120: + ir_codes = RC_MAP_HAUPPAUGE; + mask_keydown = 0x0040000; /* Enable GPIO18 line on both edges */ + mask_keyup = 0x0040000; + mask_keycode = 0xffff; + raw_decode = true; + break; + case SAA7134_BOARD_LEADTEK_WINFAST_TV2100_FM: + ir_codes = RC_MAP_LEADTEK_Y04G0051; + mask_keydown = 0x0040000; /* Enable GPIO18 line on both edges */ + mask_keyup = 0x0040000; + mask_keycode = 0xffff; + raw_decode = true; + break; + } + if (NULL == ir_codes) { + pr_err("Oops: IR config error [card=%d]\n", dev->board); + return -ENODEV; + } + + ir = kzalloc_obj(*ir); + rc = rc_allocate_device(RC_DRIVER_SCANCODE); + if (!ir || !rc) { + err = -ENOMEM; + goto err_out_free; + } + + ir->dev = rc; + dev->remote = ir; + + /* init hardware-specific stuff */ + ir->mask_keycode = mask_keycode; + ir->mask_keydown = mask_keydown; + ir->mask_keyup = mask_keyup; + ir->polling = polling; + ir->raw_decode = raw_decode; + + /* init input device */ + snprintf(ir->phys, sizeof(ir->phys), "pci-%s/ir0", + pci_name(dev->pci)); + + rc->priv = dev; + rc->open = saa7134_ir_open; + rc->close = saa7134_ir_close; + if (raw_decode) { + rc->driver_type = RC_DRIVER_IR_RAW; + rc->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER; + } + + rc->device_name = saa7134_boards[dev->board].name; + rc->input_phys = ir->phys; + rc->input_id.bustype = BUS_PCI; + rc->input_id.version = 1; + if (dev->pci->subsystem_vendor) { + rc->input_id.vendor = dev->pci->subsystem_vendor; + rc->input_id.product = dev->pci->subsystem_device; + } else { + rc->input_id.vendor = dev->pci->vendor; + rc->input_id.product = dev->pci->device; + } + rc->dev.parent = &dev->pci->dev; + rc->map_name = ir_codes; + rc->driver_name = MODULE_NAME; + rc->min_timeout = 1; + rc->timeout = IR_DEFAULT_TIMEOUT; + rc->max_timeout = 10 * IR_DEFAULT_TIMEOUT; + + err = rc_register_device(rc); + if (err) + goto err_out_free; + + return 0; + +err_out_free: + rc_free_device(rc); + dev->remote = NULL; + kfree(ir); + return err; +} + +void saa7134_input_fini(struct saa7134_dev *dev) +{ + if (NULL == dev->remote) + return; + + rc_unregister_device(dev->remote->dev); + rc_free_device(dev->remote->dev); + kfree(dev->remote); + dev->remote = NULL; +} + +void saa7134_probe_i2c_ir(struct saa7134_dev *dev) +{ + struct i2c_board_info info; + struct i2c_msg msg_msi = { + .addr = 0x50, + .flags = I2C_M_RD, + .len = 0, + .buf = NULL, + }; + int rc; + + if (disable_ir) { + input_dbg("IR has been disabled, not probing for i2c remote\n"); + return; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + memset(&dev->init_data, 0, sizeof(dev->init_data)); + strscpy(info.type, "ir_video", I2C_NAME_SIZE); + + switch (dev->board) { + case SAA7134_BOARD_PINNACLE_PCTV_110i: + case SAA7134_BOARD_PINNACLE_PCTV_310i: + dev->init_data.name = "Pinnacle PCTV"; + if (pinnacle_remote == 0) { + dev->init_data.get_key = get_key_pinnacle_color; + dev->init_data.ir_codes = RC_MAP_PINNACLE_COLOR; + info.addr = 0x47; + } else { + dev->init_data.get_key = get_key_pinnacle_grey; + dev->init_data.ir_codes = RC_MAP_PINNACLE_GREY; + info.addr = 0x47; + } + break; + case SAA7134_BOARD_UPMOST_PURPLE_TV: + dev->init_data.name = "Purple TV"; + dev->init_data.get_key = get_key_purpletv; + dev->init_data.ir_codes = RC_MAP_PURPLETV; + info.addr = 0x7a; + break; + case SAA7134_BOARD_MSI_TVATANYWHERE_PLUS: + dev->init_data.name = "MSI TV@nywhere Plus"; + dev->init_data.get_key = get_key_msi_tvanywhere_plus; + dev->init_data.ir_codes = RC_MAP_MSI_TVANYWHERE_PLUS; + /* + * MSI TV@nyware Plus requires more frequent polling + * otherwise it will miss some keypresses + */ + dev->init_data.polling_interval = 50; + info.addr = 0x30; + /* MSI TV@nywhere Plus controller doesn't seem to + respond to probes unless we read something from + an existing device. Weird... + REVISIT: might no longer be needed */ + rc = i2c_transfer(&dev->i2c_adap, &msg_msi, 1); + input_dbg("probe 0x%02x @ %s: %s\n", + msg_msi.addr, dev->i2c_adap.name, + (1 == rc) ? "yes" : "no"); + break; + case SAA7134_BOARD_SNAZIO_TVPVR_PRO: + dev->init_data.name = "SnaZio* TVPVR PRO"; + dev->init_data.get_key = get_key_msi_tvanywhere_plus; + dev->init_data.ir_codes = RC_MAP_MSI_TVANYWHERE_PLUS; + /* + * MSI TV@nyware Plus requires more frequent polling + * otherwise it will miss some keypresses + */ + dev->init_data.polling_interval = 50; + info.addr = 0x30; + /* + * MSI TV@nywhere Plus controller doesn't seem to + * respond to probes unless we read something from + * an existing device. Weird... + * REVISIT: might no longer be needed + */ + rc = i2c_transfer(&dev->i2c_adap, &msg_msi, 1); + input_dbg("probe 0x%02x @ %s: %s\n", + msg_msi.addr, dev->i2c_adap.name, + (rc == 1) ? "yes" : "no"); + break; + case SAA7134_BOARD_KWORLD_PC150U: + /* copied and modified from MSI TV@nywhere Plus */ + dev->init_data.name = "Kworld PC150-U"; + dev->init_data.get_key = get_key_kworld_pc150u; + dev->init_data.ir_codes = RC_MAP_KWORLD_PC150U; + info.addr = 0x30; + /* MSI TV@nywhere Plus controller doesn't seem to + respond to probes unless we read something from + an existing device. Weird... + REVISIT: might no longer be needed */ + rc = i2c_transfer(&dev->i2c_adap, &msg_msi, 1); + input_dbg("probe 0x%02x @ %s: %s\n", + msg_msi.addr, dev->i2c_adap.name, + (1 == rc) ? "yes" : "no"); + break; + case SAA7134_BOARD_HAUPPAUGE_HVR1110: + dev->init_data.name = saa7134_boards[dev->board].name; + dev->init_data.ir_codes = RC_MAP_HAUPPAUGE; + dev->init_data.type = RC_PROTO_BIT_RC5 | + RC_PROTO_BIT_RC6_MCE | RC_PROTO_BIT_RC6_6A_32; + dev->init_data.internal_get_key_func = IR_KBD_GET_KEY_HAUP_XVR; + info.addr = 0x71; + break; + case SAA7134_BOARD_BEHOLD_607FM_MK3: + case SAA7134_BOARD_BEHOLD_607FM_MK5: + case SAA7134_BOARD_BEHOLD_609FM_MK3: + case SAA7134_BOARD_BEHOLD_609FM_MK5: + case SAA7134_BOARD_BEHOLD_607RDS_MK3: + case SAA7134_BOARD_BEHOLD_607RDS_MK5: + case SAA7134_BOARD_BEHOLD_609RDS_MK3: + case SAA7134_BOARD_BEHOLD_609RDS_MK5: + case SAA7134_BOARD_BEHOLD_M6: + case SAA7134_BOARD_BEHOLD_M63: + case SAA7134_BOARD_BEHOLD_M6_EXTRA: + case SAA7134_BOARD_BEHOLD_H6: + case SAA7134_BOARD_BEHOLD_X7: + case SAA7134_BOARD_BEHOLD_H7: + case SAA7134_BOARD_BEHOLD_A7: + dev->init_data.name = "BeholdTV"; + dev->init_data.get_key = get_key_beholdm6xx; + dev->init_data.ir_codes = RC_MAP_BEHOLD; + dev->init_data.type = RC_PROTO_BIT_NECX; + info.addr = 0x2d; + break; + case SAA7134_BOARD_AVERMEDIA_CARDBUS_501: + case SAA7134_BOARD_AVERMEDIA_CARDBUS_506: + info.addr = 0x40; + break; + case SAA7134_BOARD_AVERMEDIA_A706: + info.addr = 0x41; + break; + case SAA7134_BOARD_FLYDVB_TRIO: + dev->init_data.name = "FlyDVB Trio"; + dev->init_data.get_key = get_key_flydvb_trio; + dev->init_data.ir_codes = RC_MAP_FLYDVB; + info.addr = 0x0b; + break; + default: + input_dbg("No I2C IR support for board %x\n", dev->board); + return; + } + + if (dev->init_data.name) + info.platform_data = &dev->init_data; + i2c_new_client_device(&dev->i2c_adap, &info); +} + +static int saa7134_raw_decode_irq(struct saa7134_dev *dev) +{ + struct saa7134_card_ir *ir = dev->remote; + int space; + + /* Generate initial event */ + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + space = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2) & ir->mask_keydown; + ir_raw_event_store_edge(dev->remote->dev, !space); + + return 1; +} diff --git a/drivers/media/pci/saa7134/saa7134-reg.h b/drivers/media/pci/saa7134/saa7134-reg.h new file mode 100644 index 000000000..56b12641d --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134-reg.h @@ -0,0 +1,377 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * + * philips saa7134 registers + */ + +/* ------------------------------------------------------------------ */ +/* + * PCI ID's + */ +#ifndef PCI_DEVICE_ID_PHILIPS_SAA7130 +# define PCI_DEVICE_ID_PHILIPS_SAA7130 0x7130 +#endif +#ifndef PCI_DEVICE_ID_PHILIPS_SAA7133 +# define PCI_DEVICE_ID_PHILIPS_SAA7133 0x7133 +#endif +#ifndef PCI_DEVICE_ID_PHILIPS_SAA7134 +# define PCI_DEVICE_ID_PHILIPS_SAA7134 0x7134 +#endif +#ifndef PCI_DEVICE_ID_PHILIPS_SAA7135 +# define PCI_DEVICE_ID_PHILIPS_SAA7135 0x7135 +#endif + +/* ------------------------------------------------------------------ */ +/* + * registers -- 32 bit + */ + +/* DMA channels, n = 0 ... 6 */ +#define SAA7134_RS_BA1(n) ((0x200 >> 2) + 4*n) +#define SAA7134_RS_BA2(n) ((0x204 >> 2) + 4*n) +#define SAA7134_RS_PITCH(n) ((0x208 >> 2) + 4*n) +#define SAA7134_RS_CONTROL(n) ((0x20c >> 2) + 4*n) +#define SAA7134_RS_CONTROL_WSWAP (0x01 << 25) +#define SAA7134_RS_CONTROL_BSWAP (0x01 << 24) +#define SAA7134_RS_CONTROL_BURST_2 (0x01 << 21) +#define SAA7134_RS_CONTROL_BURST_4 (0x02 << 21) +#define SAA7134_RS_CONTROL_BURST_8 (0x03 << 21) +#define SAA7134_RS_CONTROL_BURST_16 (0x04 << 21) +#define SAA7134_RS_CONTROL_BURST_32 (0x05 << 21) +#define SAA7134_RS_CONTROL_BURST_64 (0x06 << 21) +#define SAA7134_RS_CONTROL_BURST_MAX (0x07 << 21) +#define SAA7134_RS_CONTROL_ME (0x01 << 20) +#define SAA7134_FIFO_SIZE (0x2a0 >> 2) +#define SAA7134_THRESHOULD (0x2a4 >> 2) + +#define SAA7133_NUM_SAMPLES (0x588 >> 2) +#define SAA7133_AUDIO_CHANNEL (0x58c >> 2) +#define SAA7133_AUDIO_FORMAT (0x58f >> 2) +#define SAA7133_DIGITAL_OUTPUT_SEL1 (0x46c >> 2) +#define SAA7133_DIGITAL_OUTPUT_SEL2 (0x470 >> 2) +#define SAA7133_DIGITAL_INPUT_XBAR1 (0x464 >> 2) +#define SAA7133_ANALOG_IO_SELECT (0x594 >> 2) + +/* main control */ +#define SAA7134_MAIN_CTRL (0x2a8 >> 2) +#define SAA7134_MAIN_CTRL_VPLLE (1 << 15) +#define SAA7134_MAIN_CTRL_APLLE (1 << 14) +#define SAA7134_MAIN_CTRL_EXOSC (1 << 13) +#define SAA7134_MAIN_CTRL_EVFE1 (1 << 12) +#define SAA7134_MAIN_CTRL_EVFE2 (1 << 11) +#define SAA7134_MAIN_CTRL_ESFE (1 << 10) +#define SAA7134_MAIN_CTRL_EBADC (1 << 9) +#define SAA7134_MAIN_CTRL_EBDAC (1 << 8) +#define SAA7134_MAIN_CTRL_TE6 (1 << 6) +#define SAA7134_MAIN_CTRL_TE5 (1 << 5) +#define SAA7134_MAIN_CTRL_TE4 (1 << 4) +#define SAA7134_MAIN_CTRL_TE3 (1 << 3) +#define SAA7134_MAIN_CTRL_TE2 (1 << 2) +#define SAA7134_MAIN_CTRL_TE1 (1 << 1) +#define SAA7134_MAIN_CTRL_TE0 (1 << 0) + +/* DMA status */ +#define SAA7134_DMA_STATUS (0x2ac >> 2) + +/* audio / video status */ +#define SAA7134_AV_STATUS (0x2c0 >> 2) +#define SAA7134_AV_STATUS_STEREO (1 << 17) +#define SAA7134_AV_STATUS_DUAL (1 << 16) +#define SAA7134_AV_STATUS_PILOT (1 << 15) +#define SAA7134_AV_STATUS_SMB (1 << 14) +#define SAA7134_AV_STATUS_DMB (1 << 13) +#define SAA7134_AV_STATUS_VDSP (1 << 12) +#define SAA7134_AV_STATUS_IIC_STATUS (3 << 10) +#define SAA7134_AV_STATUS_MVM (7 << 7) +#define SAA7134_AV_STATUS_FIDT (1 << 6) +#define SAA7134_AV_STATUS_INTL (1 << 5) +#define SAA7134_AV_STATUS_RDCAP (1 << 4) +#define SAA7134_AV_STATUS_PWR_ON (1 << 3) +#define SAA7134_AV_STATUS_LOAD_ERR (1 << 2) +#define SAA7134_AV_STATUS_TRIG_ERR (1 << 1) +#define SAA7134_AV_STATUS_CONF_ERR (1 << 0) + +/* interrupt */ +#define SAA7134_IRQ1 (0x2c4 >> 2) +#define SAA7134_IRQ1_INTE_RA3_1 (1 << 25) +#define SAA7134_IRQ1_INTE_RA3_0 (1 << 24) +#define SAA7134_IRQ1_INTE_RA2_3 (1 << 19) +#define SAA7134_IRQ1_INTE_RA2_2 (1 << 18) +#define SAA7134_IRQ1_INTE_RA2_1 (1 << 17) +#define SAA7134_IRQ1_INTE_RA2_0 (1 << 16) +#define SAA7134_IRQ1_INTE_RA1_3 (1 << 11) +#define SAA7134_IRQ1_INTE_RA1_2 (1 << 10) +#define SAA7134_IRQ1_INTE_RA1_1 (1 << 9) +#define SAA7134_IRQ1_INTE_RA1_0 (1 << 8) +#define SAA7134_IRQ1_INTE_RA0_7 (1 << 7) +#define SAA7134_IRQ1_INTE_RA0_6 (1 << 6) +#define SAA7134_IRQ1_INTE_RA0_5 (1 << 5) +#define SAA7134_IRQ1_INTE_RA0_4 (1 << 4) +#define SAA7134_IRQ1_INTE_RA0_3 (1 << 3) +#define SAA7134_IRQ1_INTE_RA0_2 (1 << 2) +#define SAA7134_IRQ1_INTE_RA0_1 (1 << 1) +#define SAA7134_IRQ1_INTE_RA0_0 (1 << 0) + +#define SAA7134_IRQ2 (0x2c8 >> 2) +#define SAA7134_IRQ2_INTE_GPIO23_N (1 << 17) /* negative edge */ +#define SAA7134_IRQ2_INTE_GPIO23_P (1 << 16) /* positive edge */ +#define SAA7134_IRQ2_INTE_GPIO22_N (1 << 15) /* negative edge */ +#define SAA7134_IRQ2_INTE_GPIO22_P (1 << 14) /* positive edge */ +#define SAA7134_IRQ2_INTE_GPIO18_N (1 << 13) /* negative edge */ +#define SAA7134_IRQ2_INTE_GPIO18_P (1 << 12) /* positive edge */ +#define SAA7134_IRQ2_INTE_GPIO16_N (1 << 11) /* negative edge */ +#define SAA7134_IRQ2_INTE_GPIO16_P (1 << 10) /* positive edge */ +#define SAA7134_IRQ2_INTE_SC2 (1 << 9) +#define SAA7134_IRQ2_INTE_SC1 (1 << 8) +#define SAA7134_IRQ2_INTE_SC0 (1 << 7) +#define SAA7134_IRQ2_INTE_DEC4 (1 << 6) +#define SAA7134_IRQ2_INTE_DEC3 (1 << 5) +#define SAA7134_IRQ2_INTE_DEC2 (1 << 4) +#define SAA7134_IRQ2_INTE_DEC1 (1 << 3) +#define SAA7134_IRQ2_INTE_DEC0 (1 << 2) +#define SAA7134_IRQ2_INTE_PE (1 << 1) +#define SAA7134_IRQ2_INTE_AR (1 << 0) + +#define SAA7134_IRQ_REPORT (0x2cc >> 2) +#define SAA7134_IRQ_REPORT_GPIO23 (1 << 17) +#define SAA7134_IRQ_REPORT_GPIO22 (1 << 16) +#define SAA7134_IRQ_REPORT_GPIO18 (1 << 15) +#define SAA7134_IRQ_REPORT_GPIO16 (1 << 14) +#define SAA7134_IRQ_REPORT_LOAD_ERR (1 << 13) +#define SAA7134_IRQ_REPORT_CONF_ERR (1 << 12) +#define SAA7134_IRQ_REPORT_TRIG_ERR (1 << 11) +#define SAA7134_IRQ_REPORT_MMC (1 << 10) +#define SAA7134_IRQ_REPORT_FIDT (1 << 9) +#define SAA7134_IRQ_REPORT_INTL (1 << 8) +#define SAA7134_IRQ_REPORT_RDCAP (1 << 7) +#define SAA7134_IRQ_REPORT_PWR_ON (1 << 6) +#define SAA7134_IRQ_REPORT_PE (1 << 5) +#define SAA7134_IRQ_REPORT_AR (1 << 4) +#define SAA7134_IRQ_REPORT_DONE_RA3 (1 << 3) +#define SAA7134_IRQ_REPORT_DONE_RA2 (1 << 2) +#define SAA7134_IRQ_REPORT_DONE_RA1 (1 << 1) +#define SAA7134_IRQ_REPORT_DONE_RA0 (1 << 0) +#define SAA7134_IRQ_STATUS (0x2d0 >> 2) + + +/* ------------------------------------------------------------------ */ +/* + * registers -- 8 bit + */ + +/* video decoder */ +#define SAA7134_INCR_DELAY 0x101 +#define SAA7134_ANALOG_IN_CTRL1 0x102 +#define SAA7134_ANALOG_IN_CTRL2 0x103 +#define SAA7134_ANALOG_IN_CTRL3 0x104 +#define SAA7134_ANALOG_IN_CTRL4 0x105 +#define SAA7134_HSYNC_START 0x106 +#define SAA7134_HSYNC_STOP 0x107 +#define SAA7134_SYNC_CTRL 0x108 +#define SAA7134_SYNC_CTRL_AUFD (1 << 7) +#define SAA7134_LUMA_CTRL 0x109 +#define SAA7134_LUMA_CTRL_LDEL (1 << 5) +#define SAA7134_DEC_LUMA_BRIGHT 0x10a +#define SAA7134_DEC_LUMA_CONTRAST 0x10b +#define SAA7134_DEC_CHROMA_SATURATION 0x10c +#define SAA7134_DEC_CHROMA_HUE 0x10d +#define SAA7134_CHROMA_CTRL1 0x10e +#define SAA7134_CHROMA_CTRL1_AUTO0 (1 << 1) +#define SAA7134_CHROMA_CTRL1_FCTC (1 << 2) +#define SAA7134_CHROMA_GAIN 0x10f +#define SAA7134_CHROMA_CTRL2 0x110 +#define SAA7134_MODE_DELAY_CTRL 0x111 + +#define SAA7134_ANALOG_ADC 0x114 +#define SAA7134_ANALOG_ADC_AUTO1 (1 << 2) +#define SAA7134_VGATE_START 0x115 +#define SAA7134_VGATE_STOP 0x116 +#define SAA7134_MISC_VGATE_MSB 0x117 +#define SAA7134_RAW_DATA_GAIN 0x118 +#define SAA7134_RAW_DATA_OFFSET 0x119 +#define SAA7134_STATUS_VIDEO1 0x11e +#define SAA7134_STATUS_VIDEO2 0x11f + +/* video scaler */ +#define SAA7134_SOURCE_TIMING1 0x000 +#define SAA7134_SOURCE_TIMING2 0x001 +#define SAA7134_REGION_ENABLE 0x004 +#define SAA7134_SCALER_STATUS0 0x006 +#define SAA7134_SCALER_STATUS1 0x007 +#define SAA7134_START_GREEN 0x00c +#define SAA7134_START_BLUE 0x00d +#define SAA7134_START_RED 0x00e +#define SAA7134_GREEN_PATH(x) (0x010 +x) +#define SAA7134_BLUE_PATH(x) (0x020 +x) +#define SAA7134_RED_PATH(x) (0x030 +x) + +#define TASK_A 0x040 +#define TASK_B 0x080 +#define SAA7134_TASK_CONDITIONS(t) (0x000 +t) +#define SAA7134_FIELD_HANDLING(t) (0x001 +t) +#define SAA7134_DATA_PATH(t) (0x002 +t) +#define SAA7134_VBI_H_START1(t) (0x004 +t) +#define SAA7134_VBI_H_START2(t) (0x005 +t) +#define SAA7134_VBI_H_STOP1(t) (0x006 +t) +#define SAA7134_VBI_H_STOP2(t) (0x007 +t) +#define SAA7134_VBI_V_START1(t) (0x008 +t) +#define SAA7134_VBI_V_START2(t) (0x009 +t) +#define SAA7134_VBI_V_STOP1(t) (0x00a +t) +#define SAA7134_VBI_V_STOP2(t) (0x00b +t) +#define SAA7134_VBI_H_LEN1(t) (0x00c +t) +#define SAA7134_VBI_H_LEN2(t) (0x00d +t) +#define SAA7134_VBI_V_LEN1(t) (0x00e +t) +#define SAA7134_VBI_V_LEN2(t) (0x00f +t) + +#define SAA7134_VIDEO_H_START1(t) (0x014 +t) +#define SAA7134_VIDEO_H_START2(t) (0x015 +t) +#define SAA7134_VIDEO_H_STOP1(t) (0x016 +t) +#define SAA7134_VIDEO_H_STOP2(t) (0x017 +t) +#define SAA7134_VIDEO_V_START1(t) (0x018 +t) +#define SAA7134_VIDEO_V_START2(t) (0x019 +t) +#define SAA7134_VIDEO_V_STOP1(t) (0x01a +t) +#define SAA7134_VIDEO_V_STOP2(t) (0x01b +t) +#define SAA7134_VIDEO_PIXELS1(t) (0x01c +t) +#define SAA7134_VIDEO_PIXELS2(t) (0x01d +t) +#define SAA7134_VIDEO_LINES1(t) (0x01e +t) +#define SAA7134_VIDEO_LINES2(t) (0x01f +t) + +#define SAA7134_H_PRESCALE(t) (0x020 +t) +#define SAA7134_ACC_LENGTH(t) (0x021 +t) +#define SAA7134_LEVEL_CTRL(t) (0x022 +t) +#define SAA7134_FIR_PREFILTER_CTRL(t) (0x023 +t) +#define SAA7134_LUMA_BRIGHT(t) (0x024 +t) +#define SAA7134_LUMA_CONTRAST(t) (0x025 +t) +#define SAA7134_CHROMA_SATURATION(t) (0x026 +t) +#define SAA7134_VBI_H_SCALE_INC1(t) (0x028 +t) +#define SAA7134_VBI_H_SCALE_INC2(t) (0x029 +t) +#define SAA7134_VBI_PHASE_OFFSET_LUMA(t) (0x02a +t) +#define SAA7134_VBI_PHASE_OFFSET_CHROMA(t) (0x02b +t) +#define SAA7134_H_SCALE_INC1(t) (0x02c +t) +#define SAA7134_H_SCALE_INC2(t) (0x02d +t) +#define SAA7134_H_PHASE_OFF_LUMA(t) (0x02e +t) +#define SAA7134_H_PHASE_OFF_CHROMA(t) (0x02f +t) +#define SAA7134_V_SCALE_RATIO1(t) (0x030 +t) +#define SAA7134_V_SCALE_RATIO2(t) (0x031 +t) +#define SAA7134_V_FILTER(t) (0x032 +t) +#define SAA7134_V_PHASE_OFFSET0(t) (0x034 +t) +#define SAA7134_V_PHASE_OFFSET1(t) (0x035 +t) +#define SAA7134_V_PHASE_OFFSET2(t) (0x036 +t) +#define SAA7134_V_PHASE_OFFSET3(t) (0x037 +t) + +/* clipping & dma */ +#define SAA7134_OFMT_VIDEO_A 0x300 +#define SAA7134_OFMT_DATA_A 0x301 +#define SAA7134_OFMT_VIDEO_B 0x302 +#define SAA7134_OFMT_DATA_B 0x303 +#define SAA7134_ALPHA_NOCLIP 0x304 +#define SAA7134_ALPHA_CLIP 0x305 +#define SAA7134_UV_PIXEL 0x308 +#define SAA7134_CLIP_RED 0x309 +#define SAA7134_CLIP_GREEN 0x30a +#define SAA7134_CLIP_BLUE 0x30b + +/* i2c bus */ +#define SAA7134_I2C_ATTR_STATUS 0x180 +#define SAA7134_I2C_DATA 0x181 +#define SAA7134_I2C_CLOCK_SELECT 0x182 +#define SAA7134_I2C_TIMER 0x183 + +/* audio */ +#define SAA7134_NICAM_ADD_DATA1 0x140 +#define SAA7134_NICAM_ADD_DATA2 0x141 +#define SAA7134_NICAM_STATUS 0x142 +#define SAA7134_AUDIO_STATUS 0x143 +#define SAA7134_NICAM_ERROR_COUNT 0x144 +#define SAA7134_IDENT_SIF 0x145 +#define SAA7134_LEVEL_READOUT1 0x146 +#define SAA7134_LEVEL_READOUT2 0x147 +#define SAA7134_NICAM_ERROR_LOW 0x148 +#define SAA7134_NICAM_ERROR_HIGH 0x149 +#define SAA7134_DCXO_IDENT_CTRL 0x14a +#define SAA7134_DEMODULATOR 0x14b +#define SAA7134_AGC_GAIN_SELECT 0x14c +#define SAA7134_CARRIER1_FREQ0 0x150 +#define SAA7134_CARRIER1_FREQ1 0x151 +#define SAA7134_CARRIER1_FREQ2 0x152 +#define SAA7134_CARRIER2_FREQ0 0x154 +#define SAA7134_CARRIER2_FREQ1 0x155 +#define SAA7134_CARRIER2_FREQ2 0x156 +#define SAA7134_NUM_SAMPLES0 0x158 +#define SAA7134_NUM_SAMPLES1 0x159 +#define SAA7134_NUM_SAMPLES2 0x15a +#define SAA7134_AUDIO_FORMAT_CTRL 0x15b +#define SAA7134_MONITOR_SELECT 0x160 +#define SAA7134_FM_DEEMPHASIS 0x161 +#define SAA7134_FM_DEMATRIX 0x162 +#define SAA7134_CHANNEL1_LEVEL 0x163 +#define SAA7134_CHANNEL2_LEVEL 0x164 +#define SAA7134_NICAM_CONFIG 0x165 +#define SAA7134_NICAM_LEVEL_ADJUST 0x166 +#define SAA7134_STEREO_DAC_OUTPUT_SELECT 0x167 +#define SAA7134_I2S_OUTPUT_FORMAT 0x168 +#define SAA7134_I2S_OUTPUT_SELECT 0x169 +#define SAA7134_I2S_OUTPUT_LEVEL 0x16a +#define SAA7134_DSP_OUTPUT_SELECT 0x16b +#define SAA7134_AUDIO_MUTE_CTRL 0x16c +#define SAA7134_SIF_SAMPLE_FREQ 0x16d +#define SAA7134_ANALOG_IO_SELECT 0x16e +#define SAA7134_AUDIO_CLOCK0 0x170 +#define SAA7134_AUDIO_CLOCK1 0x171 +#define SAA7134_AUDIO_CLOCK2 0x172 +#define SAA7134_AUDIO_PLL_CTRL 0x173 +#define SAA7134_AUDIO_CLOCKS_PER_FIELD0 0x174 +#define SAA7134_AUDIO_CLOCKS_PER_FIELD1 0x175 +#define SAA7134_AUDIO_CLOCKS_PER_FIELD2 0x176 + +/* video port output */ +#define SAA7134_VIDEO_PORT_CTRL0 0x190 +#define SAA7134_VIDEO_PORT_CTRL1 0x191 +#define SAA7134_VIDEO_PORT_CTRL2 0x192 +#define SAA7134_VIDEO_PORT_CTRL3 0x193 +#define SAA7134_VIDEO_PORT_CTRL4 0x194 +#define SAA7134_VIDEO_PORT_CTRL5 0x195 +#define SAA7134_VIDEO_PORT_CTRL6 0x196 +#define SAA7134_VIDEO_PORT_CTRL7 0x197 +#define SAA7134_VIDEO_PORT_CTRL8 0x198 + +/* transport stream interface */ +#define SAA7134_TS_PARALLEL 0x1a0 +#define SAA7134_TS_PARALLEL_SERIAL 0x1a1 +#define SAA7134_TS_SERIAL0 0x1a2 +#define SAA7134_TS_SERIAL1 0x1a3 +#define SAA7134_TS_DMA0 0x1a4 +#define SAA7134_TS_DMA1 0x1a5 +#define SAA7134_TS_DMA2 0x1a6 + +/* GPIO Controls */ +#define SAA7134_GPIO_GPRESCAN 0x80 +#define SAA7134_GPIO_27_25 0x0E + +#define SAA7134_GPIO_GPMODE0 0x1B0 +#define SAA7134_GPIO_GPMODE1 0x1B1 +#define SAA7134_GPIO_GPMODE2 0x1B2 +#define SAA7134_GPIO_GPMODE3 0x1B3 +#define SAA7134_GPIO_GPSTATUS0 0x1B4 +#define SAA7134_GPIO_GPSTATUS1 0x1B5 +#define SAA7134_GPIO_GPSTATUS2 0x1B6 +#define SAA7134_GPIO_GPSTATUS3 0x1B7 + +/* I2S output */ +#define SAA7134_I2S_AUDIO_OUTPUT 0x1c0 + +/* test modes */ +#define SAA7134_SPECIAL_MODE 0x1d0 +#define SAA7134_PRODUCTION_TEST_MODE 0x1d1 + +/* audio -- saa7133 + saa7135 only */ +#define SAA7135_DSP_RWSTATE 0x580 +#define SAA7135_DSP_RWSTATE_ERR (1 << 3) +#define SAA7135_DSP_RWSTATE_IDA (1 << 2) +#define SAA7135_DSP_RWSTATE_RDB (1 << 1) +#define SAA7135_DSP_RWSTATE_WRR (1 << 0) + +#define SAA7135_DSP_RWCLEAR 0x586 +#define SAA7135_DSP_RWCLEAR_RERR 1 + +#define SAA7133_I2S_AUDIO_CONTROL 0x591 diff --git a/drivers/media/pci/saa7134/saa7134-ts.c b/drivers/media/pci/saa7134/saa7134-ts.c new file mode 100644 index 000000000..1b4403306 --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134-ts.c @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * device driver for philips saa7134 based TV cards + * video4linux video interface + * + * (c) 2001,02 Gerd Knorr [SuSE Labs] + */ + +#include "saa7134.h" +#include "saa7134-reg.h" + +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ + +static unsigned int ts_debug; +module_param(ts_debug, int, 0644); +MODULE_PARM_DESC(ts_debug,"enable debug messages [ts]"); + +#define ts_dbg(fmt, arg...) do { \ + if (ts_debug) \ + printk(KERN_DEBUG pr_fmt("ts: " fmt), ## arg); \ + } while (0) + +/* ------------------------------------------------------------------ */ +static int buffer_activate(struct saa7134_dev *dev, + struct saa7134_buf *buf, + struct saa7134_buf *next) +{ + + ts_dbg("buffer_activate [%p]", buf); + buf->top_seen = 0; + + if (!dev->ts_started) + dev->ts_field = V4L2_FIELD_TOP; + + if (NULL == next) + next = buf; + if (V4L2_FIELD_TOP == dev->ts_field) { + ts_dbg("- [top] buf=%p next=%p\n", buf, next); + saa_writel(SAA7134_RS_BA1(5),saa7134_buffer_base(buf)); + saa_writel(SAA7134_RS_BA2(5),saa7134_buffer_base(next)); + dev->ts_field = V4L2_FIELD_BOTTOM; + } else { + ts_dbg("- [bottom] buf=%p next=%p\n", buf, next); + saa_writel(SAA7134_RS_BA1(5),saa7134_buffer_base(next)); + saa_writel(SAA7134_RS_BA2(5),saa7134_buffer_base(buf)); + dev->ts_field = V4L2_FIELD_TOP; + } + + /* start DMA */ + saa7134_set_dmabits(dev); + + mod_timer(&dev->ts_q.timeout, jiffies+TS_BUFFER_TIMEOUT); + + if (!dev->ts_started) + saa7134_ts_start(dev); + + return 0; +} + +int saa7134_ts_buffer_init(struct vb2_buffer *vb2) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2); + struct saa7134_dmaqueue *dmaq = vb2->vb2_queue->drv_priv; + struct saa7134_buf *buf = container_of(vbuf, struct saa7134_buf, vb2); + + dmaq->curr = NULL; + buf->activate = buffer_activate; + + return 0; +} +EXPORT_SYMBOL_GPL(saa7134_ts_buffer_init); + +int saa7134_ts_buffer_prepare(struct vb2_buffer *vb2) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2); + struct saa7134_dmaqueue *dmaq = vb2->vb2_queue->drv_priv; + struct saa7134_dev *dev = dmaq->dev; + struct saa7134_buf *buf = container_of(vbuf, struct saa7134_buf, vb2); + struct sg_table *dma = vb2_dma_sg_plane_desc(vb2, 0); + unsigned int lines, llength, size; + + ts_dbg("buffer_prepare [%p]\n", buf); + + llength = TS_PACKET_SIZE; + lines = dev->ts.nr_packets; + + size = lines * llength; + if (vb2_plane_size(vb2, 0) < size) + return -EINVAL; + + vb2_set_plane_payload(vb2, 0, size); + vbuf->field = dev->field; + + return saa7134_pgtable_build(dev->pci, &dmaq->pt, dma->sgl, dma->nents, + saa7134_buffer_startpage(buf)); +} +EXPORT_SYMBOL_GPL(saa7134_ts_buffer_prepare); + +int saa7134_ts_queue_setup(struct vb2_queue *q, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct saa7134_dmaqueue *dmaq = q->drv_priv; + struct saa7134_dev *dev = dmaq->dev; + int size = TS_PACKET_SIZE * dev->ts.nr_packets; + + if (0 == *nbuffers) + *nbuffers = dev->ts.nr_bufs; + *nbuffers = saa7134_buffer_count(size, *nbuffers); + if (*nbuffers < 3) + *nbuffers = 3; + *nplanes = 1; + sizes[0] = size; + return 0; +} +EXPORT_SYMBOL_GPL(saa7134_ts_queue_setup); + +int saa7134_ts_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct saa7134_dmaqueue *dmaq = vq->drv_priv; + struct saa7134_dev *dev = dmaq->dev; + + /* + * Planar video capture and TS share the same DMA channel, + * so only one can be active at a time. + */ + if (vb2_is_busy(&dev->video_vbq) && dev->fmt->planar) { + struct saa7134_buf *buf, *tmp; + + list_for_each_entry_safe(buf, tmp, &dmaq->queue, entry) { + list_del(&buf->entry); + vb2_buffer_done(&buf->vb2.vb2_buf, + VB2_BUF_STATE_QUEUED); + } + if (dmaq->curr) { + vb2_buffer_done(&dmaq->curr->vb2.vb2_buf, + VB2_BUF_STATE_QUEUED); + dmaq->curr = NULL; + } + return -EBUSY; + } + dmaq->seq_nr = 0; + return 0; +} +EXPORT_SYMBOL_GPL(saa7134_ts_start_streaming); + +void saa7134_ts_stop_streaming(struct vb2_queue *vq) +{ + struct saa7134_dmaqueue *dmaq = vq->drv_priv; + struct saa7134_dev *dev = dmaq->dev; + + saa7134_ts_stop(dev); + saa7134_stop_streaming(dev, dmaq); +} +EXPORT_SYMBOL_GPL(saa7134_ts_stop_streaming); + +struct vb2_ops saa7134_ts_qops = { + .queue_setup = saa7134_ts_queue_setup, + .buf_init = saa7134_ts_buffer_init, + .buf_prepare = saa7134_ts_buffer_prepare, + .buf_queue = saa7134_vb2_buffer_queue, + .stop_streaming = saa7134_ts_stop_streaming, +}; +EXPORT_SYMBOL_GPL(saa7134_ts_qops); + +/* ----------------------------------------------------------- */ +/* exported stuff */ + +static unsigned int tsbufs = 8; +module_param(tsbufs, int, 0444); +MODULE_PARM_DESC(tsbufs, "number of ts buffers for read/write IO, range 2-32"); + +static unsigned int ts_nr_packets = 64; +module_param(ts_nr_packets, int, 0444); +MODULE_PARM_DESC(ts_nr_packets,"size of a ts buffers (in ts packets)"); + +int saa7134_ts_init_hw(struct saa7134_dev *dev) +{ + /* deactivate TS softreset */ + saa_writeb(SAA7134_TS_SERIAL1, 0x00); + /* TSSOP high active, TSVAL high active, TSLOCK ignored */ + saa_writeb(SAA7134_TS_PARALLEL, 0x6c); + saa_writeb(SAA7134_TS_PARALLEL_SERIAL, (TS_PACKET_SIZE-1)); + saa_writeb(SAA7134_TS_DMA0, ((dev->ts.nr_packets-1)&0xff)); + saa_writeb(SAA7134_TS_DMA1, (((dev->ts.nr_packets-1)>>8)&0xff)); + /* TSNOPIT=0, TSCOLAP=0 */ + saa_writeb(SAA7134_TS_DMA2, + ((((dev->ts.nr_packets-1)>>16)&0x3f) | 0x00)); + + return 0; +} + +int saa7134_ts_init1(struct saa7134_dev *dev) +{ + /* sanitycheck insmod options */ + if (tsbufs < 2) + tsbufs = 2; + if (tsbufs > VIDEO_MAX_FRAME) + tsbufs = VIDEO_MAX_FRAME; + if (ts_nr_packets < 4) + ts_nr_packets = 4; + if (ts_nr_packets > 312) + ts_nr_packets = 312; + dev->ts.nr_bufs = tsbufs; + dev->ts.nr_packets = ts_nr_packets; + + INIT_LIST_HEAD(&dev->ts_q.queue); + timer_setup(&dev->ts_q.timeout, saa7134_buffer_timeout, 0); + dev->ts_q.dev = dev; + dev->ts_q.need_two = 1; + dev->ts_started = 0; + saa7134_pgtable_alloc(dev->pci, &dev->ts_q.pt); + + /* init TS hw */ + saa7134_ts_init_hw(dev); + + return 0; +} + +/* Function for stop TS */ +int saa7134_ts_stop(struct saa7134_dev *dev) +{ + ts_dbg("TS stop\n"); + + if (!dev->ts_started) + return 0; + + /* Stop TS stream */ + switch (saa7134_boards[dev->board].ts_type) { + case SAA7134_MPEG_TS_PARALLEL: + saa_writeb(SAA7134_TS_PARALLEL, 0x6c); + dev->ts_started = 0; + break; + case SAA7134_MPEG_TS_SERIAL: + saa_writeb(SAA7134_TS_SERIAL0, 0x40); + dev->ts_started = 0; + break; + } + return 0; +} + +/* Function for start TS */ +int saa7134_ts_start(struct saa7134_dev *dev) +{ + ts_dbg("TS start\n"); + + if (WARN_ON(dev->ts_started)) + return 0; + + /* dma: setup channel 5 (= TS) */ + saa_writeb(SAA7134_TS_DMA0, (dev->ts.nr_packets - 1) & 0xff); + saa_writeb(SAA7134_TS_DMA1, + ((dev->ts.nr_packets - 1) >> 8) & 0xff); + /* TSNOPIT=0, TSCOLAP=0 */ + saa_writeb(SAA7134_TS_DMA2, + (((dev->ts.nr_packets - 1) >> 16) & 0x3f) | 0x00); + saa_writel(SAA7134_RS_PITCH(5), TS_PACKET_SIZE); + saa_writel(SAA7134_RS_CONTROL(5), SAA7134_RS_CONTROL_BURST_16 | + SAA7134_RS_CONTROL_ME | + (dev->ts_q.pt.dma >> 12)); + + /* reset hardware TS buffers */ + saa_writeb(SAA7134_TS_SERIAL1, 0x00); + saa_writeb(SAA7134_TS_SERIAL1, 0x03); + saa_writeb(SAA7134_TS_SERIAL1, 0x00); + saa_writeb(SAA7134_TS_SERIAL1, 0x01); + + /* TS clock non-inverted */ + saa_writeb(SAA7134_TS_SERIAL1, 0x00); + + /* Start TS stream */ + switch (saa7134_boards[dev->board].ts_type) { + case SAA7134_MPEG_TS_PARALLEL: + saa_writeb(SAA7134_TS_SERIAL0, 0x40); + saa_writeb(SAA7134_TS_PARALLEL, 0xec | + (saa7134_boards[dev->board].ts_force_val << 4)); + break; + case SAA7134_MPEG_TS_SERIAL: + saa_writeb(SAA7134_TS_SERIAL0, 0xd8); + saa_writeb(SAA7134_TS_PARALLEL, 0x6c | + (saa7134_boards[dev->board].ts_force_val << 4)); + saa_writeb(SAA7134_TS_PARALLEL_SERIAL, 0xbc); + saa_writeb(SAA7134_TS_SERIAL1, 0x02); + break; + } + + dev->ts_started = 1; + + return 0; +} + +int saa7134_ts_fini(struct saa7134_dev *dev) +{ + timer_delete_sync(&dev->ts_q.timeout); + saa7134_pgtable_free(dev->pci, &dev->ts_q.pt); + return 0; +} + +void saa7134_irq_ts_done(struct saa7134_dev *dev, unsigned long status) +{ + enum v4l2_field field; + + spin_lock(&dev->slock); + if (dev->ts_q.curr) { + field = dev->ts_field; + if (field != V4L2_FIELD_TOP) { + if ((status & 0x100000) != 0x000000) + goto done; + } else { + if ((status & 0x100000) != 0x100000) + goto done; + } + saa7134_buffer_finish(dev, &dev->ts_q, VB2_BUF_STATE_DONE); + } + saa7134_buffer_next(dev,&dev->ts_q); + + done: + spin_unlock(&dev->slock); +} diff --git a/drivers/media/pci/saa7134/saa7134-tvaudio.c b/drivers/media/pci/saa7134/saa7134-tvaudio.c new file mode 100644 index 000000000..9e0c442ab --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134-tvaudio.c @@ -0,0 +1,1068 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * device driver for philips saa7134 based TV cards + * tv audio decoder (fm stereo, nicam, ...) + * + * (c) 2001-03 Gerd Knorr [SuSE Labs] + */ + +#include "saa7134.h" +#include "saa7134-reg.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ + +static unsigned int audio_debug; +module_param(audio_debug, int, 0644); +MODULE_PARM_DESC(audio_debug,"enable debug messages [tv audio]"); + +static unsigned int audio_ddep; +module_param(audio_ddep, int, 0644); +MODULE_PARM_DESC(audio_ddep,"audio ddep overwrite"); + +static int audio_clock_override = UNSET; +module_param(audio_clock_override, int, 0644); + +static int audio_clock_tweak; +module_param(audio_clock_tweak, int, 0644); +MODULE_PARM_DESC(audio_clock_tweak, "Audio clock tick fine tuning for cards with audio crystal that's slightly off (range [-1024 .. 1024])"); + +#define audio_dbg(level, fmt, arg...) do { \ + if (audio_debug >= level) \ + printk(KERN_DEBUG pr_fmt("audio: " fmt), ## arg); \ + } while (0) + +/* msecs */ +#define SCAN_INITIAL_DELAY 1000 +#define SCAN_SAMPLE_DELAY 200 +#define SCAN_SUBCARRIER_DELAY 2000 + +/* ------------------------------------------------------------------ */ +/* saa7134 code */ + +static struct mainscan { + char *name; + v4l2_std_id std; + int carr; +} mainscan[] = { + { + .name = "MN", + .std = V4L2_STD_MN, + .carr = 4500, + },{ + .name = "BGH", + .std = V4L2_STD_B | V4L2_STD_GH, + .carr = 5500, + },{ + .name = "I", + .std = V4L2_STD_PAL_I, + .carr = 6000, + },{ + .name = "DKL", + .std = V4L2_STD_DK | V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC, + .carr = 6500, + } +}; + +static struct saa7134_tvaudio tvaudio[] = { + { + .name = "PAL-B/G FM-stereo", + .std = V4L2_STD_PAL_BG, + .mode = TVAUDIO_FM_BG_STEREO, + .carr1 = 5500, + .carr2 = 5742, + },{ + .name = "PAL-D/K1 FM-stereo", + .std = V4L2_STD_PAL_DK, + .carr1 = 6500, + .carr2 = 6258, + .mode = TVAUDIO_FM_BG_STEREO, + },{ + .name = "PAL-D/K2 FM-stereo", + .std = V4L2_STD_PAL_DK, + .carr1 = 6500, + .carr2 = 6742, + .mode = TVAUDIO_FM_BG_STEREO, + },{ + .name = "PAL-D/K3 FM-stereo", + .std = V4L2_STD_PAL_DK, + .carr1 = 6500, + .carr2 = 5742, + .mode = TVAUDIO_FM_BG_STEREO, + },{ + .name = "PAL-B/G NICAM", + .std = V4L2_STD_PAL_BG, + .carr1 = 5500, + .carr2 = 5850, + .mode = TVAUDIO_NICAM_FM, + },{ + .name = "PAL-I NICAM", + .std = V4L2_STD_PAL_I, + .carr1 = 6000, + .carr2 = 6552, + .mode = TVAUDIO_NICAM_FM, + },{ + .name = "PAL-D/K NICAM", + .std = V4L2_STD_PAL_DK, + .carr1 = 6500, + .carr2 = 5850, + .mode = TVAUDIO_NICAM_FM, + },{ + .name = "SECAM-L NICAM", + .std = V4L2_STD_SECAM_L, + .carr1 = 6500, + .carr2 = 5850, + .mode = TVAUDIO_NICAM_AM, + },{ + .name = "SECAM-D/K NICAM", + .std = V4L2_STD_SECAM_DK, + .carr1 = 6500, + .carr2 = 5850, + .mode = TVAUDIO_NICAM_FM, + },{ + .name = "NTSC-A2 FM-stereo", + .std = V4L2_STD_NTSC, + .carr1 = 4500, + .carr2 = 4724, + .mode = TVAUDIO_FM_K_STEREO, + },{ + .name = "NTSC-M", + .std = V4L2_STD_NTSC, + .carr1 = 4500, + .carr2 = -1, + .mode = TVAUDIO_FM_MONO, + } +}; +#define TVAUDIO ARRAY_SIZE(tvaudio) + +/* ------------------------------------------------------------------ */ + +static u32 tvaudio_carr2reg(u32 carrier) +{ + u64 a = carrier; + + a <<= 24; + do_div(a,12288); + return a; +} + +static void tvaudio_setcarrier(struct saa7134_dev *dev, + int primary, int secondary) +{ + if (-1 == secondary) + secondary = primary; + saa_writel(SAA7134_CARRIER1_FREQ0 >> 2, tvaudio_carr2reg(primary)); + saa_writel(SAA7134_CARRIER2_FREQ0 >> 2, tvaudio_carr2reg(secondary)); +} + +#define SAA7134_MUTE_MASK 0xbb +#define SAA7134_MUTE_ANALOG 0x04 +#define SAA7134_MUTE_I2S 0x40 + +static void mute_input_7134(struct saa7134_dev *dev) +{ + unsigned int mute; + struct saa7134_input *in; + int ausel=0, ics=0, ocs=0; + int mask; + + /* look what is to do ... */ + in = dev->input; + mute = (dev->ctl_mute || + (dev->automute && (&card(dev).radio) != in)); + if (card(dev).mute.type) { + /* + * 7130 - we'll mute using some unconnected audio input + * 7134 - we'll probably should switch external mux with gpio + */ + if (mute) + in = &card(dev).mute; + } + + if (dev->hw_mute == mute && + dev->hw_input == in && !dev->insuspend) { + audio_dbg(1, "mute/input: nothing to do [mute=%d,input=%s]\n", + mute, saa7134_input_name[in->type]); + return; + } + + audio_dbg(1, "ctl_mute=%d automute=%d input=%s => mute=%d input=%s\n", + dev->ctl_mute, dev->automute, + saa7134_input_name[dev->input->type], mute, + saa7134_input_name[in->type]); + dev->hw_mute = mute; + dev->hw_input = in; + + if (PCI_DEVICE_ID_PHILIPS_SAA7134 == dev->pci->device) + /* 7134 mute */ + saa_writeb(SAA7134_AUDIO_MUTE_CTRL, mute ? + SAA7134_MUTE_MASK | + SAA7134_MUTE_ANALOG | + SAA7134_MUTE_I2S : + SAA7134_MUTE_MASK); + + /* switch internal audio mux */ + switch (in->amux) { + case TV: ausel=0xc0; ics=0x00; ocs=0x02; break; + case LINE1: ausel=0x80; ics=0x00; ocs=0x00; break; + case LINE2: ausel=0x80; ics=0x08; ocs=0x01; break; + case LINE2_LEFT: ausel=0x80; ics=0x08; ocs=0x05; break; + } + saa_andorb(SAA7134_AUDIO_FORMAT_CTRL, 0xc0, ausel); + saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x08, ics); + saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x07, ocs); + // for oss, we need to change the clock configuration + if (in->amux == TV) + saa_andorb(SAA7134_SIF_SAMPLE_FREQ, 0x03, 0x00); + else + saa_andorb(SAA7134_SIF_SAMPLE_FREQ, 0x03, 0x01); + + /* switch gpio-connected external audio mux */ + if (0 == card(dev).gpiomask) + return; + + mask = card(dev).gpiomask; + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, mask, mask); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, mask, in->gpio); + saa7134_track_gpio(dev, saa7134_input_name[in->type]); +} + +static void tvaudio_setmode(struct saa7134_dev *dev, + struct saa7134_tvaudio *audio, + char *note) +{ + int acpf, tweak = 0; + + if (dev->tvnorm->id == V4L2_STD_NTSC) { + acpf = 0x19066; + } else { + acpf = 0x1e000; + } + if (audio_clock_tweak > -1024 && audio_clock_tweak < 1024) + tweak = audio_clock_tweak; + + if (note) + audio_dbg(1, "tvaudio_setmode: %s %s [%d.%03d/%d.%03d MHz] acpf=%d%+d\n", + note, audio->name, + audio->carr1 / 1000, audio->carr1 % 1000, + audio->carr2 / 1000, audio->carr2 % 1000, + acpf, tweak); + + acpf += tweak; + saa_writeb(SAA7134_AUDIO_CLOCKS_PER_FIELD0, (acpf & 0x0000ff) >> 0); + saa_writeb(SAA7134_AUDIO_CLOCKS_PER_FIELD1, (acpf & 0x00ff00) >> 8); + saa_writeb(SAA7134_AUDIO_CLOCKS_PER_FIELD2, (acpf & 0x030000) >> 16); + tvaudio_setcarrier(dev,audio->carr1,audio->carr2); + + switch (audio->mode) { + case TVAUDIO_FM_MONO: + case TVAUDIO_FM_BG_STEREO: + saa_writeb(SAA7134_DEMODULATOR, 0x00); + saa_writeb(SAA7134_DCXO_IDENT_CTRL, 0x00); + saa_writeb(SAA7134_FM_DEEMPHASIS, 0x22); + saa_writeb(SAA7134_FM_DEMATRIX, 0x80); + saa_writeb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0xa0); + break; + case TVAUDIO_FM_K_STEREO: + saa_writeb(SAA7134_DEMODULATOR, 0x00); + saa_writeb(SAA7134_DCXO_IDENT_CTRL, 0x01); + saa_writeb(SAA7134_FM_DEEMPHASIS, 0x22); + saa_writeb(SAA7134_FM_DEMATRIX, 0x80); + saa_writeb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0xa0); + break; + case TVAUDIO_NICAM_FM: + saa_writeb(SAA7134_DEMODULATOR, 0x10); + saa_writeb(SAA7134_DCXO_IDENT_CTRL, 0x00); + saa_writeb(SAA7134_FM_DEEMPHASIS, 0x44); + saa_writeb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0xa1); + saa_writeb(SAA7134_NICAM_CONFIG, 0x00); + break; + case TVAUDIO_NICAM_AM: + saa_writeb(SAA7134_DEMODULATOR, 0x12); + saa_writeb(SAA7134_DCXO_IDENT_CTRL, 0x00); + saa_writeb(SAA7134_FM_DEEMPHASIS, 0x44); + saa_writeb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0xa1); + saa_writeb(SAA7134_NICAM_CONFIG, 0x00); + break; + case TVAUDIO_FM_SAT_STEREO: + /* not implemented (yet) */ + break; + } +} + +static int tvaudio_sleep(struct saa7134_dev *dev, int timeout) +{ + if (dev->thread.scan1 == dev->thread.scan2 && + !kthread_should_stop()) { + if (timeout < 0) { + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + } else { + schedule_timeout_interruptible + (msecs_to_jiffies(timeout)); + } + } + return dev->thread.scan1 != dev->thread.scan2; +} + +static int tvaudio_checkcarrier(struct saa7134_dev *dev, struct mainscan *scan) +{ + __s32 left,right,value; + + if (!(dev->tvnorm->id & scan->std)) { + audio_dbg(1, "skipping %d.%03d MHz [%4s]\n", + scan->carr / 1000, scan->carr % 1000, scan->name); + return 0; + } + + if (audio_debug > 1) { + int i; + audio_dbg(1, "debug %d:", scan->carr); + for (i = -150; i <= 150; i += 30) { + tvaudio_setcarrier(dev,scan->carr+i,scan->carr+i); + saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + if (tvaudio_sleep(dev,SCAN_SAMPLE_DELAY)) + return -1; + value = saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + if (0 == i) + pr_cont(" # %6d # ", value >> 16); + else + pr_cont(" %6d", value >> 16); + } + pr_cont("\n"); + } + + tvaudio_setcarrier(dev,scan->carr-90,scan->carr-90); + saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + if (tvaudio_sleep(dev,SCAN_SAMPLE_DELAY)) + return -1; + left = saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + + tvaudio_setcarrier(dev,scan->carr+90,scan->carr+90); + saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + if (tvaudio_sleep(dev,SCAN_SAMPLE_DELAY)) + return -1; + right = saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + + left >>= 16; + right >>= 16; + value = left > right ? left - right : right - left; + audio_dbg(1, "scanning %d.%03d MHz [%4s] => dc is %5d [%d/%d]\n", + scan->carr / 1000, scan->carr % 1000, + scan->name, value, left, right); + return value; +} + + +static int tvaudio_getstereo(struct saa7134_dev *dev, struct saa7134_tvaudio *audio) +{ + __u32 idp, nicam, nicam_status; + int retval = -1; + + switch (audio->mode) { + case TVAUDIO_FM_MONO: + return V4L2_TUNER_SUB_MONO; + case TVAUDIO_FM_K_STEREO: + case TVAUDIO_FM_BG_STEREO: + idp = (saa_readb(SAA7134_IDENT_SIF) & 0xe0) >> 5; + audio_dbg(1, "getstereo: fm/stereo: idp=0x%x\n", idp); + if (0x03 == (idp & 0x03)) + retval = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + else if (0x05 == (idp & 0x05)) + retval = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + else if (0x01 == (idp & 0x01)) + retval = V4L2_TUNER_SUB_MONO; + break; + case TVAUDIO_FM_SAT_STEREO: + /* not implemented (yet) */ + break; + case TVAUDIO_NICAM_FM: + case TVAUDIO_NICAM_AM: + nicam = saa_readb(SAA7134_AUDIO_STATUS); + audio_dbg(1, "getstereo: nicam=0x%x\n", nicam); + if (nicam & 0x1) { + nicam_status = saa_readb(SAA7134_NICAM_STATUS); + audio_dbg(1, "getstereo: nicam_status=0x%x\n", + nicam_status); + + switch (nicam_status & 0x03) { + case 0x01: + retval = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + break; + case 0x02: + retval = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + break; + default: + retval = V4L2_TUNER_SUB_MONO; + } + } else { + /* No nicam detected */ + } + break; + } + if (retval != -1) + audio_dbg(1, "found audio subchannels:%s%s%s%s\n", + (retval & V4L2_TUNER_SUB_MONO) ? " mono" : "", + (retval & V4L2_TUNER_SUB_STEREO) ? " stereo" : "", + (retval & V4L2_TUNER_SUB_LANG1) ? " lang1" : "", + (retval & V4L2_TUNER_SUB_LANG2) ? " lang2" : ""); + return retval; +} + +static int tvaudio_setstereo(struct saa7134_dev *dev, struct saa7134_tvaudio *audio, + u32 mode) +{ + static char *name[] = { + [ V4L2_TUNER_MODE_MONO ] = "mono", + [ V4L2_TUNER_MODE_STEREO ] = "stereo", + [ V4L2_TUNER_MODE_LANG1 ] = "lang1", + [ V4L2_TUNER_MODE_LANG2 ] = "lang2", + [ V4L2_TUNER_MODE_LANG1_LANG2 ] = "lang1+lang2", + }; + static u32 fm[] = { + [ V4L2_TUNER_MODE_MONO ] = 0x00, /* ch1 */ + [ V4L2_TUNER_MODE_STEREO ] = 0x80, /* auto */ + [ V4L2_TUNER_MODE_LANG1 ] = 0x00, /* ch1 */ + [ V4L2_TUNER_MODE_LANG2 ] = 0x01, /* ch2 */ + [ V4L2_TUNER_MODE_LANG1_LANG2 ] = 0x80, /* auto */ + }; + u32 reg; + + switch (audio->mode) { + case TVAUDIO_FM_MONO: + /* nothing to do ... */ + break; + case TVAUDIO_FM_K_STEREO: + case TVAUDIO_FM_BG_STEREO: + case TVAUDIO_NICAM_AM: + case TVAUDIO_NICAM_FM: + audio_dbg(1, "setstereo [fm] => %s\n", + name[mode % ARRAY_SIZE(name)]); + reg = fm[ mode % ARRAY_SIZE(fm) ]; + saa_writeb(SAA7134_FM_DEMATRIX, reg); + break; + case TVAUDIO_FM_SAT_STEREO: + /* Not implemented */ + break; + } + return 0; +} + +static int tvaudio_thread(void *data) +{ + struct saa7134_dev *dev = data; + int carr_vals[ARRAY_SIZE(mainscan)]; + unsigned int i, audio, nscan; + int max1,max2,carrier,rx,mode,lastmode,default_carrier; + + set_freezable(); + + for (;;) { + tvaudio_sleep(dev,-1); + if (kthread_should_stop()) + goto done; + + restart: + try_to_freeze(); + + dev->thread.scan1 = dev->thread.scan2; + audio_dbg(1, "tvaudio thread scan start [%d]\n", + dev->thread.scan1); + dev->tvaudio = NULL; + + saa_writeb(SAA7134_MONITOR_SELECT, 0xa0); + saa_writeb(SAA7134_FM_DEMATRIX, 0x80); + + if (dev->ctl_automute) + dev->automute = 1; + + mute_input_7134(dev); + + /* give the tuner some time */ + if (tvaudio_sleep(dev,SCAN_INITIAL_DELAY)) + goto restart; + + max1 = 0; + max2 = 0; + nscan = 0; + carrier = 0; + default_carrier = 0; + for (i = 0; i < ARRAY_SIZE(mainscan); i++) { + if (!(dev->tvnorm->id & mainscan[i].std)) + continue; + if (!default_carrier) + default_carrier = mainscan[i].carr; + nscan++; + } + + if (1 == nscan) { + /* only one candidate -- skip scan ;) */ + audio_dbg(1, "only one main carrier candidate - skipping scan\n"); + max1 = 12345; + carrier = default_carrier; + } else { + /* scan for the main carrier */ + saa_writeb(SAA7134_MONITOR_SELECT,0x00); + tvaudio_setmode(dev,&tvaudio[0],NULL); + for (i = 0; i < ARRAY_SIZE(mainscan); i++) { + carr_vals[i] = tvaudio_checkcarrier(dev, mainscan+i); + if (dev->thread.scan1 != dev->thread.scan2) + goto restart; + } + for (max1 = 0, max2 = 0, i = 0; i < ARRAY_SIZE(mainscan); i++) { + if (max1 < carr_vals[i]) { + max2 = max1; + max1 = carr_vals[i]; + carrier = mainscan[i].carr; + } else if (max2 < carr_vals[i]) { + max2 = carr_vals[i]; + } + } + } + + if (0 != carrier && max1 > 2000 && max1 > max2*3) { + /* found good carrier */ + audio_dbg(1, "found %s main sound carrier @ %d.%03d MHz [%d/%d]\n", + dev->tvnorm->name, carrier/1000, carrier%1000, + max1, max2); + dev->last_carrier = carrier; + dev->automute = 0; + + } else if (0 != dev->last_carrier) { + /* no carrier -- try last detected one as fallback */ + carrier = dev->last_carrier; + audio_dbg(1, "audio carrier scan failed, using %d.%03d MHz [last detected]\n", + carrier/1000, carrier%1000); + dev->automute = 1; + + } else { + /* no carrier + no fallback -- use default */ + carrier = default_carrier; + audio_dbg(1, "audio carrier scan failed, using %d.%03d MHz [default]\n", + carrier/1000, carrier%1000); + dev->automute = 1; + } + tvaudio_setcarrier(dev,carrier,carrier); + saa_andorb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0x30, 0x00); + saa7134_tvaudio_setmute(dev); + /* find the exact tv audio norm */ + for (audio = UNSET, i = 0; i < TVAUDIO; i++) { + if (dev->tvnorm->id != UNSET && + !(dev->tvnorm->id & tvaudio[i].std)) + continue; + if (tvaudio[i].carr1 != carrier) + continue; + /* Note: at least the primary carrier is right here */ + if (UNSET == audio) + audio = i; + tvaudio_setmode(dev,&tvaudio[i],"trying"); + if (tvaudio_sleep(dev,SCAN_SUBCARRIER_DELAY)) + goto restart; + if (-1 != tvaudio_getstereo(dev,&tvaudio[i])) { + audio = i; + break; + } + } + saa_andorb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0x30, 0x30); + if (UNSET == audio) + continue; + tvaudio_setmode(dev,&tvaudio[audio],"using"); + + tvaudio_setstereo(dev,&tvaudio[audio],V4L2_TUNER_MODE_MONO); + dev->tvaudio = &tvaudio[audio]; + + lastmode = 42; + for (;;) { + + try_to_freeze(); + + if (tvaudio_sleep(dev,5000)) + goto restart; + if (kthread_should_stop()) + break; + if (UNSET == dev->thread.mode) { + rx = tvaudio_getstereo(dev, &tvaudio[audio]); + mode = saa7134_tvaudio_rx2mode(rx); + } else { + mode = dev->thread.mode; + } + if (lastmode != mode) { + tvaudio_setstereo(dev,&tvaudio[audio],mode); + lastmode = mode; + } + } + } + + done: + dev->thread.stopped = 1; + return 0; +} + +/* ------------------------------------------------------------------ */ +/* saa7133 / saa7135 code */ + +static char *stdres[0x20] = { + [0x00] = "no standard detected", + [0x01] = "B/G (in progress)", + [0x02] = "D/K (in progress)", + [0x03] = "M (in progress)", + + [0x04] = "B/G A2", + [0x05] = "B/G NICAM", + [0x06] = "D/K A2 (1)", + [0x07] = "D/K A2 (2)", + [0x08] = "D/K A2 (3)", + [0x09] = "D/K NICAM", + [0x0a] = "L NICAM", + [0x0b] = "I NICAM", + + [0x0c] = "M Korea", + [0x0d] = "M BTSC ", + [0x0e] = "M EIAJ", + + [0x0f] = "FM radio / IF 10.7 / 50 deemp", + [0x10] = "FM radio / IF 10.7 / 75 deemp", + [0x11] = "FM radio / IF sel / 50 deemp", + [0x12] = "FM radio / IF sel / 75 deemp", + + [0x13 ... 0x1e ] = "unknown", + [0x1f] = "??? [in progress]", +}; + +#define DSP_RETRY 32 +#define DSP_DELAY 16 +#define SAA7135_DSP_RWCLEAR_RERR 1 + +static inline int saa_dsp_reset_error_bit(struct saa7134_dev *dev) +{ + int state = saa_readb(SAA7135_DSP_RWSTATE); + if (unlikely(state & SAA7135_DSP_RWSTATE_ERR)) { + audio_dbg(2, "%s: resetting error bit\n", dev->name); + saa_writeb(SAA7135_DSP_RWCLEAR, SAA7135_DSP_RWCLEAR_RERR); + } + return 0; +} + +static inline int saa_dsp_wait_bit(struct saa7134_dev *dev, int bit) +{ + int state, count = DSP_RETRY; + + state = saa_readb(SAA7135_DSP_RWSTATE); + if (unlikely(state & SAA7135_DSP_RWSTATE_ERR)) { + pr_warn("%s: dsp access error\n", dev->name); + saa_dsp_reset_error_bit(dev); + return -EIO; + } + while (0 == (state & bit)) { + if (unlikely(0 == count)) { + pr_err("dsp access wait timeout [bit=%s]\n", + (bit & SAA7135_DSP_RWSTATE_WRR) ? "WRR" : + (bit & SAA7135_DSP_RWSTATE_RDB) ? "RDB" : + (bit & SAA7135_DSP_RWSTATE_IDA) ? "IDA" : + "???"); + return -EIO; + } + saa_wait(DSP_DELAY); + state = saa_readb(SAA7135_DSP_RWSTATE); + count--; + } + return 0; +} + + +int saa_dsp_writel(struct saa7134_dev *dev, int reg, u32 value) +{ + int err; + + audio_dbg(2, "dsp write reg 0x%x = 0x%06x\n", + (reg << 2) & 0xffffffff, value); + err = saa_dsp_wait_bit(dev,SAA7135_DSP_RWSTATE_WRR); + if (err < 0) + return err; + saa_writel(reg,value); + err = saa_dsp_wait_bit(dev,SAA7135_DSP_RWSTATE_WRR); + if (err < 0) + return err; + return 0; +} + +static int getstereo_7133(struct saa7134_dev *dev) +{ + int retval = V4L2_TUNER_SUB_MONO; + u32 value; + + value = saa_readl(0x528 >> 2); + if (value & 0x20) + retval = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + if (value & 0x40) + retval = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + return retval; +} + +static int mute_input_7133(struct saa7134_dev *dev) +{ + u32 reg = 0; + u32 xbarin, xbarout; + int mask; + struct saa7134_input *in; + + xbarin = 0x03; + switch (dev->input->amux) { + case TV: + reg = 0x02; + xbarin = 0; + break; + case LINE1: + reg = 0x00; + break; + case LINE2: + case LINE2_LEFT: + reg = 0x09; + break; + } + saa_dsp_writel(dev, 0x464 >> 2, xbarin); + if (dev->ctl_mute) { + reg = 0x07; + xbarout = 0xbbbbbb; + } else + xbarout = 0xbbbb10; + saa_dsp_writel(dev, 0x46c >> 2, xbarout); + + saa_writel(0x594 >> 2, reg); + + + /* switch gpio-connected external audio mux */ + if (0 != card(dev).gpiomask) { + mask = card(dev).gpiomask; + + if (card(dev).mute.type && dev->ctl_mute) + in = &card(dev).mute; + else + in = dev->input; + + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, mask, mask); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, mask, in->gpio); + saa7134_track_gpio(dev, saa7134_input_name[in->type]); + } + + return 0; +} + +static int tvaudio_thread_ddep(void *data) +{ + struct saa7134_dev *dev = data; + u32 value, norms; + + set_freezable(); + for (;;) { + tvaudio_sleep(dev,-1); + if (kthread_should_stop()) + goto done; + restart: + try_to_freeze(); + + dev->thread.scan1 = dev->thread.scan2; + audio_dbg(1, "tvaudio thread scan start [%d]\n", + dev->thread.scan1); + + if (audio_ddep >= 0x04 && audio_ddep <= 0x0e) { + /* insmod option override */ + norms = (audio_ddep << 2) | 0x01; + audio_dbg(1, "ddep override: %s\n", + stdres[audio_ddep]); + } else if (&card(dev).radio == dev->input) { + audio_dbg(1, "FM Radio\n"); + if (dev->tuner_type == TUNER_PHILIPS_TDA8290) { + norms = (0x11 << 2) | 0x01; + /* set IF frequency to 5.5 MHz */ + saa_dsp_writel(dev, 0x42c >> 2, 0x729555); + } else { + norms = (0x0f << 2) | 0x01; + } + } else { + /* (let chip) scan for sound carrier */ + norms = 0; + if (dev->tvnorm->id & (V4L2_STD_B | V4L2_STD_GH)) + norms |= 0x04; + if (dev->tvnorm->id & V4L2_STD_PAL_I) + norms |= 0x20; + if (dev->tvnorm->id & V4L2_STD_DK) + norms |= 0x08; + if (dev->tvnorm->id & V4L2_STD_MN) + norms |= 0x40; + if (dev->tvnorm->id & (V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC)) + norms |= 0x10; + if (0 == norms) + norms = 0x7c; /* all */ + audio_dbg(1, "scanning:%s%s%s%s%s\n", + (norms & 0x04) ? " B/G" : "", + (norms & 0x08) ? " D/K" : "", + (norms & 0x10) ? " L/L'" : "", + (norms & 0x20) ? " I" : "", + (norms & 0x40) ? " M" : ""); + } + + /* kick automatic standard detection */ + saa_dsp_writel(dev, 0x454 >> 2, 0); + saa_dsp_writel(dev, 0x454 >> 2, norms | 0x80); + + /* setup crossbars */ + saa_dsp_writel(dev, 0x464 >> 2, 0x000000); + saa_dsp_writel(dev, 0x470 >> 2, 0x101010); + + if (tvaudio_sleep(dev,3000)) + goto restart; + value = saa_readl(0x528 >> 2) & 0xffffff; + + audio_dbg(1, "tvaudio thread status: 0x%x [%s%s%s]\n", + value, stdres[value & 0x1f], + (value & 0x000020) ? ",stereo" : "", + (value & 0x000040) ? ",dual" : ""); + audio_dbg(1, "detailed status: %s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s\n", + (value & 0x000080) ? " A2/EIAJ pilot tone " : "", + (value & 0x000100) ? " A2/EIAJ dual " : "", + (value & 0x000200) ? " A2/EIAJ stereo " : "", + (value & 0x000400) ? " A2/EIAJ noise mute " : "", + + (value & 0x000800) ? " BTSC/FM radio pilot " : "", + (value & 0x001000) ? " SAP carrier " : "", + (value & 0x002000) ? " BTSC stereo noise mute " : "", + (value & 0x004000) ? " SAP noise mute " : "", + (value & 0x008000) ? " VDSP " : "", + + (value & 0x010000) ? " NICST " : "", + (value & 0x020000) ? " NICDU " : "", + (value & 0x040000) ? " NICAM muted " : "", + (value & 0x080000) ? " NICAM reserve sound " : "", + + (value & 0x100000) ? " init done " : ""); + } + + done: + dev->thread.stopped = 1; + return 0; +} + +/* ------------------------------------------------------------------ */ +/* common stuff + external entry points */ + +void saa7134_enable_i2s(struct saa7134_dev *dev) +{ + int i2s_format; + + if (!card_is_empress(dev)) + return; + + if (dev->pci->device == PCI_DEVICE_ID_PHILIPS_SAA7130) + return; + + /* configure GPIO for out */ + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0x0E000000, 0x00000000); + + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + /* Set I2S format (SONY) */ + saa_writeb(SAA7133_I2S_AUDIO_CONTROL, 0x00); + /* Start I2S */ + saa_writeb(SAA7134_I2S_AUDIO_OUTPUT, 0x11); + break; + + case PCI_DEVICE_ID_PHILIPS_SAA7134: + i2s_format = (dev->input->amux == TV) ? 0x00 : 0x01; + + /* enable I2S audio output for the mpeg encoder */ + saa_writeb(SAA7134_I2S_OUTPUT_SELECT, 0x80); + saa_writeb(SAA7134_I2S_OUTPUT_FORMAT, i2s_format); + saa_writeb(SAA7134_I2S_OUTPUT_LEVEL, 0x0F); + saa_writeb(SAA7134_I2S_AUDIO_OUTPUT, 0x01); + break; + + default: + break; + } +} + +int saa7134_tvaudio_rx2mode(u32 rx) +{ + u32 mode; + + mode = V4L2_TUNER_MODE_MONO; + if (rx & V4L2_TUNER_SUB_STEREO) + mode = V4L2_TUNER_MODE_STEREO; + else if (rx & V4L2_TUNER_SUB_LANG1) + mode = V4L2_TUNER_MODE_LANG1; + else if (rx & V4L2_TUNER_SUB_LANG2) + mode = V4L2_TUNER_MODE_LANG2; + return mode; +} + +void saa7134_tvaudio_setmute(struct saa7134_dev *dev) +{ + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7130: + case PCI_DEVICE_ID_PHILIPS_SAA7134: + mute_input_7134(dev); + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + mute_input_7133(dev); + break; + } +} + +void saa7134_tvaudio_setinput(struct saa7134_dev *dev, + struct saa7134_input *in) +{ + dev->input = in; + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7130: + case PCI_DEVICE_ID_PHILIPS_SAA7134: + mute_input_7134(dev); + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + mute_input_7133(dev); + break; + } + saa7134_enable_i2s(dev); +} + +void saa7134_tvaudio_setvolume(struct saa7134_dev *dev, int level) +{ + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + saa_writeb(SAA7134_CHANNEL1_LEVEL, level & 0x1f); + saa_writeb(SAA7134_CHANNEL2_LEVEL, level & 0x1f); + saa_writeb(SAA7134_NICAM_LEVEL_ADJUST, level & 0x1f); + break; + } +} + +int saa7134_tvaudio_getstereo(struct saa7134_dev *dev) +{ + int retval = V4L2_TUNER_SUB_MONO; + + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + if (dev->tvaudio) + retval = tvaudio_getstereo(dev,dev->tvaudio); + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + retval = getstereo_7133(dev); + break; + } + return retval; +} + +void saa7134_tvaudio_init(struct saa7134_dev *dev) +{ + int clock = saa7134_boards[dev->board].audio_clock; + + if (UNSET != audio_clock_override) + clock = audio_clock_override; + + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + /* init all audio registers */ + saa_writeb(SAA7134_AUDIO_PLL_CTRL, 0x00); + if (need_resched()) + schedule(); + else + udelay(10); + + saa_writeb(SAA7134_AUDIO_CLOCK0, clock & 0xff); + saa_writeb(SAA7134_AUDIO_CLOCK1, (clock >> 8) & 0xff); + saa_writeb(SAA7134_AUDIO_CLOCK2, (clock >> 16) & 0xff); + /* frame locked audio is mandatory for NICAM */ + saa_writeb(SAA7134_AUDIO_PLL_CTRL, 0x01); + saa_writeb(SAA7134_NICAM_ERROR_LOW, 0x14); + saa_writeb(SAA7134_NICAM_ERROR_HIGH, 0x50); + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + saa_writel(0x598 >> 2, clock); + saa_dsp_writel(dev, 0x474 >> 2, 0x00); + saa_dsp_writel(dev, 0x450 >> 2, 0x00); + } +} + +int saa7134_tvaudio_init2(struct saa7134_dev *dev) +{ + int (*my_thread)(void *data) = NULL; + + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + my_thread = tvaudio_thread; + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + my_thread = tvaudio_thread_ddep; + break; + } + + dev->thread.thread = NULL; + dev->thread.scan1 = dev->thread.scan2 = 0; + if (my_thread) { + saa7134_tvaudio_init(dev); + /* start tvaudio thread */ + dev->thread.thread = kthread_run(my_thread, dev, "%s", dev->name); + if (IS_ERR(dev->thread.thread)) { + pr_warn("%s: kernel_thread() failed\n", + dev->name); + /* XXX: missing error handling here */ + } + } + + saa7134_enable_i2s(dev); + return 0; +} + +int saa7134_tvaudio_close(struct saa7134_dev *dev) +{ + dev->automute = 1; + /* anything else to undo? */ + return 0; +} + +int saa7134_tvaudio_fini(struct saa7134_dev *dev) +{ + /* shutdown tvaudio thread */ + if (dev->thread.thread && !dev->thread.stopped) + kthread_stop(dev->thread.thread); + + saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x07, 0x00); /* LINE1 */ + return 0; +} + +int saa7134_tvaudio_do_scan(struct saa7134_dev *dev) +{ + if (dev->input->amux != TV) { + audio_dbg(1, "sound IF not in use, skipping scan\n"); + dev->automute = 0; + saa7134_tvaudio_setmute(dev); + } else if (dev->thread.thread) { + dev->thread.mode = UNSET; + dev->thread.scan2++; + + if (!dev->insuspend && !dev->thread.stopped) + wake_up_process(dev->thread.thread); + } else { + dev->automute = 0; + saa7134_tvaudio_setmute(dev); + } + return 0; +} + +EXPORT_SYMBOL(saa_dsp_writel); +EXPORT_SYMBOL(saa7134_tvaudio_setmute); diff --git a/drivers/media/pci/saa7134/saa7134-vbi.c b/drivers/media/pci/saa7134/saa7134-vbi.c new file mode 100644 index 000000000..28bf77449 --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134-vbi.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * device driver for philips saa7134 based TV cards + * video4linux video interface + * + * (c) 2001,02 Gerd Knorr [SuSE Labs] + */ + +#include "saa7134.h" +#include "saa7134-reg.h" + +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ + +static unsigned int vbi_debug; +module_param(vbi_debug, int, 0644); +MODULE_PARM_DESC(vbi_debug,"enable debug messages [vbi]"); + +static unsigned int vbibufs = 4; +module_param(vbibufs, int, 0444); +MODULE_PARM_DESC(vbibufs,"number of vbi buffers, range 2-32"); + +#define vbi_dbg(fmt, arg...) do { \ + if (vbi_debug) \ + printk(KERN_DEBUG pr_fmt("vbi: " fmt), ## arg); \ + } while (0) + +/* ------------------------------------------------------------------ */ + +#define VBI_LINE_COUNT 17 +#define VBI_LINE_LENGTH 2048 +#define VBI_SCALE 0x200 + +static void task_init(struct saa7134_dev *dev, struct saa7134_buf *buf, + int task) +{ + struct saa7134_tvnorm *norm = dev->tvnorm; + + /* setup video scaler */ + saa_writeb(SAA7134_VBI_H_START1(task), norm->h_start & 0xff); + saa_writeb(SAA7134_VBI_H_START2(task), norm->h_start >> 8); + saa_writeb(SAA7134_VBI_H_STOP1(task), norm->h_stop & 0xff); + saa_writeb(SAA7134_VBI_H_STOP2(task), norm->h_stop >> 8); + saa_writeb(SAA7134_VBI_V_START1(task), norm->vbi_v_start_0 & 0xff); + saa_writeb(SAA7134_VBI_V_START2(task), norm->vbi_v_start_0 >> 8); + saa_writeb(SAA7134_VBI_V_STOP1(task), norm->vbi_v_stop_0 & 0xff); + saa_writeb(SAA7134_VBI_V_STOP2(task), norm->vbi_v_stop_0 >> 8); + + saa_writeb(SAA7134_VBI_H_SCALE_INC1(task), VBI_SCALE & 0xff); + saa_writeb(SAA7134_VBI_H_SCALE_INC2(task), VBI_SCALE >> 8); + saa_writeb(SAA7134_VBI_PHASE_OFFSET_LUMA(task), 0x00); + saa_writeb(SAA7134_VBI_PHASE_OFFSET_CHROMA(task), 0x00); + + saa_writeb(SAA7134_VBI_H_LEN1(task), dev->vbi_hlen & 0xff); + saa_writeb(SAA7134_VBI_H_LEN2(task), dev->vbi_hlen >> 8); + saa_writeb(SAA7134_VBI_V_LEN1(task), dev->vbi_vlen & 0xff); + saa_writeb(SAA7134_VBI_V_LEN2(task), dev->vbi_vlen >> 8); + + saa_andorb(SAA7134_DATA_PATH(task), 0xc0, 0x00); +} + +/* ------------------------------------------------------------------ */ + +static int buffer_activate(struct saa7134_dev *dev, + struct saa7134_buf *buf, + struct saa7134_buf *next) +{ + struct saa7134_dmaqueue *dmaq = buf->vb2.vb2_buf.vb2_queue->drv_priv; + unsigned long control, base; + + vbi_dbg("buffer_activate [%p]\n", buf); + buf->top_seen = 0; + + task_init(dev, buf, TASK_A); + task_init(dev, buf, TASK_B); + saa_writeb(SAA7134_OFMT_DATA_A, 0x06); + saa_writeb(SAA7134_OFMT_DATA_B, 0x06); + + /* DMA: setup channel 2+3 (= VBI Task A+B) */ + base = saa7134_buffer_base(buf); + control = SAA7134_RS_CONTROL_BURST_16 | + SAA7134_RS_CONTROL_ME | + (dmaq->pt.dma >> 12); + saa_writel(SAA7134_RS_BA1(2), base); + saa_writel(SAA7134_RS_BA2(2), base + dev->vbi_hlen * dev->vbi_vlen); + saa_writel(SAA7134_RS_PITCH(2), dev->vbi_hlen); + saa_writel(SAA7134_RS_CONTROL(2), control); + saa_writel(SAA7134_RS_BA1(3), base); + saa_writel(SAA7134_RS_BA2(3), base + dev->vbi_hlen * dev->vbi_vlen); + saa_writel(SAA7134_RS_PITCH(3), dev->vbi_hlen); + saa_writel(SAA7134_RS_CONTROL(3), control); + + /* start DMA */ + saa7134_set_dmabits(dev); + mod_timer(&dmaq->timeout, jiffies + BUFFER_TIMEOUT); + + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb2) +{ + struct saa7134_dmaqueue *dmaq = vb2->vb2_queue->drv_priv; + struct saa7134_dev *dev = dmaq->dev; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2); + struct saa7134_buf *buf = container_of(vbuf, struct saa7134_buf, vb2); + struct sg_table *dma = vb2_dma_sg_plane_desc(vb2, 0); + unsigned int size; + + if (dma->sgl->offset) { + pr_err("The buffer is not page-aligned\n"); + return -EINVAL; + } + size = dev->vbi_hlen * dev->vbi_vlen * 2; + if (vb2_plane_size(vb2, 0) < size) + return -EINVAL; + + vb2_set_plane_payload(vb2, 0, size); + + return saa7134_pgtable_build(dev->pci, &dmaq->pt, dma->sgl, dma->nents, + saa7134_buffer_startpage(buf)); +} + +static int queue_setup(struct vb2_queue *q, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct saa7134_dmaqueue *dmaq = q->drv_priv; + struct saa7134_dev *dev = dmaq->dev; + unsigned int size; + + dev->vbi_vlen = dev->tvnorm->vbi_v_stop_0 - dev->tvnorm->vbi_v_start_0 + 1; + if (dev->vbi_vlen > VBI_LINE_COUNT) + dev->vbi_vlen = VBI_LINE_COUNT; + dev->vbi_hlen = VBI_LINE_LENGTH; + size = dev->vbi_hlen * dev->vbi_vlen * 2; + + *nbuffers = saa7134_buffer_count(size, *nbuffers); + *nplanes = 1; + sizes[0] = size; + return 0; +} + +static int buffer_init(struct vb2_buffer *vb2) +{ + struct saa7134_dmaqueue *dmaq = vb2->vb2_queue->drv_priv; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2); + struct saa7134_buf *buf = container_of(vbuf, struct saa7134_buf, vb2); + + dmaq->curr = NULL; + buf->activate = buffer_activate; + return 0; +} + +const struct vb2_ops saa7134_vbi_qops = { + .queue_setup = queue_setup, + .buf_init = buffer_init, + .buf_prepare = buffer_prepare, + .buf_queue = saa7134_vb2_buffer_queue, + .start_streaming = saa7134_vb2_start_streaming, + .stop_streaming = saa7134_vb2_stop_streaming, +}; + +/* ------------------------------------------------------------------ */ + +int saa7134_vbi_init1(struct saa7134_dev *dev) +{ + INIT_LIST_HEAD(&dev->vbi_q.queue); + timer_setup(&dev->vbi_q.timeout, saa7134_buffer_timeout, 0); + dev->vbi_q.dev = dev; + + if (vbibufs < 2) + vbibufs = 2; + if (vbibufs > VIDEO_MAX_FRAME) + vbibufs = VIDEO_MAX_FRAME; + return 0; +} + +int saa7134_vbi_fini(struct saa7134_dev *dev) +{ + /* nothing */ + timer_delete_sync(&dev->vbi_q.timeout); + return 0; +} + +void saa7134_irq_vbi_done(struct saa7134_dev *dev, unsigned long status) +{ + spin_lock(&dev->slock); + if (dev->vbi_q.curr) { + /* make sure we have seen both fields */ + if ((status & 0x10) == 0x00) { + dev->vbi_q.curr->top_seen = 1; + goto done; + } + if (!dev->vbi_q.curr->top_seen) + goto done; + + saa7134_buffer_finish(dev, &dev->vbi_q, VB2_BUF_STATE_DONE); + } + saa7134_buffer_next(dev, &dev->vbi_q); + + done: + spin_unlock(&dev->slock); +} diff --git a/drivers/media/pci/saa7134/saa7134-video.c b/drivers/media/pci/saa7134/saa7134-video.c new file mode 100644 index 000000000..2b1672737 --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134-video.c @@ -0,0 +1,1877 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * device driver for philips saa7134 based TV cards + * video4linux video interface + * + * (c) 2001-03 Gerd Knorr [SuSE Labs] + */ + +#include "saa7134.h" +#include "saa7134-reg.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* ------------------------------------------------------------------ */ + +unsigned int video_debug; +static unsigned int gbuffers = 8; +static unsigned int noninterlaced; /* 0 */ +static unsigned int gbufsize = 720*576*4; +static unsigned int gbufsize_max = 720*576*4; +static char secam[] = "--"; +module_param(video_debug, int, 0644); +MODULE_PARM_DESC(video_debug,"enable debug messages [video]"); +module_param(gbuffers, int, 0444); +MODULE_PARM_DESC(gbuffers,"number of capture buffers, range 2-32"); +module_param(noninterlaced, int, 0644); +MODULE_PARM_DESC(noninterlaced,"capture non interlaced video"); +module_param_string(secam, secam, sizeof(secam), 0644); +MODULE_PARM_DESC(secam, "force SECAM variant, either DK,L or Lc"); + + +#define video_dbg(fmt, arg...) do { \ + if (video_debug & 0x04) \ + printk(KERN_DEBUG pr_fmt("video: " fmt), ## arg); \ + } while (0) + +/* ------------------------------------------------------------------ */ +/* Defines for Video Output Port Register at address 0x191 */ + +/* Bit 0: VIP code T bit polarity */ + +#define VP_T_CODE_P_NON_INVERTED 0x00 +#define VP_T_CODE_P_INVERTED 0x01 + +/* ------------------------------------------------------------------ */ +/* Defines for Video Output Port Register at address 0x195 */ + +/* Bit 2: Video output clock delay control */ + +#define VP_CLK_CTRL2_NOT_DELAYED 0x00 +#define VP_CLK_CTRL2_DELAYED 0x04 + +/* Bit 1: Video output clock invert control */ + +#define VP_CLK_CTRL1_NON_INVERTED 0x00 +#define VP_CLK_CTRL1_INVERTED 0x02 + +/* ------------------------------------------------------------------ */ +/* Defines for Video Output Port Register at address 0x196 */ + +/* Bits 2 to 0: VSYNC pin video vertical sync type */ + +#define VP_VS_TYPE_MASK 0x07 + +#define VP_VS_TYPE_OFF 0x00 +#define VP_VS_TYPE_V123 0x01 +#define VP_VS_TYPE_V_ITU 0x02 +#define VP_VS_TYPE_VGATE_L 0x03 +#define VP_VS_TYPE_RESERVED1 0x04 +#define VP_VS_TYPE_RESERVED2 0x05 +#define VP_VS_TYPE_F_ITU 0x06 +#define VP_VS_TYPE_SC_FID 0x07 + +/* ------------------------------------------------------------------ */ +/* data structs for video */ + +static int video_out[][9] = { + [CCIR656] = { 0x00, 0xb1, 0x00, 0xa1, 0x00, 0x04, 0x06, 0x00, 0x00 }, +}; + +static struct saa7134_format formats[] = { + { + .fourcc = V4L2_PIX_FMT_GREY, + .depth = 8, + .pm = 0x06, + },{ + .fourcc = V4L2_PIX_FMT_RGB555, + .depth = 16, + .pm = 0x13 | 0x80, + },{ + .fourcc = V4L2_PIX_FMT_RGB555X, + .depth = 16, + .pm = 0x13 | 0x80, + .bswap = 1, + },{ + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .pm = 0x10 | 0x80, + },{ + .fourcc = V4L2_PIX_FMT_RGB565X, + .depth = 16, + .pm = 0x10 | 0x80, + .bswap = 1, + },{ + .fourcc = V4L2_PIX_FMT_BGR24, + .depth = 24, + .pm = 0x11, + },{ + .fourcc = V4L2_PIX_FMT_RGB24, + .depth = 24, + .pm = 0x11, + .bswap = 1, + },{ + .fourcc = V4L2_PIX_FMT_BGR32, + .depth = 32, + .pm = 0x12, + },{ + .fourcc = V4L2_PIX_FMT_RGB32, + .depth = 32, + .pm = 0x12, + .bswap = 1, + .wswap = 1, + },{ + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .pm = 0x00, + .bswap = 1, + .yuv = 1, + },{ + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .pm = 0x00, + .yuv = 1, + },{ + .fourcc = V4L2_PIX_FMT_YUV422P, + .depth = 16, + .pm = 0x09, + .yuv = 1, + .planar = 1, + .hshift = 1, + .vshift = 0, + },{ + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = 12, + .pm = 0x0a, + .yuv = 1, + .planar = 1, + .hshift = 1, + .vshift = 1, + },{ + .fourcc = V4L2_PIX_FMT_YVU420, + .depth = 12, + .pm = 0x0a, + .yuv = 1, + .planar = 1, + .uvswap = 1, + .hshift = 1, + .vshift = 1, + } +}; +#define FORMATS ARRAY_SIZE(formats) + +#define NORM_625_50 \ + .h_start = 0, \ + .h_stop = 719, \ + .video_v_start = 24, \ + .video_v_stop = 311, \ + .vbi_v_start_0 = 7, \ + .vbi_v_stop_0 = 23, \ + .vbi_v_start_1 = 319, \ + .src_timing = 4 + +#define NORM_525_60 \ + .h_start = 0, \ + .h_stop = 719, \ + .video_v_start = 23, \ + .video_v_stop = 262, \ + .vbi_v_start_0 = 10, \ + .vbi_v_stop_0 = 21, \ + .vbi_v_start_1 = 273, \ + .src_timing = 7 + +static struct saa7134_tvnorm tvnorms[] = { + { + .name = "PAL", /* autodetect */ + .id = V4L2_STD_PAL, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + + },{ + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + + },{ + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + + },{ + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + + },{ + .name = "NTSC", + .id = V4L2_STD_NTSC, + NORM_525_60, + + .sync_control = 0x59, + .luma_control = 0x40, + .chroma_ctrl1 = 0x89, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x0e, + .vgate_misc = 0x18, + + },{ + .name = "SECAM", + .id = V4L2_STD_SECAM, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x1b, + .chroma_ctrl1 = 0xd1, + .chroma_gain = 0x80, + .chroma_ctrl2 = 0x00, + .vgate_misc = 0x1c, + + },{ + .name = "SECAM-DK", + .id = V4L2_STD_SECAM_DK, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x1b, + .chroma_ctrl1 = 0xd1, + .chroma_gain = 0x80, + .chroma_ctrl2 = 0x00, + .vgate_misc = 0x1c, + + },{ + .name = "SECAM-L", + .id = V4L2_STD_SECAM_L, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x1b, + .chroma_ctrl1 = 0xd1, + .chroma_gain = 0x80, + .chroma_ctrl2 = 0x00, + .vgate_misc = 0x1c, + + },{ + .name = "SECAM-Lc", + .id = V4L2_STD_SECAM_LC, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x1b, + .chroma_ctrl1 = 0xd1, + .chroma_gain = 0x80, + .chroma_ctrl2 = 0x00, + .vgate_misc = 0x1c, + + },{ + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + NORM_525_60, + + .sync_control = 0x59, + .luma_control = 0x40, + .chroma_ctrl1 = 0xb9, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x0e, + .vgate_misc = 0x18, + + },{ + .name = "PAL-Nc", + .id = V4L2_STD_PAL_Nc, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0xa1, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + + },{ + .name = "PAL-60", + .id = V4L2_STD_PAL_60, + + .h_start = 0, + .h_stop = 719, + .video_v_start = 23, + .video_v_stop = 262, + .vbi_v_start_0 = 10, + .vbi_v_stop_0 = 21, + .vbi_v_start_1 = 273, + .src_timing = 7, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + } +}; +#define TVNORMS ARRAY_SIZE(tvnorms) + +static struct saa7134_format* format_by_fourcc(unsigned int fourcc) +{ + unsigned int i; + + for (i = 0; i < FORMATS; i++) + if (formats[i].fourcc == fourcc) + return formats+i; + return NULL; +} + +/* ------------------------------------------------------------------ */ + +static void set_tvnorm(struct saa7134_dev *dev, struct saa7134_tvnorm *norm) +{ + video_dbg("set tv norm = %s\n", norm->name); + dev->tvnorm = norm; + + /* setup cropping */ + dev->crop_bounds.left = norm->h_start; + dev->crop_defrect.left = norm->h_start; + dev->crop_bounds.width = norm->h_stop - norm->h_start +1; + dev->crop_defrect.width = norm->h_stop - norm->h_start +1; + + dev->crop_bounds.top = (norm->vbi_v_stop_0+1)*2; + dev->crop_defrect.top = norm->video_v_start*2; + dev->crop_bounds.height = ((norm->id & V4L2_STD_525_60) ? 524 : 624) + - dev->crop_bounds.top; + dev->crop_defrect.height = (norm->video_v_stop - norm->video_v_start +1)*2; + + dev->crop_current = dev->crop_defrect; + + saa7134_set_tvnorm_hw(dev); +} + +static void video_mux(struct saa7134_dev *dev, int input) +{ + video_dbg("video input = %d [%s]\n", + input, saa7134_input_name[card_in(dev, input).type]); + dev->ctl_input = input; + set_tvnorm(dev, dev->tvnorm); + saa7134_tvaudio_setinput(dev, &card_in(dev, input)); +} + + +static void saa7134_set_decoder(struct saa7134_dev *dev) +{ + int luma_control, sync_control, chroma_ctrl1, mux; + + struct saa7134_tvnorm *norm = dev->tvnorm; + mux = card_in(dev, dev->ctl_input).vmux; + + luma_control = norm->luma_control; + sync_control = norm->sync_control; + chroma_ctrl1 = norm->chroma_ctrl1; + + if (mux > 5) + luma_control |= 0x80; /* svideo */ + if (noninterlaced || dev->nosignal) + sync_control |= 0x20; + + /* switch on auto standard detection */ + sync_control |= SAA7134_SYNC_CTRL_AUFD; + chroma_ctrl1 |= SAA7134_CHROMA_CTRL1_AUTO0; + chroma_ctrl1 &= ~SAA7134_CHROMA_CTRL1_FCTC; + luma_control &= ~SAA7134_LUMA_CTRL_LDEL; + + /* setup video decoder */ + saa_writeb(SAA7134_INCR_DELAY, 0x08); + saa_writeb(SAA7134_ANALOG_IN_CTRL1, 0xc0 | mux); + saa_writeb(SAA7134_ANALOG_IN_CTRL2, 0x00); + + saa_writeb(SAA7134_ANALOG_IN_CTRL3, 0x90); + saa_writeb(SAA7134_ANALOG_IN_CTRL4, 0x90); + saa_writeb(SAA7134_HSYNC_START, 0xeb); + saa_writeb(SAA7134_HSYNC_STOP, 0xe0); + saa_writeb(SAA7134_SOURCE_TIMING1, norm->src_timing); + + saa_writeb(SAA7134_SYNC_CTRL, sync_control); + saa_writeb(SAA7134_LUMA_CTRL, luma_control); + saa_writeb(SAA7134_DEC_LUMA_BRIGHT, dev->ctl_bright); + + saa_writeb(SAA7134_DEC_LUMA_CONTRAST, + dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast); + + saa_writeb(SAA7134_DEC_CHROMA_SATURATION, + dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation); + + saa_writeb(SAA7134_DEC_CHROMA_HUE, dev->ctl_hue); + saa_writeb(SAA7134_CHROMA_CTRL1, chroma_ctrl1); + saa_writeb(SAA7134_CHROMA_GAIN, norm->chroma_gain); + + saa_writeb(SAA7134_CHROMA_CTRL2, norm->chroma_ctrl2); + saa_writeb(SAA7134_MODE_DELAY_CTRL, 0x00); + + saa_writeb(SAA7134_ANALOG_ADC, 0x01); + saa_writeb(SAA7134_VGATE_START, 0x11); + saa_writeb(SAA7134_VGATE_STOP, 0xfe); + saa_writeb(SAA7134_MISC_VGATE_MSB, norm->vgate_misc); + saa_writeb(SAA7134_RAW_DATA_GAIN, 0x40); + saa_writeb(SAA7134_RAW_DATA_OFFSET, 0x80); +} + +void saa7134_set_tvnorm_hw(struct saa7134_dev *dev) +{ + saa7134_set_decoder(dev); + + saa_call_all(dev, video, s_std, dev->tvnorm->id); + /* Set the correct norm for the saa6752hs. This function + does nothing if there is no saa6752hs. */ + saa_call_empress(dev, video, s_std, dev->tvnorm->id); +} + +static void set_h_prescale(struct saa7134_dev *dev, int task, int prescale) +{ + static const struct { + int xpsc; + int xacl; + int xc2_1; + int xdcg; + int vpfy; + } vals[] = { + /* XPSC XACL XC2_1 XDCG VPFY */ + { 1, 0, 0, 0, 0 }, + { 2, 2, 1, 2, 2 }, + { 3, 4, 1, 3, 2 }, + { 4, 8, 1, 4, 2 }, + { 5, 8, 1, 4, 2 }, + { 6, 8, 1, 4, 3 }, + { 7, 8, 1, 4, 3 }, + { 8, 15, 0, 4, 3 }, + { 9, 15, 0, 4, 3 }, + { 10, 16, 1, 5, 3 }, + }; + static const int count = ARRAY_SIZE(vals); + int i; + + for (i = 0; i < count; i++) + if (vals[i].xpsc == prescale) + break; + if (i == count) + return; + + saa_writeb(SAA7134_H_PRESCALE(task), vals[i].xpsc); + saa_writeb(SAA7134_ACC_LENGTH(task), vals[i].xacl); + saa_writeb(SAA7134_LEVEL_CTRL(task), + (vals[i].xc2_1 << 3) | (vals[i].xdcg)); + saa_andorb(SAA7134_FIR_PREFILTER_CTRL(task), 0x0f, + (vals[i].vpfy << 2) | vals[i].vpfy); +} + +static void set_v_scale(struct saa7134_dev *dev, int task, int yscale) +{ + int val,mirror; + + saa_writeb(SAA7134_V_SCALE_RATIO1(task), yscale & 0xff); + saa_writeb(SAA7134_V_SCALE_RATIO2(task), yscale >> 8); + + mirror = (dev->ctl_mirror) ? 0x02 : 0x00; + if (yscale < 2048) { + /* LPI */ + video_dbg("yscale LPI yscale=%d\n", yscale); + saa_writeb(SAA7134_V_FILTER(task), 0x00 | mirror); + saa_writeb(SAA7134_LUMA_CONTRAST(task), 0x40); + saa_writeb(SAA7134_CHROMA_SATURATION(task), 0x40); + } else { + /* ACM */ + val = 0x40 * 1024 / yscale; + video_dbg("yscale ACM yscale=%d val=0x%x\n", yscale, val); + saa_writeb(SAA7134_V_FILTER(task), 0x01 | mirror); + saa_writeb(SAA7134_LUMA_CONTRAST(task), val); + saa_writeb(SAA7134_CHROMA_SATURATION(task), val); + } + saa_writeb(SAA7134_LUMA_BRIGHT(task), 0x80); +} + +static void set_size(struct saa7134_dev *dev, int task, + int width, int height, int interlace) +{ + int prescale,xscale,yscale,y_even,y_odd; + int h_start, h_stop, v_start, v_stop; + int div = interlace ? 2 : 1; + + /* setup video scaler */ + h_start = dev->crop_current.left; + v_start = dev->crop_current.top/2; + h_stop = (dev->crop_current.left + dev->crop_current.width -1); + v_stop = (dev->crop_current.top + dev->crop_current.height -1)/2; + + saa_writeb(SAA7134_VIDEO_H_START1(task), h_start & 0xff); + saa_writeb(SAA7134_VIDEO_H_START2(task), h_start >> 8); + saa_writeb(SAA7134_VIDEO_H_STOP1(task), h_stop & 0xff); + saa_writeb(SAA7134_VIDEO_H_STOP2(task), h_stop >> 8); + saa_writeb(SAA7134_VIDEO_V_START1(task), v_start & 0xff); + saa_writeb(SAA7134_VIDEO_V_START2(task), v_start >> 8); + saa_writeb(SAA7134_VIDEO_V_STOP1(task), v_stop & 0xff); + saa_writeb(SAA7134_VIDEO_V_STOP2(task), v_stop >> 8); + + prescale = dev->crop_current.width / width; + if (0 == prescale) + prescale = 1; + xscale = 1024 * dev->crop_current.width / prescale / width; + yscale = 512 * div * dev->crop_current.height / height; + video_dbg("prescale=%d xscale=%d yscale=%d\n", + prescale, xscale, yscale); + set_h_prescale(dev,task,prescale); + saa_writeb(SAA7134_H_SCALE_INC1(task), xscale & 0xff); + saa_writeb(SAA7134_H_SCALE_INC2(task), xscale >> 8); + set_v_scale(dev,task,yscale); + + saa_writeb(SAA7134_VIDEO_PIXELS1(task), width & 0xff); + saa_writeb(SAA7134_VIDEO_PIXELS2(task), width >> 8); + saa_writeb(SAA7134_VIDEO_LINES1(task), height/div & 0xff); + saa_writeb(SAA7134_VIDEO_LINES2(task), height/div >> 8); + + /* deinterlace y offsets */ + y_odd = dev->ctl_y_odd; + y_even = dev->ctl_y_even; + saa_writeb(SAA7134_V_PHASE_OFFSET0(task), y_odd); + saa_writeb(SAA7134_V_PHASE_OFFSET1(task), y_even); + saa_writeb(SAA7134_V_PHASE_OFFSET2(task), y_odd); + saa_writeb(SAA7134_V_PHASE_OFFSET3(task), y_even); +} + +/* ------------------------------------------------------------------ */ + +/* + * Media Controller helper functions + */ + +static int saa7134_enable_analog_tuner(struct saa7134_dev *dev) +{ +#ifdef CONFIG_MEDIA_CONTROLLER + struct media_device *mdev = dev->media_dev; + struct media_entity *source; + struct media_link *link, *found_link = NULL; + int ret, active_links = 0; + + if (!mdev || !dev->decoder) + return 0; + + /* + * This will find the tuner that is connected into the decoder. + * Technically, this is not 100% correct, as the device may be + * using an analog input instead of the tuner. However, as we can't + * do DVB streaming while the DMA engine is being used for V4L2, + * this should be enough for the actual needs. + */ + list_for_each_entry(link, &dev->decoder->links, list) { + if (link->sink->entity == dev->decoder) { + found_link = link; + if (link->flags & MEDIA_LNK_FL_ENABLED) + active_links++; + break; + } + } + + if (active_links == 1 || !found_link) + return 0; + + source = found_link->source->entity; + list_for_each_entry(link, &source->links, list) { + struct media_entity *sink; + int flags = 0; + + sink = link->sink->entity; + + if (sink == dev->decoder) + flags = MEDIA_LNK_FL_ENABLED; + + ret = media_entity_setup_link(link, flags); + if (ret) { + pr_err("Couldn't change link %s->%s to %s. Error %d\n", + source->name, sink->name, + flags ? "enabled" : "disabled", + ret); + return ret; + } + } +#endif + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int buffer_activate(struct saa7134_dev *dev, + struct saa7134_buf *buf, + struct saa7134_buf *next) +{ + struct saa7134_dmaqueue *dmaq = buf->vb2.vb2_buf.vb2_queue->drv_priv; + unsigned long base,control,bpl; + unsigned long bpl_uv, lines_uv, base2, base3; /* planar */ + + video_dbg("buffer_activate buf=%p\n", buf); + buf->top_seen = 0; + + set_size(dev, TASK_A, dev->width, dev->height, + V4L2_FIELD_HAS_BOTH(dev->field)); + if (dev->fmt->yuv) + saa_andorb(SAA7134_DATA_PATH(TASK_A), 0x3f, 0x03); + else + saa_andorb(SAA7134_DATA_PATH(TASK_A), 0x3f, 0x01); + saa_writeb(SAA7134_OFMT_VIDEO_A, dev->fmt->pm); + + /* DMA: setup channel 0 (= Video Task A0) */ + base = saa7134_buffer_base(buf); + if (dev->fmt->planar) + bpl = dev->width; + else + bpl = (dev->width * dev->fmt->depth) / 8; + control = SAA7134_RS_CONTROL_BURST_16 | + SAA7134_RS_CONTROL_ME | + (dmaq->pt.dma >> 12); + if (dev->fmt->bswap) + control |= SAA7134_RS_CONTROL_BSWAP; + if (dev->fmt->wswap) + control |= SAA7134_RS_CONTROL_WSWAP; + if (V4L2_FIELD_HAS_BOTH(dev->field)) { + /* interlaced */ + saa_writel(SAA7134_RS_BA1(0),base); + saa_writel(SAA7134_RS_BA2(0),base+bpl); + saa_writel(SAA7134_RS_PITCH(0),bpl*2); + } else { + /* non-interlaced */ + saa_writel(SAA7134_RS_BA1(0),base); + saa_writel(SAA7134_RS_BA2(0),base); + saa_writel(SAA7134_RS_PITCH(0),bpl); + } + saa_writel(SAA7134_RS_CONTROL(0),control); + + if (dev->fmt->planar) { + /* DMA: setup channel 4+5 (= planar task A) */ + bpl_uv = bpl >> dev->fmt->hshift; + lines_uv = dev->height >> dev->fmt->vshift; + base2 = base + bpl * dev->height; + base3 = base2 + bpl_uv * lines_uv; + if (dev->fmt->uvswap) + swap(base2, base3); + video_dbg("uv: bpl=%ld lines=%ld base2/3=%ld/%ld\n", + bpl_uv,lines_uv,base2,base3); + if (V4L2_FIELD_HAS_BOTH(dev->field)) { + /* interlaced */ + saa_writel(SAA7134_RS_BA1(4),base2); + saa_writel(SAA7134_RS_BA2(4),base2+bpl_uv); + saa_writel(SAA7134_RS_PITCH(4),bpl_uv*2); + saa_writel(SAA7134_RS_BA1(5),base3); + saa_writel(SAA7134_RS_BA2(5),base3+bpl_uv); + saa_writel(SAA7134_RS_PITCH(5),bpl_uv*2); + } else { + /* non-interlaced */ + saa_writel(SAA7134_RS_BA1(4),base2); + saa_writel(SAA7134_RS_BA2(4),base2); + saa_writel(SAA7134_RS_PITCH(4),bpl_uv); + saa_writel(SAA7134_RS_BA1(5),base3); + saa_writel(SAA7134_RS_BA2(5),base3); + saa_writel(SAA7134_RS_PITCH(5),bpl_uv); + } + saa_writel(SAA7134_RS_CONTROL(4),control); + saa_writel(SAA7134_RS_CONTROL(5),control); + } + + /* start DMA */ + saa7134_set_dmabits(dev); + mod_timer(&dmaq->timeout, jiffies + BUFFER_TIMEOUT); + return 0; +} + +static int buffer_init(struct vb2_buffer *vb2) +{ + struct saa7134_dmaqueue *dmaq = vb2->vb2_queue->drv_priv; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2); + struct saa7134_buf *buf = container_of(vbuf, struct saa7134_buf, vb2); + + dmaq->curr = NULL; + buf->activate = buffer_activate; + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb2) +{ + struct saa7134_dmaqueue *dmaq = vb2->vb2_queue->drv_priv; + struct saa7134_dev *dev = dmaq->dev; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2); + struct saa7134_buf *buf = container_of(vbuf, struct saa7134_buf, vb2); + struct sg_table *dma = vb2_dma_sg_plane_desc(vb2, 0); + unsigned int size; + + if (dma->sgl->offset) { + pr_err("The buffer is not page-aligned\n"); + return -EINVAL; + } + size = (dev->width * dev->height * dev->fmt->depth) >> 3; + if (vb2_plane_size(vb2, 0) < size) + return -EINVAL; + + vb2_set_plane_payload(vb2, 0, size); + vbuf->field = dev->field; + + return saa7134_pgtable_build(dev->pci, &dmaq->pt, dma->sgl, dma->nents, + saa7134_buffer_startpage(buf)); +} + +static int queue_setup(struct vb2_queue *q, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct saa7134_dmaqueue *dmaq = q->drv_priv; + struct saa7134_dev *dev = dmaq->dev; + int size = dev->fmt->depth * dev->width * dev->height >> 3; + + if (dev->width < 48 || + dev->height < 32 || + dev->width/4 > dev->crop_current.width || + dev->height/4 > dev->crop_current.height || + dev->width > dev->crop_bounds.width || + dev->height > dev->crop_bounds.height) + return -EINVAL; + + *nbuffers = saa7134_buffer_count(size, *nbuffers); + *nplanes = 1; + sizes[0] = size; + + saa7134_enable_analog_tuner(dev); + + return 0; +} + +/* + * move buffer to hardware queue + */ +void saa7134_vb2_buffer_queue(struct vb2_buffer *vb) +{ + struct saa7134_dmaqueue *dmaq = vb->vb2_queue->drv_priv; + struct saa7134_dev *dev = dmaq->dev; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct saa7134_buf *buf = container_of(vbuf, struct saa7134_buf, vb2); + + saa7134_buffer_queue(dev, dmaq, buf); +} +EXPORT_SYMBOL_GPL(saa7134_vb2_buffer_queue); + +int saa7134_vb2_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct saa7134_dmaqueue *dmaq = vq->drv_priv; + struct saa7134_dev *dev = dmaq->dev; + + /* + * Planar video capture and TS share the same DMA channel, + * so only one can be active at a time. + */ + if (card_is_empress(dev) && vb2_is_busy(&dev->empress_vbq) && + dmaq == &dev->video_q && dev->fmt->planar) { + struct saa7134_buf *buf, *tmp; + + list_for_each_entry_safe(buf, tmp, &dmaq->queue, entry) { + list_del(&buf->entry); + vb2_buffer_done(&buf->vb2.vb2_buf, + VB2_BUF_STATE_QUEUED); + } + if (dmaq->curr) { + vb2_buffer_done(&dmaq->curr->vb2.vb2_buf, + VB2_BUF_STATE_QUEUED); + dmaq->curr = NULL; + } + return -EBUSY; + } + + /* The SAA7134 has a 1K FIFO; the datasheet suggests that when + * configured conservatively, there's 22 usec of buffering for video. + * We therefore request a DMA latency of 20 usec, giving us 2 usec of + * margin in case the FIFO is configured differently to the datasheet. + * Unfortunately, I lack register-level documentation to check the + * Linux FIFO setup and confirm the perfect value. + */ + if ((dmaq == &dev->video_q && !vb2_is_streaming(&dev->vbi_vbq)) || + (dmaq == &dev->vbi_q && !vb2_is_streaming(&dev->video_vbq))) + cpu_latency_qos_add_request(&dev->qos_request, 20); + dmaq->seq_nr = 0; + + return 0; +} + +void saa7134_vb2_stop_streaming(struct vb2_queue *vq) +{ + struct saa7134_dmaqueue *dmaq = vq->drv_priv; + struct saa7134_dev *dev = dmaq->dev; + + saa7134_stop_streaming(dev, dmaq); + + if ((dmaq == &dev->video_q && !vb2_is_streaming(&dev->vbi_vbq)) || + (dmaq == &dev->vbi_q && !vb2_is_streaming(&dev->video_vbq))) + cpu_latency_qos_remove_request(&dev->qos_request); +} + +static const struct vb2_ops vb2_qops = { + .queue_setup = queue_setup, + .buf_init = buffer_init, + .buf_prepare = buffer_prepare, + .buf_queue = saa7134_vb2_buffer_queue, + .start_streaming = saa7134_vb2_start_streaming, + .stop_streaming = saa7134_vb2_stop_streaming, +}; + +/* ------------------------------------------------------------------ */ + +static int saa7134_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct saa7134_dev *dev = container_of(ctrl->handler, struct saa7134_dev, ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + dev->ctl_bright = ctrl->val; + saa_writeb(SAA7134_DEC_LUMA_BRIGHT, ctrl->val); + break; + case V4L2_CID_HUE: + dev->ctl_hue = ctrl->val; + saa_writeb(SAA7134_DEC_CHROMA_HUE, ctrl->val); + break; + case V4L2_CID_CONTRAST: + dev->ctl_contrast = ctrl->val; + saa_writeb(SAA7134_DEC_LUMA_CONTRAST, + dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast); + break; + case V4L2_CID_SATURATION: + dev->ctl_saturation = ctrl->val; + saa_writeb(SAA7134_DEC_CHROMA_SATURATION, + dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation); + break; + case V4L2_CID_AUDIO_MUTE: + dev->ctl_mute = ctrl->val; + saa7134_tvaudio_setmute(dev); + break; + case V4L2_CID_AUDIO_VOLUME: + dev->ctl_volume = ctrl->val; + saa7134_tvaudio_setvolume(dev,dev->ctl_volume); + break; + case V4L2_CID_PRIVATE_INVERT: + dev->ctl_invert = ctrl->val; + saa_writeb(SAA7134_DEC_LUMA_CONTRAST, + dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast); + saa_writeb(SAA7134_DEC_CHROMA_SATURATION, + dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation); + break; + case V4L2_CID_HFLIP: + dev->ctl_mirror = ctrl->val; + break; + case V4L2_CID_PRIVATE_Y_EVEN: + dev->ctl_y_even = ctrl->val; + break; + case V4L2_CID_PRIVATE_Y_ODD: + dev->ctl_y_odd = ctrl->val; + break; + case V4L2_CID_PRIVATE_AUTOMUTE: + { + struct v4l2_priv_tun_config tda9887_cfg; + + tda9887_cfg.tuner = TUNER_TDA9887; + tda9887_cfg.priv = &dev->tda9887_conf; + + dev->ctl_automute = ctrl->val; + if (dev->tda9887_conf) { + if (dev->ctl_automute) + dev->tda9887_conf |= TDA9887_AUTOMUTE; + else + dev->tda9887_conf &= ~TDA9887_AUTOMUTE; + + saa_call_all(dev, tuner, s_config, &tda9887_cfg); + } + break; + } + default: + return -EINVAL; + } + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int video_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct saa7134_dev *dev = video_drvdata(file); + int ret = v4l2_fh_open(file); + + if (ret < 0) + return ret; + + mutex_lock(&dev->lock); + if (vdev->vfl_type == VFL_TYPE_RADIO) { + /* switch to radio mode */ + saa7134_tvaudio_setinput(dev, &card(dev).radio); + saa_call_all(dev, tuner, s_radio); + } else { + /* switch to video/vbi mode */ + video_mux(dev, dev->ctl_input); + } + mutex_unlock(&dev->lock); + + return 0; +} + +static int video_release(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct saa7134_dev *dev = video_drvdata(file); + struct saa6588_command cmd; + + mutex_lock(&dev->lock); + saa7134_tvaudio_close(dev); + + if (vdev->vfl_type == VFL_TYPE_RADIO) + v4l2_fh_release(file); + else + _vb2_fop_release(file, NULL); + + /* ts-capture will not work in planar mode, so turn it off Hac: 04.05*/ + saa_andorb(SAA7134_OFMT_VIDEO_A, 0x1f, 0); + saa_andorb(SAA7134_OFMT_VIDEO_B, 0x1f, 0); + saa_andorb(SAA7134_OFMT_DATA_A, 0x1f, 0); + saa_andorb(SAA7134_OFMT_DATA_B, 0x1f, 0); + + saa_call_all(dev, tuner, standby); + if (vdev->vfl_type == VFL_TYPE_RADIO) + saa_call_all(dev, core, command, SAA6588_CMD_CLOSE, &cmd); + mutex_unlock(&dev->lock); + + return 0; +} + +static ssize_t radio_read(struct file *file, char __user *data, + size_t count, loff_t *ppos) +{ + struct saa7134_dev *dev = video_drvdata(file); + struct saa6588_command cmd; + + cmd.block_count = count/3; + cmd.nonblocking = file->f_flags & O_NONBLOCK; + cmd.buffer = data; + cmd.instance = file; + cmd.result = -ENODEV; + + mutex_lock(&dev->lock); + saa_call_all(dev, core, command, SAA6588_CMD_READ, &cmd); + mutex_unlock(&dev->lock); + + return cmd.result; +} + +static __poll_t radio_poll(struct file *file, poll_table *wait) +{ + struct saa7134_dev *dev = video_drvdata(file); + struct saa6588_command cmd; + __poll_t rc = v4l2_ctrl_poll(file, wait); + + cmd.instance = file; + cmd.event_list = wait; + cmd.poll_mask = 0; + mutex_lock(&dev->lock); + saa_call_all(dev, core, command, SAA6588_CMD_POLL, &cmd); + mutex_unlock(&dev->lock); + + return rc | cmd.poll_mask; +} + +/* ------------------------------------------------------------------ */ + +static int saa7134_try_get_set_fmt_vbi_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct saa7134_dev *dev = video_drvdata(file); + struct saa7134_tvnorm *norm = dev->tvnorm; + + memset(&f->fmt.vbi.reserved, 0, sizeof(f->fmt.vbi.reserved)); + f->fmt.vbi.sampling_rate = 6750000 * 4; + f->fmt.vbi.samples_per_line = 2048 /* VBI_LINE_LENGTH */; + f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + f->fmt.vbi.offset = 64 * 4; + f->fmt.vbi.start[0] = norm->vbi_v_start_0; + f->fmt.vbi.count[0] = norm->vbi_v_stop_0 - norm->vbi_v_start_0 +1; + f->fmt.vbi.start[1] = norm->vbi_v_start_1; + f->fmt.vbi.count[1] = f->fmt.vbi.count[0]; + f->fmt.vbi.flags = 0; /* VBI_UNSYNC VBI_INTERLACED */ + + return 0; +} + +static int saa7134_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct saa7134_dev *dev = video_drvdata(file); + + f->fmt.pix.width = dev->width; + f->fmt.pix.height = dev->height; + f->fmt.pix.field = dev->field; + f->fmt.pix.pixelformat = dev->fmt->fourcc; + if (dev->fmt->planar) + f->fmt.pix.bytesperline = f->fmt.pix.width; + else + f->fmt.pix.bytesperline = + (f->fmt.pix.width * dev->fmt->depth) / 8; + f->fmt.pix.sizeimage = + (f->fmt.pix.height * f->fmt.pix.width * dev->fmt->depth) / 8; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + return 0; +} + +static int saa7134_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct saa7134_dev *dev = video_drvdata(file); + struct saa7134_format *fmt; + enum v4l2_field field; + unsigned int maxw, maxh; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + maxw = min(dev->crop_current.width*4, dev->crop_bounds.width); + maxh = min(dev->crop_current.height*4, dev->crop_bounds.height); + + if (V4L2_FIELD_ANY == field) { + field = (f->fmt.pix.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + default: + field = V4L2_FIELD_INTERLACED; + break; + } + + f->fmt.pix.field = field; + if (f->fmt.pix.width < 48) + f->fmt.pix.width = 48; + if (f->fmt.pix.height < 32) + f->fmt.pix.height = 32; + if (f->fmt.pix.width > maxw) + f->fmt.pix.width = maxw; + if (f->fmt.pix.height > maxh) + f->fmt.pix.height = maxh; + f->fmt.pix.width &= ~0x03; + if (fmt->planar) + f->fmt.pix.bytesperline = f->fmt.pix.width; + else + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) / 8; + f->fmt.pix.sizeimage = + (f->fmt.pix.height * f->fmt.pix.width * fmt->depth) / 8; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int saa7134_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct saa7134_dev *dev = video_drvdata(file); + int err; + + err = saa7134_try_fmt_vid_cap(file, priv, f); + if (0 != err) + return err; + + dev->fmt = format_by_fourcc(f->fmt.pix.pixelformat); + dev->width = f->fmt.pix.width; + dev->height = f->fmt.pix.height; + dev->field = f->fmt.pix.field; + return 0; +} + +int saa7134_enum_input(struct file *file, void *priv, struct v4l2_input *i) +{ + struct saa7134_dev *dev = video_drvdata(file); + unsigned int n; + + n = i->index; + if (n >= SAA7134_INPUT_MAX) + return -EINVAL; + if (card_in(dev, i->index).type == SAA7134_NO_INPUT) + return -EINVAL; + i->index = n; + strscpy(i->name, saa7134_input_name[card_in(dev, n).type], + sizeof(i->name)); + switch (card_in(dev, n).type) { + case SAA7134_INPUT_TV: + case SAA7134_INPUT_TV_MONO: + i->type = V4L2_INPUT_TYPE_TUNER; + break; + default: + i->type = V4L2_INPUT_TYPE_CAMERA; + break; + } + if (n == dev->ctl_input) { + int v1 = saa_readb(SAA7134_STATUS_VIDEO1); + int v2 = saa_readb(SAA7134_STATUS_VIDEO2); + + if (0 != (v1 & 0x40)) + i->status |= V4L2_IN_ST_NO_H_LOCK; + if (0 != (v2 & 0x40)) + i->status |= V4L2_IN_ST_NO_SIGNAL; + if (0 != (v2 & 0x0e)) + i->status |= V4L2_IN_ST_MACROVISION; + } + i->std = SAA7134_NORMS; + return 0; +} +EXPORT_SYMBOL_GPL(saa7134_enum_input); + +int saa7134_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct saa7134_dev *dev = video_drvdata(file); + + *i = dev->ctl_input; + return 0; +} +EXPORT_SYMBOL_GPL(saa7134_g_input); + +int saa7134_s_input(struct file *file, void *priv, unsigned int i) +{ + struct saa7134_dev *dev = video_drvdata(file); + + if (i >= SAA7134_INPUT_MAX) + return -EINVAL; + if (card_in(dev, i).type == SAA7134_NO_INPUT) + return -EINVAL; + video_mux(dev, i); + return 0; +} +EXPORT_SYMBOL_GPL(saa7134_s_input); + +int saa7134_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct saa7134_dev *dev = video_drvdata(file); + + strscpy(cap->driver, "saa7134", sizeof(cap->driver)); + strscpy(cap->card, saa7134_boards[dev->board].name, + sizeof(cap->card)); + cap->capabilities = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | + V4L2_CAP_RADIO | V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VBI_CAPTURE | V4L2_CAP_DEVICE_CAPS; + if (dev->tuner_type != TUNER_ABSENT && dev->tuner_type != UNSET) + cap->capabilities |= V4L2_CAP_TUNER; + if (dev->has_rds) + cap->capabilities |= V4L2_CAP_RDS_CAPTURE; + + return 0; +} +EXPORT_SYMBOL_GPL(saa7134_querycap); + +int saa7134_s_std(struct file *file, void *priv, v4l2_std_id id) +{ + struct saa7134_dev *dev = video_drvdata(file); + unsigned int i; + v4l2_std_id fixup; + + for (i = 0; i < TVNORMS; i++) + if (id == tvnorms[i].id) + break; + + if (i == TVNORMS) + for (i = 0; i < TVNORMS; i++) + if (id & tvnorms[i].id) + break; + if (i == TVNORMS) + return -EINVAL; + + if ((id & V4L2_STD_SECAM) && (secam[0] != '-')) { + if (secam[0] == 'L' || secam[0] == 'l') { + if (secam[1] == 'C' || secam[1] == 'c') + fixup = V4L2_STD_SECAM_LC; + else + fixup = V4L2_STD_SECAM_L; + } else { + if (secam[0] == 'D' || secam[0] == 'd') + fixup = V4L2_STD_SECAM_DK; + else + fixup = V4L2_STD_SECAM; + } + for (i = 0; i < TVNORMS; i++) { + if (fixup == tvnorms[i].id) + break; + } + if (i == TVNORMS) + return -EINVAL; + } + + set_tvnorm(dev, &tvnorms[i]); + + saa7134_tvaudio_do_scan(dev); + return 0; +} +EXPORT_SYMBOL_GPL(saa7134_s_std); + +int saa7134_g_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct saa7134_dev *dev = video_drvdata(file); + + *id = dev->tvnorm->id; + return 0; +} +EXPORT_SYMBOL_GPL(saa7134_g_std); + +static v4l2_std_id saa7134_read_std(struct saa7134_dev *dev) +{ + static v4l2_std_id stds[] = { + V4L2_STD_UNKNOWN, + V4L2_STD_NTSC, + V4L2_STD_PAL, + V4L2_STD_SECAM }; + + v4l2_std_id result = 0; + + u8 st1 = saa_readb(SAA7134_STATUS_VIDEO1); + u8 st2 = saa_readb(SAA7134_STATUS_VIDEO2); + + if (!(st2 & 0x1)) /* RDCAP == 0 */ + result = V4L2_STD_UNKNOWN; + else + result = stds[st1 & 0x03]; + + return result; +} + +int saa7134_querystd(struct file *file, void *priv, v4l2_std_id *std) +{ + struct saa7134_dev *dev = video_drvdata(file); + *std &= saa7134_read_std(dev); + return 0; +} +EXPORT_SYMBOL_GPL(saa7134_querystd); + +static int saa7134_g_pixelaspect(struct file *file, void *priv, + int type, struct v4l2_fract *f) +{ + struct saa7134_dev *dev = video_drvdata(file); + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (dev->tvnorm->id & V4L2_STD_525_60) { + f->numerator = 11; + f->denominator = 10; + } + if (dev->tvnorm->id & V4L2_STD_625_50) { + f->numerator = 54; + f->denominator = 59; + } + return 0; +} + +static int saa7134_g_selection(struct file *file, void *priv, struct v4l2_selection *sel) +{ + struct saa7134_dev *dev = video_drvdata(file); + + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = dev->crop_current; + break; + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r = dev->crop_defrect; + break; + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r = dev->crop_bounds; + break; + default: + return -EINVAL; + } + return 0; +} + +static int saa7134_s_selection(struct file *file, void *priv, struct v4l2_selection *sel) +{ + struct saa7134_dev *dev = video_drvdata(file); + struct v4l2_rect *b = &dev->crop_bounds; + struct v4l2_rect *c = &dev->crop_current; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + if (vb2_is_streaming(&dev->video_vbq)) + return -EBUSY; + + *c = sel->r; + if (c->top < b->top) + c->top = b->top; + if (c->top > b->top + b->height) + c->top = b->top + b->height; + if (c->height > b->top - c->top + b->height) + c->height = b->top - c->top + b->height; + + if (c->left < b->left) + c->left = b->left; + if (c->left > b->left + b->width) + c->left = b->left + b->width; + if (c->width > b->left - c->left + b->width) + c->width = b->left - c->left + b->width; + sel->r = *c; + return 0; +} + +int saa7134_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct saa7134_dev *dev = video_drvdata(file); + int n; + + if (0 != t->index) + return -EINVAL; + memset(t, 0, sizeof(*t)); + for (n = 0; n < SAA7134_INPUT_MAX; n++) { + if (card_in(dev, n).type == SAA7134_INPUT_TV || + card_in(dev, n).type == SAA7134_INPUT_TV_MONO) + break; + } + if (n == SAA7134_INPUT_MAX) + return -EINVAL; + if (card_in(dev, n).type != SAA7134_NO_INPUT) { + strscpy(t->name, "Television", sizeof(t->name)); + t->type = V4L2_TUNER_ANALOG_TV; + saa_call_all(dev, tuner, g_tuner, t); + t->capability = V4L2_TUNER_CAP_NORM | + V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1 | + V4L2_TUNER_CAP_LANG2; + t->rxsubchans = saa7134_tvaudio_getstereo(dev); + t->audmode = saa7134_tvaudio_rx2mode(t->rxsubchans); + } + if (0 != (saa_readb(SAA7134_STATUS_VIDEO1) & 0x03)) + t->signal = 0xffff; + return 0; +} +EXPORT_SYMBOL_GPL(saa7134_g_tuner); + +int saa7134_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *t) +{ + struct saa7134_dev *dev = video_drvdata(file); + int rx, mode; + + if (0 != t->index) + return -EINVAL; + + mode = dev->thread.mode; + if (UNSET == mode) { + rx = saa7134_tvaudio_getstereo(dev); + mode = saa7134_tvaudio_rx2mode(rx); + } + if (mode != t->audmode) + dev->thread.mode = t->audmode; + + return 0; +} +EXPORT_SYMBOL_GPL(saa7134_s_tuner); + +int saa7134_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct saa7134_dev *dev = video_drvdata(file); + + if (0 != f->tuner) + return -EINVAL; + + saa_call_all(dev, tuner, g_frequency, f); + + return 0; +} +EXPORT_SYMBOL_GPL(saa7134_g_frequency); + +int saa7134_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct saa7134_dev *dev = video_drvdata(file); + + if (0 != f->tuner) + return -EINVAL; + + saa_call_all(dev, tuner, s_frequency, f); + + saa7134_tvaudio_do_scan(dev); + return 0; +} +EXPORT_SYMBOL_GPL(saa7134_s_frequency); + +static int saa7134_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index >= FORMATS) + return -EINVAL; + + f->pixelformat = formats[f->index].fourcc; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int vidioc_g_register (struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct saa7134_dev *dev = video_drvdata(file); + + reg->val = saa_readb(reg->reg & 0xffffff); + reg->size = 1; + return 0; +} + +static int vidioc_s_register (struct file *file, void *priv, + const struct v4l2_dbg_register *reg) +{ + struct saa7134_dev *dev = video_drvdata(file); + + saa_writeb(reg->reg & 0xffffff, reg->val); + return 0; +} +#endif + +static int radio_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct saa7134_dev *dev = video_drvdata(file); + + if (0 != t->index) + return -EINVAL; + + strscpy(t->name, "Radio", sizeof(t->name)); + + saa_call_all(dev, tuner, g_tuner, t); + t->audmode &= V4L2_TUNER_MODE_MONO | V4L2_TUNER_MODE_STEREO; + if (dev->input->amux == TV) { + t->signal = 0xf800 - ((saa_readb(0x581) & 0x1f) << 11); + t->rxsubchans = (saa_readb(0x529) & 0x08) ? + V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; + } + return 0; +} +static int radio_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *t) +{ + struct saa7134_dev *dev = video_drvdata(file); + + if (0 != t->index) + return -EINVAL; + + saa_call_all(dev, tuner, s_tuner, t); + return 0; +} + +static const struct v4l2_file_operations video_fops = +{ + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = saa7134_querycap, + .vidioc_enum_fmt_vid_cap = saa7134_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = saa7134_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = saa7134_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = saa7134_s_fmt_vid_cap, + .vidioc_g_fmt_vbi_cap = saa7134_try_get_set_fmt_vbi_cap, + .vidioc_try_fmt_vbi_cap = saa7134_try_get_set_fmt_vbi_cap, + .vidioc_s_fmt_vbi_cap = saa7134_try_get_set_fmt_vbi_cap, + .vidioc_g_pixelaspect = saa7134_g_pixelaspect, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_s_std = saa7134_s_std, + .vidioc_g_std = saa7134_g_std, + .vidioc_querystd = saa7134_querystd, + .vidioc_enum_input = saa7134_enum_input, + .vidioc_g_input = saa7134_g_input, + .vidioc_s_input = saa7134_s_input, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_g_tuner = saa7134_g_tuner, + .vidioc_s_tuner = saa7134_s_tuner, + .vidioc_g_selection = saa7134_g_selection, + .vidioc_s_selection = saa7134_s_selection, + .vidioc_g_frequency = saa7134_g_frequency, + .vidioc_s_frequency = saa7134_s_frequency, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = vidioc_g_register, + .vidioc_s_register = vidioc_s_register, +#endif + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct v4l2_file_operations radio_fops = { + .owner = THIS_MODULE, + .open = video_open, + .read = radio_read, + .release = video_release, + .unlocked_ioctl = video_ioctl2, + .poll = radio_poll, +}; + +static const struct v4l2_ioctl_ops radio_ioctl_ops = { + .vidioc_querycap = saa7134_querycap, + .vidioc_g_tuner = radio_g_tuner, + .vidioc_s_tuner = radio_s_tuner, + .vidioc_g_frequency = saa7134_g_frequency, + .vidioc_s_frequency = saa7134_s_frequency, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* ----------------------------------------------------------- */ +/* exported stuff */ + +struct video_device saa7134_video_template = { + .name = "saa7134-video", + .fops = &video_fops, + .ioctl_ops = &video_ioctl_ops, + .tvnorms = SAA7134_NORMS, +}; + +struct video_device saa7134_radio_template = { + .name = "saa7134-radio", + .fops = &radio_fops, + .ioctl_ops = &radio_ioctl_ops, +}; + +static const struct v4l2_ctrl_ops saa7134_ctrl_ops = { + .s_ctrl = saa7134_s_ctrl, +}; + +static const struct v4l2_ctrl_config saa7134_ctrl_invert = { + .ops = &saa7134_ctrl_ops, + .id = V4L2_CID_PRIVATE_INVERT, + .name = "Invert", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, +}; + +static const struct v4l2_ctrl_config saa7134_ctrl_y_odd = { + .ops = &saa7134_ctrl_ops, + .id = V4L2_CID_PRIVATE_Y_ODD, + .name = "Y Offset Odd Field", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 128, + .step = 1, +}; + +static const struct v4l2_ctrl_config saa7134_ctrl_y_even = { + .ops = &saa7134_ctrl_ops, + .id = V4L2_CID_PRIVATE_Y_EVEN, + .name = "Y Offset Even Field", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 128, + .step = 1, +}; + +static const struct v4l2_ctrl_config saa7134_ctrl_automute = { + .ops = &saa7134_ctrl_ops, + .id = V4L2_CID_PRIVATE_AUTOMUTE, + .name = "Automute", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, +}; + +int saa7134_video_init1(struct saa7134_dev *dev) +{ + struct v4l2_ctrl_handler *hdl = &dev->ctrl_handler; + struct vb2_queue *q; + int ret; + + /* sanitycheck insmod options */ + if (gbuffers < 2 || gbuffers > VIDEO_MAX_FRAME) + gbuffers = 2; + if (gbufsize > gbufsize_max) + gbufsize = gbufsize_max; + gbufsize = (gbufsize + PAGE_SIZE - 1) & PAGE_MASK; + + v4l2_ctrl_handler_init(hdl, 11); + v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, + V4L2_CID_CONTRAST, 0, 127, 1, 68); + v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, + V4L2_CID_SATURATION, 0, 127, 1, 64); + v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, -15, 15, 1, 0); + v4l2_ctrl_new_custom(hdl, &saa7134_ctrl_invert, NULL); + v4l2_ctrl_new_custom(hdl, &saa7134_ctrl_y_odd, NULL); + v4l2_ctrl_new_custom(hdl, &saa7134_ctrl_y_even, NULL); + v4l2_ctrl_new_custom(hdl, &saa7134_ctrl_automute, NULL); + if (hdl->error) + return hdl->error; + if (card_has_radio(dev)) { + hdl = &dev->radio_ctrl_handler; + v4l2_ctrl_handler_init(hdl, 2); + v4l2_ctrl_add_handler(hdl, &dev->ctrl_handler, + v4l2_ctrl_radio_filter, false); + if (hdl->error) + return hdl->error; + } + dev->ctl_mute = 1; + + if (dev->tda9887_conf && saa7134_ctrl_automute.def) + dev->tda9887_conf |= TDA9887_AUTOMUTE; + dev->automute = 0; + + INIT_LIST_HEAD(&dev->video_q.queue); + timer_setup(&dev->video_q.timeout, saa7134_buffer_timeout, 0); + dev->video_q.dev = dev; + dev->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24); + dev->width = 720; + dev->height = 576; + dev->field = V4L2_FIELD_INTERLACED; + + if (saa7134_boards[dev->board].video_out) + saa7134_videoport_init(dev); + + q = &dev->video_vbq; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + /* + * Do not add VB2_USERPTR unless explicitly requested: the saa7134 DMA + * engine cannot handle transfers that do not start at the beginning + * of a page. A user-provided pointer can start anywhere in a page, so + * USERPTR support is a no-go unless the application knows about these + * limitations and has special support for this. + */ + q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ; + if (saa7134_userptr) + q->io_modes |= VB2_USERPTR; + q->drv_priv = &dev->video_q; + q->ops = &vb2_qops; + q->gfp_flags = GFP_DMA32; + q->mem_ops = &vb2_dma_sg_memops; + q->buf_struct_size = sizeof(struct saa7134_buf); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &dev->lock; + q->dev = &dev->pci->dev; + ret = vb2_queue_init(q); + if (ret) + goto err_free_ctrl; + ret = saa7134_pgtable_alloc(dev->pci, &dev->video_q.pt); + if (ret) + goto err_free_ctrl; + + q = &dev->vbi_vbq; + q->type = V4L2_BUF_TYPE_VBI_CAPTURE; + /* Don't add VB2_USERPTR, see comment above */ + q->io_modes = VB2_MMAP | VB2_READ; + if (saa7134_userptr) + q->io_modes |= VB2_USERPTR; + q->drv_priv = &dev->vbi_q; + q->ops = &saa7134_vbi_qops; + q->gfp_flags = GFP_DMA32; + q->mem_ops = &vb2_dma_sg_memops; + q->buf_struct_size = sizeof(struct saa7134_buf); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &dev->lock; + q->dev = &dev->pci->dev; + ret = vb2_queue_init(q); + if (ret) { + saa7134_pgtable_free(dev->pci, &dev->video_q.pt); + goto err_free_ctrl; + } + + ret = saa7134_pgtable_alloc(dev->pci, &dev->vbi_q.pt); + if (ret) { + saa7134_pgtable_free(dev->pci, &dev->video_q.pt); + goto err_free_ctrl; + } + + return 0; + +err_free_ctrl: + v4l2_ctrl_handler_free(&dev->ctrl_handler); + if (card_has_radio(dev)) + v4l2_ctrl_handler_free(&dev->radio_ctrl_handler); + return ret; +} + +void saa7134_video_fini(struct saa7134_dev *dev) +{ + timer_delete_sync(&dev->video_q.timeout); + /* free stuff */ + saa7134_pgtable_free(dev->pci, &dev->video_q.pt); + saa7134_pgtable_free(dev->pci, &dev->vbi_q.pt); + v4l2_ctrl_handler_free(&dev->ctrl_handler); + if (card_has_radio(dev)) + v4l2_ctrl_handler_free(&dev->radio_ctrl_handler); +} + +int saa7134_videoport_init(struct saa7134_dev *dev) +{ + /* enable video output */ + int vo = saa7134_boards[dev->board].video_out; + int video_reg; + unsigned int vid_port_opts = saa7134_boards[dev->board].vid_port_opts; + + /* Configure videoport */ + saa_writeb(SAA7134_VIDEO_PORT_CTRL0, video_out[vo][0]); + video_reg = video_out[vo][1]; + if (vid_port_opts & SET_T_CODE_POLARITY_NON_INVERTED) + video_reg &= ~VP_T_CODE_P_INVERTED; + saa_writeb(SAA7134_VIDEO_PORT_CTRL1, video_reg); + saa_writeb(SAA7134_VIDEO_PORT_CTRL2, video_out[vo][2]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL4, video_out[vo][4]); + video_reg = video_out[vo][5]; + if (vid_port_opts & SET_CLOCK_NOT_DELAYED) + video_reg &= ~VP_CLK_CTRL2_DELAYED; + if (vid_port_opts & SET_CLOCK_INVERTED) + video_reg |= VP_CLK_CTRL1_INVERTED; + saa_writeb(SAA7134_VIDEO_PORT_CTRL5, video_reg); + video_reg = video_out[vo][6]; + if (vid_port_opts & SET_VSYNC_OFF) { + video_reg &= ~VP_VS_TYPE_MASK; + video_reg |= VP_VS_TYPE_OFF; + } + saa_writeb(SAA7134_VIDEO_PORT_CTRL6, video_reg); + saa_writeb(SAA7134_VIDEO_PORT_CTRL7, video_out[vo][7]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL8, video_out[vo][8]); + + /* Start videoport */ + saa_writeb(SAA7134_VIDEO_PORT_CTRL3, video_out[vo][3]); + + return 0; +} + +int saa7134_video_init2(struct saa7134_dev *dev) +{ + /* init video hw */ + set_tvnorm(dev,&tvnorms[0]); + video_mux(dev,0); + v4l2_ctrl_handler_setup(&dev->ctrl_handler); + saa7134_tvaudio_setmute(dev); + saa7134_tvaudio_setvolume(dev,dev->ctl_volume); + return 0; +} + +void saa7134_irq_video_signalchange(struct saa7134_dev *dev) +{ + static const char *st[] = { + "(no signal)", "NTSC", "PAL", "SECAM" }; + u32 st1,st2; + + st1 = saa_readb(SAA7134_STATUS_VIDEO1); + st2 = saa_readb(SAA7134_STATUS_VIDEO2); + video_dbg("DCSDT: pll: %s, sync: %s, norm: %s\n", + (st1 & 0x40) ? "not locked" : "locked", + (st2 & 0x40) ? "no" : "yes", + st[st1 & 0x03]); + dev->nosignal = (st1 & 0x40) || (st2 & 0x40) || !(st2 & 0x1); + + if (dev->nosignal) { + /* no video signal -> mute audio */ + if (dev->ctl_automute) + dev->automute = 1; + saa7134_tvaudio_setmute(dev); + } else { + /* wake up tvaudio audio carrier scan thread */ + saa7134_tvaudio_do_scan(dev); + } + + if ((st2 & 0x80) && !noninterlaced && !dev->nosignal) + saa_clearb(SAA7134_SYNC_CTRL, 0x20); + else + saa_setb(SAA7134_SYNC_CTRL, 0x20); + + if (dev->mops && dev->mops->signal_change) + dev->mops->signal_change(dev); +} + + +void saa7134_irq_video_done(struct saa7134_dev *dev, unsigned long status) +{ + enum v4l2_field field; + + spin_lock(&dev->slock); + if (dev->video_q.curr) { + field = dev->field; + if (V4L2_FIELD_HAS_BOTH(field)) { + /* make sure we have seen both fields */ + if ((status & 0x10) == 0x00) { + dev->video_q.curr->top_seen = 1; + goto done; + } + if (!dev->video_q.curr->top_seen) + goto done; + } else if (field == V4L2_FIELD_TOP) { + if ((status & 0x10) != 0x10) + goto done; + } else if (field == V4L2_FIELD_BOTTOM) { + if ((status & 0x10) != 0x00) + goto done; + } + saa7134_buffer_finish(dev, &dev->video_q, VB2_BUF_STATE_DONE); + } + saa7134_buffer_next(dev, &dev->video_q); + + done: + spin_unlock(&dev->slock); +} diff --git a/drivers/media/pci/saa7134/saa7134.h b/drivers/media/pci/saa7134/saa7134.h new file mode 100644 index 000000000..9f27e3775 --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134.h @@ -0,0 +1,907 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * + * v4l2 device driver for philips saa7134 based TV cards + * + * (c) 2001,02 Gerd Knorr + */ + +#define SAA7134_VERSION "0, 2, 17" + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_VIDEO_SAA7134_DVB) +#include +#endif +#include "tda8290.h" + +#define UNSET (-1U) + +/* ----------------------------------------------------------- */ +/* enums */ + +enum saa7134_tvaudio_mode { + TVAUDIO_FM_MONO = 1, + TVAUDIO_FM_BG_STEREO = 2, + TVAUDIO_FM_SAT_STEREO = 3, + TVAUDIO_FM_K_STEREO = 4, + TVAUDIO_NICAM_AM = 5, + TVAUDIO_NICAM_FM = 6, +}; + +enum saa7134_audio_in { + TV = 1, + LINE1 = 2, + LINE2 = 3, + LINE2_LEFT, +}; + +enum saa7134_video_out { + CCIR656 = 1, +}; + +/* ----------------------------------------------------------- */ +/* static data */ + +struct saa7134_tvnorm { + char *name; + v4l2_std_id id; + + /* video decoder */ + unsigned int sync_control; + unsigned int luma_control; + unsigned int chroma_ctrl1; + unsigned int chroma_gain; + unsigned int chroma_ctrl2; + unsigned int vgate_misc; + + /* video scaler */ + unsigned int h_start; + unsigned int h_stop; + unsigned int video_v_start; + unsigned int video_v_stop; + unsigned int vbi_v_start_0; + unsigned int vbi_v_stop_0; + unsigned int src_timing; + unsigned int vbi_v_start_1; +}; + +struct saa7134_tvaudio { + char *name; + v4l2_std_id std; + enum saa7134_tvaudio_mode mode; + int carr1; + int carr2; +}; + +struct saa7134_format { + unsigned int fourcc; + unsigned int depth; + unsigned int pm; + unsigned int vshift; /* vertical downsampling (for planar yuv) */ + unsigned int hshift; /* horizontal downsampling (for planar yuv) */ + unsigned int bswap:1; + unsigned int wswap:1; + unsigned int yuv:1; + unsigned int planar:1; + unsigned int uvswap:1; +}; + +struct saa7134_card_ir { + struct rc_dev *dev; + + char phys[32]; + + u32 polling; + u32 last_gpio; + u32 mask_keycode, mask_keydown, mask_keyup; + + bool running; + + struct timer_list timer; + + /* IR core raw decoding */ + u32 raw_decode; +}; + +/* ----------------------------------------------------------- */ +/* card configuration */ + +#define SAA7134_BOARD_NOAUTO UNSET +#define SAA7134_BOARD_UNKNOWN 0 +#define SAA7134_BOARD_PROTEUS_PRO 1 +#define SAA7134_BOARD_FLYVIDEO3000 2 +#define SAA7134_BOARD_FLYVIDEO2000 3 +#define SAA7134_BOARD_EMPRESS 4 +#define SAA7134_BOARD_MONSTERTV 5 +#define SAA7134_BOARD_MD9717 6 +#define SAA7134_BOARD_TVSTATION_RDS 7 +#define SAA7134_BOARD_CINERGY400 8 +#define SAA7134_BOARD_MD5044 9 +#define SAA7134_BOARD_KWORLD 10 +#define SAA7134_BOARD_CINERGY600 11 +#define SAA7134_BOARD_MD7134 12 +#define SAA7134_BOARD_TYPHOON_90031 13 +#define SAA7134_BOARD_ELSA 14 +#define SAA7134_BOARD_ELSA_500TV 15 +#define SAA7134_BOARD_ASUSTeK_TVFM7134 16 +#define SAA7134_BOARD_VA1000POWER 17 +#define SAA7134_BOARD_BMK_MPEX_NOTUNER 18 +#define SAA7134_BOARD_VIDEOMATE_TV 19 +#define SAA7134_BOARD_CRONOS_PLUS 20 +#define SAA7134_BOARD_10MOONSTVMASTER 21 +#define SAA7134_BOARD_MD2819 22 +#define SAA7134_BOARD_BMK_MPEX_TUNER 23 +#define SAA7134_BOARD_TVSTATION_DVR 24 +#define SAA7134_BOARD_ASUSTEK_TVFM7133 25 +#define SAA7134_BOARD_PINNACLE_PCTV_STEREO 26 +#define SAA7134_BOARD_MANLI_MTV002 27 +#define SAA7134_BOARD_MANLI_MTV001 28 +#define SAA7134_BOARD_TG3000TV 29 +#define SAA7134_BOARD_ECS_TVP3XP 30 +#define SAA7134_BOARD_ECS_TVP3XP_4CB5 31 +#define SAA7134_BOARD_AVACSSMARTTV 32 +#define SAA7134_BOARD_AVERMEDIA_DVD_EZMAKER 33 +#define SAA7134_BOARD_NOVAC_PRIMETV7133 34 +#define SAA7134_BOARD_AVERMEDIA_STUDIO_305 35 +#define SAA7134_BOARD_UPMOST_PURPLE_TV 36 +#define SAA7134_BOARD_ITEMS_MTV005 37 +#define SAA7134_BOARD_CINERGY200 38 +#define SAA7134_BOARD_FLYTVPLATINUM_MINI 39 +#define SAA7134_BOARD_VIDEOMATE_TV_PVR 40 +#define SAA7134_BOARD_VIDEOMATE_TV_GOLD_PLUS 41 +#define SAA7134_BOARD_SABRENT_SBTTVFM 42 +#define SAA7134_BOARD_ZOLID_XPERT_TV7134 43 +#define SAA7134_BOARD_EMPIRE_PCI_TV_RADIO_LE 44 +#define SAA7134_BOARD_AVERMEDIA_STUDIO_307 45 +#define SAA7134_BOARD_AVERMEDIA_CARDBUS 46 +#define SAA7134_BOARD_CINERGY400_CARDBUS 47 +#define SAA7134_BOARD_CINERGY600_MK3 48 +#define SAA7134_BOARD_VIDEOMATE_GOLD_PLUS 49 +#define SAA7134_BOARD_PINNACLE_300I_DVBT_PAL 50 +#define SAA7134_BOARD_PROVIDEO_PV952 51 +#define SAA7134_BOARD_AVERMEDIA_305 52 +#define SAA7134_BOARD_ASUSTeK_TVFM7135 53 +#define SAA7134_BOARD_FLYTVPLATINUM_FM 54 +#define SAA7134_BOARD_FLYDVBTDUO 55 +#define SAA7134_BOARD_AVERMEDIA_307 56 +#define SAA7134_BOARD_AVERMEDIA_GO_007_FM 57 +#define SAA7134_BOARD_ADS_INSTANT_TV 58 +#define SAA7134_BOARD_KWORLD_VSTREAM_XPERT 59 +#define SAA7134_BOARD_FLYDVBT_DUO_CARDBUS 60 +#define SAA7134_BOARD_PHILIPS_TOUGH 61 +#define SAA7134_BOARD_VIDEOMATE_TV_GOLD_PLUSII 62 +#define SAA7134_BOARD_KWORLD_XPERT 63 +#define SAA7134_BOARD_FLYTV_DIGIMATRIX 64 +#define SAA7134_BOARD_KWORLD_TERMINATOR 65 +#define SAA7134_BOARD_YUAN_TUN900 66 +#define SAA7134_BOARD_BEHOLD_409FM 67 +#define SAA7134_BOARD_GOTVIEW_7135 68 +#define SAA7134_BOARD_PHILIPS_EUROPA 69 +#define SAA7134_BOARD_VIDEOMATE_DVBT_300 70 +#define SAA7134_BOARD_VIDEOMATE_DVBT_200 71 +#define SAA7134_BOARD_RTD_VFG7350 72 +#define SAA7134_BOARD_RTD_VFG7330 73 +#define SAA7134_BOARD_FLYTVPLATINUM_MINI2 74 +#define SAA7134_BOARD_AVERMEDIA_AVERTVHD_A180 75 +#define SAA7134_BOARD_MONSTERTV_MOBILE 76 +#define SAA7134_BOARD_PINNACLE_PCTV_110i 77 +#define SAA7134_BOARD_ASUSTeK_P7131_DUAL 78 +#define SAA7134_BOARD_SEDNA_PC_TV_CARDBUS 79 +#define SAA7134_BOARD_ASUSTEK_DIGIMATRIX_TV 80 +#define SAA7134_BOARD_PHILIPS_TIGER 81 +#define SAA7134_BOARD_MSI_TVATANYWHERE_PLUS 82 +#define SAA7134_BOARD_CINERGY250PCI 83 +#define SAA7134_BOARD_FLYDVB_TRIO 84 +#define SAA7134_BOARD_AVERMEDIA_777 85 +#define SAA7134_BOARD_FLYDVBT_LR301 86 +#define SAA7134_BOARD_ADS_DUO_CARDBUS_PTV331 87 +#define SAA7134_BOARD_TEVION_DVBT_220RF 88 +#define SAA7134_BOARD_ELSA_700TV 89 +#define SAA7134_BOARD_KWORLD_ATSC110 90 +#define SAA7134_BOARD_AVERMEDIA_A169_B 91 +#define SAA7134_BOARD_AVERMEDIA_A169_B1 92 +#define SAA7134_BOARD_MD7134_BRIDGE_2 93 +#define SAA7134_BOARD_FLYDVBT_HYBRID_CARDBUS 94 +#define SAA7134_BOARD_FLYVIDEO3000_NTSC 95 +#define SAA7134_BOARD_MEDION_MD8800_QUADRO 96 +#define SAA7134_BOARD_FLYDVBS_LR300 97 +#define SAA7134_BOARD_PROTEUS_2309 98 +#define SAA7134_BOARD_AVERMEDIA_A16AR 99 +#define SAA7134_BOARD_ASUS_EUROPA2_HYBRID 100 +#define SAA7134_BOARD_PINNACLE_PCTV_310i 101 +#define SAA7134_BOARD_AVERMEDIA_STUDIO_507 102 +#define SAA7134_BOARD_VIDEOMATE_DVBT_200A 103 +#define SAA7134_BOARD_HAUPPAUGE_HVR1110 104 +#define SAA7134_BOARD_CINERGY_HT_PCMCIA 105 +#define SAA7134_BOARD_ENCORE_ENLTV 106 +#define SAA7134_BOARD_ENCORE_ENLTV_FM 107 +#define SAA7134_BOARD_CINERGY_HT_PCI 108 +#define SAA7134_BOARD_PHILIPS_TIGER_S 109 +#define SAA7134_BOARD_AVERMEDIA_M102 110 +#define SAA7134_BOARD_ASUS_P7131_4871 111 +#define SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA 112 +#define SAA7134_BOARD_ECS_TVP3XP_4CB6 113 +#define SAA7134_BOARD_KWORLD_DVBT_210 114 +#define SAA7134_BOARD_SABRENT_TV_PCB05 115 +#define SAA7134_BOARD_10MOONSTVMASTER3 116 +#define SAA7134_BOARD_AVERMEDIA_SUPER_007 117 +#define SAA7134_BOARD_BEHOLD_401 118 +#define SAA7134_BOARD_BEHOLD_403 119 +#define SAA7134_BOARD_BEHOLD_403FM 120 +#define SAA7134_BOARD_BEHOLD_405 121 +#define SAA7134_BOARD_BEHOLD_405FM 122 +#define SAA7134_BOARD_BEHOLD_407 123 +#define SAA7134_BOARD_BEHOLD_407FM 124 +#define SAA7134_BOARD_BEHOLD_409 125 +#define SAA7134_BOARD_BEHOLD_505FM 126 +#define SAA7134_BOARD_BEHOLD_507_9FM 127 +#define SAA7134_BOARD_BEHOLD_COLUMBUS_TVFM 128 +#define SAA7134_BOARD_BEHOLD_607FM_MK3 129 +#define SAA7134_BOARD_BEHOLD_M6 130 +#define SAA7134_BOARD_TWINHAN_DTV_DVB_3056 131 +#define SAA7134_BOARD_GENIUS_TVGO_A11MCE 132 +#define SAA7134_BOARD_PHILIPS_SNAKE 133 +#define SAA7134_BOARD_CREATIX_CTX953 134 +#define SAA7134_BOARD_MSI_TVANYWHERE_AD11 135 +#define SAA7134_BOARD_AVERMEDIA_CARDBUS_506 136 +#define SAA7134_BOARD_AVERMEDIA_A16D 137 +#define SAA7134_BOARD_AVERMEDIA_M115 138 +#define SAA7134_BOARD_VIDEOMATE_T750 139 +#define SAA7134_BOARD_AVERMEDIA_A700_PRO 140 +#define SAA7134_BOARD_AVERMEDIA_A700_HYBRID 141 +#define SAA7134_BOARD_BEHOLD_H6 142 +#define SAA7134_BOARD_BEHOLD_M63 143 +#define SAA7134_BOARD_BEHOLD_M6_EXTRA 144 +#define SAA7134_BOARD_AVERMEDIA_M103 145 +#define SAA7134_BOARD_ASUSTeK_P7131_ANALOG 146 +#define SAA7134_BOARD_ASUSTeK_TIGER_3IN1 147 +#define SAA7134_BOARD_ENCORE_ENLTV_FM53 148 +#define SAA7134_BOARD_AVERMEDIA_M135A 149 +#define SAA7134_BOARD_REAL_ANGEL_220 150 +#define SAA7134_BOARD_ADS_INSTANT_HDTV_PCI 151 +#define SAA7134_BOARD_ASUSTeK_TIGER 152 +#define SAA7134_BOARD_KWORLD_PLUS_TV_ANALOG 153 +#define SAA7134_BOARD_AVERMEDIA_GO_007_FM_PLUS 154 +#define SAA7134_BOARD_HAUPPAUGE_HVR1150 155 +#define SAA7134_BOARD_HAUPPAUGE_HVR1120 156 +#define SAA7134_BOARD_AVERMEDIA_STUDIO_507UA 157 +#define SAA7134_BOARD_AVERMEDIA_CARDBUS_501 158 +#define SAA7134_BOARD_BEHOLD_505RDS_MK5 159 +#define SAA7134_BOARD_BEHOLD_507RDS_MK3 160 +#define SAA7134_BOARD_BEHOLD_507RDS_MK5 161 +#define SAA7134_BOARD_BEHOLD_607FM_MK5 162 +#define SAA7134_BOARD_BEHOLD_609FM_MK3 163 +#define SAA7134_BOARD_BEHOLD_609FM_MK5 164 +#define SAA7134_BOARD_BEHOLD_607RDS_MK3 165 +#define SAA7134_BOARD_BEHOLD_607RDS_MK5 166 +#define SAA7134_BOARD_BEHOLD_609RDS_MK3 167 +#define SAA7134_BOARD_BEHOLD_609RDS_MK5 168 +#define SAA7134_BOARD_VIDEOMATE_S350 169 +#define SAA7134_BOARD_AVERMEDIA_STUDIO_505 170 +#define SAA7134_BOARD_BEHOLD_X7 171 +#define SAA7134_BOARD_ROVERMEDIA_LINK_PRO_FM 172 +#define SAA7134_BOARD_ZOLID_HYBRID_PCI 173 +#define SAA7134_BOARD_ASUS_EUROPA_HYBRID 174 +#define SAA7134_BOARD_LEADTEK_WINFAST_DTV1000S 175 +#define SAA7134_BOARD_BEHOLD_505RDS_MK3 176 +#define SAA7134_BOARD_HAWELL_HW_404M7 177 +#define SAA7134_BOARD_BEHOLD_H7 178 +#define SAA7134_BOARD_BEHOLD_A7 179 +#define SAA7134_BOARD_AVERMEDIA_M733A 180 +#define SAA7134_BOARD_TECHNOTREND_BUDGET_T3000 181 +#define SAA7134_BOARD_KWORLD_PCI_SBTVD_FULLSEG 182 +#define SAA7134_BOARD_VIDEOMATE_M1F 183 +#define SAA7134_BOARD_ENCORE_ENLTV_FM3 184 +#define SAA7134_BOARD_MAGICPRO_PROHDTV_PRO2 185 +#define SAA7134_BOARD_BEHOLD_501 186 +#define SAA7134_BOARD_BEHOLD_503FM 187 +#define SAA7134_BOARD_SENSORAY811_911 188 +#define SAA7134_BOARD_KWORLD_PC150U 189 +#define SAA7134_BOARD_ASUSTeK_PS3_100 190 +#define SAA7134_BOARD_HAWELL_HW_9004V1 191 +#define SAA7134_BOARD_AVERMEDIA_A706 192 +#define SAA7134_BOARD_WIS_VOYAGER 193 +#define SAA7134_BOARD_AVERMEDIA_505 194 +#define SAA7134_BOARD_LEADTEK_WINFAST_TV2100_FM 195 +#define SAA7134_BOARD_SNAZIO_TVPVR_PRO 196 +#define SAA7134_BOARD_LEADTEK_WINFAST_HDTV200_H 197 + +#define SAA7134_MAXBOARDS 32 +#define SAA7134_INPUT_MAX 8 + +/* ----------------------------------------------------------- */ +/* Since we support 2 remote types, lets tell them apart */ + +#define SAA7134_REMOTE_GPIO 1 +#define SAA7134_REMOTE_I2C 2 + +/* ----------------------------------------------------------- */ +/* Video Output Port Register Initialization Options */ + +#define SET_T_CODE_POLARITY_NON_INVERTED (1 << 0) +#define SET_CLOCK_NOT_DELAYED (1 << 1) +#define SET_CLOCK_INVERTED (1 << 2) +#define SET_VSYNC_OFF (1 << 3) + +enum saa7134_input_types { + SAA7134_NO_INPUT = 0, + SAA7134_INPUT_MUTE, + SAA7134_INPUT_RADIO, + SAA7134_INPUT_TV, + SAA7134_INPUT_TV_MONO, + SAA7134_INPUT_COMPOSITE, + SAA7134_INPUT_COMPOSITE0, + SAA7134_INPUT_COMPOSITE1, + SAA7134_INPUT_COMPOSITE2, + SAA7134_INPUT_COMPOSITE3, + SAA7134_INPUT_COMPOSITE4, + SAA7134_INPUT_SVIDEO, + SAA7134_INPUT_SVIDEO0, + SAA7134_INPUT_SVIDEO1, + SAA7134_INPUT_COMPOSITE_OVER_SVIDEO, +}; + +struct saa7134_input { + enum saa7134_input_types type; + unsigned int vmux; + enum saa7134_audio_in amux; + unsigned int gpio; +}; + +enum saa7134_mpeg_type { + SAA7134_MPEG_UNUSED, + SAA7134_MPEG_EMPRESS, + SAA7134_MPEG_DVB, + SAA7134_MPEG_GO7007, +}; + +enum saa7134_mpeg_ts_type { + SAA7134_MPEG_TS_PARALLEL = 0, + SAA7134_MPEG_TS_SERIAL, +}; + +struct saa7134_board { + char *name; + unsigned int audio_clock; + + /* input switching */ + unsigned int gpiomask; + struct saa7134_input inputs[SAA7134_INPUT_MAX]; + struct saa7134_input radio; + struct saa7134_input mute; + + /* i2c chip info */ + unsigned int tuner_type; + unsigned int radio_type; + unsigned char tuner_addr; + unsigned char radio_addr; + unsigned char empress_addr; + unsigned char rds_addr; + + unsigned int tda9887_conf; + struct tda829x_config tda829x_conf; + + /* peripheral I/O */ + enum saa7134_video_out video_out; + enum saa7134_mpeg_type mpeg; + enum saa7134_mpeg_ts_type ts_type; + unsigned int vid_port_opts; + unsigned int ts_force_val:1; +}; + +#define card_has_radio(dev) (SAA7134_NO_INPUT != saa7134_boards[dev->board].radio.type) +#define card_is_empress(dev) (SAA7134_MPEG_EMPRESS == saa7134_boards[dev->board].mpeg) +#define card_is_dvb(dev) (SAA7134_MPEG_DVB == saa7134_boards[dev->board].mpeg) +#define card_is_go7007(dev) (SAA7134_MPEG_GO7007 == saa7134_boards[dev->board].mpeg) +#define card_has_mpeg(dev) (SAA7134_MPEG_UNUSED != saa7134_boards[dev->board].mpeg) +#define card(dev) (saa7134_boards[dev->board]) +#define card_in(dev,n) (saa7134_boards[dev->board].inputs[n]) + +#define V4L2_CID_PRIVATE_INVERT (V4L2_CID_USER_SAA7134_BASE + 0) +#define V4L2_CID_PRIVATE_Y_ODD (V4L2_CID_USER_SAA7134_BASE + 1) +#define V4L2_CID_PRIVATE_Y_EVEN (V4L2_CID_USER_SAA7134_BASE + 2) +#define V4L2_CID_PRIVATE_AUTOMUTE (V4L2_CID_USER_SAA7134_BASE + 3) + +/* ----------------------------------------------------------- */ +/* device / file handle status */ + +#define RESOURCE_VIDEO 2 +#define RESOURCE_VBI 4 +#define RESOURCE_EMPRESS 8 + +#define INTERLACE_AUTO 0 +#define INTERLACE_ON 1 +#define INTERLACE_OFF 2 + +#define BUFFER_TIMEOUT msecs_to_jiffies(500) /* 0.5 seconds */ +#define TS_BUFFER_TIMEOUT msecs_to_jiffies(1000) /* 1 second */ + +struct saa7134_dev; +struct saa7134_dma; + +/* saa7134 page table */ +struct saa7134_pgtable { + unsigned int size; + __le32 *cpu; + dma_addr_t dma; +}; + +/* tvaudio thread status */ +struct saa7134_thread { + struct task_struct *thread; + unsigned int scan1; + unsigned int scan2; + unsigned int mode; + unsigned int stopped; +}; + +/* buffer for one video/vbi/ts frame */ +struct saa7134_buf { + /* common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer vb2; + + /* saa7134 specific */ + unsigned int top_seen; + int (*activate)(struct saa7134_dev *dev, + struct saa7134_buf *buf, + struct saa7134_buf *next); + + struct list_head entry; +}; + +struct saa7134_dmaqueue { + struct saa7134_dev *dev; + struct saa7134_buf *curr; + struct list_head queue; + struct timer_list timeout; + unsigned int need_two; + unsigned int seq_nr; + struct saa7134_pgtable pt; +}; + +/* dmasound dsp status */ +struct saa7134_dmasound { + struct mutex lock; + int minor_mixer; + int minor_dsp; + unsigned int users_dsp; + + /* mixer */ + enum saa7134_audio_in input; + unsigned int count; + unsigned int line1; + unsigned int line2; + + /* dsp */ + unsigned int afmt; + unsigned int rate; + unsigned int channels; + unsigned int recording_on; + unsigned int dma_running; + unsigned int blocks; + unsigned int blksize; + unsigned int bufsize; + struct saa7134_pgtable pt; + void *vaddr; + struct scatterlist *sglist; + int sglen; + unsigned long nr_pages; + unsigned int dma_blk; + unsigned int read_offset; + unsigned int read_count; + void * priv_data; + struct snd_pcm_substream *substream; +}; + +/* ts/mpeg status */ +struct saa7134_ts { + /* TS capture */ + int nr_packets; + int nr_bufs; +}; + +/* ts/mpeg ops */ +struct saa7134_mpeg_ops { + enum saa7134_mpeg_type type; + struct list_head next; + int (*init)(struct saa7134_dev *dev); + int (*fini)(struct saa7134_dev *dev); + void (*signal_change)(struct saa7134_dev *dev); + void (*irq_ts_done)(struct saa7134_dev *dev, + unsigned long status); +}; + +enum saa7134_pads { + SAA7134_PAD_IF_INPUT, + SAA7134_PAD_VID_OUT, + SAA7134_NUM_PADS +}; + +/* global device status */ +struct saa7134_dev { + struct list_head devlist; + struct mutex lock; + spinlock_t slock; + struct v4l2_device v4l2_dev; + /* workstruct for loading modules */ + struct work_struct request_module_wk; + + /* insmod option/autodetected */ + int autodetected; + + /* various device info */ + unsigned int resources; + struct video_device *video_dev; + struct video_device *radio_dev; + struct video_device *vbi_dev; + struct saa7134_dmasound dmasound; + + /* infrared remote */ + int has_remote; + struct saa7134_card_ir *remote; + + /* pci i/o */ + char name[32]; + int nr; + struct pci_dev *pci; + unsigned char pci_rev,pci_lat; + __u32 __iomem *lmmio; + __u8 __iomem *bmmio; + + /* config info */ + unsigned int board; + unsigned int tuner_type; + unsigned int radio_type; + unsigned char tuner_addr; + unsigned char radio_addr; + + unsigned int tda9887_conf; + unsigned int gpio_value; + + /* i2c i/o */ + struct i2c_adapter i2c_adap; + struct i2c_client i2c_client; + unsigned char eedata[256]; + int has_rds; + + /* video+ts+vbi capture */ + struct saa7134_dmaqueue video_q; + struct vb2_queue video_vbq; + struct saa7134_dmaqueue vbi_q; + struct vb2_queue vbi_vbq; + enum v4l2_field field; + struct saa7134_format *fmt; + unsigned int width, height; + unsigned int vbi_hlen, vbi_vlen; + struct pm_qos_request qos_request; + + /* SAA7134_MPEG_* */ + struct saa7134_ts ts; + struct saa7134_dmaqueue ts_q; + enum v4l2_field ts_field; + int ts_started; + struct saa7134_mpeg_ops *mops; + + /* SAA7134_MPEG_EMPRESS only */ + struct video_device *empress_dev; + struct v4l2_subdev *empress_sd; + struct vb2_queue empress_vbq; + struct work_struct empress_workqueue; + int empress_started; + struct v4l2_ctrl_handler empress_ctrl_handler; + + /* various v4l controls */ + struct saa7134_tvnorm *tvnorm; /* video */ + struct saa7134_tvaudio *tvaudio; + struct v4l2_ctrl_handler ctrl_handler; + unsigned int ctl_input; + int ctl_bright; + int ctl_contrast; + int ctl_hue; + int ctl_saturation; + int ctl_mute; /* audio */ + int ctl_volume; + int ctl_invert; /* private */ + int ctl_mirror; + int ctl_y_odd; + int ctl_y_even; + int ctl_automute; + + /* crop */ + struct v4l2_rect crop_bounds; + struct v4l2_rect crop_defrect; + struct v4l2_rect crop_current; + + /* other global state info */ + unsigned int automute; + struct saa7134_thread thread; + struct saa7134_input *input; + struct saa7134_input *hw_input; + unsigned int hw_mute; + int last_carrier; + int nosignal; + unsigned int insuspend; + struct v4l2_ctrl_handler radio_ctrl_handler; + + /* I2C keyboard data */ + struct IR_i2c_init_data init_data; + +#ifdef CONFIG_MEDIA_CONTROLLER + struct media_device *media_dev; + + struct media_entity input_ent[SAA7134_INPUT_MAX + 1]; + struct media_pad input_pad[SAA7134_INPUT_MAX + 1]; + + struct media_entity demod; + struct media_pad demod_pad[SAA7134_NUM_PADS]; + + struct media_pad video_pad, vbi_pad; + struct media_entity *decoder; +#endif + +#if IS_ENABLED(CONFIG_VIDEO_SAA7134_DVB) + /* SAA7134_MPEG_DVB only */ + struct vb2_dvb_frontends frontends; + int (*original_demod_sleep)(struct dvb_frontend *fe); + int (*original_set_voltage)(struct dvb_frontend *fe, + enum fe_sec_voltage voltage); + int (*original_set_high_voltage)(struct dvb_frontend *fe, long arg); +#endif + void (*gate_ctrl)(struct saa7134_dev *dev, int open); +}; + +/* ----------------------------------------------------------- */ + +#define saa_readl(reg) readl(dev->lmmio + (reg)) +#define saa_writel(reg,value) writel((value), dev->lmmio + (reg)); +#define saa_andorl(reg,mask,value) \ + writel((readl(dev->lmmio+(reg)) & ~(mask)) |\ + ((value) & (mask)), dev->lmmio+(reg)) +#define saa_setl(reg,bit) saa_andorl((reg),(bit),(bit)) +#define saa_clearl(reg,bit) saa_andorl((reg),(bit),0) + +#define saa_readb(reg) readb(dev->bmmio + (reg)) +#define saa_writeb(reg,value) writeb((value), dev->bmmio + (reg)); +#define saa_andorb(reg,mask,value) \ + writeb((readb(dev->bmmio+(reg)) & ~(mask)) |\ + ((value) & (mask)), dev->bmmio+(reg)) +#define saa_setb(reg,bit) saa_andorb((reg),(bit),(bit)) +#define saa_clearb(reg,bit) saa_andorb((reg),(bit),0) + +#define saa_wait(us) { udelay(us); } + +#define SAA7134_NORMS (\ + V4L2_STD_PAL | V4L2_STD_PAL_N | \ + V4L2_STD_PAL_Nc | V4L2_STD_SECAM | \ + V4L2_STD_NTSC | V4L2_STD_PAL_M | \ + V4L2_STD_PAL_60) + +#define GRP_EMPRESS (1) +#define saa_call_all(dev, o, f, args...) do { \ + if (dev->gate_ctrl) \ + dev->gate_ctrl(dev, 1); \ + v4l2_device_call_all(&(dev)->v4l2_dev, 0, o, f , ##args); \ + if (dev->gate_ctrl) \ + dev->gate_ctrl(dev, 0); \ +} while (0) + +#define saa_call_empress(dev, o, f, args...) ({ \ + long _rc; \ + if (dev->gate_ctrl) \ + dev->gate_ctrl(dev, 1); \ + _rc = v4l2_device_call_until_err(&(dev)->v4l2_dev, \ + GRP_EMPRESS, o, f , ##args); \ + if (dev->gate_ctrl) \ + dev->gate_ctrl(dev, 0); \ + _rc; \ +}) + +static inline bool is_empress(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct saa7134_dev *dev = video_get_drvdata(vdev); + + return vdev->queue == &dev->empress_vbq; +} + +/* ----------------------------------------------------------- */ +/* saa7134-core.c */ + +extern struct list_head saa7134_devlist; +extern struct mutex saa7134_devlist_lock; +extern bool saa7134_userptr; + +void saa7134_track_gpio(struct saa7134_dev *dev, const char *msg); +void saa7134_set_gpio(struct saa7134_dev *dev, int bit_no, int value); + +#define SAA7134_PGTABLE_SIZE 4096 + +int saa7134_pgtable_alloc(struct pci_dev *pci, struct saa7134_pgtable *pt); +int saa7134_pgtable_build(struct pci_dev *pci, struct saa7134_pgtable *pt, + struct scatterlist *list, unsigned int length, + unsigned int startpage); +void saa7134_pgtable_free(struct pci_dev *pci, struct saa7134_pgtable *pt); + +int saa7134_buffer_count(unsigned int size, unsigned int count); +int saa7134_buffer_startpage(struct saa7134_buf *buf); +unsigned long saa7134_buffer_base(struct saa7134_buf *buf); + +int saa7134_buffer_queue(struct saa7134_dev *dev, struct saa7134_dmaqueue *q, + struct saa7134_buf *buf); +void saa7134_buffer_finish(struct saa7134_dev *dev, struct saa7134_dmaqueue *q, + unsigned int state); +void saa7134_buffer_next(struct saa7134_dev *dev, struct saa7134_dmaqueue *q); +void saa7134_buffer_timeout(struct timer_list *t); +void saa7134_stop_streaming(struct saa7134_dev *dev, struct saa7134_dmaqueue *q); + +int saa7134_set_dmabits(struct saa7134_dev *dev); + +extern int (*saa7134_dmasound_init)(struct saa7134_dev *dev); +extern int (*saa7134_dmasound_exit)(struct saa7134_dev *dev); + + +/* ----------------------------------------------------------- */ +/* saa7134-cards.c */ + +extern struct saa7134_board saa7134_boards[]; +extern const char * const saa7134_input_name[]; +extern const unsigned int saa7134_bcount; +extern struct pci_device_id saa7134_pci_tbl[]; + +extern int saa7134_board_init1(struct saa7134_dev *dev); +extern int saa7134_board_init2(struct saa7134_dev *dev); +int saa7134_tuner_callback(void *priv, int component, int command, int arg); + + +/* ----------------------------------------------------------- */ +/* saa7134-i2c.c */ + +int saa7134_i2c_register(struct saa7134_dev *dev); +int saa7134_i2c_unregister(struct saa7134_dev *dev); + + +/* ----------------------------------------------------------- */ +/* saa7134-video.c */ + +extern unsigned int video_debug; +extern struct video_device saa7134_video_template; +extern struct video_device saa7134_radio_template; + +void saa7134_vb2_buffer_queue(struct vb2_buffer *vb); +int saa7134_vb2_start_streaming(struct vb2_queue *vq, unsigned int count); +void saa7134_vb2_stop_streaming(struct vb2_queue *vq); + +int saa7134_s_std(struct file *file, void *priv, v4l2_std_id id); +int saa7134_g_std(struct file *file, void *priv, v4l2_std_id *id); +int saa7134_querystd(struct file *file, void *priv, v4l2_std_id *std); +int saa7134_enum_input(struct file *file, void *priv, struct v4l2_input *i); +int saa7134_g_input(struct file *file, void *priv, unsigned int *i); +int saa7134_s_input(struct file *file, void *priv, unsigned int i); +int saa7134_querycap(struct file *file, void *priv, + struct v4l2_capability *cap); +int saa7134_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t); +int saa7134_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *t); +int saa7134_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f); +int saa7134_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f); + +int saa7134_videoport_init(struct saa7134_dev *dev); +void saa7134_set_tvnorm_hw(struct saa7134_dev *dev); + +int saa7134_video_init1(struct saa7134_dev *dev); +int saa7134_video_init2(struct saa7134_dev *dev); +void saa7134_irq_video_signalchange(struct saa7134_dev *dev); +void saa7134_irq_video_done(struct saa7134_dev *dev, unsigned long status); +void saa7134_video_fini(struct saa7134_dev *dev); + + +/* ----------------------------------------------------------- */ +/* saa7134-ts.c */ + +#define TS_PACKET_SIZE 188 /* TS packets 188 bytes */ + +int saa7134_ts_buffer_init(struct vb2_buffer *vb2); +int saa7134_ts_buffer_prepare(struct vb2_buffer *vb2); +int saa7134_ts_queue_setup(struct vb2_queue *q, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]); +int saa7134_ts_start_streaming(struct vb2_queue *vq, unsigned int count); +void saa7134_ts_stop_streaming(struct vb2_queue *vq); + +extern struct vb2_ops saa7134_ts_qops; + +int saa7134_ts_init1(struct saa7134_dev *dev); +int saa7134_ts_fini(struct saa7134_dev *dev); +void saa7134_irq_ts_done(struct saa7134_dev *dev, unsigned long status); + +int saa7134_ts_register(struct saa7134_mpeg_ops *ops); +void saa7134_ts_unregister(struct saa7134_mpeg_ops *ops); + +int saa7134_ts_init_hw(struct saa7134_dev *dev); + +int saa7134_ts_start(struct saa7134_dev *dev); +int saa7134_ts_stop(struct saa7134_dev *dev); + +/* ----------------------------------------------------------- */ +/* saa7134-vbi.c */ + +extern const struct vb2_ops saa7134_vbi_qops; + +int saa7134_vbi_init1(struct saa7134_dev *dev); +int saa7134_vbi_fini(struct saa7134_dev *dev); +void saa7134_irq_vbi_done(struct saa7134_dev *dev, unsigned long status); + + +/* ----------------------------------------------------------- */ +/* saa7134-tvaudio.c */ + +int saa7134_tvaudio_rx2mode(u32 rx); + +void saa7134_tvaudio_setmute(struct saa7134_dev *dev); +void saa7134_tvaudio_setinput(struct saa7134_dev *dev, + struct saa7134_input *in); +void saa7134_tvaudio_setvolume(struct saa7134_dev *dev, int level); +int saa7134_tvaudio_getstereo(struct saa7134_dev *dev); + +void saa7134_tvaudio_init(struct saa7134_dev *dev); +int saa7134_tvaudio_init2(struct saa7134_dev *dev); +int saa7134_tvaudio_fini(struct saa7134_dev *dev); +int saa7134_tvaudio_do_scan(struct saa7134_dev *dev); +int saa7134_tvaudio_close(struct saa7134_dev *dev); + +int saa_dsp_writel(struct saa7134_dev *dev, int reg, u32 value); + +void saa7134_enable_i2s(struct saa7134_dev *dev); + +/* ----------------------------------------------------------- */ +/* saa7134-oss.c */ + +int saa7134_oss_init1(struct saa7134_dev *dev); +int saa7134_oss_fini(struct saa7134_dev *dev); +void saa7134_irq_oss_done(struct saa7134_dev *dev, unsigned long status); + +/* ----------------------------------------------------------- */ +/* saa7134-input.c */ + +#if defined(CONFIG_VIDEO_SAA7134_RC) +int saa7134_input_init1(struct saa7134_dev *dev); +void saa7134_input_fini(struct saa7134_dev *dev); +void saa7134_input_irq(struct saa7134_dev *dev); +void saa7134_probe_i2c_ir(struct saa7134_dev *dev); +int saa7134_ir_open(struct rc_dev *dev); +void saa7134_ir_close(struct rc_dev *dev); +#else +#define saa7134_input_init1(dev) ((void)0) +#define saa7134_input_fini(dev) ((void)0) +#define saa7134_input_irq(dev) ((void)0) +#define saa7134_probe_i2c_ir(dev) ((void)0) +#define saa7134_ir_open(dev) ((void)0) +#define saa7134_ir_close(dev) ((void)0) +#endif diff --git a/drivers/media/pci/saa7146/Kconfig b/drivers/media/pci/saa7146/Kconfig new file mode 100644 index 000000000..3bbb68a0e --- /dev/null +++ b/drivers/media/pci/saa7146/Kconfig @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_HEXIUM_GEMINI + tristate "Hexium Gemini frame grabber" + depends on PCI && VIDEO_DEV && I2C + select VIDEO_SAA7146_VV + help + This is a video4linux driver for the Hexium Gemini frame + grabber card by Hexium. Please note that the Gemini Dual + card is *not* fully supported. + + To compile this driver as a module, choose M here: the + module will be called hexium_gemini. + +config VIDEO_HEXIUM_ORION + tristate "Hexium HV-PCI6 and Orion frame grabber" + depends on PCI && VIDEO_DEV && I2C + select VIDEO_SAA7146_VV + help + This is a video4linux driver for the Hexium HV-PCI6 and + Orion frame grabber cards by Hexium. + + To compile this driver as a module, choose M here: the + module will be called hexium_orion. + +config VIDEO_MXB + tristate "Siemens-Nixdorf 'Multimedia eXtension Board'" + depends on PCI && VIDEO_DEV && I2C + select VIDEO_SAA7146_VV + select VIDEO_TUNER + select VIDEO_SAA711X if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_TDA9840 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_TEA6415C if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_TEA6420 if MEDIA_SUBDRV_AUTOSELECT + help + This is a video4linux driver for the 'Multimedia eXtension Board' + TV card by Siemens-Nixdorf. + + To compile this driver as a module, choose M here: the + module will be called mxb. diff --git a/drivers/media/pci/saa7146/Makefile b/drivers/media/pci/saa7146/Makefile new file mode 100644 index 000000000..37c9336f8 --- /dev/null +++ b/drivers/media/pci/saa7146/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_VIDEO_MXB) += mxb.o +obj-$(CONFIG_VIDEO_HEXIUM_ORION) += hexium_orion.o +obj-$(CONFIG_VIDEO_HEXIUM_GEMINI) += hexium_gemini.o + +ccflags-y += -I$(srctree)/drivers/media/i2c diff --git a/drivers/media/pci/saa7146/hexium_gemini.c b/drivers/media/pci/saa7146/hexium_gemini.c new file mode 100644 index 000000000..f2298e8bb --- /dev/null +++ b/drivers/media/pci/saa7146/hexium_gemini.c @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + hexium_gemini.c - v4l2 driver for Hexium Gemini frame grabber cards + + Visit http://www.mihu.de/linux/saa7146/ and follow the link + to "hexium" for further details about this card. + + Copyright (C) 2003 Michael Hunold + +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define DEBUG_VARIABLE debug + +#include +#include +#include + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "debug verbosity"); + +/* global variables */ +static int hexium_num; + +#define HEXIUM_GEMINI 4 +#define HEXIUM_GEMINI_DUAL 5 + +#define HEXIUM_STD (V4L2_STD_PAL | V4L2_STD_SECAM | V4L2_STD_NTSC) +#define HEXIUM_INPUTS 9 +static struct v4l2_input hexium_inputs[HEXIUM_INPUTS] = { + { 0, "CVBS 1", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, + { 1, "CVBS 2", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, + { 2, "CVBS 3", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, + { 3, "CVBS 4", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, + { 4, "CVBS 5", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, + { 5, "CVBS 6", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, + { 6, "Y/C 1", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, + { 7, "Y/C 2", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, + { 8, "Y/C 3", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, +}; + +#define HEXIUM_AUDIOS 0 + +struct hexium_data +{ + s8 adr; + u8 byte; +}; + +#define HEXIUM_GEMINI_V_1_0 1 +#define HEXIUM_GEMINI_DUAL_V_1_0 2 + +struct hexium +{ + int type; + + struct video_device video_dev; + struct i2c_adapter i2c_adapter; + + int cur_input; /* current input */ + v4l2_std_id cur_std; /* current standard */ +}; + +/* Samsung KS0127B decoder default registers */ +static u8 hexium_ks0127b[0x100]={ +/*00*/ 0x00,0x52,0x30,0x40,0x01,0x0C,0x2A,0x10, +/*08*/ 0x00,0x00,0x00,0x60,0x00,0x00,0x0F,0x06, +/*10*/ 0x00,0x00,0xE4,0xC0,0x00,0x00,0x00,0x00, +/*18*/ 0x14,0x9B,0xFE,0xFF,0xFC,0xFF,0x03,0x22, +/*20*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*28*/ 0x00,0x00,0x00,0x00,0x00,0x2C,0x9B,0x00, +/*30*/ 0x00,0x00,0x10,0x80,0x80,0x10,0x80,0x80, +/*38*/ 0x01,0x04,0x00,0x00,0x00,0x29,0xC0,0x00, +/*40*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*48*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*50*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*58*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*60*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*68*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*70*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*78*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*80*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*88*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*90*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*98*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*A0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*A8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*B0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*B8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*C0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*C8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*D0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*D8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*E0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*E8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*F0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*F8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +}; + +static struct hexium_data hexium_pal[] = { + { 0x01, 0x52 }, { 0x12, 0x64 }, { 0x2D, 0x2C }, { 0x2E, 0x9B }, { -1 , 0xFF } +}; + +static struct hexium_data hexium_ntsc[] = { + { 0x01, 0x53 }, { 0x12, 0x04 }, { 0x2D, 0x23 }, { 0x2E, 0x81 }, { -1 , 0xFF } +}; + +static struct hexium_data hexium_secam[] = { + { 0x01, 0x52 }, { 0x12, 0x64 }, { 0x2D, 0x2C }, { 0x2E, 0x9B }, { -1 , 0xFF } +}; + +static struct hexium_data hexium_input_select[] = { + { 0x02, 0x60 }, + { 0x02, 0x64 }, + { 0x02, 0x61 }, + { 0x02, 0x65 }, + { 0x02, 0x62 }, + { 0x02, 0x66 }, + { 0x02, 0x68 }, + { 0x02, 0x69 }, + { 0x02, 0x6A }, +}; + +/* fixme: h_offset = 0 for Hexium Gemini *Dual*, which + are currently *not* supported*/ +static struct saa7146_standard hexium_standards[] = { + { + .name = "PAL", .id = V4L2_STD_PAL, + .v_offset = 28, .v_field = 288, + .h_offset = 1, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 28, .v_field = 240, + .h_offset = 1, .h_pixels = 640, + .v_max_out = 480, .h_max_out = 640, + }, { + .name = "SECAM", .id = V4L2_STD_SECAM, + .v_offset = 28, .v_field = 288, + .h_offset = 1, .h_pixels = 720, + .v_max_out = 576, .h_max_out = 768, + } +}; + +/* bring hardware to a sane state. this has to be done, just in case someone + wants to capture from this device before it has been properly initialized. + the capture engine would badly fail, because no valid signal arrives on the + saa7146, thus leading to timeouts and stuff. */ +static int hexium_init_done(struct saa7146_dev *dev) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + union i2c_smbus_data data; + int i = 0; + + DEB_D("hexium_init_done called\n"); + + /* initialize the helper ics to useful values */ + for (i = 0; i < sizeof(hexium_ks0127b); i++) { + data.byte = hexium_ks0127b[i]; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x6c, 0, I2C_SMBUS_WRITE, i, I2C_SMBUS_BYTE_DATA, &data)) { + pr_err("hexium_init_done() failed for address 0x%02x\n", + i); + } + } + + return 0; +} + +static int hexium_set_input(struct hexium *hexium, int input) +{ + union i2c_smbus_data data; + + DEB_D("\n"); + + data.byte = hexium_input_select[input].byte; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x6c, 0, I2C_SMBUS_WRITE, hexium_input_select[input].adr, I2C_SMBUS_BYTE_DATA, &data)) { + return -1; + } + + return 0; +} + +static int hexium_set_standard(struct hexium *hexium, struct hexium_data *vdec) +{ + union i2c_smbus_data data; + int i = 0; + + DEB_D("\n"); + + while (vdec[i].adr != -1) { + data.byte = vdec[i].byte; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x6c, 0, I2C_SMBUS_WRITE, vdec[i].adr, I2C_SMBUS_BYTE_DATA, &data)) { + pr_err("hexium_init_done: hexium_set_standard() failed for address 0x%02x\n", + i); + return -1; + } + i++; + } + return 0; +} + +static int vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *i) +{ + DEB_EE("VIDIOC_ENUMINPUT %d\n", i->index); + + if (i->index >= HEXIUM_INPUTS) + return -EINVAL; + + memcpy(i, &hexium_inputs[i->index], sizeof(struct v4l2_input)); + + DEB_D("v4l2_ioctl: VIDIOC_ENUMINPUT %d\n", i->index); + return 0; +} + +static int vidioc_g_input(struct file *file, void *fh, unsigned int *input) +{ + struct saa7146_dev *dev = video_drvdata(file); + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + *input = hexium->cur_input; + + DEB_D("VIDIOC_G_INPUT: %d\n", *input); + return 0; +} + +static int vidioc_s_input(struct file *file, void *fh, unsigned int input) +{ + struct saa7146_dev *dev = video_drvdata(file); + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + DEB_EE("VIDIOC_S_INPUT %d\n", input); + + if (input >= HEXIUM_INPUTS) + return -EINVAL; + + hexium->cur_input = input; + hexium_set_input(hexium, input); + return 0; +} + +static struct saa7146_ext_vv vv_data; + +/* this function only gets called when the probing was successful */ +static int hexium_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct hexium *hexium; + int ret; + + DEB_EE("\n"); + + hexium = kzalloc_obj(*hexium); + if (!hexium) + return -ENOMEM; + + dev->ext_priv = hexium; + + /* enable i2c-port pins */ + saa7146_write(dev, MC1, (MASK_08 | MASK_24 | MASK_10 | MASK_26)); + + strscpy(hexium->i2c_adapter.name, "hexium gemini", + sizeof(hexium->i2c_adapter.name)); + saa7146_i2c_adapter_prepare(dev, &hexium->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); + if (i2c_add_adapter(&hexium->i2c_adapter) < 0) { + DEB_S("cannot register i2c-device. skipping.\n"); + kfree(hexium); + return -EFAULT; + } + + /* set HWControl GPIO number 2 */ + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); + + saa7146_write(dev, DD1_INIT, 0x07000700); + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + /* the rest */ + hexium->cur_input = 0; + hexium_init_done(dev); + + hexium_set_standard(hexium, hexium_pal); + hexium->cur_std = V4L2_STD_PAL; + + hexium_set_input(hexium, 0); + hexium->cur_input = 0; + + ret = saa7146_vv_init(dev, &vv_data); + if (ret) { + i2c_del_adapter(&hexium->i2c_adapter); + kfree(hexium); + return ret; + } + + vv_data.vid_ops.vidioc_enum_input = vidioc_enum_input; + vv_data.vid_ops.vidioc_g_input = vidioc_g_input; + vv_data.vid_ops.vidioc_s_input = vidioc_s_input; + ret = saa7146_register_device(&hexium->video_dev, dev, "hexium gemini", VFL_TYPE_VIDEO); + if (ret < 0) { + pr_err("cannot register capture v4l2 device. skipping.\n"); + saa7146_vv_release(dev); + i2c_del_adapter(&hexium->i2c_adapter); + kfree(hexium); + return ret; + } + + pr_info("found 'hexium gemini' frame grabber-%d\n", hexium_num); + hexium_num++; + + return 0; +} + +static int hexium_detach(struct saa7146_dev *dev) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + DEB_EE("dev:%p\n", dev); + + saa7146_unregister_device(&hexium->video_dev, dev); + saa7146_vv_release(dev); + + hexium_num--; + + i2c_del_adapter(&hexium->i2c_adapter); + kfree(hexium); + return 0; +} + +static int std_callback(struct saa7146_dev *dev, struct saa7146_standard *std) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + if (V4L2_STD_PAL == std->id) { + hexium_set_standard(hexium, hexium_pal); + hexium->cur_std = V4L2_STD_PAL; + return 0; + } else if (V4L2_STD_NTSC == std->id) { + hexium_set_standard(hexium, hexium_ntsc); + hexium->cur_std = V4L2_STD_NTSC; + return 0; + } else if (V4L2_STD_SECAM == std->id) { + hexium_set_standard(hexium, hexium_secam); + hexium->cur_std = V4L2_STD_SECAM; + return 0; + } + + return -1; +} + +static struct saa7146_extension hexium_extension; + +static struct saa7146_pci_extension_data hexium_gemini_4bnc = { + .ext_priv = "Hexium Gemini (4 BNC)", + .ext = &hexium_extension, +}; + +static struct saa7146_pci_extension_data hexium_gemini_dual_4bnc = { + .ext_priv = "Hexium Gemini Dual (4 BNC)", + .ext = &hexium_extension, +}; + +static const struct pci_device_id pci_tbl[] = { + { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146, 0x17c8, 0x2401), + .driver_data = (unsigned long)&hexium_gemini_4bnc, + }, + { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146, 0x17c8, 0x2402), + .driver_data = (unsigned long)&hexium_gemini_dual_4bnc, + }, + { } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_ext_vv vv_data = { + .inputs = HEXIUM_INPUTS, + .capabilities = 0, + .stds = &hexium_standards[0], + .num_stds = ARRAY_SIZE(hexium_standards), + .std_callback = &std_callback, +}; + +static struct saa7146_extension hexium_extension = { + .name = "hexium gemini", + .flags = SAA7146_USE_I2C_IRQ, + + .pci_tbl = &pci_tbl[0], + .module = THIS_MODULE, + + .attach = hexium_attach, + .detach = hexium_detach, + + .irq_mask = 0, + .irq_func = NULL, +}; + +static int __init hexium_init_module(void) +{ + if (0 != saa7146_register_extension(&hexium_extension)) { + DEB_S("failed to register extension\n"); + return -ENODEV; + } + + return 0; +} + +static void __exit hexium_cleanup_module(void) +{ + saa7146_unregister_extension(&hexium_extension); +} + +module_init(hexium_init_module); +module_exit(hexium_cleanup_module); + +MODULE_DESCRIPTION("video4linux-2 driver for Hexium Gemini frame grabber cards"); +MODULE_AUTHOR("Michael Hunold "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/saa7146/hexium_orion.c b/drivers/media/pci/saa7146/hexium_orion.c new file mode 100644 index 000000000..8ef2a4e82 --- /dev/null +++ b/drivers/media/pci/saa7146/hexium_orion.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + hexium_orion.c - v4l2 driver for the Hexium Orion frame grabber cards + + Visit http://www.mihu.de/linux/saa7146/ and follow the link + to "hexium" for further details about this card. + + Copyright (C) 2003 Michael Hunold + +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define DEBUG_VARIABLE debug + +#include +#include +#include + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "debug verbosity"); + +/* global variables */ +static int hexium_num; + +#define HEXIUM_HV_PCI6_ORION 1 +#define HEXIUM_ORION_1SVHS_3BNC 2 +#define HEXIUM_ORION_4BNC 3 + +#define HEXIUM_STD (V4L2_STD_PAL | V4L2_STD_SECAM | V4L2_STD_NTSC) +#define HEXIUM_INPUTS 9 +static struct v4l2_input hexium_inputs[HEXIUM_INPUTS] = { + { 0, "CVBS 1", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, + { 1, "CVBS 2", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, + { 2, "CVBS 3", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, + { 3, "CVBS 4", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, + { 4, "CVBS 5", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, + { 5, "CVBS 6", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, + { 6, "Y/C 1", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, + { 7, "Y/C 2", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, + { 8, "Y/C 3", V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, +}; + +#define HEXIUM_AUDIOS 0 + +struct hexium_data +{ + s8 adr; + u8 byte; +}; + +struct hexium +{ + int type; + struct video_device video_dev; + struct i2c_adapter i2c_adapter; + + int cur_input; /* current input */ +}; + +/* Philips SAA7110 decoder default registers */ +static u8 hexium_saa7110[53]={ +/*00*/ 0x4C,0x3C,0x0D,0xEF,0xBD,0xF0,0x00,0x00, +/*08*/ 0xF8,0xF8,0x60,0x60,0x40,0x86,0x18,0x90, +/*10*/ 0x00,0x2C,0x40,0x46,0x42,0x1A,0xFF,0xDA, +/*18*/ 0xF0,0x8B,0x00,0x00,0x00,0x00,0x00,0x00, +/*20*/ 0xD9,0x17,0x40,0x41,0x80,0x41,0x80,0x4F, +/*28*/ 0xFE,0x01,0x0F,0x0F,0x03,0x01,0x81,0x03, +/*30*/ 0x44,0x75,0x01,0x8C,0x03 +}; + +static struct { + struct hexium_data data[8]; +} hexium_input_select[] = { +{ + { /* cvbs 1 */ + { 0x06, 0x00 }, + { 0x20, 0xD9 }, + { 0x21, 0x17 }, // 0x16, + { 0x22, 0x40 }, + { 0x2C, 0x03 }, + { 0x30, 0x44 }, + { 0x31, 0x75 }, // ?? + { 0x21, 0x16 }, // 0x03, + } +}, { + { /* cvbs 2 */ + { 0x06, 0x00 }, + { 0x20, 0x78 }, + { 0x21, 0x07 }, // 0x03, + { 0x22, 0xD2 }, + { 0x2C, 0x83 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, // ? + { 0x21, 0x03 }, + } +}, { + { /* cvbs 3 */ + { 0x06, 0x00 }, + { 0x20, 0xBA }, + { 0x21, 0x07 }, // 0x05, + { 0x22, 0x91 }, + { 0x2C, 0x03 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, // ?? + { 0x21, 0x05 }, // 0x03, + } +}, { + { /* cvbs 4 */ + { 0x06, 0x00 }, + { 0x20, 0xD8 }, + { 0x21, 0x17 }, // 0x16, + { 0x22, 0x40 }, + { 0x2C, 0x03 }, + { 0x30, 0x44 }, + { 0x31, 0x75 }, // ?? + { 0x21, 0x16 }, // 0x03, + } +}, { + { /* cvbs 5 */ + { 0x06, 0x00 }, + { 0x20, 0xB8 }, + { 0x21, 0x07 }, // 0x05, + { 0x22, 0x91 }, + { 0x2C, 0x03 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, // ?? + { 0x21, 0x05 }, // 0x03, + } +}, { + { /* cvbs 6 */ + { 0x06, 0x00 }, + { 0x20, 0x7C }, + { 0x21, 0x07 }, // 0x03 + { 0x22, 0xD2 }, + { 0x2C, 0x83 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, // ?? + { 0x21, 0x03 }, + } +}, { + { /* y/c 1 */ + { 0x06, 0x80 }, + { 0x20, 0x59 }, + { 0x21, 0x17 }, + { 0x22, 0x42 }, + { 0x2C, 0xA3 }, + { 0x30, 0x44 }, + { 0x31, 0x75 }, + { 0x21, 0x12 }, + } +}, { + { /* y/c 2 */ + { 0x06, 0x80 }, + { 0x20, 0x9A }, + { 0x21, 0x17 }, + { 0x22, 0xB1 }, + { 0x2C, 0x13 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, + { 0x21, 0x14 }, + } +}, { + { /* y/c 3 */ + { 0x06, 0x80 }, + { 0x20, 0x3C }, + { 0x21, 0x27 }, + { 0x22, 0xC1 }, + { 0x2C, 0x23 }, + { 0x30, 0x44 }, + { 0x31, 0x75 }, + { 0x21, 0x21 }, + } +} +}; + +static struct saa7146_standard hexium_standards[] = { + { + .name = "PAL", .id = V4L2_STD_PAL, + .v_offset = 16, .v_field = 288, + .h_offset = 1, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 16, .v_field = 240, + .h_offset = 1, .h_pixels = 640, + .v_max_out = 480, .h_max_out = 640, + }, { + .name = "SECAM", .id = V4L2_STD_SECAM, + .v_offset = 16, .v_field = 288, + .h_offset = 1, .h_pixels = 720, + .v_max_out = 576, .h_max_out = 768, + } +}; + +/* this is only called for old HV-PCI6/Orion cards + without eeprom */ +static int hexium_probe(struct saa7146_dev *dev) +{ + struct hexium *hexium = NULL; + union i2c_smbus_data data; + int err = 0; + + DEB_EE("\n"); + + /* there are no hexium orion cards with revision 0 saa7146s */ + if (0 == dev->revision) { + return -EFAULT; + } + + hexium = kzalloc_obj(*hexium); + if (!hexium) + return -ENOMEM; + + /* enable i2c-port pins */ + saa7146_write(dev, MC1, (MASK_08 | MASK_24 | MASK_10 | MASK_26)); + + saa7146_write(dev, DD1_INIT, 0x01000100); + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + strscpy(hexium->i2c_adapter.name, "hexium orion", + sizeof(hexium->i2c_adapter.name)); + saa7146_i2c_adapter_prepare(dev, &hexium->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); + if (i2c_add_adapter(&hexium->i2c_adapter) < 0) { + DEB_S("cannot register i2c-device. skipping.\n"); + kfree(hexium); + return -EFAULT; + } + + /* set SAA7110 control GPIO 0 */ + saa7146_setgpio(dev, 0, SAA7146_GPIO_OUTHI); + /* set HWControl GPIO number 2 */ + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); + + mdelay(10); + + /* detect newer Hexium Orion cards by subsystem ids */ + if (0x17c8 == dev->pci->subsystem_vendor && 0x0101 == dev->pci->subsystem_device) { + pr_info("device is a Hexium Orion w/ 1 SVHS + 3 BNC inputs\n"); + /* we store the pointer in our private data field */ + dev->ext_priv = hexium; + hexium->type = HEXIUM_ORION_1SVHS_3BNC; + return 0; + } + + if (0x17c8 == dev->pci->subsystem_vendor && 0x2101 == dev->pci->subsystem_device) { + pr_info("device is a Hexium Orion w/ 4 BNC inputs\n"); + /* we store the pointer in our private data field */ + dev->ext_priv = hexium; + hexium->type = HEXIUM_ORION_4BNC; + return 0; + } + + /* check if this is an old hexium Orion card by looking at + a saa7110 at address 0x4e */ + err = i2c_smbus_xfer(&hexium->i2c_adapter, 0x4e, 0, I2C_SMBUS_READ, + 0x00, I2C_SMBUS_BYTE_DATA, &data); + if (err == 0) { + pr_info("device is a Hexium HV-PCI6/Orion (old)\n"); + /* we store the pointer in our private data field */ + dev->ext_priv = hexium; + hexium->type = HEXIUM_HV_PCI6_ORION; + return 0; + } + + i2c_del_adapter(&hexium->i2c_adapter); + kfree(hexium); + return -EFAULT; +} + +/* bring hardware to a sane state. this has to be done, just in case someone + wants to capture from this device before it has been properly initialized. + the capture engine would badly fail, because no valid signal arrives on the + saa7146, thus leading to timeouts and stuff. */ +static int hexium_init_done(struct saa7146_dev *dev) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + union i2c_smbus_data data; + int i = 0; + + DEB_D("hexium_init_done called\n"); + + /* initialize the helper ics to useful values */ + for (i = 0; i < sizeof(hexium_saa7110); i++) { + data.byte = hexium_saa7110[i]; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, i, I2C_SMBUS_BYTE_DATA, &data)) { + pr_err("failed for address 0x%02x\n", i); + } + } + + return 0; +} + +static int hexium_set_input(struct hexium *hexium, int input) +{ + union i2c_smbus_data data; + int i = 0; + + DEB_D("\n"); + + for (i = 0; i < 8; i++) { + int adr = hexium_input_select[input].data[i].adr; + data.byte = hexium_input_select[input].data[i].byte; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, adr, I2C_SMBUS_BYTE_DATA, &data)) { + return -1; + } + pr_debug("%d: 0x%02x => 0x%02x\n", input, adr, data.byte); + } + + return 0; +} + +static int vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *i) +{ + DEB_EE("VIDIOC_ENUMINPUT %d\n", i->index); + + if (i->index >= HEXIUM_INPUTS) + return -EINVAL; + + memcpy(i, &hexium_inputs[i->index], sizeof(struct v4l2_input)); + + DEB_D("v4l2_ioctl: VIDIOC_ENUMINPUT %d\n", i->index); + return 0; +} + +static int vidioc_g_input(struct file *file, void *fh, unsigned int *input) +{ + struct saa7146_dev *dev = video_drvdata(file); + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + *input = hexium->cur_input; + + DEB_D("VIDIOC_G_INPUT: %d\n", *input); + return 0; +} + +static int vidioc_s_input(struct file *file, void *fh, unsigned int input) +{ + struct saa7146_dev *dev = video_drvdata(file); + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + if (input >= HEXIUM_INPUTS) + return -EINVAL; + + hexium->cur_input = input; + hexium_set_input(hexium, input); + + return 0; +} + +static struct saa7146_ext_vv vv_data; + +/* this function only gets called when the probing was successful */ +static int hexium_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + int ret; + + DEB_EE("\n"); + + ret = saa7146_vv_init(dev, &vv_data); + if (ret) { + pr_err("Error in saa7146_vv_init()\n"); + return ret; + } + + vv_data.vid_ops.vidioc_enum_input = vidioc_enum_input; + vv_data.vid_ops.vidioc_g_input = vidioc_g_input; + vv_data.vid_ops.vidioc_s_input = vidioc_s_input; + if (0 != saa7146_register_device(&hexium->video_dev, dev, "hexium orion", VFL_TYPE_VIDEO)) { + pr_err("cannot register capture v4l2 device. skipping.\n"); + return -1; + } + + pr_err("found 'hexium orion' frame grabber-%d\n", hexium_num); + hexium_num++; + + /* the rest */ + hexium->cur_input = 0; + hexium_init_done(dev); + hexium_set_input(hexium, 0); + + return 0; +} + +static int hexium_detach(struct saa7146_dev *dev) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + DEB_EE("dev:%p\n", dev); + + saa7146_unregister_device(&hexium->video_dev, dev); + saa7146_vv_release(dev); + + hexium_num--; + + i2c_del_adapter(&hexium->i2c_adapter); + kfree(hexium); + return 0; +} + +static int std_callback(struct saa7146_dev *dev, struct saa7146_standard *std) +{ + return 0; +} + +static struct saa7146_extension extension; + +static struct saa7146_pci_extension_data hexium_hv_pci6 = { + .ext_priv = "Hexium HV-PCI6 / Orion", + .ext = &extension, +}; + +static struct saa7146_pci_extension_data hexium_orion_1svhs_3bnc = { + .ext_priv = "Hexium HV-PCI6 / Orion (1 SVHS/3 BNC)", + .ext = &extension, +}; + +static struct saa7146_pci_extension_data hexium_orion_4bnc = { + .ext_priv = "Hexium HV-PCI6 / Orion (4 BNC)", + .ext = &extension, +}; + +static const struct pci_device_id pci_tbl[] = { + { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146, 0x0000, 0x0000), + .driver_data = (unsigned long)&hexium_hv_pci6, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146, 0x17c8, 0x0101), + .driver_data = (unsigned long)&hexium_orion_1svhs_3bnc, + }, { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146, 0x17c8, 0x2101), + .driver_data = (unsigned long)&hexium_orion_4bnc, + }, + { } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_ext_vv vv_data = { + .inputs = HEXIUM_INPUTS, + .capabilities = 0, + .stds = &hexium_standards[0], + .num_stds = ARRAY_SIZE(hexium_standards), + .std_callback = &std_callback, +}; + +static struct saa7146_extension extension = { + .name = "hexium HV-PCI6 Orion", + .flags = 0, // SAA7146_USE_I2C_IRQ, + + .pci_tbl = &pci_tbl[0], + .module = THIS_MODULE, + + .probe = hexium_probe, + .attach = hexium_attach, + .detach = hexium_detach, + + .irq_mask = 0, + .irq_func = NULL, +}; + +static int __init hexium_init_module(void) +{ + if (0 != saa7146_register_extension(&extension)) { + DEB_S("failed to register extension\n"); + return -ENODEV; + } + + return 0; +} + +static void __exit hexium_cleanup_module(void) +{ + saa7146_unregister_extension(&extension); +} + +module_init(hexium_init_module); +module_exit(hexium_cleanup_module); + +MODULE_DESCRIPTION("video4linux-2 driver for Hexium Orion frame grabber cards"); +MODULE_AUTHOR("Michael Hunold "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/saa7146/mxb.c b/drivers/media/pci/saa7146/mxb.c new file mode 100644 index 000000000..d759e8a87 --- /dev/null +++ b/drivers/media/pci/saa7146/mxb.c @@ -0,0 +1,874 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + mxb - v4l2 driver for the Multimedia eXtension Board + + Copyright (C) 1998-2006 Michael Hunold + + Visit http://www.themm.net/~mihu/linux/saa7146/mxb.html + for further details about this card. + +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define DEBUG_VARIABLE debug + +#include +#include +#include +#include +#include +#include + +#include "tea6415c.h" +#include "tea6420.h" + +#define MXB_AUDIOS 6 + +#define I2C_SAA7111A 0x24 +#define I2C_TDA9840 0x42 +#define I2C_TEA6415C 0x43 +#define I2C_TEA6420_1 0x4c +#define I2C_TEA6420_2 0x4d +#define I2C_TUNER 0x60 + +#define MXB_BOARD_CAN_DO_VBI(dev) (dev->revision != 0) + +/* global variable */ +static int mxb_num; + +/* initial frequence the tuner will be tuned to. + in verden (lower saxony, germany) 4148 is a + channel called "phoenix" */ +static int freq = 4148; +module_param(freq, int, 0644); +MODULE_PARM_DESC(freq, "initial frequency the tuner will be tuned to while setup"); + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off device debugging (default:off)."); + +#define MXB_STD (V4L2_STD_PAL_BG | V4L2_STD_PAL_I | V4L2_STD_SECAM | V4L2_STD_NTSC) +#define MXB_INPUTS 4 +enum { TUNER, AUX1, AUX3, AUX3_YC }; + +static struct v4l2_input mxb_inputs[MXB_INPUTS] = { + { TUNER, "Tuner", V4L2_INPUT_TYPE_TUNER, 0x3f, 0, + V4L2_STD_PAL_BG | V4L2_STD_PAL_I, 0, V4L2_IN_CAP_STD }, + { AUX1, "AUX1", V4L2_INPUT_TYPE_CAMERA, 0x3f, 0, + MXB_STD, 0, V4L2_IN_CAP_STD }, + { AUX3, "AUX3 Composite", V4L2_INPUT_TYPE_CAMERA, 0x3f, 0, + MXB_STD, 0, V4L2_IN_CAP_STD }, + { AUX3_YC, "AUX3 S-Video", V4L2_INPUT_TYPE_CAMERA, 0x3f, 0, + MXB_STD, 0, V4L2_IN_CAP_STD }, +}; + +/* this array holds the information, which port of the saa7146 each + input actually uses. the mxb uses port 0 for every input */ +static struct { + int hps_source; + int hps_sync; +} input_port_selection[MXB_INPUTS] = { + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, +}; + +/* this array holds the information of the audio source (mxb_audios), + which has to be switched corresponding to the video source (mxb_channels) */ +static int video_audio_connect[MXB_INPUTS] = + { 0, 1, 3, 3 }; + +struct mxb_routing { + u32 input; + u32 output; +}; + +/* these are the available audio sources, which can switched + to the line- and cd-output individually */ +static struct v4l2_audio mxb_audios[MXB_AUDIOS] = { + { + .index = 0, + .name = "Tuner", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 1, + .name = "AUX1", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 2, + .name = "AUX2", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 3, + .name = "AUX3", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 4, + .name = "Radio (X9)", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 5, + .name = "CD-ROM (X10)", + .capability = V4L2_AUDCAP_STEREO, + } +}; + +/* These are the necessary input-output-pins for bringing one audio source + (see above) to the CD-output. Note that gain is set to 0 in this table. */ +static struct mxb_routing TEA6420_cd[MXB_AUDIOS + 1][2] = { + { { 1, 1 }, { 1, 1 } }, /* Tuner */ + { { 5, 1 }, { 6, 1 } }, /* AUX 1 */ + { { 4, 1 }, { 6, 1 } }, /* AUX 2 */ + { { 3, 1 }, { 6, 1 } }, /* AUX 3 */ + { { 1, 1 }, { 3, 1 } }, /* Radio */ + { { 1, 1 }, { 2, 1 } }, /* CD-Rom */ + { { 6, 1 }, { 6, 1 } } /* Mute */ +}; + +/* These are the necessary input-output-pins for bringing one audio source + (see above) to the line-output. Note that gain is set to 0 in this table. */ +static struct mxb_routing TEA6420_line[MXB_AUDIOS + 1][2] = { + { { 2, 3 }, { 1, 2 } }, + { { 5, 3 }, { 6, 2 } }, + { { 4, 3 }, { 6, 2 } }, + { { 3, 3 }, { 6, 2 } }, + { { 2, 3 }, { 3, 2 } }, + { { 2, 3 }, { 2, 2 } }, + { { 6, 3 }, { 6, 2 } } /* Mute */ +}; + +struct mxb +{ + struct video_device video_dev; + struct video_device vbi_dev; + + struct i2c_adapter i2c_adapter; + + struct v4l2_subdev *saa7111a; + struct v4l2_subdev *tda9840; + struct v4l2_subdev *tea6415c; + struct v4l2_subdev *tuner; + struct v4l2_subdev *tea6420_1; + struct v4l2_subdev *tea6420_2; + + int cur_mode; /* current audio mode (mono, stereo, ...) */ + int cur_input; /* current input */ + int cur_audinput; /* current audio input */ + int cur_mute; /* current mute status */ + struct v4l2_frequency cur_freq; /* current frequency the tuner is tuned to */ +}; + +#define saa7111a_call(mxb, o, f, args...) \ + v4l2_subdev_call(mxb->saa7111a, o, f, ##args) +#define tda9840_call(mxb, o, f, args...) \ + v4l2_subdev_call(mxb->tda9840, o, f, ##args) +#define tea6415c_call(mxb, o, f, args...) \ + v4l2_subdev_call(mxb->tea6415c, o, f, ##args) +#define tuner_call(mxb, o, f, args...) \ + v4l2_subdev_call(mxb->tuner, o, f, ##args) +#define call_all(dev, o, f, args...) \ + v4l2_device_call_until_err(&dev->v4l2_dev, 0, o, f, ##args) + +static void mxb_update_audmode(struct mxb *mxb) +{ + struct v4l2_tuner t = { + .audmode = mxb->cur_mode, + }; + + tda9840_call(mxb, tuner, s_tuner, &t); +} + +static inline void tea6420_route(struct mxb *mxb, int idx) +{ + v4l2_subdev_call(mxb->tea6420_1, audio, s_routing, + TEA6420_cd[idx][0].input, TEA6420_cd[idx][0].output, 0); + v4l2_subdev_call(mxb->tea6420_2, audio, s_routing, + TEA6420_cd[idx][1].input, TEA6420_cd[idx][1].output, 0); + v4l2_subdev_call(mxb->tea6420_1, audio, s_routing, + TEA6420_line[idx][0].input, TEA6420_line[idx][0].output, 0); + v4l2_subdev_call(mxb->tea6420_2, audio, s_routing, + TEA6420_line[idx][1].input, TEA6420_line[idx][1].output, 0); +} + +static struct saa7146_extension extension; + +static int mxb_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct saa7146_dev *dev = container_of(ctrl->handler, + struct saa7146_dev, ctrl_handler); + struct mxb *mxb = dev->ext_priv; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + mxb->cur_mute = ctrl->val; + /* switch the audio-source */ + tea6420_route(mxb, ctrl->val ? 6 : + video_audio_connect[mxb->cur_input]); + break; + default: + return -EINVAL; + } + return 0; +} + +static const struct v4l2_ctrl_ops mxb_ctrl_ops = { + .s_ctrl = mxb_s_ctrl, +}; + +static int mxb_probe(struct saa7146_dev *dev) +{ + struct v4l2_ctrl_handler *hdl = &dev->ctrl_handler; + struct mxb *mxb = NULL; + + v4l2_ctrl_new_std(hdl, &mxb_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); + if (hdl->error) + return hdl->error; + mxb = kzalloc_obj(struct mxb); + if (mxb == NULL) { + DEB_D("not enough kernel memory\n"); + return -ENOMEM; + } + + + snprintf(mxb->i2c_adapter.name, sizeof(mxb->i2c_adapter.name), "mxb%d", mxb_num); + + saa7146_i2c_adapter_prepare(dev, &mxb->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); + if (i2c_add_adapter(&mxb->i2c_adapter) < 0) { + DEB_S("cannot register i2c-device. skipping.\n"); + kfree(mxb); + return -EFAULT; + } + + mxb->saa7111a = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "saa7111", I2C_SAA7111A, NULL); + mxb->tea6420_1 = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "tea6420", I2C_TEA6420_1, NULL); + mxb->tea6420_2 = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "tea6420", I2C_TEA6420_2, NULL); + mxb->tea6415c = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "tea6415c", I2C_TEA6415C, NULL); + mxb->tda9840 = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "tda9840", I2C_TDA9840, NULL); + mxb->tuner = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "tuner", I2C_TUNER, NULL); + + /* check if all devices are present */ + if (!mxb->tea6420_1 || !mxb->tea6420_2 || !mxb->tea6415c || + !mxb->tda9840 || !mxb->saa7111a || !mxb->tuner) { + pr_err("did not find all i2c devices. aborting\n"); + i2c_del_adapter(&mxb->i2c_adapter); + kfree(mxb); + return -ENODEV; + } + + /* all devices are present, probe was successful */ + + /* we store the pointer in our private data field */ + dev->ext_priv = mxb; + + v4l2_ctrl_handler_setup(hdl); + + return 0; +} + +/* some init data for the saa7740, the so-called 'sound arena module'. + there are no specs available, so we simply use some init values */ +static struct { + int length; + char data[9]; +} mxb_saa7740_init[] = { + { 3, { 0x80, 0x00, 0x00 } },{ 3, { 0x80, 0x89, 0x00 } }, + { 3, { 0x80, 0xb0, 0x0a } },{ 3, { 0x00, 0x00, 0x00 } }, + { 3, { 0x49, 0x00, 0x00 } },{ 3, { 0x4a, 0x00, 0x00 } }, + { 3, { 0x4b, 0x00, 0x00 } },{ 3, { 0x4c, 0x00, 0x00 } }, + { 3, { 0x4d, 0x00, 0x00 } },{ 3, { 0x4e, 0x00, 0x00 } }, + { 3, { 0x4f, 0x00, 0x00 } },{ 3, { 0x50, 0x00, 0x00 } }, + { 3, { 0x51, 0x00, 0x00 } },{ 3, { 0x52, 0x00, 0x00 } }, + { 3, { 0x53, 0x00, 0x00 } },{ 3, { 0x54, 0x00, 0x00 } }, + { 3, { 0x55, 0x00, 0x00 } },{ 3, { 0x56, 0x00, 0x00 } }, + { 3, { 0x57, 0x00, 0x00 } },{ 3, { 0x58, 0x00, 0x00 } }, + { 3, { 0x59, 0x00, 0x00 } },{ 3, { 0x5a, 0x00, 0x00 } }, + { 3, { 0x5b, 0x00, 0x00 } },{ 3, { 0x5c, 0x00, 0x00 } }, + { 3, { 0x5d, 0x00, 0x00 } },{ 3, { 0x5e, 0x00, 0x00 } }, + { 3, { 0x5f, 0x00, 0x00 } },{ 3, { 0x60, 0x00, 0x00 } }, + { 3, { 0x61, 0x00, 0x00 } },{ 3, { 0x62, 0x00, 0x00 } }, + { 3, { 0x63, 0x00, 0x00 } },{ 3, { 0x64, 0x00, 0x00 } }, + { 3, { 0x65, 0x00, 0x00 } },{ 3, { 0x66, 0x00, 0x00 } }, + { 3, { 0x67, 0x00, 0x00 } },{ 3, { 0x68, 0x00, 0x00 } }, + { 3, { 0x69, 0x00, 0x00 } },{ 3, { 0x6a, 0x00, 0x00 } }, + { 3, { 0x6b, 0x00, 0x00 } },{ 3, { 0x6c, 0x00, 0x00 } }, + { 3, { 0x6d, 0x00, 0x00 } },{ 3, { 0x6e, 0x00, 0x00 } }, + { 3, { 0x6f, 0x00, 0x00 } },{ 3, { 0x70, 0x00, 0x00 } }, + { 3, { 0x71, 0x00, 0x00 } },{ 3, { 0x72, 0x00, 0x00 } }, + { 3, { 0x73, 0x00, 0x00 } },{ 3, { 0x74, 0x00, 0x00 } }, + { 3, { 0x75, 0x00, 0x00 } },{ 3, { 0x76, 0x00, 0x00 } }, + { 3, { 0x77, 0x00, 0x00 } },{ 3, { 0x41, 0x00, 0x42 } }, + { 3, { 0x42, 0x10, 0x42 } },{ 3, { 0x43, 0x20, 0x42 } }, + { 3, { 0x44, 0x30, 0x42 } },{ 3, { 0x45, 0x00, 0x01 } }, + { 3, { 0x46, 0x00, 0x01 } },{ 3, { 0x47, 0x00, 0x01 } }, + { 3, { 0x48, 0x00, 0x01 } }, + { 9, { 0x01, 0x03, 0xc5, 0x5c, 0x7a, 0x85, 0x01, 0x00, 0x54 } }, + { 9, { 0x21, 0x03, 0xc5, 0x5c, 0x7a, 0x85, 0x01, 0x00, 0x54 } }, + { 9, { 0x09, 0x0b, 0xb4, 0x6b, 0x74, 0x85, 0x95, 0x00, 0x34 } }, + { 9, { 0x29, 0x0b, 0xb4, 0x6b, 0x74, 0x85, 0x95, 0x00, 0x34 } }, + { 9, { 0x11, 0x17, 0x43, 0x62, 0x68, 0x89, 0xd1, 0xff, 0xb0 } }, + { 9, { 0x31, 0x17, 0x43, 0x62, 0x68, 0x89, 0xd1, 0xff, 0xb0 } }, + { 9, { 0x19, 0x20, 0x62, 0x51, 0x5a, 0x95, 0x19, 0x01, 0x50 } }, + { 9, { 0x39, 0x20, 0x62, 0x51, 0x5a, 0x95, 0x19, 0x01, 0x50 } }, + { 9, { 0x05, 0x3e, 0xd2, 0x69, 0x4e, 0x9a, 0x51, 0x00, 0xf0 } }, + { 9, { 0x25, 0x3e, 0xd2, 0x69, 0x4e, 0x9a, 0x51, 0x00, 0xf0 } }, + { 9, { 0x0d, 0x3d, 0xa1, 0x40, 0x7d, 0x9f, 0x29, 0xfe, 0x14 } }, + { 9, { 0x2d, 0x3d, 0xa1, 0x40, 0x7d, 0x9f, 0x29, 0xfe, 0x14 } }, + { 9, { 0x15, 0x73, 0xa1, 0x50, 0x5d, 0xa6, 0xf5, 0xfe, 0x38 } }, + { 9, { 0x35, 0x73, 0xa1, 0x50, 0x5d, 0xa6, 0xf5, 0xfe, 0x38 } }, + { 9, { 0x1d, 0xed, 0xd0, 0x68, 0x29, 0xb4, 0xe1, 0x00, 0xb8 } }, + { 9, { 0x3d, 0xed, 0xd0, 0x68, 0x29, 0xb4, 0xe1, 0x00, 0xb8 } }, + { 3, { 0x80, 0xb3, 0x0a } }, + {-1, { 0 } } +}; + +/* bring hardware to a sane state. this has to be done, just in case someone + wants to capture from this device before it has been properly initialized. + the capture engine would badly fail, because no valid signal arrives on the + saa7146, thus leading to timeouts and stuff. */ +static int mxb_init_done(struct saa7146_dev* dev) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; + struct i2c_msg msg; + struct tuner_setup tun_setup; + v4l2_std_id std = V4L2_STD_PAL_BG; + + int i, err = 0; + + /* mute audio on tea6420s */ + tea6420_route(mxb, 6); + + /* select video mode in saa7111a */ + saa7111a_call(mxb, video, s_std, std); + + /* select tuner-output on saa7111a */ + saa7111a_call(mxb, video, s_routing, SAA7115_COMPOSITE0, + SAA7111_FMT_CCIR, 0); + + /* select a tuner type */ + tun_setup.mode_mask = T_ANALOG_TV; + tun_setup.addr = ADDR_UNSET; + tun_setup.type = TUNER_PHILIPS_PAL; + tuner_call(mxb, tuner, s_type_addr, &tun_setup); + /* tune in some frequency on tuner */ + mxb->cur_freq.tuner = 0; + mxb->cur_freq.type = V4L2_TUNER_ANALOG_TV; + mxb->cur_freq.frequency = freq; + tuner_call(mxb, tuner, s_frequency, &mxb->cur_freq); + + /* set a default video standard */ + /* These two gpio calls set the GPIO pins that control the tda9820 */ + saa7146_write(dev, GPIO_CTRL, 0x00404050); + saa7111a_call(mxb, core, s_gpio, 1); + saa7111a_call(mxb, video, s_std, std); + tuner_call(mxb, video, s_std, std); + + /* switch to tuner-channel on tea6415c */ + tea6415c_call(mxb, video, s_routing, 3, 17, 0); + + /* select tuner-output on multicable on tea6415c */ + tea6415c_call(mxb, video, s_routing, 3, 13, 0); + + /* the rest for mxb */ + mxb->cur_input = 0; + mxb->cur_audinput = video_audio_connect[mxb->cur_input]; + mxb->cur_mute = 1; + + mxb->cur_mode = V4L2_TUNER_MODE_STEREO; + mxb_update_audmode(mxb); + + /* check if the saa7740 (aka 'sound arena module') is present + on the mxb. if so, we must initialize it. due to lack of + information about the saa7740, the values were reverse + engineered. */ + msg.addr = 0x1b; + msg.flags = 0; + msg.len = mxb_saa7740_init[0].length; + msg.buf = &mxb_saa7740_init[0].data[0]; + + err = i2c_transfer(&mxb->i2c_adapter, &msg, 1); + if (err == 1) { + /* the sound arena module is a pos, that's probably the reason + philips refuses to hand out a datasheet for the saa7740... + it seems to screw up the i2c bus, so we disable fast irq + based i2c transactions here and rely on the slow and safe + polling method ... */ + extension.flags &= ~SAA7146_USE_I2C_IRQ; + for (i = 1; ; i++) { + if (-1 == mxb_saa7740_init[i].length) + break; + + msg.len = mxb_saa7740_init[i].length; + msg.buf = &mxb_saa7740_init[i].data[0]; + err = i2c_transfer(&mxb->i2c_adapter, &msg, 1); + if (err != 1) { + DEB_D("failed to initialize 'sound arena module'\n"); + goto err; + } + } + pr_info("'sound arena module' detected\n"); + } +err: + /* the rest for saa7146: you should definitely set some basic values + for the input-port handling of the saa7146. */ + + /* ext->saa has been filled by the core driver */ + + /* some stuff is done via variables */ + saa7146_set_hps_source_and_sync(dev, input_port_selection[mxb->cur_input].hps_source, + input_port_selection[mxb->cur_input].hps_sync); + + /* some stuff is done via direct write to the registers */ + + /* this is ugly, but because of the fact that this is completely + hardware dependend, it should be done directly... */ + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, DD1_INIT, 0x02000200); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + return 0; +} + +/* interrupt-handler. this gets called when irq_mask is != 0. + it must clear the interrupt-bits in irq_mask it has handled */ +/* +void mxb_irq_bh(struct saa7146_dev* dev, u32* irq_mask) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; +} +*/ + +static int vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *i) +{ + DEB_EE("VIDIOC_ENUMINPUT %d\n", i->index); + if (i->index >= MXB_INPUTS) + return -EINVAL; + memcpy(i, &mxb_inputs[i->index], sizeof(struct v4l2_input)); + return 0; +} + +static int vidioc_g_input(struct file *file, void *fh, unsigned int *i) +{ + struct saa7146_dev *dev = video_drvdata(file); + struct mxb *mxb = (struct mxb *)dev->ext_priv; + *i = mxb->cur_input; + + DEB_EE("VIDIOC_G_INPUT %d\n", *i); + return 0; +} + +static int vidioc_s_input(struct file *file, void *fh, unsigned int input) +{ + struct saa7146_dev *dev = video_drvdata(file); + struct mxb *mxb = (struct mxb *)dev->ext_priv; + int err = 0; + int i = 0; + + DEB_EE("VIDIOC_S_INPUT %d\n", input); + + if (input >= MXB_INPUTS) + return -EINVAL; + + mxb->cur_input = input; + + saa7146_set_hps_source_and_sync(dev, input_port_selection[input].hps_source, + input_port_selection[input].hps_sync); + + /* prepare switching of tea6415c and saa7111a; + have a look at the 'background'-file for further information */ + switch (input) { + case TUNER: + i = SAA7115_COMPOSITE0; + + err = tea6415c_call(mxb, video, s_routing, 3, 17, 0); + + /* connect tuner-output always to multicable */ + if (!err) + err = tea6415c_call(mxb, video, s_routing, 3, 13, 0); + break; + case AUX3_YC: + /* nothing to be done here. aux3_yc is + directly connected to the saa711a */ + i = SAA7115_SVIDEO1; + break; + case AUX3: + /* nothing to be done here. aux3 is + directly connected to the saa711a */ + i = SAA7115_COMPOSITE1; + break; + case AUX1: + i = SAA7115_COMPOSITE0; + err = tea6415c_call(mxb, video, s_routing, 1, 17, 0); + break; + } + + if (err) + return err; + + mxb->video_dev.tvnorms = mxb_inputs[input].std; + mxb->vbi_dev.tvnorms = mxb_inputs[input].std; + + /* switch video in saa7111a */ + if (saa7111a_call(mxb, video, s_routing, i, SAA7111_FMT_CCIR, 0)) + pr_err("VIDIOC_S_INPUT: could not address saa7111a\n"); + + mxb->cur_audinput = video_audio_connect[input]; + /* switch the audio-source only if necessary */ + if (0 == mxb->cur_mute) + tea6420_route(mxb, mxb->cur_audinput); + if (mxb->cur_audinput == 0) + mxb_update_audmode(mxb); + + return 0; +} + +static int vidioc_g_tuner(struct file *file, void *fh, struct v4l2_tuner *t) +{ + struct saa7146_dev *dev = video_drvdata(file); + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + if (t->index) { + DEB_D("VIDIOC_G_TUNER: channel %d does not have a tuner attached\n", + t->index); + return -EINVAL; + } + + DEB_EE("VIDIOC_G_TUNER: %d\n", t->index); + + memset(t, 0, sizeof(*t)); + strscpy(t->name, "TV Tuner", sizeof(t->name)); + t->type = V4L2_TUNER_ANALOG_TV; + t->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP; + t->audmode = mxb->cur_mode; + return call_all(dev, tuner, g_tuner, t); +} + +static int vidioc_s_tuner(struct file *file, void *fh, const struct v4l2_tuner *t) +{ + struct saa7146_dev *dev = video_drvdata(file); + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + if (t->index) { + DEB_D("VIDIOC_S_TUNER: channel %d does not have a tuner attached\n", + t->index); + return -EINVAL; + } + + mxb->cur_mode = t->audmode; + return call_all(dev, tuner, s_tuner, t); +} + +static int vidioc_querystd(struct file *file, void *fh, v4l2_std_id *norm) +{ + struct saa7146_dev *dev = video_drvdata(file); + + return call_all(dev, video, querystd, norm); +} + +static int vidioc_g_frequency(struct file *file, void *fh, struct v4l2_frequency *f) +{ + struct saa7146_dev *dev = video_drvdata(file); + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + if (f->tuner) + return -EINVAL; + *f = mxb->cur_freq; + + DEB_EE("VIDIOC_G_FREQ: freq:0x%08x\n", mxb->cur_freq.frequency); + return 0; +} + +static int vidioc_s_frequency(struct file *file, void *fh, const struct v4l2_frequency *f) +{ + struct saa7146_dev *dev = video_drvdata(file); + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + if (f->tuner) + return -EINVAL; + + if (V4L2_TUNER_ANALOG_TV != f->type) + return -EINVAL; + + DEB_EE("VIDIOC_S_FREQUENCY: freq:0x%08x\n", mxb->cur_freq.frequency); + + /* tune in desired frequency */ + tuner_call(mxb, tuner, s_frequency, f); + /* let the tuner subdev clamp the frequency to the tuner range */ + mxb->cur_freq = *f; + tuner_call(mxb, tuner, g_frequency, &mxb->cur_freq); + if (mxb->cur_audinput == 0) + mxb_update_audmode(mxb); + return 0; +} + +static int vidioc_enumaudio(struct file *file, void *fh, struct v4l2_audio *a) +{ + if (a->index >= MXB_AUDIOS) + return -EINVAL; + *a = mxb_audios[a->index]; + return 0; +} + +static int vidioc_g_audio(struct file *file, void *fh, struct v4l2_audio *a) +{ + struct saa7146_dev *dev = video_drvdata(file); + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + DEB_EE("VIDIOC_G_AUDIO\n"); + *a = mxb_audios[mxb->cur_audinput]; + return 0; +} + +static int vidioc_s_audio(struct file *file, void *fh, const struct v4l2_audio *a) +{ + struct saa7146_dev *dev = video_drvdata(file); + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + DEB_D("VIDIOC_S_AUDIO %d\n", a->index); + if (a->index >= 32 || + !(mxb_inputs[mxb->cur_input].audioset & (1 << a->index))) + return -EINVAL; + + if (mxb->cur_audinput != a->index) { + mxb->cur_audinput = a->index; + tea6420_route(mxb, a->index); + if (mxb->cur_audinput == 0) + mxb_update_audmode(mxb); + } + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int vidioc_g_register(struct file *file, void *fh, struct v4l2_dbg_register *reg) +{ + struct saa7146_dev *dev = video_drvdata(file); + + if (reg->reg > pci_resource_len(dev->pci, 0) - 4) + return -EINVAL; + reg->val = saa7146_read(dev, reg->reg); + reg->size = 4; + return 0; +} + +static int vidioc_s_register(struct file *file, void *fh, const struct v4l2_dbg_register *reg) +{ + struct saa7146_dev *dev = video_drvdata(file); + + if (reg->reg > pci_resource_len(dev->pci, 0) - 4) + return -EINVAL; + saa7146_write(dev, reg->reg, reg->val); + return 0; +} +#endif + +static struct saa7146_ext_vv vv_data; + +/* this function only gets called when the probing was successful */ +static int mxb_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct mxb *mxb; + int ret; + + DEB_EE("dev:%p\n", dev); + + ret = saa7146_vv_init(dev, &vv_data); + if (ret) { + ERR("Error in saa7146_vv_init()"); + return ret; + } + + if (mxb_probe(dev)) { + saa7146_vv_release(dev); + return -1; + } + mxb = (struct mxb *)dev->ext_priv; + + vv_data.vid_ops.vidioc_enum_input = vidioc_enum_input; + vv_data.vid_ops.vidioc_g_input = vidioc_g_input; + vv_data.vid_ops.vidioc_s_input = vidioc_s_input; + vv_data.vid_ops.vidioc_querystd = vidioc_querystd; + vv_data.vid_ops.vidioc_g_tuner = vidioc_g_tuner; + vv_data.vid_ops.vidioc_s_tuner = vidioc_s_tuner; + vv_data.vid_ops.vidioc_g_frequency = vidioc_g_frequency; + vv_data.vid_ops.vidioc_s_frequency = vidioc_s_frequency; + vv_data.vid_ops.vidioc_enumaudio = vidioc_enumaudio; + vv_data.vid_ops.vidioc_g_audio = vidioc_g_audio; + vv_data.vid_ops.vidioc_s_audio = vidioc_s_audio; +#ifdef CONFIG_VIDEO_ADV_DEBUG + vv_data.vid_ops.vidioc_g_register = vidioc_g_register; + vv_data.vid_ops.vidioc_s_register = vidioc_s_register; +#endif + vv_data.vbi_ops.vidioc_enum_input = vidioc_enum_input; + vv_data.vbi_ops.vidioc_g_input = vidioc_g_input; + vv_data.vbi_ops.vidioc_s_input = vidioc_s_input; + vv_data.vbi_ops.vidioc_querystd = vidioc_querystd; + vv_data.vbi_ops.vidioc_g_tuner = vidioc_g_tuner; + vv_data.vbi_ops.vidioc_s_tuner = vidioc_s_tuner; + vv_data.vbi_ops.vidioc_g_frequency = vidioc_g_frequency; + vv_data.vbi_ops.vidioc_s_frequency = vidioc_s_frequency; + vv_data.vbi_ops.vidioc_enumaudio = vidioc_enumaudio; + vv_data.vbi_ops.vidioc_g_audio = vidioc_g_audio; + vv_data.vbi_ops.vidioc_s_audio = vidioc_s_audio; + if (saa7146_register_device(&mxb->video_dev, dev, "mxb", VFL_TYPE_VIDEO)) { + ERR("cannot register capture v4l2 device. skipping.\n"); + saa7146_vv_release(dev); + return -1; + } + + /* initialization stuff (vbi) (only for revision > 0 and for extensions which want it)*/ + if (MXB_BOARD_CAN_DO_VBI(dev)) { + if (saa7146_register_device(&mxb->vbi_dev, dev, "mxb", VFL_TYPE_VBI)) { + ERR("cannot register vbi v4l2 device. skipping.\n"); + } + } + + pr_info("found Multimedia eXtension Board #%d\n", mxb_num); + + mxb_num++; + mxb_init_done(dev); + return 0; +} + +static int mxb_detach(struct saa7146_dev *dev) +{ + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + DEB_EE("dev:%p\n", dev); + + /* mute audio on tea6420s */ + tea6420_route(mxb, 6); + + saa7146_unregister_device(&mxb->video_dev,dev); + if (MXB_BOARD_CAN_DO_VBI(dev)) + saa7146_unregister_device(&mxb->vbi_dev, dev); + saa7146_vv_release(dev); + + mxb_num--; + + i2c_del_adapter(&mxb->i2c_adapter); + kfree(mxb); + + return 0; +} + +static int std_callback(struct saa7146_dev *dev, struct saa7146_standard *standard) +{ + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + if (V4L2_STD_PAL_I == standard->id) { + v4l2_std_id std = V4L2_STD_PAL_I; + + DEB_D("VIDIOC_S_STD: setting mxb for PAL_I\n"); + /* These two gpio calls set the GPIO pins that control the tda9820 */ + saa7146_write(dev, GPIO_CTRL, 0x00404050); + saa7111a_call(mxb, core, s_gpio, 0); + saa7111a_call(mxb, video, s_std, std); + if (mxb->cur_input == 0) + tuner_call(mxb, video, s_std, std); + } else { + v4l2_std_id std = V4L2_STD_PAL_BG; + + if (mxb->cur_input) + std = standard->id; + DEB_D("VIDIOC_S_STD: setting mxb for PAL/NTSC/SECAM\n"); + /* These two gpio calls set the GPIO pins that control the tda9820 */ + saa7146_write(dev, GPIO_CTRL, 0x00404050); + saa7111a_call(mxb, core, s_gpio, 1); + saa7111a_call(mxb, video, s_std, std); + if (mxb->cur_input == 0) + tuner_call(mxb, video, s_std, std); + } + return 0; +} + +static struct saa7146_standard standard[] = { + { + .name = "PAL-BG", .id = V4L2_STD_PAL_BG, + .v_offset = 0x17, .v_field = 288, + .h_offset = 0x14, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "PAL-I", .id = V4L2_STD_PAL_I, + .v_offset = 0x17, .v_field = 288, + .h_offset = 0x14, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 0x16, .v_field = 240, + .h_offset = 0x06, .h_pixels = 708, + .v_max_out = 480, .h_max_out = 640, + }, { + .name = "SECAM", .id = V4L2_STD_SECAM, + .v_offset = 0x14, .v_field = 288, + .h_offset = 0x14, .h_pixels = 720, + .v_max_out = 576, .h_max_out = 768, + } +}; + +static struct saa7146_pci_extension_data mxb = { + .ext_priv = "Multimedia eXtension Board", + .ext = &extension, +}; + +static const struct pci_device_id pci_tbl[] = { + { + PCI_VDEVICE_SUB(PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146, 0x0000, 0x0000), + .driver_data = (unsigned long)&mxb, + }, + { } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_ext_vv vv_data = { + .inputs = MXB_INPUTS, + .capabilities = V4L2_CAP_TUNER | V4L2_CAP_VBI_CAPTURE | V4L2_CAP_AUDIO, + .stds = &standard[0], + .num_stds = ARRAY_SIZE(standard), + .std_callback = &std_callback, +}; + +static struct saa7146_extension extension = { + .name = "Multimedia eXtension Board", + .flags = SAA7146_USE_I2C_IRQ, + + .pci_tbl = &pci_tbl[0], + .module = THIS_MODULE, + + .attach = mxb_attach, + .detach = mxb_detach, + + .irq_mask = 0, + .irq_func = NULL, +}; + +static int __init mxb_init_module(void) +{ + if (saa7146_register_extension(&extension)) { + DEB_S("failed to register extension\n"); + return -ENODEV; + } + + return 0; +} + +static void __exit mxb_cleanup_module(void) +{ + saa7146_unregister_extension(&extension); +} + +module_init(mxb_init_module); +module_exit(mxb_cleanup_module); + +MODULE_DESCRIPTION("video4linux-2 driver for the Siemens-Nixdorf 'Multimedia eXtension board'"); +MODULE_AUTHOR("Michael Hunold "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/saa7164/Kconfig b/drivers/media/pci/saa7164/Kconfig new file mode 100644 index 000000000..6655c3e50 --- /dev/null +++ b/drivers/media/pci/saa7164/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_SAA7164 + tristate "NXP SAA7164 support" + depends on DVB_CORE && VIDEO_DEV && PCI && I2C + select I2C_ALGOBIT + select FW_LOADER + select VIDEO_TUNER + select VIDEO_TVEEPROM + select DVB_TDA10048 if MEDIA_SUBDRV_AUTOSELECT + select DVB_S5H1411 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT + help + This is a video4linux driver for NXP SAA7164 based + TV cards. + + To compile this driver as a module, choose M here: the + module will be called saa7164 + diff --git a/drivers/media/pci/saa7164/Makefile b/drivers/media/pci/saa7164/Makefile new file mode 100644 index 000000000..dea076744 --- /dev/null +++ b/drivers/media/pci/saa7164/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +saa7164-objs := saa7164-cards.o saa7164-core.o saa7164-i2c.o saa7164-dvb.o \ + saa7164-fw.o saa7164-bus.o saa7164-cmd.o saa7164-api.o \ + saa7164-buffer.o saa7164-encoder.o saa7164-vbi.o + +obj-$(CONFIG_VIDEO_SAA7164) += saa7164.o + +ccflags-y += -I$(srctree)/drivers/media/tuners +ccflags-y += -I$(srctree)/drivers/media/dvb-frontends diff --git a/drivers/media/pci/saa7164/saa7164-api.c b/drivers/media/pci/saa7164/saa7164-api.c new file mode 100644 index 000000000..965d285a9 --- /dev/null +++ b/drivers/media/pci/saa7164/saa7164-api.c @@ -0,0 +1,1512 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the NXP SAA7164 PCIe bridge + * + * Copyright (c) 2010-2015 Steven Toth + */ + +#include +#include + +#include "saa7164.h" + +int saa7164_api_get_load_info(struct saa7164_dev *dev, struct tmFwInfoStruct *i) +{ + int ret; + + if (!(saa_debug & DBGLVL_CPU)) + return 0; + + dprintk(DBGLVL_API, "%s()\n", __func__); + + i->deviceinst = 0; + i->devicespec = 0; + i->mode = 0; + i->status = 0; + + ret = saa7164_cmd_send(dev, 0, GET_CUR, + GET_FW_STATUS_CONTROL, sizeof(struct tmFwInfoStruct), i); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + printk(KERN_INFO "saa7164[%d]-CPU: %d percent", dev->nr, i->CPULoad); + + return ret; +} + +int saa7164_api_collect_debug(struct saa7164_dev *dev) +{ + struct tmComResDebugGetData d; + u8 more = 255; + int ret; + + dprintk(DBGLVL_API, "%s()\n", __func__); + + while (more--) { + + memset(&d, 0, sizeof(d)); + + ret = saa7164_cmd_send(dev, 0, GET_CUR, + GET_DEBUG_DATA_CONTROL, sizeof(d), &d); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", + __func__, ret); + + if (d.dwResult != SAA_OK) + break; + + printk(KERN_INFO "saa7164[%d]-FWMSG: %s", dev->nr, + d.ucDebugData); + } + + return 0; +} + +int saa7164_api_set_debug(struct saa7164_dev *dev, u8 level) +{ + struct tmComResDebugSetLevel lvl; + int ret; + + dprintk(DBGLVL_API, "%s(level=%d)\n", __func__, level); + + /* Retrieve current state */ + ret = saa7164_cmd_send(dev, 0, GET_CUR, + SET_DEBUG_LEVEL_CONTROL, sizeof(lvl), &lvl); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + dprintk(DBGLVL_API, "%s() Was %d\n", __func__, lvl.dwDebugLevel); + + lvl.dwDebugLevel = level; + + /* set new state */ + ret = saa7164_cmd_send(dev, 0, SET_CUR, + SET_DEBUG_LEVEL_CONTROL, sizeof(lvl), &lvl); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + return ret; +} + +int saa7164_api_set_vbi_format(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct tmComResProbeCommit fmt, rsp; + int ret; + + dprintk(DBGLVL_API, "%s(nr=%d, unitid=0x%x)\n", __func__, + port->nr, port->hwcfg.unitid); + + fmt.bmHint = 0; + fmt.bFormatIndex = 1; + fmt.bFrameIndex = 1; + + /* Probe, see if it can support this format */ + ret = saa7164_cmd_send(port->dev, port->hwcfg.unitid, + SET_CUR, SAA_PROBE_CONTROL, sizeof(fmt), &fmt); + if (ret != SAA_OK) + printk(KERN_ERR "%s() set error, ret = 0x%x\n", __func__, ret); + + /* See of the format change was successful */ + ret = saa7164_cmd_send(port->dev, port->hwcfg.unitid, + GET_CUR, SAA_PROBE_CONTROL, sizeof(rsp), &rsp); + if (ret != SAA_OK) { + printk(KERN_ERR "%s() get error, ret = 0x%x\n", __func__, ret); + } else { + /* Compare requested vs received, should be same */ + if (memcmp(&fmt, &rsp, sizeof(rsp)) == 0) { + dprintk(DBGLVL_API, "SET/PROBE Verified\n"); + + /* Ask the device to select the negotiated format */ + ret = saa7164_cmd_send(port->dev, port->hwcfg.unitid, + SET_CUR, SAA_COMMIT_CONTROL, sizeof(fmt), &fmt); + if (ret != SAA_OK) + printk(KERN_ERR "%s() commit error, ret = 0x%x\n", + __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->hwcfg.unitid, + GET_CUR, SAA_COMMIT_CONTROL, sizeof(rsp), &rsp); + if (ret != SAA_OK) + printk(KERN_ERR "%s() GET commit error, ret = 0x%x\n", + __func__, ret); + + if (memcmp(&fmt, &rsp, sizeof(rsp)) != 0) { + printk(KERN_ERR "%s() memcmp error, ret = 0x%x\n", + __func__, ret); + } else + dprintk(DBGLVL_API, "SET/COMMIT Verified\n"); + + dprintk(DBGLVL_API, "rsp.bmHint = 0x%x\n", rsp.bmHint); + dprintk(DBGLVL_API, "rsp.bFormatIndex = 0x%x\n", + rsp.bFormatIndex); + dprintk(DBGLVL_API, "rsp.bFrameIndex = 0x%x\n", + rsp.bFrameIndex); + } else + printk(KERN_ERR "%s() compare failed\n", __func__); + } + + if (ret == SAA_OK) + dprintk(DBGLVL_API, "%s(nr=%d) Success\n", __func__, port->nr); + + return ret; +} + +static int saa7164_api_set_gop_size(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct tmComResEncVideoGopStructure gs; + int ret; + + dprintk(DBGLVL_ENC, "%s()\n", __func__); + + gs.ucRefFrameDist = port->encoder_params.refdist; + gs.ucGOPSize = port->encoder_params.gop_size; + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR, + EU_VIDEO_GOP_STRUCTURE_CONTROL, + sizeof(gs), &gs); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + return ret; +} + +int saa7164_api_set_encoder(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct tmComResEncVideoBitRate vb; + struct tmComResEncAudioBitRate ab; + int ret; + + dprintk(DBGLVL_ENC, "%s() unitid=0x%x\n", __func__, + port->hwcfg.sourceid); + + if (port->encoder_params.stream_type == V4L2_MPEG_STREAM_TYPE_MPEG2_PS) + port->encoder_profile = EU_PROFILE_PS_DVD; + else + port->encoder_profile = EU_PROFILE_TS_HQ; + + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR, + EU_PROFILE_CONTROL, sizeof(u8), &port->encoder_profile); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Resolution */ + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR, + EU_PROFILE_CONTROL, sizeof(u8), &port->encoder_profile); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Establish video bitrates */ + if (port->encoder_params.bitrate_mode == + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR) + vb.ucVideoBitRateMode = EU_VIDEO_BIT_RATE_MODE_CONSTANT; + else + vb.ucVideoBitRateMode = EU_VIDEO_BIT_RATE_MODE_VARIABLE_PEAK; + vb.dwVideoBitRate = port->encoder_params.bitrate; + vb.dwVideoBitRatePeak = port->encoder_params.bitrate_peak; + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR, + EU_VIDEO_BIT_RATE_CONTROL, + sizeof(struct tmComResEncVideoBitRate), + &vb); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Establish audio bitrates */ + ab.ucAudioBitRateMode = 0; + ab.dwAudioBitRate = 384000; + ab.dwAudioBitRatePeak = ab.dwAudioBitRate; + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR, + EU_AUDIO_BIT_RATE_CONTROL, + sizeof(struct tmComResEncAudioBitRate), + &ab); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, + ret); + + saa7164_api_set_aspect_ratio(port); + saa7164_api_set_gop_size(port); + + return ret; +} + +int saa7164_api_get_encoder(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct tmComResEncVideoBitRate v; + struct tmComResEncAudioBitRate a; + struct tmComResEncVideoInputAspectRatio ar; + int ret; + + dprintk(DBGLVL_ENC, "%s() unitid=0x%x\n", __func__, + port->hwcfg.sourceid); + + port->encoder_profile = 0; + port->video_format = 0; + port->video_resolution = 0; + port->audio_format = 0; + + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR, + EU_PROFILE_CONTROL, sizeof(u8), &port->encoder_profile); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR, + EU_VIDEO_RESOLUTION_CONTROL, sizeof(u8), + &port->video_resolution); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR, + EU_VIDEO_FORMAT_CONTROL, sizeof(u8), &port->video_format); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR, + EU_VIDEO_BIT_RATE_CONTROL, sizeof(v), &v); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR, + EU_AUDIO_FORMAT_CONTROL, sizeof(u8), &port->audio_format); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR, + EU_AUDIO_BIT_RATE_CONTROL, sizeof(a), &a); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Aspect Ratio */ + ar.width = 0; + ar.height = 0; + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR, + EU_VIDEO_INPUT_ASPECT_CONTROL, + sizeof(struct tmComResEncVideoInputAspectRatio), &ar); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + dprintk(DBGLVL_ENC, "encoder_profile = %d\n", port->encoder_profile); + dprintk(DBGLVL_ENC, "video_format = %d\n", port->video_format); + dprintk(DBGLVL_ENC, "audio_format = %d\n", port->audio_format); + dprintk(DBGLVL_ENC, "video_resolution= %d\n", port->video_resolution); + dprintk(DBGLVL_ENC, "v.ucVideoBitRateMode = %d\n", + v.ucVideoBitRateMode); + dprintk(DBGLVL_ENC, "v.dwVideoBitRate = %d\n", + v.dwVideoBitRate); + dprintk(DBGLVL_ENC, "v.dwVideoBitRatePeak = %d\n", + v.dwVideoBitRatePeak); + dprintk(DBGLVL_ENC, "a.ucVideoBitRateMode = %d\n", + a.ucAudioBitRateMode); + dprintk(DBGLVL_ENC, "a.dwVideoBitRate = %d\n", + a.dwAudioBitRate); + dprintk(DBGLVL_ENC, "a.dwVideoBitRatePeak = %d\n", + a.dwAudioBitRatePeak); + dprintk(DBGLVL_ENC, "aspect.width / height = %d:%d\n", + ar.width, ar.height); + + return ret; +} + +int saa7164_api_set_aspect_ratio(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct tmComResEncVideoInputAspectRatio ar; + int ret; + + dprintk(DBGLVL_ENC, "%s(%d)\n", __func__, + port->encoder_params.ctl_aspect); + + switch (port->encoder_params.ctl_aspect) { + case V4L2_MPEG_VIDEO_ASPECT_1x1: + ar.width = 1; + ar.height = 1; + break; + case V4L2_MPEG_VIDEO_ASPECT_4x3: + ar.width = 4; + ar.height = 3; + break; + case V4L2_MPEG_VIDEO_ASPECT_16x9: + ar.width = 16; + ar.height = 9; + break; + case V4L2_MPEG_VIDEO_ASPECT_221x100: + ar.width = 221; + ar.height = 100; + break; + default: + BUG(); + } + + dprintk(DBGLVL_ENC, "%s(%d) now %d:%d\n", __func__, + port->encoder_params.ctl_aspect, + ar.width, ar.height); + + /* Aspect Ratio */ + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR, + EU_VIDEO_INPUT_ASPECT_CONTROL, + sizeof(struct tmComResEncVideoInputAspectRatio), &ar); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + return ret; +} + +int saa7164_api_set_usercontrol(struct saa7164_port *port, u8 ctl) +{ + struct saa7164_dev *dev = port->dev; + int ret; + u16 val; + + if (ctl == PU_BRIGHTNESS_CONTROL) + val = port->ctl_brightness; + else + if (ctl == PU_CONTRAST_CONTROL) + val = port->ctl_contrast; + else + if (ctl == PU_HUE_CONTROL) + val = port->ctl_hue; + else + if (ctl == PU_SATURATION_CONTROL) + val = port->ctl_saturation; + else + if (ctl == PU_SHARPNESS_CONTROL) + val = port->ctl_sharpness; + else + return -EINVAL; + + dprintk(DBGLVL_ENC, "%s() unitid=0x%x ctl=%d, val=%d\n", + __func__, port->encunit.vsourceid, ctl, val); + + ret = saa7164_cmd_send(port->dev, port->encunit.vsourceid, SET_CUR, + ctl, sizeof(u16), &val); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + return ret; +} + +int saa7164_api_get_usercontrol(struct saa7164_port *port, u8 ctl) +{ + struct saa7164_dev *dev = port->dev; + int ret; + u16 val; + + ret = saa7164_cmd_send(port->dev, port->encunit.vsourceid, GET_CUR, + ctl, sizeof(u16), &val); + if (ret != SAA_OK) { + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + return ret; + } + + dprintk(DBGLVL_ENC, "%s() ctl=%d, val=%d\n", + __func__, ctl, val); + + if (ctl == PU_BRIGHTNESS_CONTROL) + port->ctl_brightness = val; + else + if (ctl == PU_CONTRAST_CONTROL) + port->ctl_contrast = val; + else + if (ctl == PU_HUE_CONTROL) + port->ctl_hue = val; + else + if (ctl == PU_SATURATION_CONTROL) + port->ctl_saturation = val; + else + if (ctl == PU_SHARPNESS_CONTROL) + port->ctl_sharpness = val; + + return ret; +} + +int saa7164_api_set_videomux(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + u8 inputs[] = { 1, 2, 2, 2, 5, 5, 5 }; + int ret; + + dprintk(DBGLVL_ENC, "%s() v_mux=%d a_mux=%d\n", + __func__, port->mux_input, inputs[port->mux_input - 1]); + + /* Audio Mute */ + ret = saa7164_api_audio_mute(port, 1); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Video Mux */ + ret = saa7164_cmd_send(port->dev, port->vidproc.sourceid, SET_CUR, + SU_INPUT_SELECT_CONTROL, sizeof(u8), &port->mux_input); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Audio Mux */ + ret = saa7164_cmd_send(port->dev, port->audfeat.sourceid, SET_CUR, + SU_INPUT_SELECT_CONTROL, sizeof(u8), + &inputs[port->mux_input - 1]); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Audio UnMute */ + ret = saa7164_api_audio_mute(port, 0); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + return ret; +} + +int saa7164_api_audio_mute(struct saa7164_port *port, int mute) +{ + struct saa7164_dev *dev = port->dev; + u8 v = mute; + int ret; + + dprintk(DBGLVL_API, "%s(%d)\n", __func__, mute); + + ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, SET_CUR, + MUTE_CONTROL, sizeof(u8), &v); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + return ret; +} + +/* 0 = silence, 0xff = full */ +int saa7164_api_set_audio_volume(struct saa7164_port *port, s8 level) +{ + struct saa7164_dev *dev = port->dev; + s16 v, min, max; + int ret; + + dprintk(DBGLVL_API, "%s(%d)\n", __func__, level); + + /* Obtain the min/max ranges */ + ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, GET_MIN, + VOLUME_CONTROL, sizeof(u16), &min); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, GET_MAX, + VOLUME_CONTROL, sizeof(u16), &max); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, GET_CUR, + (0x01 << 8) | VOLUME_CONTROL, sizeof(u16), &v); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + dprintk(DBGLVL_API, "%s(%d) min=%d max=%d cur=%d\n", __func__, + level, min, max, v); + + v = level; + if (v < min) + v = min; + if (v > max) + v = max; + + /* Left */ + ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, SET_CUR, + (0x01 << 8) | VOLUME_CONTROL, sizeof(s16), &v); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Right */ + ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, SET_CUR, + (0x02 << 8) | VOLUME_CONTROL, sizeof(s16), &v); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, GET_CUR, + (0x01 << 8) | VOLUME_CONTROL, sizeof(u16), &v); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + dprintk(DBGLVL_API, "%s(%d) min=%d max=%d cur=%d\n", __func__, + level, min, max, v); + + return ret; +} + +int saa7164_api_set_audio_std(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct tmComResAudioDefaults lvl; + struct tmComResTunerStandard tvaudio; + int ret; + + dprintk(DBGLVL_API, "%s()\n", __func__); + + /* Establish default levels */ + lvl.ucDecoderLevel = TMHW_LEV_ADJ_DECLEV_DEFAULT; + lvl.ucDecoderFM_Level = TMHW_LEV_ADJ_DECLEV_DEFAULT; + lvl.ucMonoLevel = TMHW_LEV_ADJ_MONOLEV_DEFAULT; + lvl.ucNICAM_Level = TMHW_LEV_ADJ_NICLEV_DEFAULT; + lvl.ucSAP_Level = TMHW_LEV_ADJ_SAPLEV_DEFAULT; + lvl.ucADC_Level = TMHW_LEV_ADJ_ADCLEV_DEFAULT; + ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, SET_CUR, + AUDIO_DEFAULT_CONTROL, sizeof(struct tmComResAudioDefaults), + &lvl); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Manually select the appropriate TV audio standard */ + if (port->encodernorm.id & V4L2_STD_NTSC) { + tvaudio.std = TU_STANDARD_NTSC_M; + tvaudio.country = 1; + } else { + tvaudio.std = TU_STANDARD_PAL_I; + tvaudio.country = 44; + } + + ret = saa7164_cmd_send(port->dev, port->tunerunit.unitid, SET_CUR, + TU_STANDARD_CONTROL, sizeof(tvaudio), &tvaudio); + if (ret != SAA_OK) + printk(KERN_ERR "%s() TU_STANDARD_CONTROL error, ret = 0x%x\n", + __func__, ret); + return ret; +} + +int saa7164_api_set_audio_detection(struct saa7164_port *port, int autodetect) +{ + struct saa7164_dev *dev = port->dev; + struct tmComResTunerStandardAuto p; + int ret; + + dprintk(DBGLVL_API, "%s(%d)\n", __func__, autodetect); + + /* Disable TV Audio autodetect if not already set (buggy) */ + if (autodetect) + p.mode = TU_STANDARD_AUTO; + else + p.mode = TU_STANDARD_MANUAL; + ret = saa7164_cmd_send(port->dev, port->tunerunit.unitid, SET_CUR, + TU_STANDARD_AUTO_CONTROL, sizeof(p), &p); + if (ret != SAA_OK) + printk(KERN_ERR + "%s() TU_STANDARD_AUTO_CONTROL error, ret = 0x%x\n", + __func__, ret); + + return ret; +} + +int saa7164_api_get_videomux(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + int ret; + + ret = saa7164_cmd_send(port->dev, port->vidproc.sourceid, GET_CUR, + SU_INPUT_SELECT_CONTROL, sizeof(u8), &port->mux_input); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + dprintk(DBGLVL_ENC, "%s() v_mux=%d\n", + __func__, port->mux_input); + + return ret; +} + +static int saa7164_api_set_dif(struct saa7164_port *port, u8 reg, u8 val) +{ + struct saa7164_dev *dev = port->dev; + + u16 len = 0; + u8 buf[256]; + int ret; + u8 mas; + + dprintk(DBGLVL_API, "%s(nr=%d type=%d val=%x)\n", __func__, + port->nr, port->type, val); + + if (port->nr == 0) + mas = 0xd0; + else + mas = 0xe0; + + memset(buf, 0, sizeof(buf)); + + buf[0x00] = 0x04; + buf[0x01] = 0x00; + buf[0x02] = 0x00; + buf[0x03] = 0x00; + + buf[0x04] = 0x04; + buf[0x05] = 0x00; + buf[0x06] = 0x00; + buf[0x07] = 0x00; + + buf[0x08] = reg; + buf[0x09] = 0x26; + buf[0x0a] = mas; + buf[0x0b] = 0xb0; + + buf[0x0c] = val; + buf[0x0d] = 0x00; + buf[0x0e] = 0x00; + buf[0x0f] = 0x00; + + ret = saa7164_cmd_send(dev, port->ifunit.unitid, GET_LEN, + EXU_REGISTER_ACCESS_CONTROL, sizeof(len), &len); + if (ret != SAA_OK) { + printk(KERN_ERR "%s() error, ret(1) = 0x%x\n", __func__, ret); + return -EIO; + } + + ret = saa7164_cmd_send(dev, port->ifunit.unitid, SET_CUR, + EXU_REGISTER_ACCESS_CONTROL, len, &buf); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret(2) = 0x%x\n", __func__, ret); +#if 0 + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, buf, 16, + false); +#endif + return ret == SAA_OK ? 0 : -EIO; +} + +/* Disable the IF block AGC controls */ +int saa7164_api_configure_dif(struct saa7164_port *port, u32 std) +{ + struct saa7164_dev *dev = port->dev; + u8 agc_disable; + + dprintk(DBGLVL_API, "%s(nr=%d, 0x%x)\n", __func__, port->nr, std); + + if (std & V4L2_STD_NTSC) { + dprintk(DBGLVL_API, " NTSC\n"); + saa7164_api_set_dif(port, 0x00, 0x01); /* Video Standard */ + agc_disable = 0; + } else if (std & V4L2_STD_PAL_I) { + dprintk(DBGLVL_API, " PAL-I\n"); + saa7164_api_set_dif(port, 0x00, 0x08); /* Video Standard */ + agc_disable = 0; + } else if (std & V4L2_STD_PAL_M) { + dprintk(DBGLVL_API, " PAL-M\n"); + saa7164_api_set_dif(port, 0x00, 0x01); /* Video Standard */ + agc_disable = 0; + } else if (std & V4L2_STD_PAL_N) { + dprintk(DBGLVL_API, " PAL-N\n"); + saa7164_api_set_dif(port, 0x00, 0x01); /* Video Standard */ + agc_disable = 0; + } else if (std & V4L2_STD_PAL_Nc) { + dprintk(DBGLVL_API, " PAL-Nc\n"); + saa7164_api_set_dif(port, 0x00, 0x01); /* Video Standard */ + agc_disable = 0; + } else if (std & V4L2_STD_PAL_B) { + dprintk(DBGLVL_API, " PAL-B\n"); + saa7164_api_set_dif(port, 0x00, 0x02); /* Video Standard */ + agc_disable = 0; + } else if (std & V4L2_STD_PAL_DK) { + dprintk(DBGLVL_API, " PAL-DK\n"); + saa7164_api_set_dif(port, 0x00, 0x10); /* Video Standard */ + agc_disable = 0; + } else if (std & V4L2_STD_SECAM_L) { + dprintk(DBGLVL_API, " SECAM-L\n"); + saa7164_api_set_dif(port, 0x00, 0x20); /* Video Standard */ + agc_disable = 0; + } else { + /* Unknown standard, assume DTV */ + dprintk(DBGLVL_API, " Unknown (assuming DTV)\n"); + /* Undefinded Video Standard */ + saa7164_api_set_dif(port, 0x00, 0x80); + agc_disable = 1; + } + + saa7164_api_set_dif(port, 0x48, 0xa0); /* AGC Functions 1 */ + saa7164_api_set_dif(port, 0xc0, agc_disable); /* AGC Output Disable */ + saa7164_api_set_dif(port, 0x7c, 0x04); /* CVBS EQ */ + saa7164_api_set_dif(port, 0x04, 0x01); /* Active */ + msleep(100); + saa7164_api_set_dif(port, 0x04, 0x00); /* Active (again) */ + msleep(100); + + return 0; +} + +/* Ensure the dif is in the correct state for the operating mode + * (analog / dtv). We only configure the diff through the analog encoder + * so when we're in digital mode we need to find the appropriate encoder + * and use it to configure the DIF. + */ +int saa7164_api_initialize_dif(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct saa7164_port *p = NULL; + int ret = -EINVAL; + u32 std = 0; + + dprintk(DBGLVL_API, "%s(nr=%d type=%d)\n", __func__, + port->nr, port->type); + + if (port->type == SAA7164_MPEG_ENCODER) { + /* Pick any analog standard to init the diff. + * we'll come back during encoder_init' + * and set the correct standard if required. + */ + std = V4L2_STD_NTSC; + } else + if (port->type == SAA7164_MPEG_DVB) { + if (port->nr == SAA7164_PORT_TS1) + p = &dev->ports[SAA7164_PORT_ENC1]; + else + p = &dev->ports[SAA7164_PORT_ENC2]; + } else + if (port->type == SAA7164_MPEG_VBI) { + std = V4L2_STD_NTSC; + if (port->nr == SAA7164_PORT_VBI1) + p = &dev->ports[SAA7164_PORT_ENC1]; + else + p = &dev->ports[SAA7164_PORT_ENC2]; + } else + BUG(); + + if (p) + ret = saa7164_api_configure_dif(p, std); + + return ret; +} + +int saa7164_api_transition_port(struct saa7164_port *port, u8 mode) +{ + struct saa7164_dev *dev = port->dev; + + int ret; + + dprintk(DBGLVL_API, "%s(nr=%d unitid=0x%x,%d)\n", + __func__, port->nr, port->hwcfg.unitid, mode); + + ret = saa7164_cmd_send(port->dev, port->hwcfg.unitid, SET_CUR, + SAA_STATE_CONTROL, sizeof(mode), &mode); + if (ret != SAA_OK) + printk(KERN_ERR "%s(portnr %d unitid 0x%x) error, ret = 0x%x\n", + __func__, port->nr, port->hwcfg.unitid, ret); + + return ret; +} + +int saa7164_api_get_fw_version(struct saa7164_dev *dev, u32 *version) +{ + int ret; + + ret = saa7164_cmd_send(dev, 0, GET_CUR, + GET_FW_VERSION_CONTROL, sizeof(u32), version); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + return ret; +} + +int saa7164_api_read_eeprom(struct saa7164_dev *dev, u8 *buf, int buflen) +{ + u8 reg[] = { 0x0f, 0x00 }; + + if (buflen < 128) + return -ENOMEM; + + /* Assumption: Hauppauge eeprom is at 0xa0 on bus 0 */ + /* TODO: Pull the details from the boards struct */ + return saa7164_api_i2c_read(&dev->i2c_bus[0], 0xa0 >> 1, sizeof(reg), + ®[0], 128, buf); +} + +static int saa7164_api_configure_port_vbi(struct saa7164_dev *dev, + struct saa7164_port *port) +{ + struct tmComResVBIFormatDescrHeader *fmt = &port->vbi_fmt_ntsc; + + dprintk(DBGLVL_API, " bFormatIndex = 0x%x\n", fmt->bFormatIndex); + dprintk(DBGLVL_API, " VideoStandard = 0x%x\n", fmt->VideoStandard); + dprintk(DBGLVL_API, " StartLine = %d\n", fmt->StartLine); + dprintk(DBGLVL_API, " EndLine = %d\n", fmt->EndLine); + dprintk(DBGLVL_API, " FieldRate = %d\n", fmt->FieldRate); + dprintk(DBGLVL_API, " bNumLines = %d\n", fmt->bNumLines); + + /* Cache the hardware configuration in the port */ + + port->bufcounter = port->hwcfg.BARLocation; + port->pitch = port->hwcfg.BARLocation + (2 * sizeof(u32)); + port->bufsize = port->hwcfg.BARLocation + (3 * sizeof(u32)); + port->bufoffset = port->hwcfg.BARLocation + (4 * sizeof(u32)); + port->bufptr32l = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount) + sizeof(u32); + port->bufptr32h = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount); + port->bufptr64 = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount); + dprintk(DBGLVL_API, " = port->hwcfg.BARLocation = 0x%x\n", + port->hwcfg.BARLocation); + + dprintk(DBGLVL_API, " = VS_FORMAT_VBI (becomes dev->en[%d])\n", + port->nr); + + return 0; +} + +static int +saa7164_api_configure_port_mpeg2ts(struct saa7164_dev *dev, + struct saa7164_port *port, + struct tmComResTSFormatDescrHeader *tsfmt) +{ + dprintk(DBGLVL_API, " bFormatIndex = 0x%x\n", tsfmt->bFormatIndex); + dprintk(DBGLVL_API, " bDataOffset = 0x%x\n", tsfmt->bDataOffset); + dprintk(DBGLVL_API, " bPacketLength= 0x%x\n", tsfmt->bPacketLength); + dprintk(DBGLVL_API, " bStrideLength= 0x%x\n", tsfmt->bStrideLength); + dprintk(DBGLVL_API, " bguid = (....)\n"); + + /* Cache the hardware configuration in the port */ + + port->bufcounter = port->hwcfg.BARLocation; + port->pitch = port->hwcfg.BARLocation + (2 * sizeof(u32)); + port->bufsize = port->hwcfg.BARLocation + (3 * sizeof(u32)); + port->bufoffset = port->hwcfg.BARLocation + (4 * sizeof(u32)); + port->bufptr32l = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount) + sizeof(u32); + port->bufptr32h = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount); + port->bufptr64 = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount); + dprintk(DBGLVL_API, " = port->hwcfg.BARLocation = 0x%x\n", + port->hwcfg.BARLocation); + + dprintk(DBGLVL_API, " = VS_FORMAT_MPEGTS (becomes dev->ts[%d])\n", + port->nr); + + return 0; +} + +static int +saa7164_api_configure_port_mpeg2ps(struct saa7164_dev *dev, + struct saa7164_port *port, + struct tmComResPSFormatDescrHeader *fmt) +{ + dprintk(DBGLVL_API, " bFormatIndex = 0x%x\n", fmt->bFormatIndex); + dprintk(DBGLVL_API, " wPacketLength= 0x%x\n", fmt->wPacketLength); + dprintk(DBGLVL_API, " wPackLength= 0x%x\n", fmt->wPackLength); + dprintk(DBGLVL_API, " bPackDataType= 0x%x\n", fmt->bPackDataType); + + /* Cache the hardware configuration in the port */ + /* TODO: CHECK THIS in the port config */ + port->bufcounter = port->hwcfg.BARLocation; + port->pitch = port->hwcfg.BARLocation + (2 * sizeof(u32)); + port->bufsize = port->hwcfg.BARLocation + (3 * sizeof(u32)); + port->bufoffset = port->hwcfg.BARLocation + (4 * sizeof(u32)); + port->bufptr32l = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount) + sizeof(u32); + port->bufptr32h = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount); + port->bufptr64 = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount); + dprintk(DBGLVL_API, " = port->hwcfg.BARLocation = 0x%x\n", + port->hwcfg.BARLocation); + + dprintk(DBGLVL_API, " = VS_FORMAT_MPEGPS (becomes dev->enc[%d])\n", + port->nr); + + return 0; +} + +static int saa7164_api_dump_subdevs(struct saa7164_dev *dev, u8 *buf, int len) +{ + struct saa7164_port *tsport = NULL; + struct saa7164_port *encport = NULL; + struct saa7164_port *vbiport = NULL; + u32 idx, next_offset; + int i; + struct tmComResDescrHeader *hdr, *t; + struct tmComResExtDevDescrHeader *exthdr; + struct tmComResPathDescrHeader *pathhdr; + struct tmComResAntTermDescrHeader *anttermhdr; + struct tmComResTunerDescrHeader *tunerunithdr; + struct tmComResDMATermDescrHeader *vcoutputtermhdr; + struct tmComResTSFormatDescrHeader *tsfmt; + struct tmComResPSFormatDescrHeader *psfmt; + struct tmComResSelDescrHeader *psel; + struct tmComResProcDescrHeader *pdh; + struct tmComResAFeatureDescrHeader *afd; + struct tmComResEncoderDescrHeader *edh; + struct tmComResVBIFormatDescrHeader *vbifmt; + u32 currpath = 0; + + dprintk(DBGLVL_API, + "%s(?,?,%d) sizeof(struct tmComResDescrHeader) = %d bytes\n", + __func__, len, (u32)sizeof(struct tmComResDescrHeader)); + + for (idx = 0; idx < (len - sizeof(struct tmComResDescrHeader));) { + + hdr = (struct tmComResDescrHeader *)(buf + idx); + + if (hdr->type != CS_INTERFACE) + return SAA_ERR_NOT_SUPPORTED; + + dprintk(DBGLVL_API, "@ 0x%x =\n", idx); + switch (hdr->subtype) { + case GENERAL_REQUEST: + dprintk(DBGLVL_API, " GENERAL_REQUEST\n"); + break; + case VC_TUNER_PATH: + dprintk(DBGLVL_API, " VC_TUNER_PATH\n"); + pathhdr = (struct tmComResPathDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " pathid = 0x%x\n", + pathhdr->pathid); + currpath = pathhdr->pathid; + break; + case VC_INPUT_TERMINAL: + dprintk(DBGLVL_API, " VC_INPUT_TERMINAL\n"); + anttermhdr = + (struct tmComResAntTermDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " terminalid = 0x%x\n", + anttermhdr->terminalid); + dprintk(DBGLVL_API, " terminaltype = 0x%x\n", + anttermhdr->terminaltype); + switch (anttermhdr->terminaltype) { + case ITT_ANTENNA: + dprintk(DBGLVL_API, " = ITT_ANTENNA\n"); + break; + case LINE_CONNECTOR: + dprintk(DBGLVL_API, " = LINE_CONNECTOR\n"); + break; + case SPDIF_CONNECTOR: + dprintk(DBGLVL_API, " = SPDIF_CONNECTOR\n"); + break; + case COMPOSITE_CONNECTOR: + dprintk(DBGLVL_API, + " = COMPOSITE_CONNECTOR\n"); + break; + case SVIDEO_CONNECTOR: + dprintk(DBGLVL_API, " = SVIDEO_CONNECTOR\n"); + break; + case COMPONENT_CONNECTOR: + dprintk(DBGLVL_API, + " = COMPONENT_CONNECTOR\n"); + break; + case STANDARD_DMA: + dprintk(DBGLVL_API, " = STANDARD_DMA\n"); + break; + default: + dprintk(DBGLVL_API, " = undefined (0x%x)\n", + anttermhdr->terminaltype); + } + dprintk(DBGLVL_API, " assocterminal= 0x%x\n", + anttermhdr->assocterminal); + dprintk(DBGLVL_API, " iterminal = 0x%x\n", + anttermhdr->iterminal); + dprintk(DBGLVL_API, " controlsize = 0x%x\n", + anttermhdr->controlsize); + break; + case VC_OUTPUT_TERMINAL: + dprintk(DBGLVL_API, " VC_OUTPUT_TERMINAL\n"); + vcoutputtermhdr = + (struct tmComResDMATermDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " unitid = 0x%x\n", + vcoutputtermhdr->unitid); + dprintk(DBGLVL_API, " terminaltype = 0x%x\n", + vcoutputtermhdr->terminaltype); + switch (vcoutputtermhdr->terminaltype) { + case ITT_ANTENNA: + dprintk(DBGLVL_API, " = ITT_ANTENNA\n"); + break; + case LINE_CONNECTOR: + dprintk(DBGLVL_API, " = LINE_CONNECTOR\n"); + break; + case SPDIF_CONNECTOR: + dprintk(DBGLVL_API, " = SPDIF_CONNECTOR\n"); + break; + case COMPOSITE_CONNECTOR: + dprintk(DBGLVL_API, + " = COMPOSITE_CONNECTOR\n"); + break; + case SVIDEO_CONNECTOR: + dprintk(DBGLVL_API, " = SVIDEO_CONNECTOR\n"); + break; + case COMPONENT_CONNECTOR: + dprintk(DBGLVL_API, + " = COMPONENT_CONNECTOR\n"); + break; + case STANDARD_DMA: + dprintk(DBGLVL_API, " = STANDARD_DMA\n"); + break; + default: + dprintk(DBGLVL_API, " = undefined (0x%x)\n", + vcoutputtermhdr->terminaltype); + } + dprintk(DBGLVL_API, " assocterminal= 0x%x\n", + vcoutputtermhdr->assocterminal); + dprintk(DBGLVL_API, " sourceid = 0x%x\n", + vcoutputtermhdr->sourceid); + dprintk(DBGLVL_API, " iterminal = 0x%x\n", + vcoutputtermhdr->iterminal); + dprintk(DBGLVL_API, " BARLocation = 0x%x\n", + vcoutputtermhdr->BARLocation); + dprintk(DBGLVL_API, " flags = 0x%x\n", + vcoutputtermhdr->flags); + dprintk(DBGLVL_API, " interruptid = 0x%x\n", + vcoutputtermhdr->interruptid); + dprintk(DBGLVL_API, " buffercount = 0x%x\n", + vcoutputtermhdr->buffercount); + dprintk(DBGLVL_API, " metadatasize = 0x%x\n", + vcoutputtermhdr->metadatasize); + dprintk(DBGLVL_API, " controlsize = 0x%x\n", + vcoutputtermhdr->controlsize); + dprintk(DBGLVL_API, " numformats = 0x%x\n", + vcoutputtermhdr->numformats); + + next_offset = idx + (vcoutputtermhdr->len); + for (i = 0; i < vcoutputtermhdr->numformats; i++) { + t = (struct tmComResDescrHeader *) + (buf + next_offset); + switch (t->subtype) { + case VS_FORMAT_MPEG2TS: + tsfmt = + (struct tmComResTSFormatDescrHeader *)t; + if (currpath == 1) + tsport = &dev->ports[SAA7164_PORT_TS1]; + else + tsport = &dev->ports[SAA7164_PORT_TS2]; + memcpy(&tsport->hwcfg, vcoutputtermhdr, + sizeof(*vcoutputtermhdr)); + saa7164_api_configure_port_mpeg2ts(dev, + tsport, tsfmt); + break; + case VS_FORMAT_MPEG2PS: + psfmt = + (struct tmComResPSFormatDescrHeader *)t; + if (currpath == 1) + encport = &dev->ports[SAA7164_PORT_ENC1]; + else + encport = &dev->ports[SAA7164_PORT_ENC2]; + memcpy(&encport->hwcfg, vcoutputtermhdr, + sizeof(*vcoutputtermhdr)); + saa7164_api_configure_port_mpeg2ps(dev, + encport, psfmt); + break; + case VS_FORMAT_VBI: + vbifmt = + (struct tmComResVBIFormatDescrHeader *)t; + if (currpath == 1) + vbiport = &dev->ports[SAA7164_PORT_VBI1]; + else + vbiport = &dev->ports[SAA7164_PORT_VBI2]; + memcpy(&vbiport->hwcfg, vcoutputtermhdr, + sizeof(*vcoutputtermhdr)); + memcpy(&vbiport->vbi_fmt_ntsc, vbifmt, + sizeof(*vbifmt)); + saa7164_api_configure_port_vbi(dev, + vbiport); + break; + case VS_FORMAT_RDS: + dprintk(DBGLVL_API, + " = VS_FORMAT_RDS\n"); + break; + case VS_FORMAT_UNCOMPRESSED: + dprintk(DBGLVL_API, + " = VS_FORMAT_UNCOMPRESSED\n"); + break; + case VS_FORMAT_TYPE: + dprintk(DBGLVL_API, + " = VS_FORMAT_TYPE\n"); + break; + default: + dprintk(DBGLVL_API, + " = undefined (0x%x)\n", + t->subtype); + } + next_offset += t->len; + } + + break; + case TUNER_UNIT: + dprintk(DBGLVL_API, " TUNER_UNIT\n"); + tunerunithdr = + (struct tmComResTunerDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " unitid = 0x%x\n", + tunerunithdr->unitid); + dprintk(DBGLVL_API, " sourceid = 0x%x\n", + tunerunithdr->sourceid); + dprintk(DBGLVL_API, " iunit = 0x%x\n", + tunerunithdr->iunit); + dprintk(DBGLVL_API, " tuningstandards = 0x%x\n", + tunerunithdr->tuningstandards); + dprintk(DBGLVL_API, " controlsize = 0x%x\n", + tunerunithdr->controlsize); + dprintk(DBGLVL_API, " controls = 0x%x\n", + tunerunithdr->controls); + + if (tunerunithdr->unitid == tunerunithdr->iunit) { + if (currpath == 1) + encport = &dev->ports[SAA7164_PORT_ENC1]; + else + encport = &dev->ports[SAA7164_PORT_ENC2]; + memcpy(&encport->tunerunit, tunerunithdr, + sizeof(struct tmComResTunerDescrHeader)); + dprintk(DBGLVL_API, + " (becomes dev->enc[%d] tuner)\n", + encport->nr); + } + break; + case VC_SELECTOR_UNIT: + psel = (struct tmComResSelDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " VC_SELECTOR_UNIT\n"); + dprintk(DBGLVL_API, " unitid = 0x%x\n", + psel->unitid); + dprintk(DBGLVL_API, " nrinpins = 0x%x\n", + psel->nrinpins); + dprintk(DBGLVL_API, " sourceid = 0x%x\n", + psel->sourceid); + break; + case VC_PROCESSING_UNIT: + pdh = (struct tmComResProcDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " VC_PROCESSING_UNIT\n"); + dprintk(DBGLVL_API, " unitid = 0x%x\n", + pdh->unitid); + dprintk(DBGLVL_API, " sourceid = 0x%x\n", + pdh->sourceid); + dprintk(DBGLVL_API, " controlsize = 0x%x\n", + pdh->controlsize); + if (pdh->controlsize == 0x04) { + if (currpath == 1) + encport = &dev->ports[SAA7164_PORT_ENC1]; + else + encport = &dev->ports[SAA7164_PORT_ENC2]; + memcpy(&encport->vidproc, pdh, + sizeof(struct tmComResProcDescrHeader)); + dprintk(DBGLVL_API, " (becomes dev->enc[%d])\n", + encport->nr); + } + break; + case FEATURE_UNIT: + afd = (struct tmComResAFeatureDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " FEATURE_UNIT\n"); + dprintk(DBGLVL_API, " unitid = 0x%x\n", + afd->unitid); + dprintk(DBGLVL_API, " sourceid = 0x%x\n", + afd->sourceid); + dprintk(DBGLVL_API, " controlsize = 0x%x\n", + afd->controlsize); + if (currpath == 1) + encport = &dev->ports[SAA7164_PORT_ENC1]; + else + encport = &dev->ports[SAA7164_PORT_ENC2]; + memcpy(&encport->audfeat, afd, + sizeof(struct tmComResAFeatureDescrHeader)); + dprintk(DBGLVL_API, " (becomes dev->enc[%d])\n", + encport->nr); + break; + case ENCODER_UNIT: + edh = (struct tmComResEncoderDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " ENCODER_UNIT\n"); + dprintk(DBGLVL_API, " subtype = 0x%x\n", edh->subtype); + dprintk(DBGLVL_API, " unitid = 0x%x\n", edh->unitid); + dprintk(DBGLVL_API, " vsourceid = 0x%x\n", + edh->vsourceid); + dprintk(DBGLVL_API, " asourceid = 0x%x\n", + edh->asourceid); + dprintk(DBGLVL_API, " iunit = 0x%x\n", edh->iunit); + if (edh->iunit == edh->unitid) { + if (currpath == 1) + encport = &dev->ports[SAA7164_PORT_ENC1]; + else + encport = &dev->ports[SAA7164_PORT_ENC2]; + memcpy(&encport->encunit, edh, + sizeof(struct tmComResEncoderDescrHeader)); + dprintk(DBGLVL_API, + " (becomes dev->enc[%d])\n", + encport->nr); + } + break; + case EXTENSION_UNIT: + dprintk(DBGLVL_API, " EXTENSION_UNIT\n"); + exthdr = (struct tmComResExtDevDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " unitid = 0x%x\n", + exthdr->unitid); + dprintk(DBGLVL_API, " deviceid = 0x%x\n", + exthdr->deviceid); + dprintk(DBGLVL_API, " devicetype = 0x%x\n", + exthdr->devicetype); + if (exthdr->devicetype & 0x1) + dprintk(DBGLVL_API, " = Decoder Device\n"); + if (exthdr->devicetype & 0x2) + dprintk(DBGLVL_API, " = GPIO Source\n"); + if (exthdr->devicetype & 0x4) + dprintk(DBGLVL_API, " = Video Decoder\n"); + if (exthdr->devicetype & 0x8) + dprintk(DBGLVL_API, " = Audio Decoder\n"); + if (exthdr->devicetype & 0x20) + dprintk(DBGLVL_API, " = Crossbar\n"); + if (exthdr->devicetype & 0x40) + dprintk(DBGLVL_API, " = Tuner\n"); + if (exthdr->devicetype & 0x80) + dprintk(DBGLVL_API, " = IF PLL\n"); + if (exthdr->devicetype & 0x100) + dprintk(DBGLVL_API, " = Demodulator\n"); + if (exthdr->devicetype & 0x200) + dprintk(DBGLVL_API, " = RDS Decoder\n"); + if (exthdr->devicetype & 0x400) + dprintk(DBGLVL_API, " = Encoder\n"); + if (exthdr->devicetype & 0x800) + dprintk(DBGLVL_API, " = IR Decoder\n"); + if (exthdr->devicetype & 0x1000) + dprintk(DBGLVL_API, " = EEPROM\n"); + if (exthdr->devicetype & 0x2000) + dprintk(DBGLVL_API, + " = VBI Decoder\n"); + if (exthdr->devicetype & 0x10000) + dprintk(DBGLVL_API, + " = Streaming Device\n"); + if (exthdr->devicetype & 0x20000) + dprintk(DBGLVL_API, + " = DRM Device\n"); + if (exthdr->devicetype & 0x40000000) + dprintk(DBGLVL_API, + " = Generic Device\n"); + if (exthdr->devicetype & 0x80000000) + dprintk(DBGLVL_API, + " = Config Space Device\n"); + dprintk(DBGLVL_API, " numgpiopins = 0x%x\n", + exthdr->numgpiopins); + dprintk(DBGLVL_API, " numgpiogroups = 0x%x\n", + exthdr->numgpiogroups); + dprintk(DBGLVL_API, " controlsize = 0x%x\n", + exthdr->controlsize); + if (exthdr->devicetype & 0x80) { + if (currpath == 1) + encport = &dev->ports[SAA7164_PORT_ENC1]; + else + encport = &dev->ports[SAA7164_PORT_ENC2]; + memcpy(&encport->ifunit, exthdr, + sizeof(struct tmComResExtDevDescrHeader)); + dprintk(DBGLVL_API, + " (becomes dev->enc[%d])\n", + encport->nr); + } + break; + case PVC_INFRARED_UNIT: + dprintk(DBGLVL_API, " PVC_INFRARED_UNIT\n"); + break; + case DRM_UNIT: + dprintk(DBGLVL_API, " DRM_UNIT\n"); + break; + default: + dprintk(DBGLVL_API, "default %d\n", hdr->subtype); + } + + dprintk(DBGLVL_API, " 1.%x\n", hdr->len); + dprintk(DBGLVL_API, " 2.%x\n", hdr->type); + dprintk(DBGLVL_API, " 3.%x\n", hdr->subtype); + dprintk(DBGLVL_API, " 4.%x\n", hdr->unitid); + + idx += hdr->len; + } + + return 0; +} + +int saa7164_api_enum_subdevs(struct saa7164_dev *dev) +{ + int ret; + u32 buflen = 0; + u8 *buf; + + dprintk(DBGLVL_API, "%s()\n", __func__); + + /* Get the total descriptor length */ + ret = saa7164_cmd_send(dev, 0, GET_LEN, + GET_DESCRIPTORS_CONTROL, sizeof(buflen), &buflen); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + dprintk(DBGLVL_API, "%s() total descriptor size = %d bytes.\n", + __func__, buflen); + + /* Allocate enough storage for all of the descs */ + buf = kzalloc(buflen, GFP_KERNEL); + if (!buf) + return SAA_ERR_NO_RESOURCES; + + /* Retrieve them */ + ret = saa7164_cmd_send(dev, 0, GET_CUR, + GET_DESCRIPTORS_CONTROL, buflen, buf); + if (ret != SAA_OK) { + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + goto out; + } + + if (saa_debug & DBGLVL_API) + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, buf, + buflen & ~15, false); + + saa7164_api_dump_subdevs(dev, buf, buflen); + +out: + kfree(buf); + return ret; +} + +int saa7164_api_i2c_read(struct saa7164_i2c *bus, u8 addr, u32 reglen, u8 *reg, + u32 datalen, u8 *data) +{ + struct saa7164_dev *dev = bus->dev; + u16 len = 0; + int unitid; + u8 buf[256]; + int ret; + + dprintk(DBGLVL_API, "%s() addr=%x reglen=%d datalen=%d\n", + __func__, addr, reglen, datalen); + + if (reglen > 4) + return -EIO; + + /* Prepare the send buffer */ + /* Bytes 00-03 source register length + * 04-07 source bytes to read + * 08... register address + */ + memset(buf, 0, sizeof(buf)); + memcpy((buf + 2 * sizeof(u32) + 0), reg, reglen); + *((u32 *)(buf + 0 * sizeof(u32))) = reglen; + *((u32 *)(buf + 1 * sizeof(u32))) = datalen; + + unitid = saa7164_i2caddr_to_unitid(bus, addr); + if (unitid < 0) { + printk(KERN_ERR + "%s() error, cannot translate regaddr 0x%x to unitid\n", + __func__, addr); + return -EIO; + } + + ret = saa7164_cmd_send(bus->dev, unitid, GET_LEN, + EXU_REGISTER_ACCESS_CONTROL, sizeof(len), &len); + if (ret != SAA_OK) { + printk(KERN_ERR "%s() error, ret(1) = 0x%x\n", __func__, ret); + return -EIO; + } + + dprintk(DBGLVL_API, "%s() len = %d bytes\n", __func__, len); + + if (saa_debug & DBGLVL_I2C) + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, buf, + 32, false); + + ret = saa7164_cmd_send(bus->dev, unitid, GET_CUR, + EXU_REGISTER_ACCESS_CONTROL, len, &buf); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret(2) = 0x%x\n", __func__, ret); + else { + if (saa_debug & DBGLVL_I2C) + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, + buf, sizeof(buf), false); + memcpy(data, (buf + 2 * sizeof(u32) + reglen), datalen); + } + + return ret == SAA_OK ? 0 : -EIO; +} + +/* For a given 8 bit i2c address device, write the buffer */ +int saa7164_api_i2c_write(struct saa7164_i2c *bus, u8 addr, u32 datalen, + u8 *data) +{ + struct saa7164_dev *dev = bus->dev; + u16 len = 0; + int unitid; + int reglen; + u8 buf[256]; + int ret; + + dprintk(DBGLVL_API, "%s() addr=0x%2x len=0x%x\n", + __func__, addr, datalen); + + if ((datalen == 0) || (datalen > 232)) + return -EIO; + + memset(buf, 0, sizeof(buf)); + + unitid = saa7164_i2caddr_to_unitid(bus, addr); + if (unitid < 0) { + printk(KERN_ERR + "%s() error, cannot translate regaddr 0x%x to unitid\n", + __func__, addr); + return -EIO; + } + + reglen = saa7164_i2caddr_to_reglen(bus, addr); + if (reglen < 0) { + printk(KERN_ERR + "%s() error, cannot translate regaddr to reglen\n", + __func__); + return -EIO; + } + + ret = saa7164_cmd_send(bus->dev, unitid, GET_LEN, + EXU_REGISTER_ACCESS_CONTROL, sizeof(len), &len); + if (ret != SAA_OK) { + printk(KERN_ERR "%s() error, ret(1) = 0x%x\n", __func__, ret); + return -EIO; + } + + dprintk(DBGLVL_API, "%s() len = %d bytes unitid=0x%x\n", __func__, + len, unitid); + + /* Prepare the send buffer */ + /* Bytes 00-03 dest register length + * 04-07 dest bytes to write + * 08... register address + */ + *((u32 *)(buf + 0 * sizeof(u32))) = reglen; + *((u32 *)(buf + 1 * sizeof(u32))) = datalen - reglen; + memcpy((buf + 2 * sizeof(u32)), data, datalen); + + if (saa_debug & DBGLVL_I2C) + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, + buf, sizeof(buf), false); + + ret = saa7164_cmd_send(bus->dev, unitid, SET_CUR, + EXU_REGISTER_ACCESS_CONTROL, len, &buf); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret(2) = 0x%x\n", __func__, ret); + + return ret == SAA_OK ? 0 : -EIO; +} + +static int saa7164_api_modify_gpio(struct saa7164_dev *dev, u8 unitid, + u8 pin, u8 state) +{ + int ret; + struct tmComResGPIO t; + + dprintk(DBGLVL_API, "%s(0x%x, %d, %d)\n", + __func__, unitid, pin, state); + + if ((pin > 7) || (state > 2)) + return SAA_ERR_BAD_PARAMETER; + + t.pin = pin; + t.state = state; + + ret = saa7164_cmd_send(dev, unitid, SET_CUR, + EXU_GPIO_CONTROL, sizeof(t), &t); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", + __func__, ret); + + return ret; +} + +int saa7164_api_set_gpiobit(struct saa7164_dev *dev, u8 unitid, + u8 pin) +{ + return saa7164_api_modify_gpio(dev, unitid, pin, 1); +} + +int saa7164_api_clear_gpiobit(struct saa7164_dev *dev, u8 unitid, + u8 pin) +{ + return saa7164_api_modify_gpio(dev, unitid, pin, 0); +} + diff --git a/drivers/media/pci/saa7164/saa7164-buffer.c b/drivers/media/pci/saa7164/saa7164-buffer.c new file mode 100644 index 000000000..1f961bdfc --- /dev/null +++ b/drivers/media/pci/saa7164/saa7164-buffer.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the NXP SAA7164 PCIe bridge + * + * Copyright (c) 2010-2015 Steven Toth + */ + +#include + +#include "saa7164.h" + +/* The PCI address space for buffer handling looks like this: + * + * +-u32 wide-------------+ + * | + + * +-u64 wide------------------------------------+ + * + + + * +----------------------+ + * | CurrentBufferPtr + Pointer to current PCI buffer >-+ + * +----------------------+ | + * | Unused + | + * +----------------------+ | + * | Pitch + = 188 (bytes) | + * +----------------------+ | + * | PCI buffer size + = pitch * number of lines (312) | + * +----------------------+ | + * |0| Buf0 Write Offset + | + * +----------------------+ v + * |1| Buf1 Write Offset + | + * +----------------------+ | + * |2| Buf2 Write Offset + | + * +----------------------+ | + * |3| Buf3 Write Offset + | + * +----------------------+ | + * ... More write offsets | + * +---------------------------------------------+ | + * +0| set of ptrs to PCI pagetables + | + * +---------------------------------------------+ | + * +1| set of ptrs to PCI pagetables + <--------+ + * +---------------------------------------------+ + * +2| set of ptrs to PCI pagetables + + * +---------------------------------------------+ + * +3| set of ptrs to PCI pagetables + >--+ + * +---------------------------------------------+ | + * ... More buffer pointers | +----------------+ + * +->| pt[0] TS data | + * | +----------------+ + * | + * | +----------------+ + * +->| pt[1] TS data | + * | +----------------+ + * | etc + */ + +/* Allocate a new buffer structure and associated PCI space in bytes. + * len must be a multiple of sizeof(u64) + */ +struct saa7164_buffer *saa7164_buffer_alloc(struct saa7164_port *port, + u32 len) +{ + struct tmHWStreamParameters *params = &port->hw_streamingparams; + struct saa7164_buffer *buf = NULL; + struct saa7164_dev *dev = port->dev; + int i; + + if ((len == 0) || (len >= 65536) || (len % sizeof(u64))) { + log_warn("%s() SAA_ERR_BAD_PARAMETER\n", __func__); + goto ret; + } + + buf = kzalloc_obj(*buf); + if (!buf) + goto ret; + + buf->idx = -1; + buf->port = port; + buf->flags = SAA7164_BUFFER_FREE; + buf->pos = 0; + buf->actual_size = params->pitch * params->numberoflines; + buf->crc = 0; + /* TODO: arg len is being ignored */ + buf->pci_size = SAA7164_PT_ENTRIES * 0x1000; + buf->pt_size = (SAA7164_PT_ENTRIES * sizeof(u64)) + 0x1000; + + /* Allocate contiguous memory */ + buf->cpu = dma_alloc_coherent(&port->dev->pci->dev, buf->pci_size, + &buf->dma, GFP_KERNEL); + if (!buf->cpu) + goto fail1; + + buf->pt_cpu = dma_alloc_coherent(&port->dev->pci->dev, buf->pt_size, + &buf->pt_dma, GFP_KERNEL); + if (!buf->pt_cpu) + goto fail2; + + /* init the buffers to a known pattern, easier during debugging */ + memset(buf->cpu, 0xff, buf->pci_size); + buf->crc = crc32(0, buf->cpu, buf->actual_size); + memset(buf->pt_cpu, 0xff, buf->pt_size); + + dprintk(DBGLVL_BUF, "%s() allocated buffer @ 0x%p (%d pageptrs)\n", + __func__, buf, params->numpagetables); + dprintk(DBGLVL_BUF, " pci_cpu @ 0x%p dma @ 0x%08lx len = 0x%x\n", + buf->cpu, (long)buf->dma, buf->pci_size); + dprintk(DBGLVL_BUF, " pt_cpu @ 0x%p pt_dma @ 0x%08lx len = 0x%x\n", + buf->pt_cpu, (long)buf->pt_dma, buf->pt_size); + + /* Format the Page Table Entries to point into the data buffer */ + for (i = 0 ; i < params->numpagetables; i++) { + + *(buf->pt_cpu + i) = buf->dma + (i * 0x1000); /* TODO */ + dprintk(DBGLVL_BUF, " pt[%02d] = 0x%p -> 0x%llx\n", + i, buf->pt_cpu, (u64)*(buf->pt_cpu)); + + } + + goto ret; + +fail2: + dma_free_coherent(&port->dev->pci->dev, buf->pci_size, buf->cpu, + buf->dma); +fail1: + kfree(buf); + + buf = NULL; +ret: + return buf; +} + +int saa7164_buffer_dealloc(struct saa7164_buffer *buf) +{ + struct saa7164_dev *dev; + + if (!buf || !buf->port) + return SAA_ERR_BAD_PARAMETER; + dev = buf->port->dev; + + dprintk(DBGLVL_BUF, "%s() deallocating buffer @ 0x%p\n", + __func__, buf); + + if (buf->flags != SAA7164_BUFFER_FREE) + log_warn(" freeing a non-free buffer\n"); + + dma_free_coherent(&dev->pci->dev, buf->pci_size, buf->cpu, buf->dma); + dma_free_coherent(&dev->pci->dev, buf->pt_size, buf->pt_cpu, + buf->pt_dma); + + kfree(buf); + + return SAA_OK; +} + +int saa7164_buffer_zero_offsets(struct saa7164_port *port, int i) +{ + struct saa7164_dev *dev = port->dev; + + if ((i < 0) || (i >= port->hwcfg.buffercount)) + return -EINVAL; + + dprintk(DBGLVL_BUF, "%s(idx = %d)\n", __func__, i); + + saa7164_writel(port->bufoffset + (sizeof(u32) * i), 0); + + return 0; +} + +/* Write a buffer into the hardware */ +int saa7164_buffer_activate(struct saa7164_buffer *buf, int i) +{ + struct saa7164_port *port = buf->port; + struct saa7164_dev *dev = port->dev; + + if ((i < 0) || (i >= port->hwcfg.buffercount)) + return -EINVAL; + + dprintk(DBGLVL_BUF, "%s(idx = %d)\n", __func__, i); + + buf->idx = i; /* Note of which buffer list index position we occupy */ + buf->flags = SAA7164_BUFFER_BUSY; + buf->pos = 0; + + /* TODO: Review this in light of 32v64 assignments */ + saa7164_writel(port->bufoffset + (sizeof(u32) * i), 0); + saa7164_writel(port->bufptr32h + ((sizeof(u32) * 2) * i), buf->pt_dma); + saa7164_writel(port->bufptr32l + ((sizeof(u32) * 2) * i), 0); + + dprintk(DBGLVL_BUF, " buf[%d] offset 0x%llx (0x%x) buf 0x%llx/%llx (0x%x/%x) nr=%d\n", + buf->idx, + (u64)port->bufoffset + (i * sizeof(u32)), + saa7164_readl(port->bufoffset + (sizeof(u32) * i)), + (u64)port->bufptr32h + ((sizeof(u32) * 2) * i), + (u64)port->bufptr32l + ((sizeof(u32) * 2) * i), + saa7164_readl(port->bufptr32h + ((sizeof(u32) * i) * 2)), + saa7164_readl(port->bufptr32l + ((sizeof(u32) * i) * 2)), + buf->idx); + + return 0; +} + +int saa7164_buffer_cfg_port(struct saa7164_port *port) +{ + struct tmHWStreamParameters *params = &port->hw_streamingparams; + struct saa7164_dev *dev = port->dev; + struct saa7164_buffer *buf; + struct list_head *c, *n; + int i = 0; + + dprintk(DBGLVL_BUF, "%s(port=%d)\n", __func__, port->nr); + + saa7164_writel(port->bufcounter, 0); + saa7164_writel(port->pitch, params->pitch); + saa7164_writel(port->bufsize, params->pitch * params->numberoflines); + + dprintk(DBGLVL_BUF, " configured:\n"); + dprintk(DBGLVL_BUF, " lmmio 0x%p\n", dev->lmmio); + dprintk(DBGLVL_BUF, " bufcounter 0x%x = 0x%x\n", port->bufcounter, + saa7164_readl(port->bufcounter)); + + dprintk(DBGLVL_BUF, " pitch 0x%x = %d\n", port->pitch, + saa7164_readl(port->pitch)); + + dprintk(DBGLVL_BUF, " bufsize 0x%x = %d\n", port->bufsize, + saa7164_readl(port->bufsize)); + + dprintk(DBGLVL_BUF, " buffercount = %d\n", port->hwcfg.buffercount); + dprintk(DBGLVL_BUF, " bufoffset = 0x%x\n", port->bufoffset); + dprintk(DBGLVL_BUF, " bufptr32h = 0x%x\n", port->bufptr32h); + dprintk(DBGLVL_BUF, " bufptr32l = 0x%x\n", port->bufptr32l); + + /* Poke the buffers and offsets into PCI space */ + mutex_lock(&port->dmaqueue_lock); + list_for_each_safe(c, n, &port->dmaqueue.list) { + buf = list_entry(c, struct saa7164_buffer, list); + + BUG_ON(buf->flags != SAA7164_BUFFER_FREE); + + /* Place the buffer in the h/w queue */ + saa7164_buffer_activate(buf, i); + + /* Don't exceed the device maximum # bufs */ + BUG_ON(i > port->hwcfg.buffercount); + i++; + + } + mutex_unlock(&port->dmaqueue_lock); + + return 0; +} + +struct saa7164_user_buffer *saa7164_buffer_alloc_user(struct saa7164_dev *dev, + u32 len) +{ + struct saa7164_user_buffer *buf; + + buf = kzalloc_obj(*buf); + if (!buf) + return NULL; + + buf->data = kzalloc(len, GFP_KERNEL); + + if (!buf->data) { + kfree(buf); + return NULL; + } + + buf->actual_size = len; + buf->pos = 0; + buf->crc = 0; + + dprintk(DBGLVL_BUF, "%s() allocated user buffer @ 0x%p\n", + __func__, buf); + + return buf; +} + +void saa7164_buffer_dealloc_user(struct saa7164_user_buffer *buf) +{ + if (!buf) + return; + + kfree(buf->data); + buf->data = NULL; + + kfree(buf); +} diff --git a/drivers/media/pci/saa7164/saa7164-bus.c b/drivers/media/pci/saa7164/saa7164-bus.c new file mode 100644 index 000000000..16739895a --- /dev/null +++ b/drivers/media/pci/saa7164/saa7164-bus.c @@ -0,0 +1,467 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the NXP SAA7164 PCIe bridge + * + * Copyright (c) 2010-2015 Steven Toth + */ + +#include "saa7164.h" + +/* The message bus to/from the firmware is a ring buffer in PCI address + * space. Establish the defaults. + */ +int saa7164_bus_setup(struct saa7164_dev *dev) +{ + struct tmComResBusInfo *b = &dev->bus; + + mutex_init(&b->lock); + + b->Type = TYPE_BUS_PCIe; + b->m_wMaxReqSize = SAA_DEVICE_MAXREQUESTSIZE; + + b->m_pdwSetRing = (u8 __iomem *)(dev->bmmio + + ((u32)dev->busdesc.CommandRing)); + + b->m_dwSizeSetRing = SAA_DEVICE_BUFFERBLOCKSIZE; + + b->m_pdwGetRing = (u8 __iomem *)(dev->bmmio + + ((u32)dev->busdesc.ResponseRing)); + + b->m_dwSizeGetRing = SAA_DEVICE_BUFFERBLOCKSIZE; + + b->m_dwSetWritePos = ((u32)dev->intfdesc.BARLocation) + + (2 * sizeof(u64)); + b->m_dwSetReadPos = b->m_dwSetWritePos + (1 * sizeof(u32)); + + b->m_dwGetWritePos = b->m_dwSetWritePos + (2 * sizeof(u32)); + b->m_dwGetReadPos = b->m_dwSetWritePos + (3 * sizeof(u32)); + + return 0; +} + +void saa7164_bus_dump(struct saa7164_dev *dev) +{ + struct tmComResBusInfo *b = &dev->bus; + + dprintk(DBGLVL_BUS, "Dumping the bus structure:\n"); + dprintk(DBGLVL_BUS, " .type = %d\n", b->Type); + dprintk(DBGLVL_BUS, " .dev->bmmio = 0x%p\n", dev->bmmio); + dprintk(DBGLVL_BUS, " .m_wMaxReqSize = 0x%x\n", b->m_wMaxReqSize); + dprintk(DBGLVL_BUS, " .m_pdwSetRing = 0x%p\n", b->m_pdwSetRing); + dprintk(DBGLVL_BUS, " .m_dwSizeSetRing = 0x%x\n", b->m_dwSizeSetRing); + dprintk(DBGLVL_BUS, " .m_pdwGetRing = 0x%p\n", b->m_pdwGetRing); + dprintk(DBGLVL_BUS, " .m_dwSizeGetRing = 0x%x\n", b->m_dwSizeGetRing); + + dprintk(DBGLVL_BUS, " .m_dwSetReadPos = 0x%x (0x%08x)\n", + b->m_dwSetReadPos, saa7164_readl(b->m_dwSetReadPos)); + + dprintk(DBGLVL_BUS, " .m_dwSetWritePos = 0x%x (0x%08x)\n", + b->m_dwSetWritePos, saa7164_readl(b->m_dwSetWritePos)); + + dprintk(DBGLVL_BUS, " .m_dwGetReadPos = 0x%x (0x%08x)\n", + b->m_dwGetReadPos, saa7164_readl(b->m_dwGetReadPos)); + + dprintk(DBGLVL_BUS, " .m_dwGetWritePos = 0x%x (0x%08x)\n", + b->m_dwGetWritePos, saa7164_readl(b->m_dwGetWritePos)); + +} + +/* Intensionally throw a BUG() if the state of the message bus looks corrupt */ +static void saa7164_bus_verify(struct saa7164_dev *dev) +{ + struct tmComResBusInfo *b = &dev->bus; + int bug = 0; + + if (saa7164_readl(b->m_dwSetReadPos) > b->m_dwSizeSetRing) + bug++; + + if (saa7164_readl(b->m_dwSetWritePos) > b->m_dwSizeSetRing) + bug++; + + if (saa7164_readl(b->m_dwGetReadPos) > b->m_dwSizeGetRing) + bug++; + + if (saa7164_readl(b->m_dwGetWritePos) > b->m_dwSizeGetRing) + bug++; + + if (bug) { + saa_debug = 0xffff; /* Ensure we get the bus dump */ + saa7164_bus_dump(dev); + saa_debug = 1024; /* Ensure we get the bus dump */ + BUG(); + } +} + +static void saa7164_bus_dumpmsg(struct saa7164_dev *dev, struct tmComResInfo *m, + void *buf) +{ + dprintk(DBGLVL_BUS, "Dumping msg structure:\n"); + dprintk(DBGLVL_BUS, " .id = %d\n", m->id); + dprintk(DBGLVL_BUS, " .flags = 0x%x\n", m->flags); + dprintk(DBGLVL_BUS, " .size = 0x%x\n", m->size); + dprintk(DBGLVL_BUS, " .command = 0x%x\n", m->command); + dprintk(DBGLVL_BUS, " .controlselector = 0x%x\n", m->controlselector); + dprintk(DBGLVL_BUS, " .seqno = %d\n", m->seqno); + if (buf) + dprintk(DBGLVL_BUS, " .buffer (ignored)\n"); +} + +/* + * Places a command or a response on the bus. The implementation does not + * know if it is a command or a response it just places the data on the + * bus depending on the bus information given in the struct tmComResBusInfo + * structure. If the command or response does not fit into the bus ring + * buffer it will be refused. + * + * Return Value: + * SAA_OK The function executed successfully. + * < 0 One or more members are not initialized. + */ +int saa7164_bus_set(struct saa7164_dev *dev, struct tmComResInfo* msg, + void *buf) +{ + struct tmComResBusInfo *bus = &dev->bus; + u32 bytes_to_write, free_write_space, timeout, curr_srp, curr_swp; + u32 new_swp, space_rem; + int ret = SAA_ERR_BAD_PARAMETER; + u16 size; + + if (!msg) { + printk(KERN_ERR "%s() !msg\n", __func__); + return SAA_ERR_BAD_PARAMETER; + } + + dprintk(DBGLVL_BUS, "%s()\n", __func__); + + saa7164_bus_verify(dev); + + if (msg->size > dev->bus.m_wMaxReqSize) { + printk(KERN_ERR "%s() Exceeded dev->bus.m_wMaxReqSize\n", + __func__); + return SAA_ERR_BAD_PARAMETER; + } + + if ((msg->size > 0) && (buf == NULL)) { + printk(KERN_ERR "%s() Missing message buffer\n", __func__); + return SAA_ERR_BAD_PARAMETER; + } + + /* Lock the bus from any other access */ + mutex_lock(&bus->lock); + + bytes_to_write = sizeof(*msg) + msg->size; + free_write_space = 0; + timeout = SAA_BUS_TIMEOUT; + curr_srp = saa7164_readl(bus->m_dwSetReadPos); + curr_swp = saa7164_readl(bus->m_dwSetWritePos); + + /* Deal with ring wrapping issues */ + if (curr_srp > curr_swp) + /* Deal with the wrapped ring */ + free_write_space = curr_srp - curr_swp; + else + /* The ring has not wrapped yet */ + free_write_space = (curr_srp + bus->m_dwSizeSetRing) - curr_swp; + + dprintk(DBGLVL_BUS, "%s() bytes_to_write = %d\n", __func__, + bytes_to_write); + + dprintk(DBGLVL_BUS, "%s() free_write_space = %d\n", __func__, + free_write_space); + + dprintk(DBGLVL_BUS, "%s() curr_srp = %x\n", __func__, curr_srp); + dprintk(DBGLVL_BUS, "%s() curr_swp = %x\n", __func__, curr_swp); + + /* Process the msg and write the content onto the bus */ + while (bytes_to_write >= free_write_space) { + + if (timeout-- == 0) { + printk(KERN_ERR "%s() bus timeout\n", __func__); + ret = SAA_ERR_NO_RESOURCES; + goto out; + } + + /* TODO: Review this delay, efficient? */ + /* Wait, allowing the hardware fetch time */ + mdelay(1); + + /* Check the space usage again */ + curr_srp = saa7164_readl(bus->m_dwSetReadPos); + + /* Deal with ring wrapping issues */ + if (curr_srp > curr_swp) + /* Deal with the wrapped ring */ + free_write_space = curr_srp - curr_swp; + else + /* Read didn't wrap around the buffer */ + free_write_space = (curr_srp + bus->m_dwSizeSetRing) - + curr_swp; + + } + + /* Calculate the new write position */ + new_swp = curr_swp + bytes_to_write; + + dprintk(DBGLVL_BUS, "%s() new_swp = %x\n", __func__, new_swp); + dprintk(DBGLVL_BUS, "%s() bus->m_dwSizeSetRing = %x\n", __func__, + bus->m_dwSizeSetRing); + + /* + * Make a copy of msg->size before it is converted to le16 since it is + * used in the code below. + */ + size = msg->size; + /* Convert to le16/le32 */ + msg->size = (__force u16)cpu_to_le16(msg->size); + msg->command = (__force u32)cpu_to_le32(msg->command); + msg->controlselector = (__force u16)cpu_to_le16(msg->controlselector); + + /* Mental Note: line 462 tmmhComResBusPCIe.cpp */ + + /* Check if we're going to wrap again */ + if (new_swp > bus->m_dwSizeSetRing) { + + /* Ring wraps */ + new_swp -= bus->m_dwSizeSetRing; + + space_rem = bus->m_dwSizeSetRing - curr_swp; + + dprintk(DBGLVL_BUS, "%s() space_rem = %x\n", __func__, + space_rem); + + dprintk(DBGLVL_BUS, "%s() sizeof(*msg) = %d\n", __func__, + (u32)sizeof(*msg)); + + if (space_rem < sizeof(*msg)) { + dprintk(DBGLVL_BUS, "%s() tr4\n", __func__); + + /* Split the msg into pieces as the ring wraps */ + memcpy_toio(bus->m_pdwSetRing + curr_swp, msg, space_rem); + memcpy_toio(bus->m_pdwSetRing, (u8 *)msg + space_rem, + sizeof(*msg) - space_rem); + + memcpy_toio(bus->m_pdwSetRing + sizeof(*msg) - space_rem, + buf, size); + + } else if (space_rem == sizeof(*msg)) { + dprintk(DBGLVL_BUS, "%s() tr5\n", __func__); + + /* Additional data at the beginning of the ring */ + memcpy_toio(bus->m_pdwSetRing + curr_swp, msg, sizeof(*msg)); + memcpy_toio(bus->m_pdwSetRing, buf, size); + + } else { + /* Additional data wraps around the ring */ + memcpy_toio(bus->m_pdwSetRing + curr_swp, msg, sizeof(*msg)); + if (size > 0) { + memcpy_toio(bus->m_pdwSetRing + curr_swp + + sizeof(*msg), buf, space_rem - + sizeof(*msg)); + memcpy_toio(bus->m_pdwSetRing, (u8 *)buf + + space_rem - sizeof(*msg), + bytes_to_write - space_rem); + } + + } + + } /* (new_swp > bus->m_dwSizeSetRing) */ + else { + dprintk(DBGLVL_BUS, "%s() tr6\n", __func__); + + /* The ring buffer doesn't wrap, two simple copies */ + memcpy_toio(bus->m_pdwSetRing + curr_swp, msg, sizeof(*msg)); + memcpy_toio(bus->m_pdwSetRing + curr_swp + sizeof(*msg), buf, + size); + } + + dprintk(DBGLVL_BUS, "%s() new_swp = %x\n", __func__, new_swp); + + /* Update the bus write position */ + saa7164_writel(bus->m_dwSetWritePos, new_swp); + + /* Convert back to cpu after writing the msg to the ringbuffer. */ + msg->size = le16_to_cpu((__force __le16)msg->size); + msg->command = le32_to_cpu((__force __le32)msg->command); + msg->controlselector = le16_to_cpu((__force __le16)msg->controlselector); + ret = SAA_OK; + +out: + saa7164_bus_dump(dev); + mutex_unlock(&bus->lock); + saa7164_bus_verify(dev); + return ret; +} + +/* + * Receive a command or a response from the bus. The implementation does not + * know if it is a command or a response it simply dequeues the data, + * depending on the bus information given in the struct tmComResBusInfo + * structure. + * + * Return Value: + * 0 The function executed successfully. + * < 0 One or more members are not initialized. + */ +int saa7164_bus_get(struct saa7164_dev *dev, struct tmComResInfo* msg, + void *buf, int peekonly) +{ + struct tmComResBusInfo *bus = &dev->bus; + u32 bytes_to_read, write_distance, curr_grp, curr_gwp, + new_grp, buf_size, space_rem; + struct tmComResInfo msg_tmp; + int ret = SAA_ERR_BAD_PARAMETER; + + saa7164_bus_verify(dev); + + if (msg == NULL) + return ret; + + if (msg->size > dev->bus.m_wMaxReqSize) { + printk(KERN_ERR "%s() Exceeded dev->bus.m_wMaxReqSize\n", + __func__); + return ret; + } + + if ((peekonly == 0) && (msg->size > 0) && (buf == NULL)) { + printk(KERN_ERR + "%s() Missing msg buf, size should be %d bytes\n", + __func__, msg->size); + return ret; + } + + mutex_lock(&bus->lock); + + /* Peek the bus to see if a msg exists, if it's not what we're expecting + * then return cleanly else read the message from the bus. + */ + curr_gwp = saa7164_readl(bus->m_dwGetWritePos); + curr_grp = saa7164_readl(bus->m_dwGetReadPos); + + if (curr_gwp == curr_grp) { + ret = SAA_ERR_EMPTY; + goto out; + } + + bytes_to_read = sizeof(*msg); + + /* Calculate write distance to current read position */ + write_distance = 0; + if (curr_gwp >= curr_grp) + /* Write doesn't wrap around the ring */ + write_distance = curr_gwp - curr_grp; + else + /* Write wraps around the ring */ + write_distance = curr_gwp + bus->m_dwSizeGetRing - curr_grp; + + if (bytes_to_read > write_distance) { + printk(KERN_ERR "%s() No message/response found\n", __func__); + ret = SAA_ERR_INVALID_COMMAND; + goto out; + } + + /* Calculate the new read position */ + new_grp = curr_grp + bytes_to_read; + if (new_grp > bus->m_dwSizeGetRing) { + + /* Ring wraps */ + new_grp -= bus->m_dwSizeGetRing; + space_rem = bus->m_dwSizeGetRing - curr_grp; + + memcpy_fromio(&msg_tmp, bus->m_pdwGetRing + curr_grp, space_rem); + memcpy_fromio((u8 *)&msg_tmp + space_rem, bus->m_pdwGetRing, + bytes_to_read - space_rem); + + } else { + /* No wrapping */ + memcpy_fromio(&msg_tmp, bus->m_pdwGetRing + curr_grp, bytes_to_read); + } + /* Convert from little endian to CPU */ + msg_tmp.size = le16_to_cpu((__force __le16)msg_tmp.size); + msg_tmp.command = le32_to_cpu((__force __le32)msg_tmp.command); + msg_tmp.controlselector = le16_to_cpu((__force __le16)msg_tmp.controlselector); + memcpy(msg, &msg_tmp, sizeof(*msg)); + + /* No need to update the read positions, because this was a peek */ + /* If the caller specifically want to peek, return */ + if (peekonly) { + goto peekout; + } + + /* Check if the command/response matches what is expected */ + if ((msg_tmp.id != msg->id) || (msg_tmp.command != msg->command) || + (msg_tmp.controlselector != msg->controlselector) || + (msg_tmp.seqno != msg->seqno) || (msg_tmp.size != msg->size)) { + + printk(KERN_ERR "%s() Unexpected msg miss-match\n", __func__); + saa7164_bus_dumpmsg(dev, msg, buf); + saa7164_bus_dumpmsg(dev, &msg_tmp, NULL); + ret = SAA_ERR_INVALID_COMMAND; + goto out; + } + + /* Get the actual command and response from the bus */ + buf_size = msg->size; + + bytes_to_read = sizeof(*msg) + msg->size; + /* Calculate write distance to current read position */ + write_distance = 0; + if (curr_gwp >= curr_grp) + /* Write doesn't wrap around the ring */ + write_distance = curr_gwp - curr_grp; + else + /* Write wraps around the ring */ + write_distance = curr_gwp + bus->m_dwSizeGetRing - curr_grp; + + if (bytes_to_read > write_distance) { + printk(KERN_ERR "%s() Invalid bus state, missing msg or mangled ring, faulty H/W / bad code?\n", + __func__); + ret = SAA_ERR_INVALID_COMMAND; + goto out; + } + + /* Calculate the new read position */ + new_grp = curr_grp + bytes_to_read; + if (new_grp > bus->m_dwSizeGetRing) { + + /* Ring wraps */ + new_grp -= bus->m_dwSizeGetRing; + space_rem = bus->m_dwSizeGetRing - curr_grp; + + if (space_rem < sizeof(*msg)) { + if (buf) + memcpy_fromio(buf, bus->m_pdwGetRing + sizeof(*msg) - + space_rem, buf_size); + + } else if (space_rem == sizeof(*msg)) { + if (buf) + memcpy_fromio(buf, bus->m_pdwGetRing, buf_size); + } else { + /* Additional data wraps around the ring */ + if (buf) { + memcpy_fromio(buf, bus->m_pdwGetRing + curr_grp + + sizeof(*msg), space_rem - sizeof(*msg)); + memcpy_fromio(buf + space_rem - sizeof(*msg), + bus->m_pdwGetRing, bytes_to_read - + space_rem); + } + + } + + } else { + /* No wrapping */ + if (buf) + memcpy_fromio(buf, bus->m_pdwGetRing + curr_grp + sizeof(*msg), + buf_size); + } + + /* Update the read positions, adjusting the ring */ + saa7164_writel(bus->m_dwGetReadPos, new_grp); + +peekout: + ret = SAA_OK; +out: + mutex_unlock(&bus->lock); + saa7164_bus_verify(dev); + return ret; +} + diff --git a/drivers/media/pci/saa7164/saa7164-cards.c b/drivers/media/pci/saa7164/saa7164-cards.c new file mode 100644 index 000000000..3ac6e8393 --- /dev/null +++ b/drivers/media/pci/saa7164/saa7164-cards.c @@ -0,0 +1,943 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the NXP SAA7164 PCIe bridge + * + * Copyright (c) 2010-2015 Steven Toth + */ + +#include +#include +#include +#include + +#include "saa7164.h" + +/* The Bridge API needs to understand register widths (in bytes) for the + * attached I2C devices, so we can simplify the virtual i2c mechansms + * and keep the -i2c.c implementation clean. + */ +#define REGLEN_0bit 0 +#define REGLEN_8bit 1 +#define REGLEN_16bit 2 + +struct saa7164_board saa7164_boards[] = { + [SAA7164_BOARD_UNKNOWN] = { + /* Bridge will not load any firmware, without knowing + * the rev this would be fatal. */ + .name = "Unknown", + }, + [SAA7164_BOARD_UNKNOWN_REV2] = { + /* Bridge will load the v2 f/w and dump descriptors */ + /* Required during new board bringup */ + .name = "Generic Rev2", + .chiprev = SAA7164_CHIP_REV2, + }, + [SAA7164_BOARD_UNKNOWN_REV3] = { + /* Bridge will load the v2 f/w and dump descriptors */ + /* Required during new board bringup */ + .name = "Generic Rev3", + .chiprev = SAA7164_CHIP_REV3, + }, + [SAA7164_BOARD_HAUPPAUGE_HVR2200] = { + .name = "Hauppauge WinTV-HVR2200", + .porta = SAA7164_MPEG_DVB, + .portb = SAA7164_MPEG_DVB, + .portc = SAA7164_MPEG_ENCODER, + .portd = SAA7164_MPEG_ENCODER, + .porte = SAA7164_MPEG_VBI, + .portf = SAA7164_MPEG_VBI, + .chiprev = SAA7164_CHIP_REV3, + .unit = {{ + .id = 0x1d, + .type = SAA7164_UNIT_EEPROM, + .name = "4K EEPROM", + .i2c_bus_nr = SAA7164_I2C_BUS_0, + .i2c_bus_addr = 0xa0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x04, + .type = SAA7164_UNIT_TUNER, + .name = "TDA18271-1", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x1b, + .type = SAA7164_UNIT_TUNER, + .name = "TDA18271-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x1e, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "TDA10048-1", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0x10 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x1f, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "TDA10048-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0x12 >> 1, + .i2c_reg_len = REGLEN_8bit, + } }, + }, + [SAA7164_BOARD_HAUPPAUGE_HVR2200_2] = { + .name = "Hauppauge WinTV-HVR2200", + .porta = SAA7164_MPEG_DVB, + .portb = SAA7164_MPEG_DVB, + .portc = SAA7164_MPEG_ENCODER, + .portd = SAA7164_MPEG_ENCODER, + .porte = SAA7164_MPEG_VBI, + .portf = SAA7164_MPEG_VBI, + .chiprev = SAA7164_CHIP_REV2, + .unit = {{ + .id = 0x06, + .type = SAA7164_UNIT_EEPROM, + .name = "4K EEPROM", + .i2c_bus_nr = SAA7164_I2C_BUS_0, + .i2c_bus_addr = 0xa0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x04, + .type = SAA7164_UNIT_TUNER, + .name = "TDA18271-1", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x05, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "TDA10048-1", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0x10 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x1e, + .type = SAA7164_UNIT_TUNER, + .name = "TDA18271-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x1f, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "TDA10048-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0x12 >> 1, + .i2c_reg_len = REGLEN_8bit, + } }, + }, + [SAA7164_BOARD_HAUPPAUGE_HVR2200_3] = { + .name = "Hauppauge WinTV-HVR2200", + .porta = SAA7164_MPEG_DVB, + .portb = SAA7164_MPEG_DVB, + .portc = SAA7164_MPEG_ENCODER, + .portd = SAA7164_MPEG_ENCODER, + .porte = SAA7164_MPEG_VBI, + .portf = SAA7164_MPEG_VBI, + .chiprev = SAA7164_CHIP_REV2, + .unit = {{ + .id = 0x1d, + .type = SAA7164_UNIT_EEPROM, + .name = "4K EEPROM", + .i2c_bus_nr = SAA7164_I2C_BUS_0, + .i2c_bus_addr = 0xa0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x04, + .type = SAA7164_UNIT_TUNER, + .name = "TDA18271-1", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x05, + .type = SAA7164_UNIT_ANALOG_DEMODULATOR, + .name = "TDA8290-1", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0x84 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x1b, + .type = SAA7164_UNIT_TUNER, + .name = "TDA18271-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x1c, + .type = SAA7164_UNIT_ANALOG_DEMODULATOR, + .name = "TDA8290-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0x84 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x1e, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "TDA10048-1", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0x10 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x1f, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "TDA10048-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0x12 >> 1, + .i2c_reg_len = REGLEN_8bit, + } }, + }, + [SAA7164_BOARD_HAUPPAUGE_HVR2200_4] = { + .name = "Hauppauge WinTV-HVR2200", + .porta = SAA7164_MPEG_DVB, + .portb = SAA7164_MPEG_DVB, + .portc = SAA7164_MPEG_ENCODER, + .portd = SAA7164_MPEG_ENCODER, + .porte = SAA7164_MPEG_VBI, + .portf = SAA7164_MPEG_VBI, + .chiprev = SAA7164_CHIP_REV3, + .unit = {{ + .id = 0x1d, + .type = SAA7164_UNIT_EEPROM, + .name = "4K EEPROM", + .i2c_bus_nr = SAA7164_I2C_BUS_0, + .i2c_bus_addr = 0xa0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x04, + .type = SAA7164_UNIT_TUNER, + .name = "TDA18271-1", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x05, + .type = SAA7164_UNIT_ANALOG_DEMODULATOR, + .name = "TDA8290-1", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0x84 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x1b, + .type = SAA7164_UNIT_TUNER, + .name = "TDA18271-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x1c, + .type = SAA7164_UNIT_ANALOG_DEMODULATOR, + .name = "TDA8290-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0x84 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x1e, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "TDA10048-1", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0x10 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x1f, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "TDA10048-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0x12 >> 1, + .i2c_reg_len = REGLEN_8bit, + } }, + }, + [SAA7164_BOARD_HAUPPAUGE_HVR2250] = { + .name = "Hauppauge WinTV-HVR2250", + .porta = SAA7164_MPEG_DVB, + .portb = SAA7164_MPEG_DVB, + .portc = SAA7164_MPEG_ENCODER, + .portd = SAA7164_MPEG_ENCODER, + .porte = SAA7164_MPEG_VBI, + .portf = SAA7164_MPEG_VBI, + .chiprev = SAA7164_CHIP_REV3, + .unit = {{ + .id = 0x22, + .type = SAA7164_UNIT_EEPROM, + .name = "4K EEPROM", + .i2c_bus_nr = SAA7164_I2C_BUS_0, + .i2c_bus_addr = 0xa0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x04, + .type = SAA7164_UNIT_TUNER, + .name = "TDA18271-1", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x07, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "CX24228/S5H1411-1 (TOP)", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0x32 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x08, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "CX24228/S5H1411-1 (QAM)", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0x34 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x1e, + .type = SAA7164_UNIT_TUNER, + .name = "TDA18271-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x20, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "CX24228/S5H1411-2 (TOP)", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0x32 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x23, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "CX24228/S5H1411-2 (QAM)", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0x34 >> 1, + .i2c_reg_len = REGLEN_8bit, + } }, + }, + [SAA7164_BOARD_HAUPPAUGE_HVR2250_2] = { + .name = "Hauppauge WinTV-HVR2250", + .porta = SAA7164_MPEG_DVB, + .portb = SAA7164_MPEG_DVB, + .portc = SAA7164_MPEG_ENCODER, + .portd = SAA7164_MPEG_ENCODER, + .porte = SAA7164_MPEG_VBI, + .portf = SAA7164_MPEG_VBI, + .chiprev = SAA7164_CHIP_REV3, + .unit = {{ + .id = 0x28, + .type = SAA7164_UNIT_EEPROM, + .name = "4K EEPROM", + .i2c_bus_nr = SAA7164_I2C_BUS_0, + .i2c_bus_addr = 0xa0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x04, + .type = SAA7164_UNIT_TUNER, + .name = "TDA18271-1", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x07, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "CX24228/S5H1411-1 (TOP)", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0x32 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x08, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "CX24228/S5H1411-1 (QAM)", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0x34 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x24, + .type = SAA7164_UNIT_TUNER, + .name = "TDA18271-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x26, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "CX24228/S5H1411-2 (TOP)", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0x32 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x29, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "CX24228/S5H1411-2 (QAM)", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0x34 >> 1, + .i2c_reg_len = REGLEN_8bit, + } }, + }, + [SAA7164_BOARD_HAUPPAUGE_HVR2250_3] = { + .name = "Hauppauge WinTV-HVR2250", + .porta = SAA7164_MPEG_DVB, + .portb = SAA7164_MPEG_DVB, + .portc = SAA7164_MPEG_ENCODER, + .portd = SAA7164_MPEG_ENCODER, + .porte = SAA7164_MPEG_VBI, + .portf = SAA7164_MPEG_VBI, + .chiprev = SAA7164_CHIP_REV3, + .unit = {{ + .id = 0x26, + .type = SAA7164_UNIT_EEPROM, + .name = "4K EEPROM", + .i2c_bus_nr = SAA7164_I2C_BUS_0, + .i2c_bus_addr = 0xa0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x04, + .type = SAA7164_UNIT_TUNER, + .name = "TDA18271-1", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x07, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "CX24228/S5H1411-1 (TOP)", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0x32 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x08, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "CX24228/S5H1411-1 (QAM)", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0x34 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x22, + .type = SAA7164_UNIT_TUNER, + .name = "TDA18271-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x24, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "CX24228/S5H1411-2 (TOP)", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0x32 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x27, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "CX24228/S5H1411-2 (QAM)", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0x34 >> 1, + .i2c_reg_len = REGLEN_8bit, + } }, + }, + [SAA7164_BOARD_HAUPPAUGE_HVR2200_5] = { + .name = "Hauppauge WinTV-HVR2200", + .porta = SAA7164_MPEG_DVB, + .portb = SAA7164_MPEG_DVB, + .chiprev = SAA7164_CHIP_REV3, + .unit = {{ + .id = 0x23, + .type = SAA7164_UNIT_EEPROM, + .name = "4K EEPROM", + .i2c_bus_nr = SAA7164_I2C_BUS_0, + .i2c_bus_addr = 0xa0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x04, + .type = SAA7164_UNIT_TUNER, + .name = "TDA18271-1", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x05, + .type = SAA7164_UNIT_ANALOG_DEMODULATOR, + .name = "TDA8290-1", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0x84 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x21, + .type = SAA7164_UNIT_TUNER, + .name = "TDA18271-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x22, + .type = SAA7164_UNIT_ANALOG_DEMODULATOR, + .name = "TDA8290-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0x84 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x24, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "TDA10048-1", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0x10 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x25, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "TDA10048-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0x12 >> 1, + .i2c_reg_len = REGLEN_8bit, + } }, + }, + [SAA7164_BOARD_HAUPPAUGE_HVR2255proto] = { + .name = "Hauppauge WinTV-HVR2255(proto)", + .porta = SAA7164_MPEG_DVB, + .portb = SAA7164_MPEG_DVB, + .portc = SAA7164_MPEG_ENCODER, + .portd = SAA7164_MPEG_ENCODER, + .porte = SAA7164_MPEG_VBI, + .portf = SAA7164_MPEG_VBI, + .chiprev = SAA7164_CHIP_REV3, + .unit = {{ + .id = 0x27, + .type = SAA7164_UNIT_EEPROM, + .name = "4K EEPROM", + .i2c_bus_nr = SAA7164_I2C_BUS_0, + .i2c_bus_addr = 0xa0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x04, + .type = SAA7164_UNIT_TUNER, + .name = "SI2157-1", + .i2c_bus_nr = SAA7164_I2C_BUS_0, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_0bit, + }, { + .id = 0x06, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "LGDT3306", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0xb2 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x24, + .type = SAA7164_UNIT_TUNER, + .name = "SI2157-2", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_0bit, + }, { + .id = 0x26, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "LGDT3306-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0x1c >> 1, + .i2c_reg_len = REGLEN_8bit, + } }, + }, + [SAA7164_BOARD_HAUPPAUGE_HVR2255] = { + .name = "Hauppauge WinTV-HVR2255", + .porta = SAA7164_MPEG_DVB, + .portb = SAA7164_MPEG_DVB, + .portc = SAA7164_MPEG_ENCODER, + .portd = SAA7164_MPEG_ENCODER, + .porte = SAA7164_MPEG_VBI, + .portf = SAA7164_MPEG_VBI, + .chiprev = SAA7164_CHIP_REV3, + .unit = {{ + .id = 0x28, + .type = SAA7164_UNIT_EEPROM, + .name = "4K EEPROM", + .i2c_bus_nr = SAA7164_I2C_BUS_0, + .i2c_bus_addr = 0xa0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x04, + .type = SAA7164_UNIT_TUNER, + .name = "SI2157-1", + .i2c_bus_nr = SAA7164_I2C_BUS_0, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_0bit, + }, { + .id = 0x06, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "LGDT3306-1", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0xb2 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x25, + .type = SAA7164_UNIT_TUNER, + .name = "SI2157-2", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_0bit, + }, { + .id = 0x27, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "LGDT3306-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0x1c >> 1, + .i2c_reg_len = REGLEN_8bit, + } }, + }, + [SAA7164_BOARD_HAUPPAUGE_HVR2205] = { + .name = "Hauppauge WinTV-HVR2205", + .porta = SAA7164_MPEG_DVB, + .portb = SAA7164_MPEG_DVB, + .portc = SAA7164_MPEG_ENCODER, + .portd = SAA7164_MPEG_ENCODER, + .porte = SAA7164_MPEG_VBI, + .portf = SAA7164_MPEG_VBI, + .chiprev = SAA7164_CHIP_REV3, + .unit = {{ + .id = 0x28, + .type = SAA7164_UNIT_EEPROM, + .name = "4K EEPROM", + .i2c_bus_nr = SAA7164_I2C_BUS_0, + .i2c_bus_addr = 0xa0 >> 1, + .i2c_reg_len = REGLEN_8bit, + }, { + .id = 0x04, + .type = SAA7164_UNIT_TUNER, + .name = "SI2157-1", + .i2c_bus_nr = SAA7164_I2C_BUS_0, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_0bit, + }, { + .id = 0x06, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "SI2168-1", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0xc8 >> 1, + .i2c_reg_len = REGLEN_0bit, + }, { + .id = 0x25, + .type = SAA7164_UNIT_TUNER, + .name = "SI2157-2", + .i2c_bus_nr = SAA7164_I2C_BUS_1, + .i2c_bus_addr = 0xc0 >> 1, + .i2c_reg_len = REGLEN_0bit, + }, { + .id = 0x27, + .type = SAA7164_UNIT_DIGITAL_DEMODULATOR, + .name = "SI2168-2", + .i2c_bus_nr = SAA7164_I2C_BUS_2, + .i2c_bus_addr = 0xcc >> 1, + .i2c_reg_len = REGLEN_0bit, + } }, + }, +}; +const unsigned int saa7164_bcount = ARRAY_SIZE(saa7164_boards); + +/* ------------------------------------------------------------------ */ +/* PCI subsystem IDs */ + +struct saa7164_subid saa7164_subids[] = { + { + .subvendor = 0x0070, + .subdevice = 0x8880, + .card = SAA7164_BOARD_HAUPPAUGE_HVR2250, + }, { + .subvendor = 0x0070, + .subdevice = 0x8810, + .card = SAA7164_BOARD_HAUPPAUGE_HVR2250, + }, { + .subvendor = 0x0070, + .subdevice = 0x8980, + .card = SAA7164_BOARD_HAUPPAUGE_HVR2200, + }, { + .subvendor = 0x0070, + .subdevice = 0x8900, + .card = SAA7164_BOARD_HAUPPAUGE_HVR2200_2, + }, { + .subvendor = 0x0070, + .subdevice = 0x8901, + .card = SAA7164_BOARD_HAUPPAUGE_HVR2200_3, + }, { + .subvendor = 0x0070, + .subdevice = 0x88A1, + .card = SAA7164_BOARD_HAUPPAUGE_HVR2250_3, + }, { + .subvendor = 0x0070, + .subdevice = 0x8891, + .card = SAA7164_BOARD_HAUPPAUGE_HVR2250_2, + }, { + .subvendor = 0x0070, + .subdevice = 0x8851, + .card = SAA7164_BOARD_HAUPPAUGE_HVR2250_2, + }, { + .subvendor = 0x0070, + .subdevice = 0x8940, + .card = SAA7164_BOARD_HAUPPAUGE_HVR2200_4, + }, { + .subvendor = 0x0070, + .subdevice = 0x8953, + .card = SAA7164_BOARD_HAUPPAUGE_HVR2200_5, + }, { + .subvendor = 0x0070, + .subdevice = 0xf111, + .card = SAA7164_BOARD_HAUPPAUGE_HVR2255, + /* Prototype card left here for documentation purposes. + .card = SAA7164_BOARD_HAUPPAUGE_HVR2255proto, + */ + }, { + .subvendor = 0x0070, + .subdevice = 0xf123, + .card = SAA7164_BOARD_HAUPPAUGE_HVR2205, + }, { + .subvendor = 0x0070, + .subdevice = 0xf120, + .card = SAA7164_BOARD_HAUPPAUGE_HVR2205, + }, +}; +const unsigned int saa7164_idcount = ARRAY_SIZE(saa7164_subids); + +void saa7164_card_list(struct saa7164_dev *dev) +{ + int i; + + if (0 == dev->pci->subsystem_vendor && + 0 == dev->pci->subsystem_device) { + printk(KERN_ERR + "%s: Board has no valid PCIe Subsystem ID and can't\n" + "%s: be autodetected. Pass card= insmod option to\n" + "%s: workaround that. Send complaints to the vendor\n" + "%s: of the TV card. Best regards,\n" + "%s: -- tux\n", + dev->name, dev->name, dev->name, dev->name, dev->name); + } else { + printk(KERN_ERR + "%s: Your board isn't known (yet) to the driver.\n" + "%s: Try to pick one of the existing card configs via\n" + "%s: card= insmod option. Updating to the latest\n" + "%s: version might help as well.\n", + dev->name, dev->name, dev->name, dev->name); + } + + printk(KERN_ERR "%s: Here are valid choices for the card= insmod option:\n", + dev->name); + + for (i = 0; i < saa7164_bcount; i++) + printk(KERN_ERR "%s: card=%d -> %s\n", + dev->name, i, saa7164_boards[i].name); +} + +/* TODO: clean this define up into the -cards.c structs */ +#define PCIEBRIDGE_UNITID 2 + +void saa7164_gpio_setup(struct saa7164_dev *dev) +{ + switch (dev->board) { + case SAA7164_BOARD_HAUPPAUGE_HVR2200: + case SAA7164_BOARD_HAUPPAUGE_HVR2200_2: + case SAA7164_BOARD_HAUPPAUGE_HVR2200_3: + case SAA7164_BOARD_HAUPPAUGE_HVR2200_4: + case SAA7164_BOARD_HAUPPAUGE_HVR2200_5: + case SAA7164_BOARD_HAUPPAUGE_HVR2250: + case SAA7164_BOARD_HAUPPAUGE_HVR2250_2: + case SAA7164_BOARD_HAUPPAUGE_HVR2250_3: + case SAA7164_BOARD_HAUPPAUGE_HVR2255proto: + case SAA7164_BOARD_HAUPPAUGE_HVR2255: + case SAA7164_BOARD_HAUPPAUGE_HVR2205: + /* + HVR2200 / HVR2250 + GPIO 2: s5h1411 / tda10048-1 demod reset + GPIO 3: s5h1411 / tda10048-2 demod reset + GPIO 7: IRBlaster Zilog reset + */ + + /* HVR2255 + * GPIO 2: lgdg3306-1 demod reset + * GPIO 3: lgdt3306-2 demod reset + */ + + /* HVR2205 + * GPIO 2: si2168-1 demod reset + * GPIO 3: si2168-2 demod reset + */ + + /* Reset parts by going in and out of reset */ + saa7164_api_clear_gpiobit(dev, PCIEBRIDGE_UNITID, 2); + saa7164_api_clear_gpiobit(dev, PCIEBRIDGE_UNITID, 3); + + msleep(20); + + saa7164_api_set_gpiobit(dev, PCIEBRIDGE_UNITID, 2); + saa7164_api_set_gpiobit(dev, PCIEBRIDGE_UNITID, 3); + break; + } +} + +static void hauppauge_eeprom(struct saa7164_dev *dev, u8 *eeprom_data) +{ + struct tveeprom tv; + + tveeprom_hauppauge_analog(&tv, eeprom_data); + + /* Make sure we support the board model */ + switch (tv.model) { + case 88001: + /* Development board - Limit circulation */ + /* WinTV-HVR2250 (PCIe, Retail, full-height bracket) + * ATSC/QAM (TDA18271/S5H1411) and basic analog, no IR, FM */ + case 88021: + /* WinTV-HVR2250 (PCIe, Retail, full-height bracket) + * ATSC/QAM (TDA18271/S5H1411) and basic analog, MCE CIR, FM */ + break; + case 88041: + /* WinTV-HVR2250 (PCIe, Retail, full-height bracket) + * ATSC/QAM (TDA18271/S5H1411) and basic analog, no IR, FM */ + break; + case 88061: + /* WinTV-HVR2250 (PCIe, Retail, full-height bracket) + * ATSC/QAM (TDA18271/S5H1411) and basic analog, FM */ + break; + case 89519: + case 89609: + /* WinTV-HVR2200 (PCIe, Retail, full-height) + * DVB-T (TDA18271/TDA10048) and basic analog, no IR */ + break; + case 89619: + /* WinTV-HVR2200 (PCIe, Retail, half-height) + * DVB-T (TDA18271/TDA10048) and basic analog, no IR */ + break; + case 151009: + /* First production board rev B2I6 */ + /* WinTV-HVR2205 (PCIe, Retail, full-height bracket) + * DVB-T/T2/C (SI2157/SI2168) and basic analog, FM */ + break; + case 151609: + /* First production board rev B2I6 */ + /* WinTV-HVR2205 (PCIe, Retail, half-height bracket) + * DVB-T/T2/C (SI2157/SI2168) and basic analog, FM */ + break; + case 151061: + /* First production board rev B1I6 */ + /* WinTV-HVR2255 (PCIe, Retail, full-height bracket) + * ATSC/QAM (SI2157/LGDT3306) and basic analog, FM */ + break; + default: + printk(KERN_ERR "%s: Warning: Unknown Hauppauge model #%d\n", + dev->name, tv.model); + break; + } + + printk(KERN_INFO "%s: Hauppauge eeprom: model=%d\n", dev->name, + tv.model); +} + +void saa7164_card_setup(struct saa7164_dev *dev) +{ + static u8 eeprom[256]; + + if (dev->i2c_bus[0].i2c_rc == 0) { + if (saa7164_api_read_eeprom(dev, &eeprom[0], + sizeof(eeprom)) < 0) + return; + } + + switch (dev->board) { + case SAA7164_BOARD_HAUPPAUGE_HVR2200: + case SAA7164_BOARD_HAUPPAUGE_HVR2200_2: + case SAA7164_BOARD_HAUPPAUGE_HVR2200_3: + case SAA7164_BOARD_HAUPPAUGE_HVR2200_4: + case SAA7164_BOARD_HAUPPAUGE_HVR2200_5: + case SAA7164_BOARD_HAUPPAUGE_HVR2250: + case SAA7164_BOARD_HAUPPAUGE_HVR2250_2: + case SAA7164_BOARD_HAUPPAUGE_HVR2250_3: + case SAA7164_BOARD_HAUPPAUGE_HVR2255proto: + case SAA7164_BOARD_HAUPPAUGE_HVR2255: + case SAA7164_BOARD_HAUPPAUGE_HVR2205: + hauppauge_eeprom(dev, &eeprom[0]); + break; + } +} + +/* With most other drivers, the kernel expects to communicate with subdrivers + * through i2c. This bridge does not allow that, it does not expose any direct + * access to I2C. Instead we have to communicate through the device f/w for + * register access to 'processing units'. Each unit has a unique + * id, regardless of how the physical implementation occurs across + * the three physical i2c buses. The being said if we want leverge of + * the existing kernel drivers for tuners and demods we have to 'speak i2c', + * to this bridge implements 3 virtual i2c buses. This is a helper function + * for those. + * + * Description: Translate the kernels notion of an i2c address and bus into + * the appropriate unitid. + */ +int saa7164_i2caddr_to_unitid(struct saa7164_i2c *bus, int addr) +{ + /* For a given bus and i2c device address, return the saa7164 unique + * unitid. < 0 on error */ + + struct saa7164_dev *dev = bus->dev; + struct saa7164_unit *unit; + int i; + + for (i = 0; i < SAA7164_MAX_UNITS; i++) { + unit = &saa7164_boards[dev->board].unit[i]; + + if (unit->type == SAA7164_UNIT_UNDEFINED) + continue; + if ((bus->nr == unit->i2c_bus_nr) && + (addr == unit->i2c_bus_addr)) + return unit->id; + } + + return -1; +} + +/* The 7164 API needs to know the i2c register length in advance. + * this is a helper function. Based on a specific chip addr and bus return the + * reg length. + */ +int saa7164_i2caddr_to_reglen(struct saa7164_i2c *bus, int addr) +{ + /* For a given bus and i2c device address, return the + * saa7164 registry address width. < 0 on error + */ + + struct saa7164_dev *dev = bus->dev; + struct saa7164_unit *unit; + int i; + + for (i = 0; i < SAA7164_MAX_UNITS; i++) { + unit = &saa7164_boards[dev->board].unit[i]; + + if (unit->type == SAA7164_UNIT_UNDEFINED) + continue; + + if ((bus->nr == unit->i2c_bus_nr) && + (addr == unit->i2c_bus_addr)) + return unit->i2c_reg_len; + } + + return -1; +} +/* TODO: implement a 'findeeprom' functio like the above and fix any other + * eeprom related todo's in -api.c. + */ + +/* Translate a unitid into a x readable device name, for display purposes. */ +char *saa7164_unitid_name(struct saa7164_dev *dev, u8 unitid) +{ + char *undefed = "UNDEFINED"; + char *bridge = "BRIDGE"; + struct saa7164_unit *unit; + int i; + + if (unitid == 0) + return bridge; + + for (i = 0; i < SAA7164_MAX_UNITS; i++) { + unit = &saa7164_boards[dev->board].unit[i]; + + if (unit->type == SAA7164_UNIT_UNDEFINED) + continue; + + if (unitid == unit->id) + return unit->name; + } + + return undefed; +} + diff --git a/drivers/media/pci/saa7164/saa7164-cmd.c b/drivers/media/pci/saa7164/saa7164-cmd.c new file mode 100644 index 000000000..a95662776 --- /dev/null +++ b/drivers/media/pci/saa7164/saa7164-cmd.c @@ -0,0 +1,540 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the NXP SAA7164 PCIe bridge + * + * Copyright (c) 2010-2015 Steven Toth + */ + +#include + +#include "saa7164.h" + +static int saa7164_cmd_alloc_seqno(struct saa7164_dev *dev) +{ + int i, ret = -1; + + mutex_lock(&dev->lock); + for (i = 0; i < SAA_CMD_MAX_MSG_UNITS; i++) { + if (dev->cmds[i].inuse == 0) { + dev->cmds[i].inuse = 1; + dev->cmds[i].signalled = 0; + dev->cmds[i].timeout = 0; + ret = dev->cmds[i].seqno; + break; + } + } + mutex_unlock(&dev->lock); + + return ret; +} + +static void saa7164_cmd_free_seqno(struct saa7164_dev *dev, u8 seqno) +{ + mutex_lock(&dev->lock); + if ((dev->cmds[seqno].inuse == 1) && + (dev->cmds[seqno].seqno == seqno)) { + dev->cmds[seqno].inuse = 0; + dev->cmds[seqno].signalled = 0; + dev->cmds[seqno].timeout = 0; + } + mutex_unlock(&dev->lock); +} + +static void saa7164_cmd_timeout_seqno(struct saa7164_dev *dev, u8 seqno) +{ + mutex_lock(&dev->lock); + if ((dev->cmds[seqno].inuse == 1) && + (dev->cmds[seqno].seqno == seqno)) { + dev->cmds[seqno].timeout = 1; + } + mutex_unlock(&dev->lock); +} + +static u32 saa7164_cmd_timeout_get(struct saa7164_dev *dev, u8 seqno) +{ + int ret = 0; + + mutex_lock(&dev->lock); + if ((dev->cmds[seqno].inuse == 1) && + (dev->cmds[seqno].seqno == seqno)) { + ret = dev->cmds[seqno].timeout; + } + mutex_unlock(&dev->lock); + + return ret; +} + +/* Commands to the f/w get marshelled to/from this code then onto the PCI + * -bus/c running buffer. */ +int saa7164_irq_dequeue(struct saa7164_dev *dev) +{ + int ret = SAA_OK, i = 0; + u32 timeout; + wait_queue_head_t *q = NULL; + u8 tmp[512]; + dprintk(DBGLVL_CMD, "%s()\n", __func__); + + /* While any outstand message on the bus exists... */ + do { + + /* Peek the msg bus */ + struct tmComResInfo tRsp = { 0, 0, 0, 0, 0, 0 }; + ret = saa7164_bus_get(dev, &tRsp, NULL, 1); + if (ret != SAA_OK) + break; + + q = &dev->cmds[tRsp.seqno].wait; + timeout = saa7164_cmd_timeout_get(dev, tRsp.seqno); + dprintk(DBGLVL_CMD, "%s() timeout = %d\n", __func__, timeout); + if (!timeout) { + dprintk(DBGLVL_CMD, + "%s() signalled seqno(%d) (for dequeue)\n", + __func__, tRsp.seqno); + dev->cmds[tRsp.seqno].signalled = 1; + wake_up(q); + } else { + printk(KERN_ERR + "%s() found timed out command on the bus\n", + __func__); + + /* Clean the bus */ + ret = saa7164_bus_get(dev, &tRsp, &tmp, 0); + printk(KERN_ERR "%s() ret = %x\n", __func__, ret); + if (ret == SAA_ERR_EMPTY) + /* Someone else already fetched the response */ + return SAA_OK; + + if (ret != SAA_OK) + return ret; + } + + /* It's unlikely to have more than 4 or 5 pending messages, + * ensure we exit at some point regardless. + */ + } while (i++ < 32); + + return ret; +} + +/* Commands to the f/w get marshelled to/from this code then onto the PCI + * -bus/c running buffer. */ +static int saa7164_cmd_dequeue(struct saa7164_dev *dev) +{ + int ret; + u32 timeout; + wait_queue_head_t *q = NULL; + u8 tmp[512]; + dprintk(DBGLVL_CMD, "%s()\n", __func__); + + while (true) { + + struct tmComResInfo tRsp = { 0, 0, 0, 0, 0, 0 }; + ret = saa7164_bus_get(dev, &tRsp, NULL, 1); + if (ret == SAA_ERR_EMPTY) + return SAA_OK; + + if (ret != SAA_OK) + return ret; + + q = &dev->cmds[tRsp.seqno].wait; + timeout = saa7164_cmd_timeout_get(dev, tRsp.seqno); + dprintk(DBGLVL_CMD, "%s() timeout = %d\n", __func__, timeout); + if (timeout) { + printk(KERN_ERR "found timed out command on the bus\n"); + + /* Clean the bus */ + ret = saa7164_bus_get(dev, &tRsp, &tmp, 0); + printk(KERN_ERR "ret = %x\n", ret); + if (ret == SAA_ERR_EMPTY) + /* Someone else already fetched the response */ + return SAA_OK; + + if (ret != SAA_OK) + return ret; + + if (tRsp.flags & PVC_CMDFLAG_CONTINUE) + printk(KERN_ERR "split response\n"); + else + saa7164_cmd_free_seqno(dev, tRsp.seqno); + + printk(KERN_ERR " timeout continue\n"); + continue; + } + + dprintk(DBGLVL_CMD, "%s() signalled seqno(%d) (for dequeue)\n", + __func__, tRsp.seqno); + dev->cmds[tRsp.seqno].signalled = 1; + wake_up(q); + return SAA_OK; + } +} + +static int saa7164_cmd_set(struct saa7164_dev *dev, struct tmComResInfo *msg, + void *buf) +{ + struct tmComResBusInfo *bus = &dev->bus; + u8 cmd_sent; + u16 size, idx; + u32 cmds; + void *tmp; + int ret = -1; + + if (!msg) { + printk(KERN_ERR "%s() !msg\n", __func__); + return SAA_ERR_BAD_PARAMETER; + } + + mutex_lock(&dev->cmds[msg->id].lock); + + size = msg->size; + cmds = size / bus->m_wMaxReqSize; + if (size % bus->m_wMaxReqSize == 0) + cmds -= 1; + + cmd_sent = 0; + + /* Split the request into smaller chunks */ + for (idx = 0; idx < cmds; idx++) { + + msg->flags |= SAA_CMDFLAG_CONTINUE; + msg->size = bus->m_wMaxReqSize; + tmp = buf + idx * bus->m_wMaxReqSize; + + ret = saa7164_bus_set(dev, msg, tmp); + if (ret != SAA_OK) { + printk(KERN_ERR "%s() set failed %d\n", __func__, ret); + + if (cmd_sent) { + ret = SAA_ERR_BUSY; + goto out; + } + ret = SAA_ERR_OVERFLOW; + goto out; + } + cmd_sent = 1; + } + + /* If not the last command... */ + if (idx != 0) + msg->flags &= ~SAA_CMDFLAG_CONTINUE; + + msg->size = size - idx * bus->m_wMaxReqSize; + + ret = saa7164_bus_set(dev, msg, buf + idx * bus->m_wMaxReqSize); + if (ret != SAA_OK) { + printk(KERN_ERR "%s() set last failed %d\n", __func__, ret); + + if (cmd_sent) { + ret = SAA_ERR_BUSY; + goto out; + } + ret = SAA_ERR_OVERFLOW; + goto out; + } + ret = SAA_OK; + +out: + mutex_unlock(&dev->cmds[msg->id].lock); + return ret; +} + +/* Wait for a signal event, without holding a mutex. Either return TIMEOUT if + * the event never occurred, or SAA_OK if it was signaled during the wait. + */ +static int saa7164_cmd_wait(struct saa7164_dev *dev, u8 seqno) +{ + wait_queue_head_t *q = NULL; + int ret = SAA_BUS_TIMEOUT; + unsigned long stamp; + int r; + + if (saa_debug >= 4) + saa7164_bus_dump(dev); + + dprintk(DBGLVL_CMD, "%s(seqno=%d)\n", __func__, seqno); + + mutex_lock(&dev->lock); + if ((dev->cmds[seqno].inuse == 1) && + (dev->cmds[seqno].seqno == seqno)) { + q = &dev->cmds[seqno].wait; + } + mutex_unlock(&dev->lock); + + if (q) { + /* If we haven't been signalled we need to wait */ + if (dev->cmds[seqno].signalled == 0) { + stamp = jiffies; + dprintk(DBGLVL_CMD, + "%s(seqno=%d) Waiting (signalled=%d)\n", + __func__, seqno, dev->cmds[seqno].signalled); + + /* Wait for signalled to be flagged or timeout */ + /* In a highly stressed system this can easily extend + * into multiple seconds before the deferred worker + * is scheduled, and we're woken up via signal. + * We typically are signalled in < 50ms but it can + * take MUCH longer. + */ + wait_event_timeout(*q, dev->cmds[seqno].signalled, + (HZ * waitsecs)); + r = time_before(jiffies, stamp + (HZ * waitsecs)); + if (r) + ret = SAA_OK; + else + saa7164_cmd_timeout_seqno(dev, seqno); + + dprintk(DBGLVL_CMD, "%s(seqno=%d) Waiting res = %d (signalled=%d)\n", + __func__, seqno, r, + dev->cmds[seqno].signalled); + } else + ret = SAA_OK; + } else + printk(KERN_ERR "%s(seqno=%d) seqno is invalid\n", + __func__, seqno); + + return ret; +} + +int saa7164_cmd_send(struct saa7164_dev *dev, u8 id, enum tmComResCmd command, + u16 controlselector, u16 size, void *buf) +{ + struct tmComResInfo command_t, *pcommand_t; + struct tmComResInfo response_t, *presponse_t; + u8 errdata[256]; + u16 resp_dsize; + u16 data_recd; + u32 loop; + int ret; + int safety = 0; + + dprintk(DBGLVL_CMD, "%s(unitid = %s (%d) , command = 0x%x, sel = 0x%x)\n", + __func__, saa7164_unitid_name(dev, id), id, + command, controlselector); + + if ((size == 0) || (buf == NULL)) { + printk(KERN_ERR "%s() Invalid param\n", __func__); + return SAA_ERR_BAD_PARAMETER; + } + + /* Prepare some basic command/response structures */ + memset(&command_t, 0, sizeof(command_t)); + memset(&response_t, 0, sizeof(response_t)); + pcommand_t = &command_t; + presponse_t = &response_t; + command_t.id = id; + command_t.command = command; + command_t.controlselector = controlselector; + command_t.size = size; + + /* Allocate a unique sequence number */ + ret = saa7164_cmd_alloc_seqno(dev); + if (ret < 0) { + printk(KERN_ERR "%s() No free sequences\n", __func__); + ret = SAA_ERR_NO_RESOURCES; + goto out; + } + + command_t.seqno = (u8)ret; + + /* Send Command */ + resp_dsize = size; + pcommand_t->size = size; + + dprintk(DBGLVL_CMD, "%s() pcommand_t.seqno = %d\n", + __func__, pcommand_t->seqno); + + dprintk(DBGLVL_CMD, "%s() pcommand_t.size = %d\n", + __func__, pcommand_t->size); + + ret = saa7164_cmd_set(dev, pcommand_t, buf); + if (ret != SAA_OK) { + printk(KERN_ERR "%s() set command failed %d\n", __func__, ret); + + if (ret != SAA_ERR_BUSY) + saa7164_cmd_free_seqno(dev, pcommand_t->seqno); + else + /* Flag a timeout, because at least one + * command was sent */ + saa7164_cmd_timeout_seqno(dev, pcommand_t->seqno); + + goto out; + } + + /* With split responses we have to collect the msgs piece by piece */ + data_recd = 0; + loop = 1; + while (loop) { + dprintk(DBGLVL_CMD, "%s() loop\n", __func__); + + ret = saa7164_cmd_wait(dev, pcommand_t->seqno); + dprintk(DBGLVL_CMD, "%s() loop ret = %d\n", __func__, ret); + + /* if power is down and this is not a power command ... */ + + if (ret == SAA_BUS_TIMEOUT) { + printk(KERN_ERR "Event timed out\n"); + saa7164_cmd_timeout_seqno(dev, pcommand_t->seqno); + return ret; + } + + if (ret != SAA_OK) { + printk(KERN_ERR "spurious error\n"); + return ret; + } + + /* Peek response */ + ret = saa7164_bus_get(dev, presponse_t, NULL, 1); + if (ret == SAA_ERR_EMPTY) { + dprintk(4, "%s() SAA_ERR_EMPTY\n", __func__); + continue; + } + if (ret != SAA_OK) { + printk(KERN_ERR "peek failed\n"); + return ret; + } + + dprintk(DBGLVL_CMD, "%s() presponse_t->seqno = %d\n", + __func__, presponse_t->seqno); + + dprintk(DBGLVL_CMD, "%s() presponse_t->flags = 0x%x\n", + __func__, presponse_t->flags); + + dprintk(DBGLVL_CMD, "%s() presponse_t->size = %d\n", + __func__, presponse_t->size); + + /* Check if the response was for our command */ + if (presponse_t->seqno != pcommand_t->seqno) { + + dprintk(DBGLVL_CMD, + "wrong event: seqno = %d, expected seqno = %d, will dequeue regardless\n", + presponse_t->seqno, pcommand_t->seqno); + + ret = saa7164_cmd_dequeue(dev); + if (ret != SAA_OK) { + printk(KERN_ERR "dequeue failed, ret = %d\n", + ret); + if (safety++ > 16) { + printk(KERN_ERR + "dequeue exceeded, safety exit\n"); + return SAA_ERR_BUSY; + } + } + + continue; + } + + if ((presponse_t->flags & PVC_RESPONSEFLAG_ERROR) != 0) { + + memset(&errdata[0], 0, sizeof(errdata)); + + ret = saa7164_bus_get(dev, presponse_t, &errdata[0], 0); + if (ret != SAA_OK) { + printk(KERN_ERR "get error(2)\n"); + return ret; + } + + saa7164_cmd_free_seqno(dev, pcommand_t->seqno); + + dprintk(DBGLVL_CMD, "%s() errdata %02x%02x%02x%02x\n", + __func__, errdata[0], errdata[1], errdata[2], + errdata[3]); + + /* Map error codes */ + dprintk(DBGLVL_CMD, "%s() cmd, error code = 0x%x\n", + __func__, errdata[0]); + + switch (errdata[0]) { + case PVC_ERRORCODE_INVALID_COMMAND: + dprintk(DBGLVL_CMD, "%s() INVALID_COMMAND\n", + __func__); + ret = SAA_ERR_INVALID_COMMAND; + break; + case PVC_ERRORCODE_INVALID_DATA: + dprintk(DBGLVL_CMD, "%s() INVALID_DATA\n", + __func__); + ret = SAA_ERR_BAD_PARAMETER; + break; + case PVC_ERRORCODE_TIMEOUT: + dprintk(DBGLVL_CMD, "%s() TIMEOUT\n", __func__); + ret = SAA_ERR_TIMEOUT; + break; + case PVC_ERRORCODE_NAK: + dprintk(DBGLVL_CMD, "%s() NAK\n", __func__); + ret = SAA_ERR_NULL_PACKET; + break; + case PVC_ERRORCODE_UNKNOWN: + case PVC_ERRORCODE_INVALID_CONTROL: + dprintk(DBGLVL_CMD, + "%s() UNKNOWN OR INVALID CONTROL\n", + __func__); + ret = SAA_ERR_NOT_SUPPORTED; + break; + default: + dprintk(DBGLVL_CMD, "%s() UNKNOWN\n", __func__); + ret = SAA_ERR_NOT_SUPPORTED; + } + + /* See of other commands are on the bus */ + if (saa7164_cmd_dequeue(dev) != SAA_OK) + printk(KERN_ERR "dequeue(2) failed\n"); + + return ret; + } + + /* If response is invalid */ + if ((presponse_t->id != pcommand_t->id) || + (presponse_t->command != pcommand_t->command) || + (presponse_t->controlselector != + pcommand_t->controlselector) || + (((resp_dsize - data_recd) != presponse_t->size) && + !(presponse_t->flags & PVC_CMDFLAG_CONTINUE)) || + ((resp_dsize - data_recd) < presponse_t->size)) { + + /* Invalid */ + dprintk(DBGLVL_CMD, "%s() Invalid\n", __func__); + ret = saa7164_bus_get(dev, presponse_t, NULL, 0); + if (ret != SAA_OK) { + printk(KERN_ERR "get failed\n"); + return ret; + } + + /* See of other commands are on the bus */ + if (saa7164_cmd_dequeue(dev) != SAA_OK) + printk(KERN_ERR "dequeue(3) failed\n"); + continue; + } + + /* OK, now we're actually getting out correct response */ + ret = saa7164_bus_get(dev, presponse_t, buf + data_recd, 0); + if (ret != SAA_OK) { + printk(KERN_ERR "get failed\n"); + return ret; + } + + data_recd = presponse_t->size + data_recd; + if (resp_dsize == data_recd) { + dprintk(DBGLVL_CMD, "%s() Resp recd\n", __func__); + break; + } + + /* See of other commands are on the bus */ + if (saa7164_cmd_dequeue(dev) != SAA_OK) + printk(KERN_ERR "dequeue(3) failed\n"); + } /* (loop) */ + + /* Release the sequence number allocation */ + saa7164_cmd_free_seqno(dev, pcommand_t->seqno); + + /* if powerdown signal all pending commands */ + + dprintk(DBGLVL_CMD, "%s() Calling dequeue then exit\n", __func__); + + /* See of other commands are on the bus */ + if (saa7164_cmd_dequeue(dev) != SAA_OK) + printk(KERN_ERR "dequeue(4) failed\n"); + + ret = SAA_OK; +out: + return ret; +} + diff --git a/drivers/media/pci/saa7164/saa7164-core.c b/drivers/media/pci/saa7164/saa7164-core.c new file mode 100644 index 000000000..ac5eb6b92 --- /dev/null +++ b/drivers/media/pci/saa7164/saa7164-core.c @@ -0,0 +1,1575 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the NXP SAA7164 PCIe bridge + * + * Copyright (c) 2010-2015 Steven Toth + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "saa7164.h" + +MODULE_DESCRIPTION("Driver for NXP SAA7164 based TV cards"); +MODULE_AUTHOR("Steven Toth "); +MODULE_LICENSE("GPL"); + +/* + * 1 Basic + * 2 + * 4 i2c + * 8 api + * 16 cmd + * 32 bus + */ + +unsigned int saa_debug; +module_param_named(debug, saa_debug, int, 0644); +MODULE_PARM_DESC(debug, "enable debug messages"); + +static unsigned int fw_debug; +module_param(fw_debug, int, 0644); +MODULE_PARM_DESC(fw_debug, "Firmware debug level def:2"); + +unsigned int encoder_buffers = SAA7164_MAX_ENCODER_BUFFERS; +module_param(encoder_buffers, int, 0644); +MODULE_PARM_DESC(encoder_buffers, "Total buffers in read queue 16-512 def:64"); + +unsigned int vbi_buffers = SAA7164_MAX_VBI_BUFFERS; +module_param(vbi_buffers, int, 0644); +MODULE_PARM_DESC(vbi_buffers, "Total buffers in read queue 16-512 def:64"); + +unsigned int waitsecs = 10; +module_param(waitsecs, int, 0644); +MODULE_PARM_DESC(waitsecs, "timeout on firmware messages"); + +static unsigned int card[] = {[0 ... (SAA7164_MAXBOARDS - 1)] = UNSET }; +module_param_array(card, int, NULL, 0444); +MODULE_PARM_DESC(card, "card type"); + +static unsigned int print_histogram = 64; +module_param(print_histogram, int, 0644); +MODULE_PARM_DESC(print_histogram, "print histogram values once"); + +unsigned int crc_checking = 1; +module_param(crc_checking, int, 0644); +MODULE_PARM_DESC(crc_checking, "enable crc sanity checking on buffers"); + +static unsigned int guard_checking = 1; +module_param(guard_checking, int, 0644); +MODULE_PARM_DESC(guard_checking, + "enable dma sanity checking for buffer overruns"); + +static bool enable_msi = true; +module_param(enable_msi, bool, 0444); +MODULE_PARM_DESC(enable_msi, + "enable the use of an msi interrupt if available"); + +static unsigned int saa7164_devcount; + +static DEFINE_MUTEX(devlist); +LIST_HEAD(saa7164_devlist); + +#define INT_SIZE 16 + +static void saa7164_pack_verifier(struct saa7164_buffer *buf) +{ + u8 *p = (u8 *)buf->cpu; + int i; + + for (i = 0; i < buf->actual_size; i += 2048) { + + if ((*(p + i + 0) != 0x00) || (*(p + i + 1) != 0x00) || + (*(p + i + 2) != 0x01) || (*(p + i + 3) != 0xBA)) { + printk(KERN_ERR "No pack at 0x%x\n", i); +#if 0 + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, + p + 1, 32, false); +#endif + } + } +} + +#define FIXED_VIDEO_PID 0xf1 +#define FIXED_AUDIO_PID 0xf2 + +static void saa7164_ts_verifier(struct saa7164_buffer *buf) +{ + struct saa7164_port *port = buf->port; + u32 i; + u8 cc, a; + u16 pid; + u8 *bufcpu = (u8 *)buf->cpu; + + port->sync_errors = 0; + port->v_cc_errors = 0; + port->a_cc_errors = 0; + + for (i = 0; i < buf->actual_size; i += 188) { + if (*(bufcpu + i) != 0x47) + port->sync_errors++; + + /* TODO: Query pid lower 8 bits, ignoring upper bits intensionally */ + pid = ((*(bufcpu + i + 1) & 0x1f) << 8) | *(bufcpu + i + 2); + cc = *(bufcpu + i + 3) & 0x0f; + + if (pid == FIXED_VIDEO_PID) { + a = ((port->last_v_cc + 1) & 0x0f); + if (a != cc) { + printk(KERN_ERR "video cc last = %x current = %x i = %d\n", + port->last_v_cc, cc, i); + port->v_cc_errors++; + } + + port->last_v_cc = cc; + } else + if (pid == FIXED_AUDIO_PID) { + a = ((port->last_a_cc + 1) & 0x0f); + if (a != cc) { + printk(KERN_ERR "audio cc last = %x current = %x i = %d\n", + port->last_a_cc, cc, i); + port->a_cc_errors++; + } + + port->last_a_cc = cc; + } + + } + + /* Only report errors if we've been through this function at least + * once already and the cached cc values are primed. First time through + * always generates errors. + */ + if (port->v_cc_errors && (port->done_first_interrupt > 1)) + printk(KERN_ERR "video pid cc, %d errors\n", port->v_cc_errors); + + if (port->a_cc_errors && (port->done_first_interrupt > 1)) + printk(KERN_ERR "audio pid cc, %d errors\n", port->a_cc_errors); + + if (port->sync_errors && (port->done_first_interrupt > 1)) + printk(KERN_ERR "sync_errors = %d\n", port->sync_errors); + + if (port->done_first_interrupt == 1) + port->done_first_interrupt++; +} + +static void saa7164_histogram_reset(struct saa7164_histogram *hg, char *name) +{ + int i; + + memset(hg, 0, sizeof(struct saa7164_histogram)); + strscpy(hg->name, name, sizeof(hg->name)); + + /* First 30ms x 1ms */ + for (i = 0; i < 30; i++) + hg->counter1[0 + i].val = i; + + /* 30 - 200ms x 10ms */ + for (i = 0; i < 18; i++) + hg->counter1[30 + i].val = 30 + (i * 10); + + /* 200 - 2000ms x 100ms */ + for (i = 0; i < 15; i++) + hg->counter1[48 + i].val = 200 + (i * 200); + + /* Catch all massive value (2secs) */ + hg->counter1[55].val = 2000; + + /* Catch all massive value (4secs) */ + hg->counter1[56].val = 4000; + + /* Catch all massive value (8secs) */ + hg->counter1[57].val = 8000; + + /* Catch all massive value (15secs) */ + hg->counter1[58].val = 15000; + + /* Catch all massive value (30secs) */ + hg->counter1[59].val = 30000; + + /* Catch all massive value (60secs) */ + hg->counter1[60].val = 60000; + + /* Catch all massive value (5mins) */ + hg->counter1[61].val = 300000; + + /* Catch all massive value (15mins) */ + hg->counter1[62].val = 900000; + + /* Catch all massive values (1hr) */ + hg->counter1[63].val = 3600000; +} + +void saa7164_histogram_update(struct saa7164_histogram *hg, u32 val) +{ + int i; + for (i = 0; i < 64; i++) { + if (val <= hg->counter1[i].val) { + hg->counter1[i].count++; + hg->counter1[i].update_time = jiffies; + break; + } + } +} + +static void saa7164_histogram_print(struct saa7164_port *port, + struct saa7164_histogram *hg) +{ + u32 entries = 0; + int i; + + printk(KERN_ERR "Histogram named %s (ms, count, last_update_jiffy)\n", hg->name); + for (i = 0; i < 64; i++) { + if (hg->counter1[i].count == 0) + continue; + + printk(KERN_ERR " %4d %12d %Ld\n", + hg->counter1[i].val, + hg->counter1[i].count, + hg->counter1[i].update_time); + + entries++; + } + printk(KERN_ERR "Total: %d\n", entries); +} + +static void saa7164_work_enchandler_helper(struct saa7164_port *port, int bufnr) +{ + struct saa7164_dev *dev = port->dev; + struct saa7164_buffer *buf = NULL; + struct saa7164_user_buffer *ubuf = NULL; + struct list_head *c, *n; + int i = 0; + u8 *p; + + mutex_lock(&port->dmaqueue_lock); + list_for_each_safe(c, n, &port->dmaqueue.list) { + + buf = list_entry(c, struct saa7164_buffer, list); + if (i++ > port->hwcfg.buffercount) { + printk(KERN_ERR "%s() illegal i count %d\n", + __func__, i); + break; + } + + if (buf->idx == bufnr) { + + /* Found the buffer, deal with it */ + dprintk(DBGLVL_IRQ, "%s() bufnr: %d\n", __func__, bufnr); + + if (crc_checking) { + /* Throw a new checksum on the dma buffer */ + buf->crc = crc32(0, buf->cpu, buf->actual_size); + } + + if (guard_checking) { + p = (u8 *)buf->cpu; + if ((*(p + buf->actual_size + 0) != 0xff) || + (*(p + buf->actual_size + 1) != 0xff) || + (*(p + buf->actual_size + 2) != 0xff) || + (*(p + buf->actual_size + 3) != 0xff) || + (*(p + buf->actual_size + 0x10) != 0xff) || + (*(p + buf->actual_size + 0x11) != 0xff) || + (*(p + buf->actual_size + 0x12) != 0xff) || + (*(p + buf->actual_size + 0x13) != 0xff)) { + printk(KERN_ERR "%s() buf %p guard buffer breach\n", + __func__, buf); +#if 0 + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, + p + buf->actual_size - 32, 64, false); +#endif + } + } + + if ((port->nr != SAA7164_PORT_VBI1) && (port->nr != SAA7164_PORT_VBI2)) { + /* Validate the incoming buffer content */ + if (port->encoder_params.stream_type == V4L2_MPEG_STREAM_TYPE_MPEG2_TS) + saa7164_ts_verifier(buf); + else if (port->encoder_params.stream_type == V4L2_MPEG_STREAM_TYPE_MPEG2_PS) + saa7164_pack_verifier(buf); + } + + /* find a free user buffer and clone to it */ + if (!list_empty(&port->list_buf_free.list)) { + + /* Pull the first buffer from the used list */ + ubuf = list_first_entry(&port->list_buf_free.list, + struct saa7164_user_buffer, list); + + if (buf->actual_size <= ubuf->actual_size) { + + memcpy(ubuf->data, buf->cpu, ubuf->actual_size); + + if (crc_checking) { + /* Throw a new checksum on the read buffer */ + ubuf->crc = crc32(0, ubuf->data, ubuf->actual_size); + } + + /* Requeue the buffer on the free list */ + ubuf->pos = 0; + + list_move_tail(&ubuf->list, + &port->list_buf_used.list); + + /* Flag any userland waiters */ + wake_up_interruptible(&port->wait_read); + + } else { + printk(KERN_ERR "buf %p bufsize fails match\n", buf); + } + + } else + printk(KERN_ERR "encirq no free buffers, increase param encoder_buffers\n"); + + /* Ensure offset into buffer remains 0, fill buffer + * with known bad data. We check for this data at a later point + * in time. */ + saa7164_buffer_zero_offsets(port, bufnr); + memset(buf->cpu, 0xff, buf->pci_size); + if (crc_checking) { + /* Throw yet aanother new checksum on the dma buffer */ + buf->crc = crc32(0, buf->cpu, buf->actual_size); + } + + break; + } + } + mutex_unlock(&port->dmaqueue_lock); +} + +static void saa7164_work_enchandler(struct work_struct *w) +{ + struct saa7164_port *port = + container_of(w, struct saa7164_port, workenc); + struct saa7164_dev *dev = port->dev; + + u32 wp, mcb, rp; + + port->last_svc_msecs_diff = port->last_svc_msecs; + port->last_svc_msecs = jiffies_to_msecs(jiffies); + + port->last_svc_msecs_diff = port->last_svc_msecs - + port->last_svc_msecs_diff; + + saa7164_histogram_update(&port->svc_interval, + port->last_svc_msecs_diff); + + port->last_irq_svc_msecs_diff = port->last_svc_msecs - + port->last_irq_msecs; + + saa7164_histogram_update(&port->irq_svc_interval, + port->last_irq_svc_msecs_diff); + + dprintk(DBGLVL_IRQ, + "%s() %Ldms elapsed irq->deferred %Ldms wp: %d rp: %d\n", + __func__, + port->last_svc_msecs_diff, + port->last_irq_svc_msecs_diff, + port->last_svc_wp, + port->last_svc_rp + ); + + /* Current write position */ + wp = saa7164_readl(port->bufcounter); + if (wp > (port->hwcfg.buffercount - 1)) { + printk(KERN_ERR "%s() illegal buf count %d\n", __func__, wp); + return; + } + + /* Most current complete buffer */ + if (wp == 0) + mcb = (port->hwcfg.buffercount - 1); + else + mcb = wp - 1; + + while (1) { + if (port->done_first_interrupt == 0) { + port->done_first_interrupt++; + rp = mcb; + } else + rp = (port->last_svc_rp + 1) % 8; + + if (rp > (port->hwcfg.buffercount - 1)) { + printk(KERN_ERR "%s() illegal rp count %d\n", __func__, rp); + break; + } + + saa7164_work_enchandler_helper(port, rp); + port->last_svc_rp = rp; + + if (rp == mcb) + break; + } + + /* TODO: Convert this into a /proc/saa7164 style readable file */ + if (print_histogram == port->nr) { + saa7164_histogram_print(port, &port->irq_interval); + saa7164_histogram_print(port, &port->svc_interval); + saa7164_histogram_print(port, &port->irq_svc_interval); + saa7164_histogram_print(port, &port->read_interval); + saa7164_histogram_print(port, &port->poll_interval); + /* TODO: fix this to preserve any previous state */ + print_histogram = 64 + port->nr; + } +} + +static void saa7164_work_vbihandler(struct work_struct *w) +{ + struct saa7164_port *port = + container_of(w, struct saa7164_port, workenc); + struct saa7164_dev *dev = port->dev; + + u32 wp, mcb, rp; + + port->last_svc_msecs_diff = port->last_svc_msecs; + port->last_svc_msecs = jiffies_to_msecs(jiffies); + port->last_svc_msecs_diff = port->last_svc_msecs - + port->last_svc_msecs_diff; + + saa7164_histogram_update(&port->svc_interval, + port->last_svc_msecs_diff); + + port->last_irq_svc_msecs_diff = port->last_svc_msecs - + port->last_irq_msecs; + + saa7164_histogram_update(&port->irq_svc_interval, + port->last_irq_svc_msecs_diff); + + dprintk(DBGLVL_IRQ, + "%s() %Ldms elapsed irq->deferred %Ldms wp: %d rp: %d\n", + __func__, + port->last_svc_msecs_diff, + port->last_irq_svc_msecs_diff, + port->last_svc_wp, + port->last_svc_rp + ); + + /* Current write position */ + wp = saa7164_readl(port->bufcounter); + if (wp > (port->hwcfg.buffercount - 1)) { + printk(KERN_ERR "%s() illegal buf count %d\n", __func__, wp); + return; + } + + /* Most current complete buffer */ + if (wp == 0) + mcb = (port->hwcfg.buffercount - 1); + else + mcb = wp - 1; + + while (1) { + if (port->done_first_interrupt == 0) { + port->done_first_interrupt++; + rp = mcb; + } else + rp = (port->last_svc_rp + 1) % 8; + + if (rp > (port->hwcfg.buffercount - 1)) { + printk(KERN_ERR "%s() illegal rp count %d\n", __func__, rp); + break; + } + + saa7164_work_enchandler_helper(port, rp); + port->last_svc_rp = rp; + + if (rp == mcb) + break; + } + + /* TODO: Convert this into a /proc/saa7164 style readable file */ + if (print_histogram == port->nr) { + saa7164_histogram_print(port, &port->irq_interval); + saa7164_histogram_print(port, &port->svc_interval); + saa7164_histogram_print(port, &port->irq_svc_interval); + saa7164_histogram_print(port, &port->read_interval); + saa7164_histogram_print(port, &port->poll_interval); + /* TODO: fix this to preserve any previous state */ + print_histogram = 64 + port->nr; + } +} + +static void saa7164_work_cmdhandler(struct work_struct *w) +{ + struct saa7164_dev *dev = container_of(w, struct saa7164_dev, workcmd); + + /* Wake up any complete commands */ + saa7164_irq_dequeue(dev); +} + +static void saa7164_buffer_deliver(struct saa7164_buffer *buf) +{ + struct saa7164_port *port = buf->port; + + /* Feed the transport payload into the kernel demux */ + dvb_dmx_swfilter_packets(&port->dvb.demux, (u8 *)buf->cpu, + SAA7164_TS_NUMBER_OF_LINES); + +} + +static irqreturn_t saa7164_irq_vbi(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + + /* Store old time */ + port->last_irq_msecs_diff = port->last_irq_msecs; + + /* Collect new stats */ + port->last_irq_msecs = jiffies_to_msecs(jiffies); + + /* Calculate stats */ + port->last_irq_msecs_diff = port->last_irq_msecs - + port->last_irq_msecs_diff; + + saa7164_histogram_update(&port->irq_interval, + port->last_irq_msecs_diff); + + dprintk(DBGLVL_IRQ, "%s() %Ldms elapsed\n", __func__, + port->last_irq_msecs_diff); + + /* Tis calls the vbi irq handler */ + schedule_work(&port->workenc); + return 0; +} + +static irqreturn_t saa7164_irq_encoder(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + + /* Store old time */ + port->last_irq_msecs_diff = port->last_irq_msecs; + + /* Collect new stats */ + port->last_irq_msecs = jiffies_to_msecs(jiffies); + + /* Calculate stats */ + port->last_irq_msecs_diff = port->last_irq_msecs - + port->last_irq_msecs_diff; + + saa7164_histogram_update(&port->irq_interval, + port->last_irq_msecs_diff); + + dprintk(DBGLVL_IRQ, "%s() %Ldms elapsed\n", __func__, + port->last_irq_msecs_diff); + + schedule_work(&port->workenc); + return 0; +} + +static irqreturn_t saa7164_irq_ts(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct saa7164_buffer *buf; + struct list_head *c, *n; + int wp, i = 0, rp; + + /* Find the current write point from the hardware */ + wp = saa7164_readl(port->bufcounter); + + BUG_ON(wp > (port->hwcfg.buffercount - 1)); + + /* Find the previous buffer to the current write point */ + if (wp == 0) + rp = (port->hwcfg.buffercount - 1); + else + rp = wp - 1; + + /* Lookup the WP in the buffer list */ + /* TODO: turn this into a worker thread */ + list_for_each_safe(c, n, &port->dmaqueue.list) { + buf = list_entry(c, struct saa7164_buffer, list); + BUG_ON(i > port->hwcfg.buffercount); + i++; + + if (buf->idx == rp) { + /* Found the buffer, deal with it */ + dprintk(DBGLVL_IRQ, "%s() wp: %d processing: %d\n", + __func__, wp, rp); + saa7164_buffer_deliver(buf); + break; + } + + } + return 0; +} + +/* Primary IRQ handler and dispatch mechanism */ +static irqreturn_t saa7164_irq(int irq, void *dev_id) +{ + struct saa7164_dev *dev = dev_id; + struct saa7164_port *porta, *portb, *portc, *portd, *porte, *portf; + + u32 intid, intstat[INT_SIZE/4]; + int i, handled = 0, bit; + + if (dev == NULL) { + printk(KERN_ERR "%s() No device specified\n", __func__); + handled = 0; + goto out; + } + + porta = &dev->ports[SAA7164_PORT_TS1]; + portb = &dev->ports[SAA7164_PORT_TS2]; + portc = &dev->ports[SAA7164_PORT_ENC1]; + portd = &dev->ports[SAA7164_PORT_ENC2]; + porte = &dev->ports[SAA7164_PORT_VBI1]; + portf = &dev->ports[SAA7164_PORT_VBI2]; + + /* Check that the hardware is accessible. If the status bytes are + * 0xFF then the device is not accessible, the IRQ belongs + * to another driver. + * 4 x u32 interrupt registers. + */ + for (i = 0; i < INT_SIZE/4; i++) { + + /* TODO: Convert into saa7164_readl() */ + /* Read the 4 hardware interrupt registers */ + intstat[i] = saa7164_readl(dev->int_status + (i * 4)); + + if (intstat[i]) + handled = 1; + } + if (handled == 0) + goto out; + + /* For each of the HW interrupt registers */ + for (i = 0; i < INT_SIZE/4; i++) { + + if (intstat[i]) { + /* Each function of the board has it's own interruptid. + * Find the function that triggered then call + * it's handler. + */ + for (bit = 0; bit < 32; bit++) { + + if (((intstat[i] >> bit) & 0x00000001) == 0) + continue; + + /* Calculate the interrupt id (0x00 to 0x7f) */ + + intid = (i * 32) + bit; + if (intid == dev->intfdesc.bInterruptId) { + /* A response to an cmd/api call */ + schedule_work(&dev->workcmd); + } else if (intid == porta->hwcfg.interruptid) { + + /* Transport path 1 */ + saa7164_irq_ts(porta); + + } else if (intid == portb->hwcfg.interruptid) { + + /* Transport path 2 */ + saa7164_irq_ts(portb); + + } else if (intid == portc->hwcfg.interruptid) { + + /* Encoder path 1 */ + saa7164_irq_encoder(portc); + + } else if (intid == portd->hwcfg.interruptid) { + + /* Encoder path 2 */ + saa7164_irq_encoder(portd); + + } else if (intid == porte->hwcfg.interruptid) { + + /* VBI path 1 */ + saa7164_irq_vbi(porte); + + } else if (intid == portf->hwcfg.interruptid) { + + /* VBI path 2 */ + saa7164_irq_vbi(portf); + + } else { + /* Find the function */ + dprintk(DBGLVL_IRQ, + "%s() unhandled interrupt reg 0x%x bit 0x%x intid = 0x%x\n", + __func__, i, bit, intid); + } + } + + /* Ack it */ + saa7164_writel(dev->int_ack + (i * 4), intstat[i]); + + } + } +out: + return IRQ_RETVAL(handled); +} + +void saa7164_getfirmwarestatus(struct saa7164_dev *dev) +{ + struct saa7164_fw_status *s = &dev->fw_status; + + dev->fw_status.status = saa7164_readl(SAA_DEVICE_SYSINIT_STATUS); + dev->fw_status.mode = saa7164_readl(SAA_DEVICE_SYSINIT_MODE); + dev->fw_status.spec = saa7164_readl(SAA_DEVICE_SYSINIT_SPEC); + dev->fw_status.inst = saa7164_readl(SAA_DEVICE_SYSINIT_INST); + dev->fw_status.cpuload = saa7164_readl(SAA_DEVICE_SYSINIT_CPULOAD); + dev->fw_status.remainheap = + saa7164_readl(SAA_DEVICE_SYSINIT_REMAINHEAP); + + dprintk(1, "Firmware status:\n"); + dprintk(1, " .status = 0x%08x\n", s->status); + dprintk(1, " .mode = 0x%08x\n", s->mode); + dprintk(1, " .spec = 0x%08x\n", s->spec); + dprintk(1, " .inst = 0x%08x\n", s->inst); + dprintk(1, " .cpuload = 0x%08x\n", s->cpuload); + dprintk(1, " .remainheap = 0x%08x\n", s->remainheap); +} + +u32 saa7164_getcurrentfirmwareversion(struct saa7164_dev *dev) +{ + u32 reg; + + reg = saa7164_readl(SAA_DEVICE_VERSION); + dprintk(1, "Device running firmware version %d.%d.%d.%d (0x%x)\n", + (reg & 0x0000fc00) >> 10, + (reg & 0x000003e0) >> 5, + (reg & 0x0000001f), + (reg & 0xffff0000) >> 16, + reg); + + return reg; +} + +/* TODO: Debugging func, remove */ +void saa7164_dumpregs(struct saa7164_dev *dev, u32 addr) +{ + int i; + + dprintk(1, "--------------------> 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n"); + + for (i = 0; i < 0x100; i += 16) + dprintk(1, "region0[0x%08x] = %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + i, + (u8)saa7164_readb(addr + i + 0), + (u8)saa7164_readb(addr + i + 1), + (u8)saa7164_readb(addr + i + 2), + (u8)saa7164_readb(addr + i + 3), + (u8)saa7164_readb(addr + i + 4), + (u8)saa7164_readb(addr + i + 5), + (u8)saa7164_readb(addr + i + 6), + (u8)saa7164_readb(addr + i + 7), + (u8)saa7164_readb(addr + i + 8), + (u8)saa7164_readb(addr + i + 9), + (u8)saa7164_readb(addr + i + 10), + (u8)saa7164_readb(addr + i + 11), + (u8)saa7164_readb(addr + i + 12), + (u8)saa7164_readb(addr + i + 13), + (u8)saa7164_readb(addr + i + 14), + (u8)saa7164_readb(addr + i + 15) + ); +} + +static void saa7164_dump_hwdesc(struct saa7164_dev *dev) +{ + dprintk(1, "@0x%p hwdesc sizeof(struct tmComResHWDescr) = %d bytes\n", + &dev->hwdesc, (u32)sizeof(struct tmComResHWDescr)); + + dprintk(1, " .bLength = 0x%x\n", dev->hwdesc.bLength); + dprintk(1, " .bDescriptorType = 0x%x\n", dev->hwdesc.bDescriptorType); + dprintk(1, " .bDescriptorSubtype = 0x%x\n", + dev->hwdesc.bDescriptorSubtype); + + dprintk(1, " .bcdSpecVersion = 0x%x\n", dev->hwdesc.bcdSpecVersion); + dprintk(1, " .dwClockFrequency = 0x%x\n", dev->hwdesc.dwClockFrequency); + dprintk(1, " .dwClockUpdateRes = 0x%x\n", dev->hwdesc.dwClockUpdateRes); + dprintk(1, " .bCapabilities = 0x%x\n", dev->hwdesc.bCapabilities); + dprintk(1, " .dwDeviceRegistersLocation = 0x%x\n", + dev->hwdesc.dwDeviceRegistersLocation); + + dprintk(1, " .dwHostMemoryRegion = 0x%x\n", + dev->hwdesc.dwHostMemoryRegion); + + dprintk(1, " .dwHostMemoryRegionSize = 0x%x\n", + dev->hwdesc.dwHostMemoryRegionSize); + + dprintk(1, " .dwHostHibernatMemRegion = 0x%x\n", + dev->hwdesc.dwHostHibernatMemRegion); + + dprintk(1, " .dwHostHibernatMemRegionSize = 0x%x\n", + dev->hwdesc.dwHostHibernatMemRegionSize); +} + +static void saa7164_dump_intfdesc(struct saa7164_dev *dev) +{ + dprintk(1, "@0x%p intfdesc sizeof(struct tmComResInterfaceDescr) = %d bytes\n", + &dev->intfdesc, (u32)sizeof(struct tmComResInterfaceDescr)); + + dprintk(1, " .bLength = 0x%x\n", dev->intfdesc.bLength); + dprintk(1, " .bDescriptorType = 0x%x\n", dev->intfdesc.bDescriptorType); + dprintk(1, " .bDescriptorSubtype = 0x%x\n", + dev->intfdesc.bDescriptorSubtype); + + dprintk(1, " .bFlags = 0x%x\n", dev->intfdesc.bFlags); + dprintk(1, " .bInterfaceType = 0x%x\n", dev->intfdesc.bInterfaceType); + dprintk(1, " .bInterfaceId = 0x%x\n", dev->intfdesc.bInterfaceId); + dprintk(1, " .bBaseInterface = 0x%x\n", dev->intfdesc.bBaseInterface); + dprintk(1, " .bInterruptId = 0x%x\n", dev->intfdesc.bInterruptId); + dprintk(1, " .bDebugInterruptId = 0x%x\n", + dev->intfdesc.bDebugInterruptId); + + dprintk(1, " .BARLocation = 0x%x\n", dev->intfdesc.BARLocation); +} + +static void saa7164_dump_busdesc(struct saa7164_dev *dev) +{ + dprintk(1, "@0x%p busdesc sizeof(struct tmComResBusDescr) = %d bytes\n", + &dev->busdesc, (u32)sizeof(struct tmComResBusDescr)); + + dprintk(1, " .CommandRing = 0x%016Lx\n", dev->busdesc.CommandRing); + dprintk(1, " .ResponseRing = 0x%016Lx\n", dev->busdesc.ResponseRing); + dprintk(1, " .CommandWrite = 0x%x\n", dev->busdesc.CommandWrite); + dprintk(1, " .CommandRead = 0x%x\n", dev->busdesc.CommandRead); + dprintk(1, " .ResponseWrite = 0x%x\n", dev->busdesc.ResponseWrite); + dprintk(1, " .ResponseRead = 0x%x\n", dev->busdesc.ResponseRead); +} + +/* Much of the hardware configuration and PCI registers are configured + * dynamically depending on firmware. We have to cache some initial + * structures then use these to locate other important structures + * from PCI space. + */ +static void saa7164_get_descriptors(struct saa7164_dev *dev) +{ + memcpy_fromio(&dev->hwdesc, dev->bmmio, sizeof(struct tmComResHWDescr)); + memcpy_fromio(&dev->intfdesc, dev->bmmio + sizeof(struct tmComResHWDescr), + sizeof(struct tmComResInterfaceDescr)); + memcpy_fromio(&dev->busdesc, dev->bmmio + dev->intfdesc.BARLocation, + sizeof(struct tmComResBusDescr)); + + if (dev->hwdesc.bLength != sizeof(struct tmComResHWDescr)) { + printk(KERN_ERR "Structure struct tmComResHWDescr is mangled\n"); + printk(KERN_ERR "Need %x got %d\n", dev->hwdesc.bLength, + (u32)sizeof(struct tmComResHWDescr)); + } else + saa7164_dump_hwdesc(dev); + + if (dev->intfdesc.bLength != sizeof(struct tmComResInterfaceDescr)) { + printk(KERN_ERR "struct struct tmComResInterfaceDescr is mangled\n"); + printk(KERN_ERR "Need %x got %d\n", dev->intfdesc.bLength, + (u32)sizeof(struct tmComResInterfaceDescr)); + } else + saa7164_dump_intfdesc(dev); + + saa7164_dump_busdesc(dev); +} + +static int saa7164_pci_quirks(struct saa7164_dev *dev) +{ + return 0; +} + +static int get_resources(struct saa7164_dev *dev) +{ + if (request_mem_region(pci_resource_start(dev->pci, 0), + pci_resource_len(dev->pci, 0), dev->name)) { + + if (request_mem_region(pci_resource_start(dev->pci, 2), + pci_resource_len(dev->pci, 2), dev->name)) + return 0; + + release_mem_region(pci_resource_start(dev->pci, 0), + pci_resource_len(dev->pci, 0)); + } + + printk(KERN_ERR "%s: can't get MMIO memory @ 0x%llx or 0x%llx\n", + dev->name, + (u64)pci_resource_start(dev->pci, 0), + (u64)pci_resource_start(dev->pci, 2)); + + return -EBUSY; +} + +static void release_resources(struct saa7164_dev *dev) +{ + release_mem_region(pci_resource_start(dev->pci, 0), + pci_resource_len(dev->pci, 0)); + + release_mem_region(pci_resource_start(dev->pci, 2), + pci_resource_len(dev->pci, 2)); +} + +static int saa7164_port_init(struct saa7164_dev *dev, int portnr) +{ + struct saa7164_port *port = NULL; + + BUG_ON((portnr < 0) || (portnr >= SAA7164_MAX_PORTS)); + + port = &dev->ports[portnr]; + + port->dev = dev; + port->nr = portnr; + + if ((portnr == SAA7164_PORT_TS1) || (portnr == SAA7164_PORT_TS2)) + port->type = SAA7164_MPEG_DVB; + else + if ((portnr == SAA7164_PORT_ENC1) || (portnr == SAA7164_PORT_ENC2)) { + port->type = SAA7164_MPEG_ENCODER; + + /* We need a deferred interrupt handler for cmd handling */ + INIT_WORK(&port->workenc, saa7164_work_enchandler); + } else if ((portnr == SAA7164_PORT_VBI1) || (portnr == SAA7164_PORT_VBI2)) { + port->type = SAA7164_MPEG_VBI; + + /* We need a deferred interrupt handler for cmd handling */ + INIT_WORK(&port->workenc, saa7164_work_vbihandler); + } else + BUG(); + + /* Init all the critical resources */ + mutex_init(&port->dvb.lock); + INIT_LIST_HEAD(&port->dmaqueue.list); + mutex_init(&port->dmaqueue_lock); + + INIT_LIST_HEAD(&port->list_buf_used.list); + INIT_LIST_HEAD(&port->list_buf_free.list); + init_waitqueue_head(&port->wait_read); + + + saa7164_histogram_reset(&port->irq_interval, "irq intervals"); + saa7164_histogram_reset(&port->svc_interval, "deferred intervals"); + saa7164_histogram_reset(&port->irq_svc_interval, + "irq to deferred intervals"); + saa7164_histogram_reset(&port->read_interval, + "encoder/vbi read() intervals"); + saa7164_histogram_reset(&port->poll_interval, + "encoder/vbi poll() intervals"); + + return 0; +} + +static int saa7164_dev_setup(struct saa7164_dev *dev) +{ + int i; + + mutex_init(&dev->lock); + atomic_inc(&dev->refcount); + dev->nr = saa7164_devcount++; + + snprintf(dev->name, sizeof(dev->name), "saa7164[%d]", dev->nr); + + scoped_guard(mutex, &devlist) { + list_add_tail(&dev->devlist, &saa7164_devlist); + } + + /* board config */ + dev->board = UNSET; + if (card[dev->nr] < saa7164_bcount) + dev->board = card[dev->nr]; + + for (i = 0; UNSET == dev->board && i < saa7164_idcount; i++) + if (dev->pci->subsystem_vendor == saa7164_subids[i].subvendor && + dev->pci->subsystem_device == + saa7164_subids[i].subdevice) + dev->board = saa7164_subids[i].card; + + if (UNSET == dev->board) { + dev->board = SAA7164_BOARD_UNKNOWN; + saa7164_card_list(dev); + } + + dev->pci_bus = dev->pci->bus->number; + dev->pci_slot = PCI_SLOT(dev->pci->devfn); + + /* I2C Defaults / setup */ + dev->i2c_bus[0].dev = dev; + dev->i2c_bus[0].nr = 0; + dev->i2c_bus[1].dev = dev; + dev->i2c_bus[1].nr = 1; + dev->i2c_bus[2].dev = dev; + dev->i2c_bus[2].nr = 2; + + /* Transport + Encoder ports 1, 2, 3, 4 - Defaults / setup */ + saa7164_port_init(dev, SAA7164_PORT_TS1); + saa7164_port_init(dev, SAA7164_PORT_TS2); + saa7164_port_init(dev, SAA7164_PORT_ENC1); + saa7164_port_init(dev, SAA7164_PORT_ENC2); + saa7164_port_init(dev, SAA7164_PORT_VBI1); + saa7164_port_init(dev, SAA7164_PORT_VBI2); + + if (get_resources(dev) < 0) { + printk(KERN_ERR "CORE %s No more PCIe resources for subsystem: %04x:%04x\n", + dev->name, dev->pci->subsystem_vendor, + dev->pci->subsystem_device); + + goto err_devlist; + } + + /* PCI/e allocations */ + dev->lmmio = pci_ioremap_bar(dev->pci, 0); + if (!dev->lmmio) { + dev_err(&dev->pci->dev, "Failed to remap MMIO BAR 0\n"); + goto err_ioremap_bar0; + } + + dev->lmmio2 = pci_ioremap_bar(dev->pci, 2); + if (!dev->lmmio2) { + dev_err(&dev->pci->dev, "Failed to remap MMIO BAR 2\n"); + goto err_ioremap_bar2; + } + + dev->bmmio = (u8 __iomem *)dev->lmmio; + dev->bmmio2 = (u8 __iomem *)dev->lmmio2; + + /* Interrupt and ack register locations offset of bmmio */ + dev->int_status = 0x183000 + 0xf80; + dev->int_ack = 0x183000 + 0xf90; + + printk(KERN_INFO + "CORE %s: subsystem: %04x:%04x, board: %s [card=%d,%s]\n", + dev->name, dev->pci->subsystem_vendor, + dev->pci->subsystem_device, saa7164_boards[dev->board].name, + dev->board, card[dev->nr] == dev->board ? + "insmod option" : "autodetected"); + + saa7164_pci_quirks(dev); + + return 0; + +err_ioremap_bar2: + iounmap(dev->lmmio); +err_ioremap_bar0: + release_resources(dev); +err_devlist: + scoped_guard(mutex, &devlist) { + list_del(&dev->devlist); + } + saa7164_devcount--; + + return -ENODEV; +} + +static void saa7164_dev_unregister(struct saa7164_dev *dev) +{ + dprintk(1, "%s()\n", __func__); + + release_resources(dev); + + if (!atomic_dec_and_test(&dev->refcount)) + return; + + iounmap(dev->lmmio); + iounmap(dev->lmmio2); + + return; +} + +#ifdef CONFIG_DEBUG_FS +static void *saa7164_seq_start(struct seq_file *s, loff_t *pos) +{ + struct saa7164_dev *dev; + loff_t index = *pos; + + mutex_lock(&devlist); + list_for_each_entry(dev, &saa7164_devlist, devlist) { + if (index-- == 0) { + mutex_unlock(&devlist); + return dev; + } + } + mutex_unlock(&devlist); + + return NULL; +} + +static void *saa7164_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + struct saa7164_dev *dev = v; + void *ret; + + mutex_lock(&devlist); + if (list_is_last(&dev->devlist, &saa7164_devlist)) + ret = NULL; + else + ret = list_next_entry(dev, devlist); + mutex_unlock(&devlist); + + ++*pos; + + return ret; +} + +static void saa7164_seq_stop(struct seq_file *s, void *v) +{ +} + +static int saa7164_seq_show(struct seq_file *m, void *v) +{ + struct saa7164_dev *dev = v; + struct tmComResBusInfo *b; + int i, c; + + seq_printf(m, "%s = %p\n", dev->name, dev); + + /* Lock the bus from any other access */ + b = &dev->bus; + mutex_lock(&b->lock); + + seq_printf(m, " .m_pdwSetWritePos = 0x%x (0x%08x)\n", + b->m_dwSetReadPos, saa7164_readl(b->m_dwSetReadPos)); + + seq_printf(m, " .m_pdwSetReadPos = 0x%x (0x%08x)\n", + b->m_dwSetWritePos, saa7164_readl(b->m_dwSetWritePos)); + + seq_printf(m, " .m_pdwGetWritePos = 0x%x (0x%08x)\n", + b->m_dwGetReadPos, saa7164_readl(b->m_dwGetReadPos)); + + seq_printf(m, " .m_pdwGetReadPos = 0x%x (0x%08x)\n", + b->m_dwGetWritePos, saa7164_readl(b->m_dwGetWritePos)); + c = 0; + seq_puts(m, "\n Set Ring:\n"); + seq_puts(m, "\n addr 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n"); + for (i = 0; i < b->m_dwSizeSetRing; i++) { + if (c == 0) + seq_printf(m, " %04x:", i); + + seq_printf(m, " %02x", readb(b->m_pdwSetRing + i)); + + if (++c == 16) { + seq_puts(m, "\n"); + c = 0; + } + } + + c = 0; + seq_puts(m, "\n Get Ring:\n"); + seq_puts(m, "\n addr 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n"); + for (i = 0; i < b->m_dwSizeGetRing; i++) { + if (c == 0) + seq_printf(m, " %04x:", i); + + seq_printf(m, " %02x", readb(b->m_pdwGetRing + i)); + + if (++c == 16) { + seq_puts(m, "\n"); + c = 0; + } + } + + mutex_unlock(&b->lock); + + return 0; +} + +static const struct seq_operations saa7164_sops = { + .start = saa7164_seq_start, + .next = saa7164_seq_next, + .stop = saa7164_seq_stop, + .show = saa7164_seq_show, +}; + +DEFINE_SEQ_ATTRIBUTE(saa7164); + +static struct dentry *saa7614_dentry; + +static void __init saa7164_debugfs_create(void) +{ + saa7614_dentry = debugfs_create_file("saa7164", 0444, NULL, NULL, + &saa7164_fops); +} + +static void __exit saa7164_debugfs_remove(void) +{ + debugfs_remove(saa7614_dentry); +} +#else +static void saa7164_debugfs_create(void) { } +static void saa7164_debugfs_remove(void) { } +#endif + +static int saa7164_thread_function(void *data) +{ + struct saa7164_dev *dev = data; + struct tmFwInfoStruct fwinfo; + u64 last_poll_time = 0; + + dprintk(DBGLVL_THR, "thread started\n"); + + set_freezable(); + + while (1) { + msleep_interruptible(100); + if (kthread_should_stop()) + break; + try_to_freeze(); + + dprintk(DBGLVL_THR, "thread running\n"); + + /* Dump the firmware debug message to console */ + /* Polling this costs us 1-2% of the arm CPU */ + /* convert this into a respnde to interrupt 0x7a */ + saa7164_api_collect_debug(dev); + + /* Monitor CPU load every 1 second */ + if ((last_poll_time + 1000 /* ms */) < jiffies_to_msecs(jiffies)) { + saa7164_api_get_load_info(dev, &fwinfo); + last_poll_time = jiffies_to_msecs(jiffies); + } + + } + + dprintk(DBGLVL_THR, "thread exiting\n"); + return 0; +} + +static bool saa7164_enable_msi(struct pci_dev *pci_dev, struct saa7164_dev *dev) +{ + int err; + + if (!enable_msi) { + printk(KERN_WARNING "%s() MSI disabled by module parameter 'enable_msi'" + , __func__); + return false; + } + + err = pci_enable_msi(pci_dev); + + if (err) { + printk(KERN_ERR "%s() Failed to enable MSI interrupt. Falling back to a shared IRQ\n", + __func__); + return false; + } + + /* no error - so request an msi interrupt */ + err = request_irq(pci_dev->irq, saa7164_irq, 0, + dev->name, dev); + + if (err) { + /* fall back to legacy interrupt */ + printk(KERN_ERR "%s() Failed to get an MSI interrupt. Falling back to a shared IRQ\n", + __func__); + pci_disable_msi(pci_dev); + return false; + } + + return true; +} + +static int saa7164_initdev(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct saa7164_dev *dev; + int err, i; + u32 version; + + dev = kzalloc_obj(*dev); + if (NULL == dev) + return -ENOMEM; + + err = v4l2_device_register(&pci_dev->dev, &dev->v4l2_dev); + if (err < 0) { + dev_err(&pci_dev->dev, "v4l2_device_register failed\n"); + goto fail_free; + } + + /* pci init */ + dev->pci = pci_dev; + if (pci_enable_device(pci_dev)) { + err = -EIO; + goto fail_free; + } + + if (saa7164_dev_setup(dev) < 0) { + err = -EINVAL; + goto fail_dev; + } + + /* print pci info */ + dev->pci_rev = pci_dev->revision; + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat); + printk(KERN_INFO "%s/0: found at %s, rev: %d, irq: %d, latency: %d, mmio: 0x%llx\n", + dev->name, + pci_name(pci_dev), dev->pci_rev, pci_dev->irq, + dev->pci_lat, + (unsigned long long)pci_resource_start(pci_dev, 0)); + + pci_set_master(pci_dev); + /* TODO */ + err = dma_set_mask(&pci_dev->dev, 0xffffffff); + if (err) { + printk("%s/0: Oops: no 32bit PCI DMA ???\n", dev->name); + goto fail_irq; + } + + /* irq bit */ + if (saa7164_enable_msi(pci_dev, dev)) { + dev->msi = true; + } else { + /* if we have an error (i.e. we don't have an interrupt) + or msi is not enabled - fallback to shared interrupt */ + + err = request_irq(pci_dev->irq, saa7164_irq, + IRQF_SHARED, dev->name, dev); + + if (err < 0) { + printk(KERN_ERR "%s: can't get IRQ %d\n", dev->name, + pci_dev->irq); + err = -EIO; + goto fail_irq; + } + } + + pci_set_drvdata(pci_dev, dev); + + /* Init the internal command list */ + for (i = 0; i < SAA_CMD_MAX_MSG_UNITS; i++) { + dev->cmds[i].seqno = i; + dev->cmds[i].inuse = 0; + mutex_init(&dev->cmds[i].lock); + init_waitqueue_head(&dev->cmds[i].wait); + } + + /* We need a deferred interrupt handler for cmd handling */ + INIT_WORK(&dev->workcmd, saa7164_work_cmdhandler); + + /* Only load the firmware if we know the board */ + if (dev->board != SAA7164_BOARD_UNKNOWN) { + + err = saa7164_downloadfirmware(dev); + if (err < 0) { + printk(KERN_ERR + "Failed to boot firmware, no features registered\n"); + goto fail_fw; + } + + saa7164_get_descriptors(dev); + saa7164_dumpregs(dev, 0); + saa7164_getcurrentfirmwareversion(dev); + saa7164_getfirmwarestatus(dev); + err = saa7164_bus_setup(dev); + if (err < 0) + printk(KERN_ERR + "Failed to setup the bus, will continue\n"); + saa7164_bus_dump(dev); + + /* Ping the running firmware via the command bus and get the + * firmware version, this checks the bus is running OK. + */ + version = 0; + if (saa7164_api_get_fw_version(dev, &version) == SAA_OK) + dprintk(1, "Bus is operating correctly using version %d.%d.%d.%d (0x%x)\n", + (version & 0x0000fc00) >> 10, + (version & 0x000003e0) >> 5, + (version & 0x0000001f), + (version & 0xffff0000) >> 16, + version); + else + printk(KERN_ERR + "Failed to communicate with the firmware\n"); + + /* Bring up the I2C buses */ + saa7164_i2c_register(&dev->i2c_bus[0]); + saa7164_i2c_register(&dev->i2c_bus[1]); + saa7164_i2c_register(&dev->i2c_bus[2]); + saa7164_gpio_setup(dev); + saa7164_card_setup(dev); + + /* Parse the dynamic device configuration, find various + * media endpoints (MPEG, WMV, PS, TS) and cache their + * configuration details into the driver, so we can + * reference them later during simething_register() func, + * interrupt handlers, deferred work handlers etc. + */ + saa7164_api_enum_subdevs(dev); + + /* Begin to create the video sub-systems and register funcs */ + if (saa7164_boards[dev->board].porta == SAA7164_MPEG_DVB) { + if (saa7164_dvb_register(&dev->ports[SAA7164_PORT_TS1]) < 0) { + printk(KERN_ERR "%s() Failed to register dvb adapters on porta\n", + __func__); + } + } + + if (saa7164_boards[dev->board].portb == SAA7164_MPEG_DVB) { + if (saa7164_dvb_register(&dev->ports[SAA7164_PORT_TS2]) < 0) { + printk(KERN_ERR"%s() Failed to register dvb adapters on portb\n", + __func__); + } + } + + if (saa7164_boards[dev->board].portc == SAA7164_MPEG_ENCODER) { + if (saa7164_encoder_register(&dev->ports[SAA7164_PORT_ENC1]) < 0) { + printk(KERN_ERR"%s() Failed to register mpeg encoder\n", + __func__); + } + } + + if (saa7164_boards[dev->board].portd == SAA7164_MPEG_ENCODER) { + if (saa7164_encoder_register(&dev->ports[SAA7164_PORT_ENC2]) < 0) { + printk(KERN_ERR"%s() Failed to register mpeg encoder\n", + __func__); + } + } + + if (saa7164_boards[dev->board].porte == SAA7164_MPEG_VBI) { + if (saa7164_vbi_register(&dev->ports[SAA7164_PORT_VBI1]) < 0) { + printk(KERN_ERR"%s() Failed to register vbi device\n", + __func__); + } + } + + if (saa7164_boards[dev->board].portf == SAA7164_MPEG_VBI) { + if (saa7164_vbi_register(&dev->ports[SAA7164_PORT_VBI2]) < 0) { + printk(KERN_ERR"%s() Failed to register vbi device\n", + __func__); + } + } + saa7164_api_set_debug(dev, fw_debug); + + if (fw_debug) { + dev->kthread = kthread_run(saa7164_thread_function, dev, + "saa7164 debug"); + if (IS_ERR(dev->kthread)) { + dev->kthread = NULL; + printk(KERN_ERR "%s() Failed to create debug kernel thread\n", + __func__); + } + } + + } /* != BOARD_UNKNOWN */ + else + printk(KERN_ERR "%s() Unsupported board detected, registering without firmware\n", + __func__); + + dprintk(1, "%s() parameter debug = %d\n", __func__, saa_debug); + dprintk(1, "%s() parameter waitsecs = %d\n", __func__, waitsecs); + +fail_fw: + return 0; + +fail_irq: + saa7164_dev_unregister(dev); +fail_dev: + pci_disable_device(pci_dev); +fail_free: + v4l2_device_unregister(&dev->v4l2_dev); + kfree(dev); + return err; +} + +static void saa7164_shutdown(struct saa7164_dev *dev) +{ + dprintk(1, "%s()\n", __func__); +} + +static void saa7164_finidev(struct pci_dev *pci_dev) +{ + struct saa7164_dev *dev = pci_get_drvdata(pci_dev); + + if (dev->board != SAA7164_BOARD_UNKNOWN) { + if (fw_debug && dev->kthread) { + kthread_stop(dev->kthread); + dev->kthread = NULL; + } + if (dev->firmwareloaded) + saa7164_api_set_debug(dev, 0x00); + } + + saa7164_histogram_print(&dev->ports[SAA7164_PORT_ENC1], + &dev->ports[SAA7164_PORT_ENC1].irq_interval); + saa7164_histogram_print(&dev->ports[SAA7164_PORT_ENC1], + &dev->ports[SAA7164_PORT_ENC1].svc_interval); + saa7164_histogram_print(&dev->ports[SAA7164_PORT_ENC1], + &dev->ports[SAA7164_PORT_ENC1].irq_svc_interval); + saa7164_histogram_print(&dev->ports[SAA7164_PORT_ENC1], + &dev->ports[SAA7164_PORT_ENC1].read_interval); + saa7164_histogram_print(&dev->ports[SAA7164_PORT_ENC1], + &dev->ports[SAA7164_PORT_ENC1].poll_interval); + saa7164_histogram_print(&dev->ports[SAA7164_PORT_VBI1], + &dev->ports[SAA7164_PORT_VBI1].read_interval); + saa7164_histogram_print(&dev->ports[SAA7164_PORT_VBI2], + &dev->ports[SAA7164_PORT_VBI2].poll_interval); + + saa7164_shutdown(dev); + + if (saa7164_boards[dev->board].porta == SAA7164_MPEG_DVB) + saa7164_dvb_unregister(&dev->ports[SAA7164_PORT_TS1]); + + if (saa7164_boards[dev->board].portb == SAA7164_MPEG_DVB) + saa7164_dvb_unregister(&dev->ports[SAA7164_PORT_TS2]); + + if (saa7164_boards[dev->board].portc == SAA7164_MPEG_ENCODER) + saa7164_encoder_unregister(&dev->ports[SAA7164_PORT_ENC1]); + + if (saa7164_boards[dev->board].portd == SAA7164_MPEG_ENCODER) + saa7164_encoder_unregister(&dev->ports[SAA7164_PORT_ENC2]); + + if (saa7164_boards[dev->board].porte == SAA7164_MPEG_VBI) + saa7164_vbi_unregister(&dev->ports[SAA7164_PORT_VBI1]); + + if (saa7164_boards[dev->board].portf == SAA7164_MPEG_VBI) + saa7164_vbi_unregister(&dev->ports[SAA7164_PORT_VBI2]); + + saa7164_i2c_unregister(&dev->i2c_bus[0]); + saa7164_i2c_unregister(&dev->i2c_bus[1]); + saa7164_i2c_unregister(&dev->i2c_bus[2]); + + /* unregister stuff */ + free_irq(pci_dev->irq, dev); + + if (dev->msi) { + pci_disable_msi(pci_dev); + dev->msi = false; + } + + pci_disable_device(pci_dev); + + mutex_lock(&devlist); + list_del(&dev->devlist); + mutex_unlock(&devlist); + + saa7164_dev_unregister(dev); + v4l2_device_unregister(&dev->v4l2_dev); + kfree(dev); +} + +static const struct pci_device_id saa7164_pci_tbl[] = { + { + /* SAA7164 */ + PCI_DEVICE(0x1131, 0x7164), + }, { + /* --- end of list --- */ + } +}; +MODULE_DEVICE_TABLE(pci, saa7164_pci_tbl); + +static struct pci_driver saa7164_pci_driver = { + .name = "saa7164", + .id_table = saa7164_pci_tbl, + .probe = saa7164_initdev, + .remove = saa7164_finidev, +}; + +static int __init saa7164_init(void) +{ + int ret = pci_register_driver(&saa7164_pci_driver); + + if (ret) + return ret; + + saa7164_debugfs_create(); + + pr_info("saa7164 driver loaded\n"); + + return 0; +} + +static void __exit saa7164_fini(void) +{ + saa7164_debugfs_remove(); + pci_unregister_driver(&saa7164_pci_driver); +} + +module_init(saa7164_init); +module_exit(saa7164_fini); diff --git a/drivers/media/pci/saa7164/saa7164-dvb.c b/drivers/media/pci/saa7164/saa7164-dvb.c new file mode 100644 index 000000000..3eb749db1 --- /dev/null +++ b/drivers/media/pci/saa7164/saa7164-dvb.c @@ -0,0 +1,740 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the NXP SAA7164 PCIe bridge + * + * Copyright (c) 2010-2015 Steven Toth + */ + +#include "saa7164.h" + +#include "tda10048.h" +#include "tda18271.h" +#include "s5h1411.h" +#include "si2157.h" +#include "si2168.h" +#include "lgdt3306a.h" + +#define DRIVER_NAME "saa7164" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +/* addr is in the card struct, get it from there */ +static struct tda10048_config hauppauge_hvr2200_1_config = { + .demod_address = 0x10 >> 1, + .output_mode = TDA10048_SERIAL_OUTPUT, + .fwbulkwritelen = TDA10048_BULKWRITE_200, + .inversion = TDA10048_INVERSION_ON, + .dtv6_if_freq_khz = TDA10048_IF_3300, + .dtv7_if_freq_khz = TDA10048_IF_3500, + .dtv8_if_freq_khz = TDA10048_IF_4000, + .clk_freq_khz = TDA10048_CLK_16000, +}; +static struct tda10048_config hauppauge_hvr2200_2_config = { + .demod_address = 0x12 >> 1, + .output_mode = TDA10048_SERIAL_OUTPUT, + .fwbulkwritelen = TDA10048_BULKWRITE_200, + .inversion = TDA10048_INVERSION_ON, + .dtv6_if_freq_khz = TDA10048_IF_3300, + .dtv7_if_freq_khz = TDA10048_IF_3500, + .dtv8_if_freq_khz = TDA10048_IF_4000, + .clk_freq_khz = TDA10048_CLK_16000, +}; + +static struct tda18271_std_map hauppauge_tda18271_std_map = { + .atsc_6 = { .if_freq = 3250, .agc_mode = 3, .std = 3, + .if_lvl = 6, .rfagc_top = 0x37 }, + .qam_6 = { .if_freq = 4000, .agc_mode = 3, .std = 0, + .if_lvl = 6, .rfagc_top = 0x37 }, +}; + +static struct tda18271_config hauppauge_hvr22x0_tuner_config = { + .std_map = &hauppauge_tda18271_std_map, + .gate = TDA18271_GATE_ANALOG, + .role = TDA18271_MASTER, +}; + +static struct tda18271_config hauppauge_hvr22x0s_tuner_config = { + .std_map = &hauppauge_tda18271_std_map, + .gate = TDA18271_GATE_ANALOG, + .role = TDA18271_SLAVE, + .output_opt = TDA18271_OUTPUT_LT_OFF, + .rf_cal_on_startup = 1 +}; + +static struct s5h1411_config hauppauge_s5h1411_config = { + .output_mode = S5H1411_SERIAL_OUTPUT, + .gpio = S5H1411_GPIO_ON, + .qam_if = S5H1411_IF_4000, + .vsb_if = S5H1411_IF_3250, + .inversion = S5H1411_INVERSION_ON, + .status_mode = S5H1411_DEMODLOCKING, + .mpeg_timing = S5H1411_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK, +}; + +static struct lgdt3306a_config hauppauge_hvr2255a_config = { + .i2c_addr = 0xb2 >> 1, + .qam_if_khz = 4000, + .vsb_if_khz = 3250, + .deny_i2c_rptr = 1, /* Disabled */ + .spectral_inversion = 0, /* Disabled */ + .mpeg_mode = LGDT3306A_MPEG_SERIAL, + .tpclk_edge = LGDT3306A_TPCLK_RISING_EDGE, + .tpvalid_polarity = LGDT3306A_TP_VALID_HIGH, + .xtalMHz = 25, /* 24 or 25 */ +}; + +static struct lgdt3306a_config hauppauge_hvr2255b_config = { + .i2c_addr = 0x1c >> 1, + .qam_if_khz = 4000, + .vsb_if_khz = 3250, + .deny_i2c_rptr = 1, /* Disabled */ + .spectral_inversion = 0, /* Disabled */ + .mpeg_mode = LGDT3306A_MPEG_SERIAL, + .tpclk_edge = LGDT3306A_TPCLK_RISING_EDGE, + .tpvalid_polarity = LGDT3306A_TP_VALID_HIGH, + .xtalMHz = 25, /* 24 or 25 */ +}; + +static struct si2157_config hauppauge_hvr2255_tuner_config = { + .inversion = 1, + .if_port = 1, +}; + +static int si2157_attach(struct saa7164_port *port, struct i2c_adapter *adapter, + struct dvb_frontend *fe, u8 addr8bit, struct si2157_config *cfg) +{ + struct i2c_board_info bi; + struct i2c_client *tuner; + + cfg->fe = fe; + + memset(&bi, 0, sizeof(bi)); + + strscpy(bi.type, "si2157", I2C_NAME_SIZE); + bi.platform_data = cfg; + bi.addr = addr8bit >> 1; + + request_module(bi.type); + + tuner = i2c_new_client_device(adapter, &bi); + if (!i2c_client_has_driver(tuner)) + return -ENODEV; + + if (!try_module_get(tuner->dev.driver->owner)) { + i2c_unregister_device(tuner); + return -ENODEV; + } + + port->i2c_client_tuner = tuner; + + return 0; +} + +static int saa7164_dvb_stop_port(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + int ret; + + ret = saa7164_api_transition_port(port, SAA_DMASTATE_STOP); + if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() stop transition failed, ret = 0x%x\n", + __func__, ret); + ret = -EIO; + } else { + dprintk(DBGLVL_DVB, "%s() Stopped\n", __func__); + ret = 0; + } + + return ret; +} + +static int saa7164_dvb_acquire_port(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + int ret; + + ret = saa7164_api_transition_port(port, SAA_DMASTATE_ACQUIRE); + if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() acquire transition failed, ret = 0x%x\n", + __func__, ret); + ret = -EIO; + } else { + dprintk(DBGLVL_DVB, "%s() Acquired\n", __func__); + ret = 0; + } + + return ret; +} + +static int saa7164_dvb_pause_port(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + int ret; + + ret = saa7164_api_transition_port(port, SAA_DMASTATE_PAUSE); + if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() pause transition failed, ret = 0x%x\n", + __func__, ret); + ret = -EIO; + } else { + dprintk(DBGLVL_DVB, "%s() Paused\n", __func__); + ret = 0; + } + + return ret; +} + +/* Firmware is very windows centric, meaning you have to transition + * the part through AVStream / KS Windows stages, forwards or backwards. + * States are: stopped, acquired (h/w), paused, started. + */ +static int saa7164_dvb_stop_streaming(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct saa7164_buffer *buf; + struct list_head *p, *q; + int ret; + + dprintk(DBGLVL_DVB, "%s(port=%d)\n", __func__, port->nr); + + ret = saa7164_dvb_pause_port(port); + ret = saa7164_dvb_acquire_port(port); + ret = saa7164_dvb_stop_port(port); + + /* Mark the hardware buffers as free */ + mutex_lock(&port->dmaqueue_lock); + list_for_each_safe(p, q, &port->dmaqueue.list) { + buf = list_entry(p, struct saa7164_buffer, list); + buf->flags = SAA7164_BUFFER_FREE; + } + mutex_unlock(&port->dmaqueue_lock); + + return ret; +} + +static int saa7164_dvb_start_port(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + int ret = 0, result; + + dprintk(DBGLVL_DVB, "%s(port=%d)\n", __func__, port->nr); + + saa7164_buffer_cfg_port(port); + + /* Acquire the hardware */ + result = saa7164_api_transition_port(port, SAA_DMASTATE_ACQUIRE); + if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() acquire transition failed, res = 0x%x\n", + __func__, result); + + /* Stop the hardware, regardless */ + result = saa7164_api_transition_port(port, SAA_DMASTATE_STOP); + if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() acquire/forced stop transition failed, res = 0x%x\n", + __func__, result); + } + ret = -EIO; + goto out; + } else + dprintk(DBGLVL_DVB, "%s() Acquired\n", __func__); + + /* Pause the hardware */ + result = saa7164_api_transition_port(port, SAA_DMASTATE_PAUSE); + if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() pause transition failed, res = 0x%x\n", + __func__, result); + + /* Stop the hardware, regardless */ + result = saa7164_api_transition_port(port, SAA_DMASTATE_STOP); + if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() pause/forced stop transition failed, res = 0x%x\n", + __func__, result); + } + + ret = -EIO; + goto out; + } else + dprintk(DBGLVL_DVB, "%s() Paused\n", __func__); + + /* Start the hardware */ + result = saa7164_api_transition_port(port, SAA_DMASTATE_RUN); + if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() run transition failed, result = 0x%x\n", + __func__, result); + + /* Stop the hardware, regardless */ + result = saa7164_api_transition_port(port, SAA_DMASTATE_STOP); + if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() run/forced stop transition failed, res = 0x%x\n", + __func__, result); + } + + ret = -EIO; + } else + dprintk(DBGLVL_DVB, "%s() Running\n", __func__); + +out: + return ret; +} + +static int saa7164_dvb_start_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct saa7164_port *port = demux->priv; + struct saa7164_dvb *dvb = &port->dvb; + struct saa7164_dev *dev = port->dev; + int ret = 0; + + dprintk(DBGLVL_DVB, "%s(port=%d)\n", __func__, port->nr); + + if (!demux->dmx.frontend) + return -EINVAL; + + if (dvb) { + mutex_lock(&dvb->lock); + if (dvb->feeding++ == 0) { + /* Start transport */ + ret = saa7164_dvb_start_port(port); + } + mutex_unlock(&dvb->lock); + dprintk(DBGLVL_DVB, "%s(port=%d) now feeding = %d\n", + __func__, port->nr, dvb->feeding); + } + + return ret; +} + +static int saa7164_dvb_stop_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct saa7164_port *port = demux->priv; + struct saa7164_dvb *dvb = &port->dvb; + struct saa7164_dev *dev = port->dev; + int ret = 0; + + dprintk(DBGLVL_DVB, "%s(port=%d)\n", __func__, port->nr); + + if (dvb) { + mutex_lock(&dvb->lock); + if (--dvb->feeding == 0) { + /* Stop transport */ + ret = saa7164_dvb_stop_streaming(port); + } + mutex_unlock(&dvb->lock); + dprintk(DBGLVL_DVB, "%s(port=%d) now feeding = %d\n", + __func__, port->nr, dvb->feeding); + } + + return ret; +} + +static int dvb_register(struct saa7164_port *port) +{ + struct saa7164_dvb *dvb = &port->dvb; + struct saa7164_dev *dev = port->dev; + struct saa7164_buffer *buf; + int result, i; + + dprintk(DBGLVL_DVB, "%s(port=%d)\n", __func__, port->nr); + + BUG_ON(port->type != SAA7164_MPEG_DVB); + + /* Sanity check that the PCI configuration space is active */ + if (port->hwcfg.BARLocation == 0) { + result = -ENOMEM; + printk(KERN_ERR "%s: dvb_register_adapter failed (errno = %d), NO PCI configuration\n", + DRIVER_NAME, result); + goto fail_adapter; + } + + /* Init and establish defaults */ + port->hw_streamingparams.bitspersample = 8; + port->hw_streamingparams.samplesperline = 188; + port->hw_streamingparams.numberoflines = + (SAA7164_TS_NUMBER_OF_LINES * 188) / 188; + + port->hw_streamingparams.pitch = 188; + port->hw_streamingparams.linethreshold = 0; + port->hw_streamingparams.pagetablelistvirt = NULL; + port->hw_streamingparams.pagetablelistphys = NULL; + port->hw_streamingparams.numpagetables = 2 + + ((SAA7164_TS_NUMBER_OF_LINES * 188) / PAGE_SIZE); + + port->hw_streamingparams.numpagetableentries = port->hwcfg.buffercount; + + /* Allocate the PCI resources */ + for (i = 0; i < port->hwcfg.buffercount; i++) { + buf = saa7164_buffer_alloc(port, + port->hw_streamingparams.numberoflines * + port->hw_streamingparams.pitch); + + if (!buf) { + result = -ENOMEM; + printk(KERN_ERR "%s: dvb_register_adapter failed (errno = %d), unable to allocate buffers\n", + DRIVER_NAME, result); + goto fail_adapter; + } + + mutex_lock(&port->dmaqueue_lock); + list_add_tail(&buf->list, &port->dmaqueue.list); + mutex_unlock(&port->dmaqueue_lock); + } + + /* register adapter */ + result = dvb_register_adapter(&dvb->adapter, DRIVER_NAME, THIS_MODULE, + &dev->pci->dev, adapter_nr); + if (result < 0) { + printk(KERN_ERR "%s: dvb_register_adapter failed (errno = %d)\n", + DRIVER_NAME, result); + goto fail_adapter; + } + dvb->adapter.priv = port; + + /* register frontend */ + result = dvb_register_frontend(&dvb->adapter, dvb->frontend); + if (result < 0) { + printk(KERN_ERR "%s: dvb_register_frontend failed (errno = %d)\n", + DRIVER_NAME, result); + goto fail_frontend; + } + + /* register demux stuff */ + dvb->demux.dmx.capabilities = + DMX_TS_FILTERING | DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING; + dvb->demux.priv = port; + dvb->demux.filternum = 256; + dvb->demux.feednum = 256; + dvb->demux.start_feed = saa7164_dvb_start_feed; + dvb->demux.stop_feed = saa7164_dvb_stop_feed; + result = dvb_dmx_init(&dvb->demux); + if (result < 0) { + printk(KERN_ERR "%s: dvb_dmx_init failed (errno = %d)\n", + DRIVER_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) { + printk(KERN_ERR "%s: dvb_dmxdev_init failed (errno = %d)\n", + DRIVER_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) { + printk(KERN_ERR "%s: add_frontend failed (DMX_FRONTEND_0, errno = %d)\n", + DRIVER_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) { + printk(KERN_ERR "%s: add_frontend failed (DMX_MEMORY_FE, errno = %d)\n", + DRIVER_NAME, result); + goto fail_fe_mem; + } + + result = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx, &dvb->fe_hw); + if (result < 0) { + printk(KERN_ERR "%s: connect_frontend failed (errno = %d)\n", + DRIVER_NAME, result); + goto fail_fe_conn; + } + + /* register network adapter */ + dvb_net_init(&dvb->adapter, &dvb->net, &dvb->demux.dmx); + return 0; + +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: + dvb_unregister_frontend(dvb->frontend); +fail_frontend: + dvb_frontend_detach(dvb->frontend); + dvb_unregister_adapter(&dvb->adapter); +fail_adapter: + return result; +} + +int saa7164_dvb_unregister(struct saa7164_port *port) +{ + struct saa7164_dvb *dvb = &port->dvb; + struct saa7164_dev *dev = port->dev; + struct saa7164_buffer *b; + struct list_head *c, *n; + struct i2c_client *client; + + dprintk(DBGLVL_DVB, "%s()\n", __func__); + + BUG_ON(port->type != SAA7164_MPEG_DVB); + + /* Remove any allocated buffers */ + mutex_lock(&port->dmaqueue_lock); + list_for_each_safe(c, n, &port->dmaqueue.list) { + b = list_entry(c, struct saa7164_buffer, list); + list_del(c); + saa7164_buffer_dealloc(b); + } + mutex_unlock(&port->dmaqueue_lock); + + if (dvb->frontend == NULL) + return 0; + + /* remove I2C client for tuner */ + client = port->i2c_client_tuner; + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); + } + + /* remove I2C client for demodulator */ + client = port->i2c_client_demod; + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); + } + + 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); + dvb_unregister_frontend(dvb->frontend); + dvb_frontend_detach(dvb->frontend); + dvb_unregister_adapter(&dvb->adapter); + return 0; +} + +/* All the DVB attach calls go here, this function gets modified + * for each new card. + */ +int saa7164_dvb_register(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct saa7164_dvb *dvb = &port->dvb; + struct saa7164_i2c *i2c_bus = NULL; + struct si2168_config si2168_config; + struct si2157_config si2157_config; + struct i2c_adapter *adapter; + struct i2c_board_info info; + struct i2c_client *client_demod; + struct i2c_client *client_tuner; + int ret; + + dprintk(DBGLVL_DVB, "%s()\n", __func__); + + /* init frontend */ + switch (dev->board) { + case SAA7164_BOARD_HAUPPAUGE_HVR2200: + case SAA7164_BOARD_HAUPPAUGE_HVR2200_2: + case SAA7164_BOARD_HAUPPAUGE_HVR2200_3: + case SAA7164_BOARD_HAUPPAUGE_HVR2200_4: + case SAA7164_BOARD_HAUPPAUGE_HVR2200_5: + i2c_bus = &dev->i2c_bus[port->nr + 1]; + switch (port->nr) { + case 0: + port->dvb.frontend = dvb_attach(tda10048_attach, + &hauppauge_hvr2200_1_config, + &i2c_bus->i2c_adap); + + if (port->dvb.frontend != NULL) { + /* TODO: addr is in the card struct */ + dvb_attach(tda18271_attach, port->dvb.frontend, + 0xc0 >> 1, &i2c_bus->i2c_adap, + &hauppauge_hvr22x0_tuner_config); + } + + break; + case 1: + port->dvb.frontend = dvb_attach(tda10048_attach, + &hauppauge_hvr2200_2_config, + &i2c_bus->i2c_adap); + + if (port->dvb.frontend != NULL) { + /* TODO: addr is in the card struct */ + dvb_attach(tda18271_attach, port->dvb.frontend, + 0xc0 >> 1, &i2c_bus->i2c_adap, + &hauppauge_hvr22x0s_tuner_config); + } + + break; + } + break; + case SAA7164_BOARD_HAUPPAUGE_HVR2250: + case SAA7164_BOARD_HAUPPAUGE_HVR2250_2: + case SAA7164_BOARD_HAUPPAUGE_HVR2250_3: + i2c_bus = &dev->i2c_bus[port->nr + 1]; + + port->dvb.frontend = dvb_attach(s5h1411_attach, + &hauppauge_s5h1411_config, + &i2c_bus->i2c_adap); + + if (port->dvb.frontend != NULL) { + if (port->nr == 0) { + /* Master TDA18271 */ + /* TODO: addr is in the card struct */ + dvb_attach(tda18271_attach, port->dvb.frontend, + 0xc0 >> 1, &i2c_bus->i2c_adap, + &hauppauge_hvr22x0_tuner_config); + } else { + /* Slave TDA18271 */ + dvb_attach(tda18271_attach, port->dvb.frontend, + 0xc0 >> 1, &i2c_bus->i2c_adap, + &hauppauge_hvr22x0s_tuner_config); + } + } + + break; + case SAA7164_BOARD_HAUPPAUGE_HVR2255proto: + case SAA7164_BOARD_HAUPPAUGE_HVR2255: + i2c_bus = &dev->i2c_bus[2]; + + if (port->nr == 0) { + port->dvb.frontend = dvb_attach(lgdt3306a_attach, + &hauppauge_hvr2255a_config, &i2c_bus->i2c_adap); + } else { + port->dvb.frontend = dvb_attach(lgdt3306a_attach, + &hauppauge_hvr2255b_config, &i2c_bus->i2c_adap); + } + + if (port->dvb.frontend != NULL) { + + if (port->nr == 0) { + si2157_attach(port, &dev->i2c_bus[0].i2c_adap, + port->dvb.frontend, 0xc0, + &hauppauge_hvr2255_tuner_config); + } else { + si2157_attach(port, &dev->i2c_bus[1].i2c_adap, + port->dvb.frontend, 0xc0, + &hauppauge_hvr2255_tuner_config); + } + } + break; + case SAA7164_BOARD_HAUPPAUGE_HVR2205: + + if (port->nr == 0) { + /* attach frontend */ + memset(&si2168_config, 0, sizeof(si2168_config)); + si2168_config.i2c_adapter = &adapter; + si2168_config.fe = &port->dvb.frontend; + si2168_config.ts_mode = SI2168_TS_SERIAL; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2168", I2C_NAME_SIZE); + info.addr = 0xc8 >> 1; + info.platform_data = &si2168_config; + request_module(info.type); + client_demod = i2c_new_client_device(&dev->i2c_bus[2].i2c_adap, &info); + if (!i2c_client_has_driver(client_demod)) + goto frontend_detach; + + if (!try_module_get(client_demod->dev.driver->owner)) { + i2c_unregister_device(client_demod); + goto frontend_detach; + } + port->i2c_client_demod = client_demod; + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.if_port = 1; + si2157_config.fe = port->dvb.frontend; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2157", I2C_NAME_SIZE); + info.addr = 0xc0 >> 1; + info.platform_data = &si2157_config; + request_module(info.type); + client_tuner = i2c_new_client_device(&dev->i2c_bus[0].i2c_adap, &info); + if (!i2c_client_has_driver(client_tuner)) { + module_put(client_demod->dev.driver->owner); + i2c_unregister_device(client_demod); + goto frontend_detach; + } + if (!try_module_get(client_tuner->dev.driver->owner)) { + i2c_unregister_device(client_tuner); + module_put(client_demod->dev.driver->owner); + i2c_unregister_device(client_demod); + goto frontend_detach; + } + port->i2c_client_tuner = client_tuner; + } else { + /* attach frontend */ + memset(&si2168_config, 0, sizeof(si2168_config)); + si2168_config.i2c_adapter = &adapter; + si2168_config.fe = &port->dvb.frontend; + si2168_config.ts_mode = SI2168_TS_SERIAL; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2168", I2C_NAME_SIZE); + info.addr = 0xcc >> 1; + info.platform_data = &si2168_config; + request_module(info.type); + client_demod = i2c_new_client_device(&dev->i2c_bus[2].i2c_adap, &info); + if (!i2c_client_has_driver(client_demod)) + goto frontend_detach; + + if (!try_module_get(client_demod->dev.driver->owner)) { + i2c_unregister_device(client_demod); + goto frontend_detach; + } + port->i2c_client_demod = client_demod; + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = port->dvb.frontend; + si2157_config.if_port = 1; + memset(&info, 0, sizeof(struct i2c_board_info)); + strscpy(info.type, "si2157", I2C_NAME_SIZE); + info.addr = 0xc0 >> 1; + info.platform_data = &si2157_config; + request_module(info.type); + client_tuner = i2c_new_client_device(&dev->i2c_bus[1].i2c_adap, &info); + if (!i2c_client_has_driver(client_tuner)) { + module_put(client_demod->dev.driver->owner); + i2c_unregister_device(client_demod); + goto frontend_detach; + } + if (!try_module_get(client_tuner->dev.driver->owner)) { + i2c_unregister_device(client_tuner); + module_put(client_demod->dev.driver->owner); + i2c_unregister_device(client_demod); + goto frontend_detach; + } + port->i2c_client_tuner = client_tuner; + } + + break; + default: + printk(KERN_ERR "%s: The frontend isn't supported\n", + dev->name); + break; + } + if (NULL == dvb->frontend) { + printk(KERN_ERR "%s() Frontend initialization failed\n", + __func__); + return -1; + } + + /* register everything */ + ret = dvb_register(port); + if (ret < 0) { + if (dvb->frontend->ops.release) + dvb->frontend->ops.release(dvb->frontend); + return ret; + } + + return 0; + +frontend_detach: + printk(KERN_ERR "%s() Frontend/I2C initialization failed\n", __func__); + return -1; +} diff --git a/drivers/media/pci/saa7164/saa7164-encoder.c b/drivers/media/pci/saa7164/saa7164-encoder.c new file mode 100644 index 000000000..64380741b --- /dev/null +++ b/drivers/media/pci/saa7164/saa7164-encoder.c @@ -0,0 +1,1144 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the NXP SAA7164 PCIe bridge + * + * Copyright (c) 2010-2015 Steven Toth + */ + +#include "saa7164.h" + +#define ENCODER_MAX_BITRATE 6500000 +#define ENCODER_MIN_BITRATE 1000000 +#define ENCODER_DEF_BITRATE 5000000 + +/* + * This is a dummy non-zero value for the sizeimage field of v4l2_pix_format. + * It is not actually used for anything since this driver does not support + * stream I/O, only read(), and because this driver produces an MPEG stream + * and not discrete frames. But the V4L2 spec doesn't allow for this value + * to be 0, so set it to 0x10000 instead. + * + * If we ever change this driver to support stream I/O, then this field + * will be the size of the streaming buffers. + */ +#define SAA7164_SIZEIMAGE (0x10000) + +static struct saa7164_tvnorm saa7164_tvnorms[] = { + { + .name = "NTSC-M", + .id = V4L2_STD_NTSC_M, + }, { + .name = "NTSC-JP", + .id = V4L2_STD_NTSC_M_JP, + } +}; + +/* Take the encoder configuration form the port struct and + * flush it to the hardware. + */ +static void saa7164_encoder_configure(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + dprintk(DBGLVL_ENC, "%s()\n", __func__); + + port->encoder_params.width = port->width; + port->encoder_params.height = port->height; + port->encoder_params.is_50hz = + (port->encodernorm.id & V4L2_STD_625_50) != 0; + + /* Set up the DIF (enable it) for analog mode by default */ + saa7164_api_initialize_dif(port); + + /* Configure the correct video standard */ + saa7164_api_configure_dif(port, port->encodernorm.id); + + /* Ensure the audio decoder is correct configured */ + saa7164_api_set_audio_std(port); +} + +static int saa7164_encoder_buffers_dealloc(struct saa7164_port *port) +{ + struct list_head *c, *n, *p, *q, *l, *v; + struct saa7164_dev *dev = port->dev; + struct saa7164_buffer *buf; + struct saa7164_user_buffer *ubuf; + + /* Remove any allocated buffers */ + mutex_lock(&port->dmaqueue_lock); + + dprintk(DBGLVL_ENC, "%s(port=%d) dmaqueue\n", __func__, port->nr); + list_for_each_safe(c, n, &port->dmaqueue.list) { + buf = list_entry(c, struct saa7164_buffer, list); + list_del(c); + saa7164_buffer_dealloc(buf); + } + + dprintk(DBGLVL_ENC, "%s(port=%d) used\n", __func__, port->nr); + list_for_each_safe(p, q, &port->list_buf_used.list) { + ubuf = list_entry(p, struct saa7164_user_buffer, list); + list_del(p); + saa7164_buffer_dealloc_user(ubuf); + } + + dprintk(DBGLVL_ENC, "%s(port=%d) free\n", __func__, port->nr); + list_for_each_safe(l, v, &port->list_buf_free.list) { + ubuf = list_entry(l, struct saa7164_user_buffer, list); + list_del(l); + saa7164_buffer_dealloc_user(ubuf); + } + + mutex_unlock(&port->dmaqueue_lock); + dprintk(DBGLVL_ENC, "%s(port=%d) done\n", __func__, port->nr); + + return 0; +} + +/* Dynamic buffer switch at encoder start time */ +static int saa7164_encoder_buffers_alloc(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct saa7164_buffer *buf; + struct saa7164_user_buffer *ubuf; + struct tmHWStreamParameters *params = &port->hw_streamingparams; + int result = -ENODEV, i; + int len = 0; + + dprintk(DBGLVL_ENC, "%s()\n", __func__); + + if (port->encoder_params.stream_type == + V4L2_MPEG_STREAM_TYPE_MPEG2_PS) { + dprintk(DBGLVL_ENC, + "%s() type=V4L2_MPEG_STREAM_TYPE_MPEG2_PS\n", + __func__); + params->samplesperline = 128; + params->numberoflines = 256; + params->pitch = 128; + params->numpagetables = 2 + + ((SAA7164_PS_NUMBER_OF_LINES * 128) / PAGE_SIZE); + } else + if (port->encoder_params.stream_type == + V4L2_MPEG_STREAM_TYPE_MPEG2_TS) { + dprintk(DBGLVL_ENC, + "%s() type=V4L2_MPEG_STREAM_TYPE_MPEG2_TS\n", + __func__); + params->samplesperline = 188; + params->numberoflines = 312; + params->pitch = 188; + params->numpagetables = 2 + + ((SAA7164_TS_NUMBER_OF_LINES * 188) / PAGE_SIZE); + } else + BUG(); + + /* Init and establish defaults */ + params->bitspersample = 8; + params->linethreshold = 0; + params->pagetablelistvirt = NULL; + params->pagetablelistphys = NULL; + params->numpagetableentries = port->hwcfg.buffercount; + + /* Allocate the PCI resources, buffers (hard) */ + for (i = 0; i < port->hwcfg.buffercount; i++) { + buf = saa7164_buffer_alloc(port, + params->numberoflines * + params->pitch); + + if (!buf) { + printk(KERN_ERR "%s() failed (errno = %d), unable to allocate buffer\n", + __func__, result); + result = -ENOMEM; + goto failed; + } else { + + mutex_lock(&port->dmaqueue_lock); + list_add_tail(&buf->list, &port->dmaqueue.list); + mutex_unlock(&port->dmaqueue_lock); + + } + } + + /* Allocate some kernel buffers for copying + * to userpsace. + */ + len = params->numberoflines * params->pitch; + + if (encoder_buffers < 16) + encoder_buffers = 16; + if (encoder_buffers > 512) + encoder_buffers = 512; + + for (i = 0; i < encoder_buffers; i++) { + + ubuf = saa7164_buffer_alloc_user(dev, len); + if (ubuf) { + mutex_lock(&port->dmaqueue_lock); + list_add_tail(&ubuf->list, &port->list_buf_free.list); + mutex_unlock(&port->dmaqueue_lock); + } + + } + + result = 0; + +failed: + return result; +} + +static int saa7164_encoder_initialize(struct saa7164_port *port) +{ + saa7164_encoder_configure(port); + return 0; +} + +/* -- V4L2 --------------------------------------------------------- */ +int saa7164_s_std(struct saa7164_port *port, v4l2_std_id id) +{ + struct saa7164_dev *dev = port->dev; + unsigned int i; + + dprintk(DBGLVL_ENC, "%s(id=0x%x)\n", __func__, (u32)id); + + for (i = 0; i < ARRAY_SIZE(saa7164_tvnorms); i++) { + if (id & saa7164_tvnorms[i].id) + break; + } + if (i == ARRAY_SIZE(saa7164_tvnorms)) + return -EINVAL; + + port->encodernorm = saa7164_tvnorms[i]; + port->std = id; + + /* Update the audio decoder while is not running in + * auto detect mode. + */ + saa7164_api_set_audio_std(port); + + dprintk(DBGLVL_ENC, "%s(id=0x%x) OK\n", __func__, (u32)id); + + return 0; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id id) +{ + struct saa7164_encoder_fh *fh = to_saa7164_encoder_fh(file); + + return saa7164_s_std(fh->port, id); +} + +int saa7164_g_std(struct saa7164_port *port, v4l2_std_id *id) +{ + *id = port->std; + return 0; +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct saa7164_encoder_fh *fh = to_saa7164_encoder_fh(file); + + return saa7164_g_std(fh->port, id); +} + +int saa7164_enum_input(struct file *file, void *priv, struct v4l2_input *i) +{ + static const char * const inputs[] = { + "tuner", "composite", "svideo", "aux", + "composite 2", "svideo 2", "aux 2" + }; + int n; + + if (i->index >= 7) + return -EINVAL; + + strscpy(i->name, inputs[i->index], sizeof(i->name)); + + if (i->index == 0) + i->type = V4L2_INPUT_TYPE_TUNER; + else + i->type = V4L2_INPUT_TYPE_CAMERA; + + for (n = 0; n < ARRAY_SIZE(saa7164_tvnorms); n++) + i->std |= saa7164_tvnorms[n].id; + + return 0; +} + +int saa7164_g_input(struct saa7164_port *port, unsigned int *i) +{ + struct saa7164_dev *dev = port->dev; + + if (saa7164_api_get_videomux(port) != SAA_OK) + return -EIO; + + *i = (port->mux_input - 1); + + dprintk(DBGLVL_ENC, "%s() input=%d\n", __func__, *i); + + return 0; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct saa7164_encoder_fh *fh = to_saa7164_encoder_fh(file); + + return saa7164_g_input(fh->port, i); +} + +int saa7164_s_input(struct saa7164_port *port, unsigned int i) +{ + struct saa7164_dev *dev = port->dev; + + dprintk(DBGLVL_ENC, "%s() input=%d\n", __func__, i); + + if (i >= 7) + return -EINVAL; + + port->mux_input = i + 1; + + if (saa7164_api_set_videomux(port) != SAA_OK) + return -EIO; + + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + struct saa7164_encoder_fh *fh = to_saa7164_encoder_fh(file); + + return saa7164_s_input(fh->port, i); +} + +int saa7164_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t) +{ + struct saa7164_encoder_fh *fh = to_saa7164_encoder_fh(file); + struct saa7164_port *port = fh->port; + struct saa7164_dev *dev = port->dev; + + if (0 != t->index) + return -EINVAL; + + strscpy(t->name, "tuner", sizeof(t->name)); + t->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO; + t->rangelow = SAA7164_TV_MIN_FREQ; + t->rangehigh = SAA7164_TV_MAX_FREQ; + + dprintk(DBGLVL_ENC, "VIDIOC_G_TUNER: tuner type %d\n", t->type); + + return 0; +} + +int saa7164_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *t) +{ + if (0 != t->index) + return -EINVAL; + + /* Update the A/V core */ + return 0; +} + +int saa7164_g_frequency(struct saa7164_port *port, struct v4l2_frequency *f) +{ + if (f->tuner) + return -EINVAL; + + f->frequency = port->freq; + return 0; +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct saa7164_encoder_fh *fh = to_saa7164_encoder_fh(file); + + return saa7164_g_frequency(fh->port, f); +} + +int saa7164_s_frequency(struct saa7164_port *port, + const struct v4l2_frequency *f) +{ + struct saa7164_dev *dev = port->dev; + struct saa7164_port *tsport; + struct dvb_frontend *fe; + + /* TODO: Pull this for the std */ + struct analog_parameters params = { + .mode = V4L2_TUNER_ANALOG_TV, + .audmode = V4L2_TUNER_MODE_STEREO, + .std = port->encodernorm.id, + .frequency = f->frequency + }; + + /* Stop the encoder */ + dprintk(DBGLVL_ENC, "%s() frequency=%d tuner=%d\n", __func__, + f->frequency, f->tuner); + + if (f->tuner != 0) + return -EINVAL; + + port->freq = clamp(f->frequency, + SAA7164_TV_MIN_FREQ, SAA7164_TV_MAX_FREQ); + + /* Update the hardware */ + if (port->nr == SAA7164_PORT_ENC1) + tsport = &dev->ports[SAA7164_PORT_TS1]; + else if (port->nr == SAA7164_PORT_ENC2) + tsport = &dev->ports[SAA7164_PORT_TS2]; + else + return -EINVAL; /* should not happen */ + + fe = tsport->dvb.frontend; + + if (fe && fe->ops.tuner_ops.set_analog_params) + fe->ops.tuner_ops.set_analog_params(fe, ¶ms); + else + printk(KERN_ERR "%s() No analog tuner, aborting\n", __func__); + + saa7164_encoder_initialize(port); + + return 0; +} + +static int vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct saa7164_encoder_fh *fh = to_saa7164_encoder_fh(file); + + return saa7164_s_frequency(fh->port, f); +} + +static int saa7164_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct saa7164_port *port = + container_of(ctrl->handler, struct saa7164_port, ctrl_handler); + struct saa7164_encoder_params *params = &port->encoder_params; + int ret = 0; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + port->ctl_brightness = ctrl->val; + saa7164_api_set_usercontrol(port, PU_BRIGHTNESS_CONTROL); + break; + case V4L2_CID_CONTRAST: + port->ctl_contrast = ctrl->val; + saa7164_api_set_usercontrol(port, PU_CONTRAST_CONTROL); + break; + case V4L2_CID_SATURATION: + port->ctl_saturation = ctrl->val; + saa7164_api_set_usercontrol(port, PU_SATURATION_CONTROL); + break; + case V4L2_CID_HUE: + port->ctl_hue = ctrl->val; + saa7164_api_set_usercontrol(port, PU_HUE_CONTROL); + break; + case V4L2_CID_SHARPNESS: + port->ctl_sharpness = ctrl->val; + saa7164_api_set_usercontrol(port, PU_SHARPNESS_CONTROL); + break; + case V4L2_CID_AUDIO_VOLUME: + port->ctl_volume = ctrl->val; + saa7164_api_set_audio_volume(port, port->ctl_volume); + break; + case V4L2_CID_MPEG_VIDEO_BITRATE: + params->bitrate = ctrl->val; + break; + case V4L2_CID_MPEG_STREAM_TYPE: + params->stream_type = ctrl->val; + break; + case V4L2_CID_MPEG_AUDIO_MUTE: + params->ctl_mute = ctrl->val; + ret = saa7164_api_audio_mute(port, params->ctl_mute); + if (ret != SAA_OK) { + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, + ret); + ret = -EIO; + } + break; + case V4L2_CID_MPEG_VIDEO_ASPECT: + params->ctl_aspect = ctrl->val; + ret = saa7164_api_set_aspect_ratio(port); + if (ret != SAA_OK) { + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, + ret); + ret = -EIO; + } + break; + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + params->bitrate_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_B_FRAMES: + params->refdist = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE_PEAK: + params->bitrate_peak = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + params->gop_size = ctrl->val; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct saa7164_encoder_fh *fh = to_saa7164_encoder_fh(file); + struct saa7164_port *port = fh->port; + struct saa7164_dev *dev = port->dev; + + strscpy(cap->driver, dev->name, sizeof(cap->driver)); + strscpy(cap->card, saa7164_boards[dev->board].name, + sizeof(cap->card)); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_TUNER | V4L2_CAP_VBI_CAPTURE | + V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index != 0) + return -EINVAL; + + f->pixelformat = V4L2_PIX_FMT_MPEG; + + return 0; +} + +static int vidioc_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct saa7164_encoder_fh *fh = to_saa7164_encoder_fh(file); + struct saa7164_port *port = fh->port; + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = SAA7164_SIZEIMAGE; + f->fmt.pix.field = V4L2_FIELD_INTERLACED; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + f->fmt.pix.width = port->width; + f->fmt.pix.height = port->height; + return 0; +} + +static int saa7164_encoder_stop_port(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + int ret; + + ret = saa7164_api_transition_port(port, SAA_DMASTATE_STOP); + if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() stop transition failed, ret = 0x%x\n", + __func__, ret); + ret = -EIO; + } else { + dprintk(DBGLVL_ENC, "%s() Stopped\n", __func__); + ret = 0; + } + + return ret; +} + +static int saa7164_encoder_acquire_port(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + int ret; + + ret = saa7164_api_transition_port(port, SAA_DMASTATE_ACQUIRE); + if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() acquire transition failed, ret = 0x%x\n", + __func__, ret); + ret = -EIO; + } else { + dprintk(DBGLVL_ENC, "%s() Acquired\n", __func__); + ret = 0; + } + + return ret; +} + +static int saa7164_encoder_pause_port(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + int ret; + + ret = saa7164_api_transition_port(port, SAA_DMASTATE_PAUSE); + if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() pause transition failed, ret = 0x%x\n", + __func__, ret); + ret = -EIO; + } else { + dprintk(DBGLVL_ENC, "%s() Paused\n", __func__); + ret = 0; + } + + return ret; +} + +/* Firmware is very windows centric, meaning you have to transition + * the part through AVStream / KS Windows stages, forwards or backwards. + * States are: stopped, acquired (h/w), paused, started. + * We have to leave here will all of the soft buffers on the free list, + * else the cfg_post() func won't have soft buffers to correctly configure. + */ +static int saa7164_encoder_stop_streaming(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct saa7164_buffer *buf; + struct saa7164_user_buffer *ubuf; + struct list_head *c, *n; + int ret; + + dprintk(DBGLVL_ENC, "%s(port=%d)\n", __func__, port->nr); + + ret = saa7164_encoder_pause_port(port); + ret = saa7164_encoder_acquire_port(port); + ret = saa7164_encoder_stop_port(port); + + dprintk(DBGLVL_ENC, "%s(port=%d) Hardware stopped\n", __func__, + port->nr); + + /* Reset the state of any allocated buffer resources */ + mutex_lock(&port->dmaqueue_lock); + + /* Reset the hard and soft buffer state */ + list_for_each_safe(c, n, &port->dmaqueue.list) { + buf = list_entry(c, struct saa7164_buffer, list); + buf->flags = SAA7164_BUFFER_FREE; + buf->pos = 0; + } + + list_for_each_safe(c, n, &port->list_buf_used.list) { + ubuf = list_entry(c, struct saa7164_user_buffer, list); + ubuf->pos = 0; + list_move_tail(&ubuf->list, &port->list_buf_free.list); + } + + mutex_unlock(&port->dmaqueue_lock); + + /* Free any allocated resources */ + saa7164_encoder_buffers_dealloc(port); + + dprintk(DBGLVL_ENC, "%s(port=%d) Released\n", __func__, port->nr); + + return ret; +} + +static int saa7164_encoder_start_streaming(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + int result, ret = 0; + + dprintk(DBGLVL_ENC, "%s(port=%d)\n", __func__, port->nr); + + port->done_first_interrupt = 0; + + /* allocate all of the PCIe DMA buffer resources on the fly, + * allowing switching between TS and PS payloads without + * requiring a complete driver reload. + */ + saa7164_encoder_buffers_alloc(port); + + /* Configure the encoder with any cache values */ + saa7164_api_set_encoder(port); + saa7164_api_get_encoder(port); + + /* Place the empty buffers on the hardware */ + saa7164_buffer_cfg_port(port); + + /* Acquire the hardware */ + result = saa7164_api_transition_port(port, SAA_DMASTATE_ACQUIRE); + if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() acquire transition failed, res = 0x%x\n", + __func__, result); + + /* Stop the hardware, regardless */ + result = saa7164_api_transition_port(port, SAA_DMASTATE_STOP); + if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() acquire/forced stop transition failed, res = 0x%x\n", + __func__, result); + } + ret = -EIO; + goto out; + } else + dprintk(DBGLVL_ENC, "%s() Acquired\n", __func__); + + /* Pause the hardware */ + result = saa7164_api_transition_port(port, SAA_DMASTATE_PAUSE); + if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() pause transition failed, res = 0x%x\n", + __func__, result); + + /* Stop the hardware, regardless */ + result = saa7164_api_transition_port(port, SAA_DMASTATE_STOP); + if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() pause/forced stop transition failed, res = 0x%x\n", + __func__, result); + } + + ret = -EIO; + goto out; + } else + dprintk(DBGLVL_ENC, "%s() Paused\n", __func__); + + /* Start the hardware */ + result = saa7164_api_transition_port(port, SAA_DMASTATE_RUN); + if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() run transition failed, result = 0x%x\n", + __func__, result); + + /* Stop the hardware, regardless */ + result = saa7164_api_transition_port(port, SAA_DMASTATE_STOP); + if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() run/forced stop transition failed, res = 0x%x\n", + __func__, result); + } + + ret = -EIO; + } else + dprintk(DBGLVL_ENC, "%s() Running\n", __func__); + +out: + return ret; +} + +static int fops_open(struct file *file) +{ + struct saa7164_dev *dev; + struct saa7164_port *port; + struct saa7164_encoder_fh *fh; + + port = (struct saa7164_port *)video_get_drvdata(video_devdata(file)); + if (!port) + return -ENODEV; + + dev = port->dev; + + dprintk(DBGLVL_ENC, "%s()\n", __func__); + + /* allocate + initialize per filehandle data */ + fh = kzalloc_obj(*fh); + if (NULL == fh) + return -ENOMEM; + + fh->port = port; + v4l2_fh_init(&fh->fh, video_devdata(file)); + v4l2_fh_add(&fh->fh, file); + + return 0; +} + +static int fops_release(struct file *file) +{ + struct saa7164_encoder_fh *fh = to_saa7164_encoder_fh(file); + struct saa7164_port *port = fh->port; + struct saa7164_dev *dev = port->dev; + + dprintk(DBGLVL_ENC, "%s()\n", __func__); + + /* Shut device down on last close */ + if (atomic_cmpxchg(&fh->v4l_reading, 1, 0) == 1) { + if (atomic_dec_return(&port->v4l_reader_count) == 0) { + /* stop mpeg capture then cancel buffers */ + saa7164_encoder_stop_streaming(port); + } + } + + v4l2_fh_del(&fh->fh, file); + v4l2_fh_exit(&fh->fh); + kfree(fh); + + return 0; +} + +static struct +saa7164_user_buffer *saa7164_enc_next_buf(struct saa7164_port *port) +{ + struct saa7164_user_buffer *ubuf = NULL; + struct saa7164_dev *dev = port->dev; + u32 crc; + + mutex_lock(&port->dmaqueue_lock); + if (!list_empty(&port->list_buf_used.list)) { + ubuf = list_first_entry(&port->list_buf_used.list, + struct saa7164_user_buffer, list); + + if (crc_checking) { + crc = crc32(0, ubuf->data, ubuf->actual_size); + if (crc != ubuf->crc) { + printk(KERN_ERR + "%s() ubuf %p crc became invalid, was 0x%x became 0x%x\n", + __func__, + ubuf, ubuf->crc, crc); + } + } + + } + mutex_unlock(&port->dmaqueue_lock); + + dprintk(DBGLVL_ENC, "%s() returns %p\n", __func__, ubuf); + + return ubuf; +} + +static ssize_t fops_read(struct file *file, char __user *buffer, + size_t count, loff_t *pos) +{ + struct saa7164_encoder_fh *fh = to_saa7164_encoder_fh(file); + struct saa7164_port *port = fh->port; + struct saa7164_user_buffer *ubuf = NULL; + struct saa7164_dev *dev = port->dev; + int ret = 0; + int rem, cnt; + u8 *p; + + port->last_read_msecs_diff = port->last_read_msecs; + port->last_read_msecs = jiffies_to_msecs(jiffies); + port->last_read_msecs_diff = port->last_read_msecs - + port->last_read_msecs_diff; + + saa7164_histogram_update(&port->read_interval, + port->last_read_msecs_diff); + + if (*pos) { + printk(KERN_ERR "%s() ESPIPE\n", __func__); + return -ESPIPE; + } + + if (atomic_cmpxchg(&fh->v4l_reading, 0, 1) == 0) { + if (atomic_inc_return(&port->v4l_reader_count) == 1) { + + if (saa7164_encoder_initialize(port) < 0) { + printk(KERN_ERR "%s() EINVAL\n", __func__); + return -EINVAL; + } + + saa7164_encoder_start_streaming(port); + msleep(200); + } + } + + /* blocking wait for buffer */ + if ((file->f_flags & O_NONBLOCK) == 0) { + if (wait_event_interruptible(port->wait_read, + saa7164_enc_next_buf(port))) { + printk(KERN_ERR "%s() ERESTARTSYS\n", __func__); + return -ERESTARTSYS; + } + } + + /* Pull the first buffer from the used list */ + ubuf = saa7164_enc_next_buf(port); + + while ((count > 0) && ubuf) { + + /* set remaining bytes to copy */ + rem = ubuf->actual_size - ubuf->pos; + cnt = rem > count ? count : rem; + + p = ubuf->data + ubuf->pos; + + dprintk(DBGLVL_ENC, + "%s() count=%d cnt=%d rem=%d buf=%p buf->pos=%d\n", + __func__, (int)count, cnt, rem, ubuf, ubuf->pos); + + if (copy_to_user(buffer, p, cnt)) { + printk(KERN_ERR "%s() copy_to_user failed\n", __func__); + if (!ret) { + printk(KERN_ERR "%s() EFAULT\n", __func__); + ret = -EFAULT; + } + goto err; + } + + ubuf->pos += cnt; + count -= cnt; + buffer += cnt; + ret += cnt; + + if (ubuf->pos > ubuf->actual_size) + printk(KERN_ERR "read() pos > actual, huh?\n"); + + if (ubuf->pos == ubuf->actual_size) { + + /* finished with current buffer, take next buffer */ + + /* Requeue the buffer on the free list */ + ubuf->pos = 0; + + mutex_lock(&port->dmaqueue_lock); + list_move_tail(&ubuf->list, &port->list_buf_free.list); + mutex_unlock(&port->dmaqueue_lock); + + /* Dequeue next */ + if ((file->f_flags & O_NONBLOCK) == 0) { + if (wait_event_interruptible(port->wait_read, + saa7164_enc_next_buf(port))) { + break; + } + } + ubuf = saa7164_enc_next_buf(port); + } + } +err: + if (!ret && !ubuf) + ret = -EAGAIN; + + return ret; +} + +static __poll_t fops_poll(struct file *file, poll_table *wait) +{ + __poll_t req_events = poll_requested_events(wait); + struct saa7164_encoder_fh *fh = to_saa7164_encoder_fh(file); + struct saa7164_port *port = fh->port; + __poll_t mask = v4l2_ctrl_poll(file, wait); + + port->last_poll_msecs_diff = port->last_poll_msecs; + port->last_poll_msecs = jiffies_to_msecs(jiffies); + port->last_poll_msecs_diff = port->last_poll_msecs - + port->last_poll_msecs_diff; + + saa7164_histogram_update(&port->poll_interval, + port->last_poll_msecs_diff); + + if (!(req_events & (EPOLLIN | EPOLLRDNORM))) + return mask; + + if (atomic_cmpxchg(&fh->v4l_reading, 0, 1) == 0) { + if (atomic_inc_return(&port->v4l_reader_count) == 1) { + if (saa7164_encoder_initialize(port) < 0) + return mask | EPOLLERR; + saa7164_encoder_start_streaming(port); + msleep(200); + } + } + + /* Pull the first buffer from the used list */ + if (!list_empty(&port->list_buf_used.list)) + mask |= EPOLLIN | EPOLLRDNORM; + + return mask; +} + +static const struct v4l2_ctrl_ops saa7164_ctrl_ops = { + .s_ctrl = saa7164_s_ctrl, +}; + +static const struct v4l2_file_operations mpeg_fops = { + .owner = THIS_MODULE, + .open = fops_open, + .release = fops_release, + .read = fops_read, + .poll = fops_poll, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops mpeg_ioctl_ops = { + .vidioc_s_std = vidioc_s_std, + .vidioc_g_std = vidioc_g_std, + .vidioc_enum_input = saa7164_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_g_tuner = saa7164_g_tuner, + .vidioc_s_tuner = saa7164_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_fmt_vid_cap, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static struct video_device saa7164_mpeg_template = { + .name = "saa7164", + .fops = &mpeg_fops, + .ioctl_ops = &mpeg_ioctl_ops, + .minor = -1, + .tvnorms = SAA7164_NORMS, + .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_TUNER, +}; + +static struct video_device *saa7164_encoder_alloc( + struct saa7164_port *port, + struct pci_dev *pci, + struct video_device *template, + char *type) +{ + struct video_device *vfd; + struct saa7164_dev *dev = port->dev; + + dprintk(DBGLVL_ENC, "%s()\n", __func__); + + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + + *vfd = *template; + snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)", dev->name, + type, saa7164_boards[dev->board].name); + + vfd->v4l2_dev = &dev->v4l2_dev; + vfd->release = video_device_release; + return vfd; +} + +int saa7164_encoder_register(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct v4l2_ctrl_handler *hdl = &port->ctrl_handler; + int result = -ENODEV; + + dprintk(DBGLVL_ENC, "%s()\n", __func__); + + BUG_ON(port->type != SAA7164_MPEG_ENCODER); + + /* Sanity check that the PCI configuration space is active */ + if (port->hwcfg.BARLocation == 0) { + printk(KERN_ERR "%s() failed (errno = %d), NO PCI configuration\n", + __func__, result); + result = -ENOMEM; + goto fail_pci; + } + + /* Establish encoder defaults here */ + /* Set default TV standard */ + port->encodernorm = saa7164_tvnorms[0]; + port->width = 720; + port->mux_input = 1; /* Composite */ + port->video_format = EU_VIDEO_FORMAT_MPEG_2; + port->audio_format = 0; + port->video_resolution = 0; + port->freq = SAA7164_TV_MIN_FREQ; + + v4l2_ctrl_handler_init(hdl, 14); + v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 127); + v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 66); + v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 62); + v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops, + V4L2_CID_HUE, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops, + V4L2_CID_SHARPNESS, 0x0, 0x0f, 1, 8); + v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops, + V4L2_CID_MPEG_AUDIO_MUTE, 0x0, 0x01, 1, 0); + v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, -83, 24, 1, 20); + v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE, + ENCODER_MIN_BITRATE, ENCODER_MAX_BITRATE, + 100000, ENCODER_DEF_BITRATE); + v4l2_ctrl_new_std_menu(hdl, &saa7164_ctrl_ops, + V4L2_CID_MPEG_STREAM_TYPE, + V4L2_MPEG_STREAM_TYPE_MPEG2_TS, 0, + V4L2_MPEG_STREAM_TYPE_MPEG2_PS); + v4l2_ctrl_new_std_menu(hdl, &saa7164_ctrl_ops, + V4L2_CID_MPEG_VIDEO_ASPECT, + V4L2_MPEG_VIDEO_ASPECT_221x100, 0, + V4L2_MPEG_VIDEO_ASPECT_4x3); + v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, 1, 255, 1, 15); + v4l2_ctrl_new_std_menu(hdl, &saa7164_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE_MODE, + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, 0, + V4L2_MPEG_VIDEO_BITRATE_MODE_VBR); + v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops, + V4L2_CID_MPEG_VIDEO_B_FRAMES, 1, 3, 1, 1); + v4l2_ctrl_new_std(hdl, &saa7164_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE_PEAK, + ENCODER_MIN_BITRATE, ENCODER_MAX_BITRATE, + 100000, ENCODER_DEF_BITRATE); + if (hdl->error) { + result = hdl->error; + goto fail_hdl; + } + + port->std = V4L2_STD_NTSC_M; + + if (port->encodernorm.id & V4L2_STD_525_60) + port->height = 480; + else + port->height = 576; + + /* Allocate and register the video device node */ + port->v4l_device = saa7164_encoder_alloc(port, + dev->pci, &saa7164_mpeg_template, "mpeg"); + + if (!port->v4l_device) { + printk(KERN_INFO "%s: can't allocate mpeg device\n", + dev->name); + result = -ENOMEM; + goto fail_hdl; + } + + port->v4l_device->ctrl_handler = hdl; + v4l2_ctrl_handler_setup(hdl); + video_set_drvdata(port->v4l_device, port); + result = video_register_device(port->v4l_device, + VFL_TYPE_VIDEO, -1); + if (result < 0) { + printk(KERN_INFO "%s: can't register mpeg device\n", + dev->name); + goto fail_reg; + } + + printk(KERN_INFO "%s: registered device video%d [mpeg]\n", + dev->name, port->v4l_device->num); + + /* Configure the hardware defaults */ + saa7164_api_set_videomux(port); + saa7164_api_set_usercontrol(port, PU_BRIGHTNESS_CONTROL); + saa7164_api_set_usercontrol(port, PU_CONTRAST_CONTROL); + saa7164_api_set_usercontrol(port, PU_HUE_CONTROL); + saa7164_api_set_usercontrol(port, PU_SATURATION_CONTROL); + saa7164_api_set_usercontrol(port, PU_SHARPNESS_CONTROL); + saa7164_api_audio_mute(port, 0); + saa7164_api_set_audio_volume(port, 20); + saa7164_api_set_aspect_ratio(port); + + /* Disable audio standard detection, it's buggy */ + saa7164_api_set_audio_detection(port, 0); + + saa7164_api_set_encoder(port); + saa7164_api_get_encoder(port); + return 0; + +fail_reg: + video_device_release(port->v4l_device); + port->v4l_device = NULL; +fail_hdl: + v4l2_ctrl_handler_free(hdl); +fail_pci: + return result; +} + +void saa7164_encoder_unregister(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + + dprintk(DBGLVL_ENC, "%s(port=%d)\n", __func__, port->nr); + + BUG_ON(port->type != SAA7164_MPEG_ENCODER); + + if (port->v4l_device) { + if (port->v4l_device->minor != -1) + video_unregister_device(port->v4l_device); + else + video_device_release(port->v4l_device); + + port->v4l_device = NULL; + } + v4l2_ctrl_handler_free(&port->ctrl_handler); + + dprintk(DBGLVL_ENC, "%s(port=%d) done\n", __func__, port->nr); +} + diff --git a/drivers/media/pci/saa7164/saa7164-fw.c b/drivers/media/pci/saa7164/saa7164-fw.c new file mode 100644 index 000000000..341cef624 --- /dev/null +++ b/drivers/media/pci/saa7164/saa7164-fw.c @@ -0,0 +1,597 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the NXP SAA7164 PCIe bridge + * + * Copyright (c) 2010-2015 Steven Toth + */ + +#include +#include + +#include "saa7164.h" + +#define SAA7164_REV2_FIRMWARE "v4l-saa7164-1.0.2-3.fw" +#define SAA7164_REV2_FIRMWARE_SIZE 4038864 + +#define SAA7164_REV3_FIRMWARE "NXP7164-2010-03-10.1.fw" +#define SAA7164_REV3_FIRMWARE_SIZE 4019072 + +struct fw_header { + u32 firmwaresize; + u32 bslsize; + u32 reserved; + u32 version; +}; + +static int saa7164_dl_wait_ack(struct saa7164_dev *dev, u32 reg) +{ + u32 timeout = SAA_DEVICE_TIMEOUT; + while ((saa7164_readl(reg) & 0x01) == 0) { + timeout -= 10; + if (timeout == 0) { + printk(KERN_ERR "%s() timeout (no d/l ack)\n", + __func__); + return -EBUSY; + } + msleep(100); + } + + return 0; +} + +static int saa7164_dl_wait_clr(struct saa7164_dev *dev, u32 reg) +{ + u32 timeout = SAA_DEVICE_TIMEOUT; + while (saa7164_readl(reg) & 0x01) { + timeout -= 10; + if (timeout == 0) { + printk(KERN_ERR "%s() timeout (no d/l clr)\n", + __func__); + return -EBUSY; + } + msleep(100); + } + + return 0; +} + +/* TODO: move dlflags into dev-> and change to write/readl/b */ +/* TODO: Excessive levels of debug */ +static int saa7164_downloadimage(struct saa7164_dev *dev, u8 *src, u32 srcsize, + u32 dlflags, u8 __iomem *dst, u32 dstsize) +{ + u32 reg, timeout, offset; + u8 *srcbuf = NULL; + int ret; + + u32 dlflag = dlflags; + u32 dlflag_ack = dlflag + 4; + u32 drflag = dlflag_ack + 4; + u32 drflag_ack = drflag + 4; + u32 bleflag = drflag_ack + 4; + + dprintk(DBGLVL_FW, + "%s(image=%p, size=%d, flags=0x%x, dst=%p, dstsize=0x%x)\n", + __func__, src, srcsize, dlflags, dst, dstsize); + + if ((src == NULL) || (dst == NULL)) { + ret = -EIO; + goto out; + } + + srcbuf = kzalloc(4 * 1048576, GFP_KERNEL); + if (NULL == srcbuf) { + ret = -ENOMEM; + goto out; + } + + if (srcsize > (4*1048576)) { + ret = -ENOMEM; + goto out; + } + + memcpy(srcbuf, src, srcsize); + + dprintk(DBGLVL_FW, "%s() dlflag = 0x%x\n", __func__, dlflag); + dprintk(DBGLVL_FW, "%s() dlflag_ack = 0x%x\n", __func__, dlflag_ack); + dprintk(DBGLVL_FW, "%s() drflag = 0x%x\n", __func__, drflag); + dprintk(DBGLVL_FW, "%s() drflag_ack = 0x%x\n", __func__, drflag_ack); + dprintk(DBGLVL_FW, "%s() bleflag = 0x%x\n", __func__, bleflag); + + reg = saa7164_readl(dlflag); + dprintk(DBGLVL_FW, "%s() dlflag (0x%x)= 0x%x\n", __func__, dlflag, reg); + if (reg == 1) + dprintk(DBGLVL_FW, + "%s() Download flag already set, please reboot\n", + __func__); + + /* Indicate download start */ + saa7164_writel(dlflag, 1); + ret = saa7164_dl_wait_ack(dev, dlflag_ack); + if (ret < 0) + goto out; + + /* Ack download start, then wait for wait */ + saa7164_writel(dlflag, 0); + ret = saa7164_dl_wait_clr(dev, dlflag_ack); + if (ret < 0) + goto out; + + /* Deal with the raw firmware, in the appropriate chunk size */ + for (offset = 0; srcsize > dstsize; + srcsize -= dstsize, offset += dstsize) { + + dprintk(DBGLVL_FW, "%s() memcpy %d\n", __func__, dstsize); + memcpy_toio(dst, srcbuf + offset, dstsize); + + /* Flag the data as ready */ + saa7164_writel(drflag, 1); + ret = saa7164_dl_wait_ack(dev, drflag_ack); + if (ret < 0) + goto out; + + /* Wait for indication data was received */ + saa7164_writel(drflag, 0); + ret = saa7164_dl_wait_clr(dev, drflag_ack); + if (ret < 0) + goto out; + + } + + dprintk(DBGLVL_FW, "%s() memcpy(l) %d\n", __func__, dstsize); + /* Write last block to the device */ + memcpy_toio(dst, srcbuf+offset, srcsize); + + /* Flag the data as ready */ + saa7164_writel(drflag, 1); + ret = saa7164_dl_wait_ack(dev, drflag_ack); + if (ret < 0) + goto out; + + saa7164_writel(drflag, 0); + timeout = 0; + while (saa7164_readl(bleflag) != SAA_DEVICE_IMAGE_BOOTING) { + if (saa7164_readl(bleflag) & SAA_DEVICE_IMAGE_CORRUPT) { + printk(KERN_ERR "%s() image corrupt\n", __func__); + ret = -EBUSY; + goto out; + } + + if (saa7164_readl(bleflag) & SAA_DEVICE_MEMORY_CORRUPT) { + printk(KERN_ERR "%s() device memory corrupt\n", + __func__); + ret = -EBUSY; + goto out; + } + + msleep(10); /* Checkpatch throws a < 20ms warning */ + if (timeout++ > 60) + break; + } + + printk(KERN_INFO "%s() Image downloaded, booting...\n", __func__); + + ret = saa7164_dl_wait_clr(dev, drflag_ack); + if (ret < 0) + goto out; + + printk(KERN_INFO "%s() Image booted successfully.\n", __func__); + ret = 0; + +out: + kfree(srcbuf); + return ret; +} + +/* TODO: Excessive debug */ +/* Load the firmware. Optionally it can be in ROM or newer versions + * can be on disk, saving the expense of the ROM hardware. */ +int saa7164_downloadfirmware(struct saa7164_dev *dev) +{ + /* u32 second_timeout = 60 * SAA_DEVICE_TIMEOUT; */ + u32 tmp, filesize, version, err_flags, first_timeout, fwlength; + u32 second_timeout, updatebootloader = 1, bootloadersize = 0; + const struct firmware *fw = NULL; + struct fw_header *hdr, *boothdr = NULL, *fwhdr; + u32 bootloaderversion = 0, fwloadersize; + u8 *bootloaderoffset = NULL, *fwloaderoffset; + char *fwname; + int ret; + + dprintk(DBGLVL_FW, "%s()\n", __func__); + + if (saa7164_boards[dev->board].chiprev == SAA7164_CHIP_REV2) { + fwname = SAA7164_REV2_FIRMWARE; + fwlength = SAA7164_REV2_FIRMWARE_SIZE; + } else { + fwname = SAA7164_REV3_FIRMWARE; + fwlength = SAA7164_REV3_FIRMWARE_SIZE; + } + + version = saa7164_getcurrentfirmwareversion(dev); + + if (version == 0x00) { + + second_timeout = 100; + first_timeout = 100; + err_flags = saa7164_readl(SAA_BOOTLOADERERROR_FLAGS); + dprintk(DBGLVL_FW, "%s() err_flags = %x\n", + __func__, err_flags); + + while (err_flags != SAA_DEVICE_IMAGE_BOOTING) { + dprintk(DBGLVL_FW, "%s() err_flags = %x\n", + __func__, err_flags); + msleep(10); /* Checkpatch throws a < 20ms warning */ + + if (err_flags & SAA_DEVICE_IMAGE_CORRUPT) { + printk(KERN_ERR "%s() firmware corrupt\n", + __func__); + break; + } + if (err_flags & SAA_DEVICE_MEMORY_CORRUPT) { + printk(KERN_ERR "%s() device memory corrupt\n", + __func__); + break; + } + if (err_flags & SAA_DEVICE_NO_IMAGE) { + printk(KERN_ERR "%s() no first image\n", + __func__); + break; + } + if (err_flags & SAA_DEVICE_IMAGE_SEARCHING) { + first_timeout -= 10; + if (first_timeout == 0) { + printk(KERN_ERR + "%s() no first image\n", + __func__); + break; + } + } else if (err_flags & SAA_DEVICE_IMAGE_LOADING) { + second_timeout -= 10; + if (second_timeout == 0) { + printk(KERN_ERR + "%s() FW load time exceeded\n", + __func__); + break; + } + } else { + second_timeout -= 10; + if (second_timeout == 0) { + printk(KERN_ERR + "%s() Unknown bootloader flags 0x%x\n", + __func__, err_flags); + break; + } + } + + err_flags = saa7164_readl(SAA_BOOTLOADERERROR_FLAGS); + } /* While != Booting */ + + if (err_flags == SAA_DEVICE_IMAGE_BOOTING) { + dprintk(DBGLVL_FW, "%s() Loader 1 has loaded.\n", + __func__); + first_timeout = SAA_DEVICE_TIMEOUT; + second_timeout = 100; + + err_flags = saa7164_readl(SAA_SECONDSTAGEERROR_FLAGS); + dprintk(DBGLVL_FW, "%s() err_flags2 = %x\n", + __func__, err_flags); + while (err_flags != SAA_DEVICE_IMAGE_BOOTING) { + dprintk(DBGLVL_FW, "%s() err_flags2 = %x\n", + __func__, err_flags); + msleep(10); /* Checkpatch throws a < 20ms warning */ + + if (err_flags & SAA_DEVICE_IMAGE_CORRUPT) { + printk(KERN_ERR + "%s() firmware corrupt\n", + __func__); + break; + } + if (err_flags & SAA_DEVICE_MEMORY_CORRUPT) { + printk(KERN_ERR + "%s() device memory corrupt\n", + __func__); + break; + } + if (err_flags & SAA_DEVICE_NO_IMAGE) { + printk(KERN_ERR "%s() no second image\n", + __func__); + break; + } + if (err_flags & SAA_DEVICE_IMAGE_SEARCHING) { + first_timeout -= 10; + if (first_timeout == 0) { + printk(KERN_ERR + "%s() no second image\n", + __func__); + break; + } + } else if (err_flags & + SAA_DEVICE_IMAGE_LOADING) { + second_timeout -= 10; + if (second_timeout == 0) { + printk(KERN_ERR + "%s() FW load time exceeded\n", + __func__); + break; + } + } else { + second_timeout -= 10; + if (second_timeout == 0) { + printk(KERN_ERR + "%s() Unknown bootloader flags 0x%x\n", + __func__, err_flags); + break; + } + } + + err_flags = + saa7164_readl(SAA_SECONDSTAGEERROR_FLAGS); + } /* err_flags != SAA_DEVICE_IMAGE_BOOTING */ + + dprintk(DBGLVL_FW, "%s() Loader flags 1:0x%x 2:0x%x.\n", + __func__, + saa7164_readl(SAA_BOOTLOADERERROR_FLAGS), + saa7164_readl(SAA_SECONDSTAGEERROR_FLAGS)); + + } /* err_flags == SAA_DEVICE_IMAGE_BOOTING */ + + /* It's possible for both firmwares to have booted, + * but that doesn't mean they've finished booting yet. + */ + if ((saa7164_readl(SAA_BOOTLOADERERROR_FLAGS) == + SAA_DEVICE_IMAGE_BOOTING) && + (saa7164_readl(SAA_SECONDSTAGEERROR_FLAGS) == + SAA_DEVICE_IMAGE_BOOTING)) { + + + dprintk(DBGLVL_FW, "%s() Loader 2 has loaded.\n", + __func__); + + first_timeout = SAA_DEVICE_TIMEOUT; + while (first_timeout) { + msleep(10); /* Checkpatch throws a < 20ms warning */ + + version = + saa7164_getcurrentfirmwareversion(dev); + if (version) { + dprintk(DBGLVL_FW, + "%s() All f/w loaded successfully\n", + __func__); + break; + } else { + first_timeout -= 10; + if (first_timeout == 0) { + printk(KERN_ERR + "%s() FW did not boot\n", + __func__); + break; + } + } + } + } + version = saa7164_getcurrentfirmwareversion(dev); + } /* version == 0 */ + + /* Has the firmware really booted? */ + if ((saa7164_readl(SAA_BOOTLOADERERROR_FLAGS) == + SAA_DEVICE_IMAGE_BOOTING) && + (saa7164_readl(SAA_SECONDSTAGEERROR_FLAGS) == + SAA_DEVICE_IMAGE_BOOTING) && (version == 0)) { + + printk(KERN_ERR + "%s() The firmware hung, probably bad firmware\n", + __func__); + + /* Tell the second stage loader we have a deadlock */ + saa7164_writel(SAA_DEVICE_DEADLOCK_DETECTED_OFFSET, + SAA_DEVICE_DEADLOCK_DETECTED); + + saa7164_getfirmwarestatus(dev); + + return -ENOMEM; + } + + dprintk(DBGLVL_FW, "Device has Firmware Version %d.%d.%d.%d\n", + (version & 0x0000fc00) >> 10, + (version & 0x000003e0) >> 5, + (version & 0x0000001f), + (version & 0xffff0000) >> 16); + + /* Load the firmware from the disk if required */ + if (version == 0) { + + printk(KERN_INFO "%s() Waiting for firmware upload (%s)\n", + __func__, fwname); + + ret = request_firmware(&fw, fwname, &dev->pci->dev); + if (ret) { + printk(KERN_ERR "%s() Upload failed. (file not found?)\n", + __func__); + return -ENOMEM; + } + + printk(KERN_INFO "%s() firmware read %zu bytes.\n", + __func__, fw->size); + + if (fw->size != fwlength) { + printk(KERN_ERR "saa7164: firmware incorrect size %zu != %u\n", + fw->size, fwlength); + ret = -ENOMEM; + goto out; + } + + printk(KERN_INFO "%s() firmware loaded.\n", __func__); + + hdr = (struct fw_header *)fw->data; + printk(KERN_INFO "Firmware file header part 1:\n"); + printk(KERN_INFO " .FirmwareSize = 0x%x\n", hdr->firmwaresize); + printk(KERN_INFO " .BSLSize = 0x%x\n", hdr->bslsize); + printk(KERN_INFO " .Reserved = 0x%x\n", hdr->reserved); + printk(KERN_INFO " .Version = 0x%x\n", hdr->version); + + /* Retrieve bootloader if reqd */ + if ((hdr->firmwaresize == 0) && (hdr->bslsize == 0)) + /* Second bootloader in the firmware file */ + filesize = hdr->reserved * 16; + else + filesize = (hdr->firmwaresize + hdr->bslsize) * + 16 + sizeof(struct fw_header); + + printk(KERN_INFO "%s() SecBootLoader.FileSize = %d\n", + __func__, filesize); + + /* Get bootloader (if reqd) and firmware header */ + if ((hdr->firmwaresize == 0) && (hdr->bslsize == 0)) { + /* Second boot loader is required */ + + /* Get the loader header */ + boothdr = (struct fw_header *)(fw->data + + sizeof(struct fw_header)); + + bootloaderversion = + saa7164_readl(SAA_DEVICE_2ND_VERSION); + dprintk(DBGLVL_FW, "Onboard BootLoader:\n"); + dprintk(DBGLVL_FW, "->Flag 0x%x\n", + saa7164_readl(SAA_BOOTLOADERERROR_FLAGS)); + dprintk(DBGLVL_FW, "->Ack 0x%x\n", + saa7164_readl(SAA_DATAREADY_FLAG_ACK)); + dprintk(DBGLVL_FW, "->FW Version 0x%x\n", version); + dprintk(DBGLVL_FW, "->Loader Version 0x%x\n", + bootloaderversion); + + if ((saa7164_readl(SAA_BOOTLOADERERROR_FLAGS) == + 0x03) && (saa7164_readl(SAA_DATAREADY_FLAG_ACK) + == 0x00) && (version == 0x00)) { + + dprintk(DBGLVL_FW, "BootLoader version in rom %d.%d.%d.%d\n", + (bootloaderversion & 0x0000fc00) >> 10, + (bootloaderversion & 0x000003e0) >> 5, + (bootloaderversion & 0x0000001f), + (bootloaderversion & 0xffff0000) >> 16 + ); + dprintk(DBGLVL_FW, "BootLoader version in file %d.%d.%d.%d\n", + (boothdr->version & 0x0000fc00) >> 10, + (boothdr->version & 0x000003e0) >> 5, + (boothdr->version & 0x0000001f), + (boothdr->version & 0xffff0000) >> 16 + ); + + if (bootloaderversion == boothdr->version) + updatebootloader = 0; + } + + /* Calculate offset to firmware header */ + tmp = (boothdr->firmwaresize + boothdr->bslsize) * 16 + + (sizeof(struct fw_header) + + sizeof(struct fw_header)); + + fwhdr = (struct fw_header *)(fw->data+tmp); + } else { + /* No second boot loader */ + fwhdr = hdr; + } + + dprintk(DBGLVL_FW, "Firmware version in file %d.%d.%d.%d\n", + (fwhdr->version & 0x0000fc00) >> 10, + (fwhdr->version & 0x000003e0) >> 5, + (fwhdr->version & 0x0000001f), + (fwhdr->version & 0xffff0000) >> 16 + ); + + if (version == fwhdr->version) { + /* No download, firmware already on board */ + ret = 0; + goto out; + } + + if ((hdr->firmwaresize == 0) && (hdr->bslsize == 0)) { + if (updatebootloader) { + /* Get ready to upload the bootloader */ + bootloadersize = (boothdr->firmwaresize + + boothdr->bslsize) * 16 + + sizeof(struct fw_header); + + bootloaderoffset = (u8 *)(fw->data + + sizeof(struct fw_header)); + + dprintk(DBGLVL_FW, "bootloader d/l starts.\n"); + printk(KERN_INFO "%s() FirmwareSize = 0x%x\n", + __func__, boothdr->firmwaresize); + printk(KERN_INFO "%s() BSLSize = 0x%x\n", + __func__, boothdr->bslsize); + printk(KERN_INFO "%s() Reserved = 0x%x\n", + __func__, boothdr->reserved); + printk(KERN_INFO "%s() Version = 0x%x\n", + __func__, boothdr->version); + ret = saa7164_downloadimage( + dev, + bootloaderoffset, + bootloadersize, + SAA_DOWNLOAD_FLAGS, + dev->bmmio + SAA_DEVICE_DOWNLOAD_OFFSET, + SAA_DEVICE_BUFFERBLOCKSIZE); + if (ret < 0) { + printk(KERN_ERR + "bootloader d/l has failed\n"); + goto out; + } + dprintk(DBGLVL_FW, + "bootloader download complete.\n"); + + } + + printk(KERN_ERR "starting firmware download(2)\n"); + bootloadersize = (boothdr->firmwaresize + + boothdr->bslsize) * 16 + + sizeof(struct fw_header); + + bootloaderoffset = + (u8 *)(fw->data + sizeof(struct fw_header)); + + fwloaderoffset = bootloaderoffset + bootloadersize; + + /* TODO: fix this bounds overrun here with old f/ws */ + fwloadersize = (fwhdr->firmwaresize + fwhdr->bslsize) * + 16 + sizeof(struct fw_header); + + ret = saa7164_downloadimage( + dev, + fwloaderoffset, + fwloadersize, + SAA_DEVICE_2ND_DOWNLOADFLAG_OFFSET, + dev->bmmio + SAA_DEVICE_2ND_DOWNLOAD_OFFSET, + SAA_DEVICE_2ND_BUFFERBLOCKSIZE); + if (ret < 0) { + printk(KERN_ERR "firmware download failed\n"); + goto out; + } + printk(KERN_ERR "firmware download complete.\n"); + + } else { + + /* No bootloader update reqd, download firmware only */ + printk(KERN_ERR "starting firmware download(3)\n"); + + ret = saa7164_downloadimage( + dev, + (u8 *)fw->data, + fw->size, + SAA_DOWNLOAD_FLAGS, + dev->bmmio + SAA_DEVICE_DOWNLOAD_OFFSET, + SAA_DEVICE_BUFFERBLOCKSIZE); + if (ret < 0) { + printk(KERN_ERR "firmware download failed\n"); + goto out; + } + printk(KERN_ERR "firmware download complete.\n"); + } + } + + dev->firmwareloaded = 1; + ret = 0; + +out: + release_firmware(fw); + return ret; +} diff --git a/drivers/media/pci/saa7164/saa7164-i2c.c b/drivers/media/pci/saa7164/saa7164-i2c.c new file mode 100644 index 000000000..3b11bf889 --- /dev/null +++ b/drivers/media/pci/saa7164/saa7164-i2c.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the NXP SAA7164 PCIe bridge + * + * Copyright (c) 2010-2015 Steven Toth + */ + +#include +#include +#include +#include +#include + +#include "saa7164.h" + +static int i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, int num) +{ + struct saa7164_i2c *bus = i2c_adap->algo_data; + struct saa7164_dev *dev = bus->dev; + int i, retval = 0; + + dprintk(DBGLVL_I2C, "%s(num = %d)\n", __func__, num); + + for (i = 0 ; i < num; i++) { + dprintk(DBGLVL_I2C, "%s(num = %d) addr = 0x%02x len = 0x%x\n", + __func__, num, msgs[i].addr, msgs[i].len); + if (msgs[i].flags & I2C_M_RD) { + retval = saa7164_api_i2c_read(bus, + msgs[i].addr, + 0 /* reglen */, + NULL /* reg */, msgs[i].len, msgs[i].buf); + } else if (i + 1 < num && (msgs[i + 1].flags & I2C_M_RD) && + msgs[i].addr == msgs[i + 1].addr) { + /* write then read from same address */ + + retval = saa7164_api_i2c_read(bus, msgs[i].addr, + msgs[i].len, msgs[i].buf, + msgs[i+1].len, msgs[i+1].buf + ); + + i++; + + if (retval < 0) + goto err; + } else { + /* write */ + retval = saa7164_api_i2c_write(bus, msgs[i].addr, + msgs[i].len, msgs[i].buf); + } + if (retval < 0) + goto err; + } + return num; + +err: + return retval; +} + +static u32 saa7164_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C; +} + +static const struct i2c_algorithm saa7164_i2c_algo_template = { + .master_xfer = i2c_xfer, + .functionality = saa7164_functionality, +}; + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_adapter saa7164_i2c_adap_template = { + .name = "saa7164", + .owner = THIS_MODULE, + .algo = &saa7164_i2c_algo_template, +}; + +static const struct i2c_client saa7164_i2c_client_template = { + .name = "saa7164 internal", +}; + +int saa7164_i2c_register(struct saa7164_i2c *bus) +{ + struct saa7164_dev *dev = bus->dev; + + dprintk(DBGLVL_I2C, "%s(bus = %d)\n", __func__, bus->nr); + + bus->i2c_adap = saa7164_i2c_adap_template; + bus->i2c_client = saa7164_i2c_client_template; + + bus->i2c_adap.dev.parent = &dev->pci->dev; + + strscpy(bus->i2c_adap.name, bus->dev->name, + sizeof(bus->i2c_adap.name)); + + bus->i2c_adap.algo_data = bus; + i2c_set_adapdata(&bus->i2c_adap, bus); + i2c_add_adapter(&bus->i2c_adap); + + bus->i2c_client.adapter = &bus->i2c_adap; + + if (0 != bus->i2c_rc) + printk(KERN_ERR "%s: i2c bus %d register FAILED\n", + dev->name, bus->nr); + + return bus->i2c_rc; +} + +int saa7164_i2c_unregister(struct saa7164_i2c *bus) +{ + i2c_del_adapter(&bus->i2c_adap); + return 0; +} diff --git a/drivers/media/pci/saa7164/saa7164-reg.h b/drivers/media/pci/saa7164/saa7164-reg.h new file mode 100644 index 000000000..3f9d12b69 --- /dev/null +++ b/drivers/media/pci/saa7164/saa7164-reg.h @@ -0,0 +1,205 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the NXP SAA7164 PCIe bridge + * + * Copyright (c) 2010-2015 Steven Toth + */ + +/* TODO: Retest the driver with errors expressed as negatives */ + +/* Result codes */ +#define SAA_OK 0 +#define SAA_ERR_BAD_PARAMETER 0x09 +#define SAA_ERR_NO_RESOURCES 0x0c +#define SAA_ERR_NOT_SUPPORTED 0x13 +#define SAA_ERR_BUSY 0x15 +#define SAA_ERR_READ 0x17 +#define SAA_ERR_TIMEOUT 0x1f +#define SAA_ERR_OVERFLOW 0x20 +#define SAA_ERR_EMPTY 0x22 +#define SAA_ERR_NOT_STARTED 0x23 +#define SAA_ERR_ALREADY_STARTED 0x24 +#define SAA_ERR_NOT_STOPPED 0x25 +#define SAA_ERR_ALREADY_STOPPED 0x26 +#define SAA_ERR_INVALID_COMMAND 0x3e +#define SAA_ERR_NULL_PACKET 0x59 + +/* Errors and flags from the silicon */ +#define PVC_ERRORCODE_UNKNOWN 0x00 +#define PVC_ERRORCODE_INVALID_COMMAND 0x01 +#define PVC_ERRORCODE_INVALID_CONTROL 0x02 +#define PVC_ERRORCODE_INVALID_DATA 0x03 +#define PVC_ERRORCODE_TIMEOUT 0x04 +#define PVC_ERRORCODE_NAK 0x05 +#define PVC_RESPONSEFLAG_ERROR 0x01 +#define PVC_RESPONSEFLAG_OVERFLOW 0x02 +#define PVC_RESPONSEFLAG_RESET 0x04 +#define PVC_RESPONSEFLAG_INTERFACE 0x08 +#define PVC_RESPONSEFLAG_CONTINUED 0x10 +#define PVC_CMDFLAG_INTERRUPT 0x02 +#define PVC_CMDFLAG_INTERFACE 0x04 +#define PVC_CMDFLAG_SERIALIZE 0x08 +#define PVC_CMDFLAG_CONTINUE 0x10 + +/* Silicon Commands */ +#define GET_DESCRIPTORS_CONTROL 0x01 +#define GET_STRING_CONTROL 0x03 +#define GET_LANGUAGE_CONTROL 0x05 +#define SET_POWER_CONTROL 0x07 +#define GET_FW_STATUS_CONTROL 0x08 +#define GET_FW_VERSION_CONTROL 0x09 +#define SET_DEBUG_LEVEL_CONTROL 0x0B +#define GET_DEBUG_DATA_CONTROL 0x0C +#define GET_PRODUCTION_INFO_CONTROL 0x0D + +/* cmd defines */ +#define SAA_CMDFLAG_CONTINUE 0x10 +#define SAA_CMD_MAX_MSG_UNITS 256 + +/* Some defines */ +#define SAA_BUS_TIMEOUT 50 +#define SAA_DEVICE_TIMEOUT 5000 +#define SAA_DEVICE_MAXREQUESTSIZE 256 + +/* Register addresses */ +#define SAA_DEVICE_VERSION 0x30 +#define SAA_DOWNLOAD_FLAGS 0x34 +#define SAA_DOWNLOAD_FLAG 0x34 +#define SAA_DOWNLOAD_FLAG_ACK 0x38 +#define SAA_DATAREADY_FLAG 0x3C +#define SAA_DATAREADY_FLAG_ACK 0x40 + +/* Boot loader register and bit definitions */ +#define SAA_BOOTLOADERERROR_FLAGS 0x44 +#define SAA_DEVICE_IMAGE_SEARCHING 0x01 +#define SAA_DEVICE_IMAGE_LOADING 0x02 +#define SAA_DEVICE_IMAGE_BOOTING 0x03 +#define SAA_DEVICE_IMAGE_CORRUPT 0x04 +#define SAA_DEVICE_MEMORY_CORRUPT 0x08 +#define SAA_DEVICE_NO_IMAGE 0x10 + +/* Register addresses */ +#define SAA_DEVICE_2ND_VERSION 0x50 +#define SAA_DEVICE_2ND_DOWNLOADFLAG_OFFSET 0x54 + +/* Register addresses */ +#define SAA_SECONDSTAGEERROR_FLAGS 0x64 + +/* Bootloader regs and flags */ +#define SAA_DEVICE_DEADLOCK_DETECTED_OFFSET 0x6C +#define SAA_DEVICE_DEADLOCK_DETECTED 0xDEADDEAD + +/* Basic firmware status registers */ +#define SAA_DEVICE_SYSINIT_STATUS_OFFSET 0x70 +#define SAA_DEVICE_SYSINIT_STATUS 0x70 +#define SAA_DEVICE_SYSINIT_MODE 0x74 +#define SAA_DEVICE_SYSINIT_SPEC 0x78 +#define SAA_DEVICE_SYSINIT_INST 0x7C +#define SAA_DEVICE_SYSINIT_CPULOAD 0x80 +#define SAA_DEVICE_SYSINIT_REMAINHEAP 0x84 + +#define SAA_DEVICE_DOWNLOAD_OFFSET 0x1000 +#define SAA_DEVICE_BUFFERBLOCKSIZE 0x1000 + +#define SAA_DEVICE_2ND_BUFFERBLOCKSIZE 0x100000 +#define SAA_DEVICE_2ND_DOWNLOAD_OFFSET 0x200000 + +/* Descriptors */ +#define CS_INTERFACE 0x24 + +/* Descriptor subtypes */ +#define VC_INPUT_TERMINAL 0x02 +#define VC_OUTPUT_TERMINAL 0x03 +#define VC_SELECTOR_UNIT 0x04 +#define VC_PROCESSING_UNIT 0x05 +#define FEATURE_UNIT 0x06 +#define TUNER_UNIT 0x09 +#define ENCODER_UNIT 0x0A +#define EXTENSION_UNIT 0x0B +#define VC_TUNER_PATH 0xF0 +#define PVC_HARDWARE_DESCRIPTOR 0xF1 +#define PVC_INTERFACE_DESCRIPTOR 0xF2 +#define PVC_INFRARED_UNIT 0xF3 +#define DRM_UNIT 0xF4 +#define GENERAL_REQUEST 0xF5 + +/* Format Types */ +#define VS_FORMAT_TYPE 0x02 +#define VS_FORMAT_TYPE_I 0x01 +#define VS_FORMAT_UNCOMPRESSED 0x04 +#define VS_FRAME_UNCOMPRESSED 0x05 +#define VS_FORMAT_MPEG2PS 0x09 +#define VS_FORMAT_MPEG2TS 0x0A +#define VS_FORMAT_MPEG4SL 0x0B +#define VS_FORMAT_WM9 0x0C +#define VS_FORMAT_DIVX 0x0D +#define VS_FORMAT_VBI 0x0E +#define VS_FORMAT_RDS 0x0F + +/* Device extension commands */ +#define EXU_REGISTER_ACCESS_CONTROL 0x00 +#define EXU_GPIO_CONTROL 0x01 +#define EXU_GPIO_GROUP_CONTROL 0x02 +#define EXU_INTERRUPT_CONTROL 0x03 + +/* State Transition and args */ +#define SAA_PROBE_CONTROL 0x01 +#define SAA_COMMIT_CONTROL 0x02 +#define SAA_STATE_CONTROL 0x03 +#define SAA_DMASTATE_STOP 0x00 +#define SAA_DMASTATE_ACQUIRE 0x01 +#define SAA_DMASTATE_PAUSE 0x02 +#define SAA_DMASTATE_RUN 0x03 + +/* A/V Mux Input Selector */ +#define SU_INPUT_SELECT_CONTROL 0x01 + +/* Encoder Profiles */ +#define EU_PROFILE_PS_DVD 0x06 +#define EU_PROFILE_TS_HQ 0x09 +#define EU_VIDEO_FORMAT_MPEG_2 0x02 + +/* Tuner */ +#define TU_AUDIO_MODE_CONTROL 0x17 + +/* Video Formats */ +#define TU_STANDARD_CONTROL 0x00 +#define TU_STANDARD_AUTO_CONTROL 0x01 +#define TU_STANDARD_NONE 0x00 +#define TU_STANDARD_NTSC_M 0x01 +#define TU_STANDARD_PAL_I 0x08 +#define TU_STANDARD_MANUAL 0x00 +#define TU_STANDARD_AUTO 0x01 + +/* Video Controls */ +#define PU_BRIGHTNESS_CONTROL 0x02 +#define PU_CONTRAST_CONTROL 0x03 +#define PU_HUE_CONTROL 0x06 +#define PU_SATURATION_CONTROL 0x07 +#define PU_SHARPNESS_CONTROL 0x08 + +/* Audio Controls */ +#define MUTE_CONTROL 0x01 +#define VOLUME_CONTROL 0x02 +#define AUDIO_DEFAULT_CONTROL 0x0D + +/* Default Volume Levels */ +#define TMHW_LEV_ADJ_DECLEV_DEFAULT 0x00 +#define TMHW_LEV_ADJ_MONOLEV_DEFAULT 0x00 +#define TMHW_LEV_ADJ_NICLEV_DEFAULT 0x00 +#define TMHW_LEV_ADJ_SAPLEV_DEFAULT 0x00 +#define TMHW_LEV_ADJ_ADCLEV_DEFAULT 0x00 + +/* Encoder Related Commands */ +#define EU_PROFILE_CONTROL 0x00 +#define EU_VIDEO_FORMAT_CONTROL 0x01 +#define EU_VIDEO_BIT_RATE_CONTROL 0x02 +#define EU_VIDEO_RESOLUTION_CONTROL 0x03 +#define EU_VIDEO_GOP_STRUCTURE_CONTROL 0x04 +#define EU_VIDEO_INPUT_ASPECT_CONTROL 0x0A +#define EU_AUDIO_FORMAT_CONTROL 0x0C +#define EU_AUDIO_BIT_RATE_CONTROL 0x0D + +/* Firmware Debugging */ +#define SET_DEBUG_LEVEL_CONTROL 0x0B +#define GET_DEBUG_DATA_CONTROL 0x0C diff --git a/drivers/media/pci/saa7164/saa7164-types.h b/drivers/media/pci/saa7164/saa7164-types.h new file mode 100644 index 000000000..00f163b38 --- /dev/null +++ b/drivers/media/pci/saa7164/saa7164-types.h @@ -0,0 +1,428 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the NXP SAA7164 PCIe bridge + * + * Copyright (c) 2010-2015 Steven Toth + */ + +/* TODO: Cleanup and shorten the namespace */ + +/* Some structures are passed directly to/from the firmware and + * have strict alignment requirements. This is one of them. + */ +struct tmComResHWDescr { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + u16 bcdSpecVersion; + u32 dwClockFrequency; + u32 dwClockUpdateRes; + u8 bCapabilities; + u32 dwDeviceRegistersLocation; + u32 dwHostMemoryRegion; + u32 dwHostMemoryRegionSize; + u32 dwHostHibernatMemRegion; + u32 dwHostHibernatMemRegionSize; +} __attribute__((packed)); + +/* This is DWORD aligned on windows but I can't find the right + * gcc syntax to match the binary data from the device. + * I've manually padded with Reserved[3] bytes to match the hardware, + * but this could break if GCC decides to pack in a different way. + */ +struct tmComResInterfaceDescr { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + u8 bFlags; + u8 bInterfaceType; + u8 bInterfaceId; + u8 bBaseInterface; + u8 bInterruptId; + u8 bDebugInterruptId; + u8 BARLocation; + u8 Reserved[3]; +}; + +struct tmComResBusDescr { + u64 CommandRing; + u64 ResponseRing; + u32 CommandWrite; + u32 CommandRead; + u32 ResponseWrite; + u32 ResponseRead; +}; + +enum tmBusType { + NONE = 0, + TYPE_BUS_PCI = 1, + TYPE_BUS_PCIe = 2, + TYPE_BUS_USB = 3, + TYPE_BUS_I2C = 4 +}; + +struct tmComResBusInfo { + enum tmBusType Type; + u16 m_wMaxReqSize; + u8 __iomem *m_pdwSetRing; + u32 m_dwSizeSetRing; + u8 __iomem *m_pdwGetRing; + u32 m_dwSizeGetRing; + u32 m_dwSetWritePos; + u32 m_dwSetReadPos; + u32 m_dwGetWritePos; + u32 m_dwGetReadPos; + + /* All access is protected */ + struct mutex lock; + +}; + +struct tmComResInfo { + u8 id; + u8 flags; + u16 size; + u32 command; + u16 controlselector; + u8 seqno; +} __attribute__((packed)); + +enum tmComResCmd { + SET_CUR = 0x01, + GET_CUR = 0x81, + GET_MIN = 0x82, + GET_MAX = 0x83, + GET_RES = 0x84, + GET_LEN = 0x85, + GET_INFO = 0x86, + GET_DEF = 0x87 +}; + +struct cmd { + u8 seqno; + u32 inuse; + u32 timeout; + u32 signalled; + struct mutex lock; + wait_queue_head_t wait; +}; + +struct tmDescriptor { + u32 pathid; + u32 size; + void *descriptor; +}; + +struct tmComResDescrHeader { + u8 len; + u8 type; + u8 subtype; + u8 unitid; +} __attribute__((packed)); + +struct tmComResExtDevDescrHeader { + u8 len; + u8 type; + u8 subtype; + u8 unitid; + u32 devicetype; + u16 deviceid; + u32 numgpiopins; + u8 numgpiogroups; + u8 controlsize; +} __attribute__((packed)); + +struct tmComResGPIO { + u32 pin; + u8 state; +} __attribute__((packed)); + +struct tmComResPathDescrHeader { + u8 len; + u8 type; + u8 subtype; + u8 pathid; +} __attribute__((packed)); + +/* terminaltype */ +enum tmComResTermType { + ITT_ANTENNA = 0x0203, + LINE_CONNECTOR = 0x0603, + SPDIF_CONNECTOR = 0x0605, + COMPOSITE_CONNECTOR = 0x0401, + SVIDEO_CONNECTOR = 0x0402, + COMPONENT_CONNECTOR = 0x0403, + STANDARD_DMA = 0xF101 +}; + +struct tmComResAntTermDescrHeader { + u8 len; + u8 type; + u8 subtype; + u8 terminalid; + u16 terminaltype; + u8 assocterminal; + u8 iterminal; + u8 controlsize; +} __attribute__((packed)); + +struct tmComResTunerDescrHeader { + u8 len; + u8 type; + u8 subtype; + u8 unitid; + u8 sourceid; + u8 iunit; + u32 tuningstandards; + u8 controlsize; + u32 controls; +} __attribute__((packed)); + +enum tmBufferFlag { + /* the buffer does not contain any valid data */ + TM_BUFFER_FLAG_EMPTY, + + /* the buffer is filled with valid data */ + TM_BUFFER_FLAG_DONE, + + /* the buffer is the dummy buffer - TODO??? */ + TM_BUFFER_FLAG_DUMMY_BUFFER +}; + +struct tmBuffer { + u64 *pagetablevirt; + u64 pagetablephys; + u16 offset; + u8 *context; + u64 timestamp; + enum tmBufferFlag BufferFlag; + u32 lostbuffers; + u32 validbuffers; + u64 *dummypagevirt; + u64 dummypagephys; + u64 *addressvirt; +}; + +struct tmHWStreamParameters { + u32 bitspersample; + u32 samplesperline; + u32 numberoflines; + u32 pitch; + u32 linethreshold; + u64 **pagetablelistvirt; + u64 *pagetablelistphys; + u32 numpagetables; + u32 numpagetableentries; +}; + +struct tmStreamParameters { + struct tmHWStreamParameters HWStreamParameters; + u64 qwDummyPageTablePhys; + u64 *pDummyPageTableVirt; +}; + +struct tmComResDMATermDescrHeader { + u8 len; + u8 type; + u8 subtyle; + u8 unitid; + u16 terminaltype; + u8 assocterminal; + u8 sourceid; + u8 iterminal; + u32 BARLocation; + u8 flags; + u8 interruptid; + u8 buffercount; + u8 metadatasize; + u8 numformats; + u8 controlsize; +} __attribute__((packed)); + +/* + * + * Description: + * This is the transport stream format header. + * + * Settings: + * bLength - The size of this descriptor in bytes. + * bDescriptorType - CS_INTERFACE. + * bDescriptorSubtype - VS_FORMAT_MPEG2TS descriptor subtype. + * bFormatIndex - A non-zero constant that uniquely identifies the + * format. + * bDataOffset - Offset to TSP packet within MPEG-2 TS transport + * stride, in bytes. + * bPacketLength - Length of TSP packet, in bytes (typically 188). + * bStrideLength - Length of MPEG-2 TS transport stride. + * guidStrideFormat - A Globally Unique Identifier indicating the + * format of the stride data (if any). Set to zeros + * if there is no Stride Data, or if the Stride + * Data is to be ignored by the application. + * + */ +struct tmComResTSFormatDescrHeader { + u8 len; + u8 type; + u8 subtype; + u8 bFormatIndex; + u8 bDataOffset; + u8 bPacketLength; + u8 bStrideLength; + u8 guidStrideFormat[16]; +} __attribute__((packed)); + +/* Encoder related structures */ + +/* A/V Mux Selector */ +struct tmComResSelDescrHeader { + u8 len; + u8 type; + u8 subtype; + u8 unitid; + u8 nrinpins; + u8 sourceid; +} __attribute__((packed)); + +/* A/V Audio processor definitions */ +struct tmComResProcDescrHeader { + u8 len; + u8 type; + u8 subtype; + u8 unitid; + u8 sourceid; + u16 wreserved; + u8 controlsize; +} __attribute__((packed)); + +/* Video bitrate control message */ +#define EU_VIDEO_BIT_RATE_MODE_CONSTANT (0) +#define EU_VIDEO_BIT_RATE_MODE_VARIABLE_AVERAGE (1) +#define EU_VIDEO_BIT_RATE_MODE_VARIABLE_PEAK (2) +struct tmComResEncVideoBitRate { + u8 ucVideoBitRateMode; + u32 dwVideoBitRate; + u32 dwVideoBitRatePeak; +} __attribute__((packed)); + +/* Video Encoder Aspect Ratio message */ +struct tmComResEncVideoInputAspectRatio { + u8 width; + u8 height; +} __attribute__((packed)); + +/* Video Encoder GOP IBP message */ +/* 1. IPPPPPPPPPPPPPP */ +/* 2. IBPBPBPBPBPBPBP */ +/* 3. IBBPBBPBBPBBP */ +#define SAA7164_ENCODER_DEFAULT_GOP_DIST (1) +#define SAA7164_ENCODER_DEFAULT_GOP_SIZE (15) +struct tmComResEncVideoGopStructure { + u8 ucGOPSize; /* GOP Size 12, 15 */ + u8 ucRefFrameDist; /* Reference Frame Distance */ +} __attribute__((packed)); + +/* Encoder processor definition */ +struct tmComResEncoderDescrHeader { + u8 len; + u8 type; + u8 subtype; + u8 unitid; + u8 vsourceid; + u8 asourceid; + u8 iunit; + u32 dwmControlCap; + u32 dwmProfileCap; + u32 dwmVidFormatCap; + u8 bmVidBitrateCap; + u16 wmVidResolutionsCap; + u16 wmVidFrmRateCap; + u32 dwmAudFormatCap; + u8 bmAudBitrateCap; +} __attribute__((packed)); + +/* Audio processor definition */ +struct tmComResAFeatureDescrHeader { + u8 len; + u8 type; + u8 subtype; + u8 unitid; + u8 sourceid; + u8 controlsize; +} __attribute__((packed)); + +/* Audio control messages */ +struct tmComResAudioDefaults { + u8 ucDecoderLevel; + u8 ucDecoderFM_Level; + u8 ucMonoLevel; + u8 ucNICAM_Level; + u8 ucSAP_Level; + u8 ucADC_Level; +} __attribute__((packed)); + +/* Audio bitrate control message */ +struct tmComResEncAudioBitRate { + u8 ucAudioBitRateMode; + u32 dwAudioBitRate; + u32 dwAudioBitRatePeak; +} __attribute__((packed)); + +/* Tuner / AV Decoder messages */ +struct tmComResTunerStandard { + u8 std; + u32 country; +} __attribute__((packed)); + +struct tmComResTunerStandardAuto { + u8 mode; +} __attribute__((packed)); + +/* EEPROM definition for PS stream types */ +struct tmComResPSFormatDescrHeader { + u8 len; + u8 type; + u8 subtype; + u8 bFormatIndex; + u16 wPacketLength; + u16 wPackLength; + u8 bPackDataType; +} __attribute__((packed)); + +/* VBI control structure */ +struct tmComResVBIFormatDescrHeader { + u8 len; + u8 type; + u8 subtype; /* VS_FORMAT_VBI */ + u8 bFormatIndex; + u32 VideoStandard; /* See KS_AnalogVideoStandard, NTSC = 1 */ + u8 StartLine; /* NTSC Start = 10 */ + u8 EndLine; /* NTSC = 21 */ + u8 FieldRate; /* 60 for NTSC */ + u8 bNumLines; /* Unused - scheduled for removal */ +} __attribute__((packed)); + +struct tmComResProbeCommit { + u16 bmHint; + u8 bFormatIndex; + u8 bFrameIndex; +} __attribute__((packed)); + +struct tmComResDebugSetLevel { + u32 dwDebugLevel; +} __attribute__((packed)); + +struct tmComResDebugGetData { + u32 dwResult; + u8 ucDebugData[256]; +} __attribute__((packed)); + +struct tmFwInfoStruct { + u32 status; + u32 mode; + u32 devicespec; + u32 deviceinst; + u32 CPULoad; + u32 RemainHeap; + u32 CPUClock; + u32 RAMSpeed; +} __attribute__((packed)); diff --git a/drivers/media/pci/saa7164/saa7164-vbi.c b/drivers/media/pci/saa7164/saa7164-vbi.c new file mode 100644 index 000000000..bfa6ee295 --- /dev/null +++ b/drivers/media/pci/saa7164/saa7164-vbi.c @@ -0,0 +1,765 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the NXP SAA7164 PCIe bridge + * + * Copyright (c) 2010-2015 Steven Toth + */ + +#include "saa7164.h" + +/* Take the encoder configuration from the port struct and + * flush it to the hardware. + */ +static void saa7164_vbi_configure(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + dprintk(DBGLVL_VBI, "%s()\n", __func__); + + port->vbi_params.width = port->enc_port->width; + port->vbi_params.height = port->enc_port->height; + port->vbi_params.is_50hz = + (port->enc_port->encodernorm.id & V4L2_STD_625_50) != 0; + + /* Set up the DIF (enable it) for analog mode by default */ + saa7164_api_initialize_dif(port); + dprintk(DBGLVL_VBI, "%s() ends\n", __func__); +} + +static int saa7164_vbi_buffers_dealloc(struct saa7164_port *port) +{ + struct list_head *c, *n, *p, *q, *l, *v; + struct saa7164_dev *dev = port->dev; + struct saa7164_buffer *buf; + struct saa7164_user_buffer *ubuf; + + /* Remove any allocated buffers */ + mutex_lock(&port->dmaqueue_lock); + + dprintk(DBGLVL_VBI, "%s(port=%d) dmaqueue\n", __func__, port->nr); + list_for_each_safe(c, n, &port->dmaqueue.list) { + buf = list_entry(c, struct saa7164_buffer, list); + list_del(c); + saa7164_buffer_dealloc(buf); + } + + dprintk(DBGLVL_VBI, "%s(port=%d) used\n", __func__, port->nr); + list_for_each_safe(p, q, &port->list_buf_used.list) { + ubuf = list_entry(p, struct saa7164_user_buffer, list); + list_del(p); + saa7164_buffer_dealloc_user(ubuf); + } + + dprintk(DBGLVL_VBI, "%s(port=%d) free\n", __func__, port->nr); + list_for_each_safe(l, v, &port->list_buf_free.list) { + ubuf = list_entry(l, struct saa7164_user_buffer, list); + list_del(l); + saa7164_buffer_dealloc_user(ubuf); + } + + mutex_unlock(&port->dmaqueue_lock); + dprintk(DBGLVL_VBI, "%s(port=%d) done\n", __func__, port->nr); + + return 0; +} + +/* Dynamic buffer switch at vbi start time */ +static int saa7164_vbi_buffers_alloc(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct saa7164_buffer *buf; + struct saa7164_user_buffer *ubuf; + struct tmHWStreamParameters *params = &port->hw_streamingparams; + int result = -ENODEV, i; + int len = 0; + + dprintk(DBGLVL_VBI, "%s()\n", __func__); + + /* TODO: NTSC SPECIFIC */ + /* Init and establish defaults */ + params->samplesperline = 1440; + params->numberoflines = 18; + params->pitch = 1440; + params->numpagetables = 2 + + ((params->numberoflines * params->pitch) / PAGE_SIZE); + params->bitspersample = 8; + params->linethreshold = 0; + params->pagetablelistvirt = NULL; + params->pagetablelistphys = NULL; + params->numpagetableentries = port->hwcfg.buffercount; + + /* Allocate the PCI resources, buffers (hard) */ + for (i = 0; i < port->hwcfg.buffercount; i++) { + buf = saa7164_buffer_alloc(port, + params->numberoflines * + params->pitch); + + if (!buf) { + printk(KERN_ERR "%s() failed (errno = %d), unable to allocate buffer\n", + __func__, result); + result = -ENOMEM; + goto failed; + } else { + + mutex_lock(&port->dmaqueue_lock); + list_add_tail(&buf->list, &port->dmaqueue.list); + mutex_unlock(&port->dmaqueue_lock); + + } + } + + /* Allocate some kernel buffers for copying + * to userpsace. + */ + len = params->numberoflines * params->pitch; + + if (vbi_buffers < 16) + vbi_buffers = 16; + if (vbi_buffers > 512) + vbi_buffers = 512; + + for (i = 0; i < vbi_buffers; i++) { + + ubuf = saa7164_buffer_alloc_user(dev, len); + if (ubuf) { + mutex_lock(&port->dmaqueue_lock); + list_add_tail(&ubuf->list, &port->list_buf_free.list); + mutex_unlock(&port->dmaqueue_lock); + } + + } + + result = 0; + +failed: + return result; +} + + +static int saa7164_vbi_initialize(struct saa7164_port *port) +{ + saa7164_vbi_configure(port); + return 0; +} + +/* -- V4L2 --------------------------------------------------------- */ +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id id) +{ + struct saa7164_vbi_fh *fh = to_saa7164_vbi_fh(file); + + return saa7164_s_std(fh->port->enc_port, id); +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct saa7164_vbi_fh *fh = to_saa7164_vbi_fh(file); + + return saa7164_g_std(fh->port->enc_port, id); +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct saa7164_vbi_fh *fh = to_saa7164_vbi_fh(file); + + return saa7164_g_input(fh->port->enc_port, i); +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + struct saa7164_vbi_fh *fh = to_saa7164_vbi_fh(file); + + return saa7164_s_input(fh->port->enc_port, i); +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct saa7164_vbi_fh *fh = to_saa7164_vbi_fh(file); + + return saa7164_g_frequency(fh->port->enc_port, f); +} + +static int vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct saa7164_vbi_fh *fh = to_saa7164_vbi_fh(file); + int ret = saa7164_s_frequency(fh->port->enc_port, f); + + if (ret == 0) + saa7164_vbi_initialize(fh->port); + return ret; +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct saa7164_vbi_fh *fh = to_saa7164_vbi_fh(file); + struct saa7164_port *port = fh->port; + struct saa7164_dev *dev = port->dev; + + strscpy(cap->driver, dev->name, sizeof(cap->driver)); + strscpy(cap->card, saa7164_boards[dev->board].name, + sizeof(cap->card)); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_TUNER | V4L2_CAP_VBI_CAPTURE | + V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int saa7164_vbi_stop_port(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + int ret; + + ret = saa7164_api_transition_port(port, SAA_DMASTATE_STOP); + if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() stop transition failed, ret = 0x%x\n", + __func__, ret); + ret = -EIO; + } else { + dprintk(DBGLVL_VBI, "%s() Stopped\n", __func__); + ret = 0; + } + + return ret; +} + +static int saa7164_vbi_acquire_port(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + int ret; + + ret = saa7164_api_transition_port(port, SAA_DMASTATE_ACQUIRE); + if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() acquire transition failed, ret = 0x%x\n", + __func__, ret); + ret = -EIO; + } else { + dprintk(DBGLVL_VBI, "%s() Acquired\n", __func__); + ret = 0; + } + + return ret; +} + +static int saa7164_vbi_pause_port(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + int ret; + + ret = saa7164_api_transition_port(port, SAA_DMASTATE_PAUSE); + if ((ret != SAA_OK) && (ret != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() pause transition failed, ret = 0x%x\n", + __func__, ret); + ret = -EIO; + } else { + dprintk(DBGLVL_VBI, "%s() Paused\n", __func__); + ret = 0; + } + + return ret; +} + +/* Firmware is very windows centric, meaning you have to transition + * the part through AVStream / KS Windows stages, forwards or backwards. + * States are: stopped, acquired (h/w), paused, started. + * We have to leave here will all of the soft buffers on the free list, + * else the cfg_post() func won't have soft buffers to correctly configure. + */ +static int saa7164_vbi_stop_streaming(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct saa7164_buffer *buf; + struct saa7164_user_buffer *ubuf; + struct list_head *c, *n; + int ret; + + dprintk(DBGLVL_VBI, "%s(port=%d)\n", __func__, port->nr); + + ret = saa7164_vbi_pause_port(port); + ret = saa7164_vbi_acquire_port(port); + ret = saa7164_vbi_stop_port(port); + + dprintk(DBGLVL_VBI, "%s(port=%d) Hardware stopped\n", __func__, + port->nr); + + /* Reset the state of any allocated buffer resources */ + mutex_lock(&port->dmaqueue_lock); + + /* Reset the hard and soft buffer state */ + list_for_each_safe(c, n, &port->dmaqueue.list) { + buf = list_entry(c, struct saa7164_buffer, list); + buf->flags = SAA7164_BUFFER_FREE; + buf->pos = 0; + } + + list_for_each_safe(c, n, &port->list_buf_used.list) { + ubuf = list_entry(c, struct saa7164_user_buffer, list); + ubuf->pos = 0; + list_move_tail(&ubuf->list, &port->list_buf_free.list); + } + + mutex_unlock(&port->dmaqueue_lock); + + /* Free any allocated resources */ + saa7164_vbi_buffers_dealloc(port); + + dprintk(DBGLVL_VBI, "%s(port=%d) Released\n", __func__, port->nr); + + return ret; +} + +static int saa7164_vbi_start_streaming(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + int result, ret = 0; + + dprintk(DBGLVL_VBI, "%s(port=%d)\n", __func__, port->nr); + + port->done_first_interrupt = 0; + + /* allocate all of the PCIe DMA buffer resources on the fly, + * allowing switching between TS and PS payloads without + * requiring a complete driver reload. + */ + saa7164_vbi_buffers_alloc(port); + + /* Configure the encoder with any cache values */ +#if 0 + saa7164_api_set_encoder(port); + saa7164_api_get_encoder(port); +#endif + + /* Place the empty buffers on the hardware */ + saa7164_buffer_cfg_port(port); + + /* Negotiate format */ + if (saa7164_api_set_vbi_format(port) != SAA_OK) { + printk(KERN_ERR "%s() No supported VBI format\n", __func__); + ret = -EIO; + goto out; + } + + /* Acquire the hardware */ + result = saa7164_api_transition_port(port, SAA_DMASTATE_ACQUIRE); + if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() acquire transition failed, res = 0x%x\n", + __func__, result); + + ret = -EIO; + goto out; + } else + dprintk(DBGLVL_VBI, "%s() Acquired\n", __func__); + + /* Pause the hardware */ + result = saa7164_api_transition_port(port, SAA_DMASTATE_PAUSE); + if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() pause transition failed, res = 0x%x\n", + __func__, result); + + /* Stop the hardware, regardless */ + result = saa7164_vbi_stop_port(port); + if (result != SAA_OK) { + printk(KERN_ERR "%s() pause/forced stop transition failed, res = 0x%x\n", + __func__, result); + } + + ret = -EIO; + goto out; + } else + dprintk(DBGLVL_VBI, "%s() Paused\n", __func__); + + /* Start the hardware */ + result = saa7164_api_transition_port(port, SAA_DMASTATE_RUN); + if ((result != SAA_OK) && (result != SAA_ERR_ALREADY_STOPPED)) { + printk(KERN_ERR "%s() run transition failed, result = 0x%x\n", + __func__, result); + + /* Stop the hardware, regardless */ + result = saa7164_vbi_acquire_port(port); + result = saa7164_vbi_stop_port(port); + if (result != SAA_OK) { + printk(KERN_ERR "%s() run/forced stop transition failed, res = 0x%x\n", + __func__, result); + } + + ret = -EIO; + } else + dprintk(DBGLVL_VBI, "%s() Running\n", __func__); + +out: + return ret; +} + +static int saa7164_vbi_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + /* ntsc */ + f->fmt.vbi.samples_per_line = 1440; + f->fmt.vbi.sampling_rate = 27000000; + f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + f->fmt.vbi.offset = 0; + f->fmt.vbi.flags = 0; + f->fmt.vbi.start[0] = 10; + f->fmt.vbi.count[0] = 18; + f->fmt.vbi.start[1] = 263 + 10 + 1; + f->fmt.vbi.count[1] = 18; + memset(f->fmt.vbi.reserved, 0, sizeof(f->fmt.vbi.reserved)); + return 0; +} + +static int fops_open(struct file *file) +{ + struct saa7164_dev *dev; + struct saa7164_port *port; + struct saa7164_vbi_fh *fh; + + port = (struct saa7164_port *)video_get_drvdata(video_devdata(file)); + if (!port) + return -ENODEV; + + dev = port->dev; + + dprintk(DBGLVL_VBI, "%s()\n", __func__); + + /* allocate + initialize per filehandle data */ + fh = kzalloc_obj(*fh); + if (NULL == fh) + return -ENOMEM; + + fh->port = port; + v4l2_fh_init(&fh->fh, video_devdata(file)); + v4l2_fh_add(&fh->fh, file); + + return 0; +} + +static int fops_release(struct file *file) +{ + struct saa7164_vbi_fh *fh = to_saa7164_vbi_fh(file); + struct saa7164_port *port = fh->port; + struct saa7164_dev *dev = port->dev; + + dprintk(DBGLVL_VBI, "%s()\n", __func__); + + /* Shut device down on last close */ + if (atomic_cmpxchg(&fh->v4l_reading, 1, 0) == 1) { + if (atomic_dec_return(&port->v4l_reader_count) == 0) { + /* stop vbi capture then cancel buffers */ + saa7164_vbi_stop_streaming(port); + } + } + + v4l2_fh_del(&fh->fh, file); + v4l2_fh_exit(&fh->fh); + kfree(fh); + + return 0; +} + +static struct +saa7164_user_buffer *saa7164_vbi_next_buf(struct saa7164_port *port) +{ + struct saa7164_user_buffer *ubuf = NULL; + struct saa7164_dev *dev = port->dev; + u32 crc; + + mutex_lock(&port->dmaqueue_lock); + if (!list_empty(&port->list_buf_used.list)) { + ubuf = list_first_entry(&port->list_buf_used.list, + struct saa7164_user_buffer, list); + + if (crc_checking) { + crc = crc32(0, ubuf->data, ubuf->actual_size); + if (crc != ubuf->crc) { + printk(KERN_ERR "%s() ubuf %p crc became invalid, was 0x%x became 0x%x\n", + __func__, + ubuf, ubuf->crc, crc); + } + } + + } + mutex_unlock(&port->dmaqueue_lock); + + dprintk(DBGLVL_VBI, "%s() returns %p\n", __func__, ubuf); + + return ubuf; +} + +static ssize_t fops_read(struct file *file, char __user *buffer, + size_t count, loff_t *pos) +{ + struct saa7164_vbi_fh *fh = to_saa7164_vbi_fh(file); + struct saa7164_port *port = fh->port; + struct saa7164_user_buffer *ubuf = NULL; + struct saa7164_dev *dev = port->dev; + int ret = 0; + int rem, cnt; + u8 *p; + + port->last_read_msecs_diff = port->last_read_msecs; + port->last_read_msecs = jiffies_to_msecs(jiffies); + port->last_read_msecs_diff = port->last_read_msecs - + port->last_read_msecs_diff; + + saa7164_histogram_update(&port->read_interval, + port->last_read_msecs_diff); + + if (*pos) { + printk(KERN_ERR "%s() ESPIPE\n", __func__); + return -ESPIPE; + } + + if (atomic_cmpxchg(&fh->v4l_reading, 0, 1) == 0) { + if (atomic_inc_return(&port->v4l_reader_count) == 1) { + + if (saa7164_vbi_initialize(port) < 0) { + printk(KERN_ERR "%s() EINVAL\n", __func__); + return -EINVAL; + } + + saa7164_vbi_start_streaming(port); + msleep(200); + } + } + + /* blocking wait for buffer */ + if ((file->f_flags & O_NONBLOCK) == 0) { + if (wait_event_interruptible(port->wait_read, + saa7164_vbi_next_buf(port))) { + printk(KERN_ERR "%s() ERESTARTSYS\n", __func__); + return -ERESTARTSYS; + } + } + + /* Pull the first buffer from the used list */ + ubuf = saa7164_vbi_next_buf(port); + + while ((count > 0) && ubuf) { + + /* set remaining bytes to copy */ + rem = ubuf->actual_size - ubuf->pos; + cnt = rem > count ? count : rem; + + p = ubuf->data + ubuf->pos; + + dprintk(DBGLVL_VBI, + "%s() count=%d cnt=%d rem=%d buf=%p buf->pos=%d\n", + __func__, (int)count, cnt, rem, ubuf, ubuf->pos); + + if (copy_to_user(buffer, p, cnt)) { + printk(KERN_ERR "%s() copy_to_user failed\n", __func__); + if (!ret) { + printk(KERN_ERR "%s() EFAULT\n", __func__); + ret = -EFAULT; + } + goto err; + } + + ubuf->pos += cnt; + count -= cnt; + buffer += cnt; + ret += cnt; + + if (ubuf->pos > ubuf->actual_size) + printk(KERN_ERR "read() pos > actual, huh?\n"); + + if (ubuf->pos == ubuf->actual_size) { + + /* finished with current buffer, take next buffer */ + + /* Requeue the buffer on the free list */ + ubuf->pos = 0; + + mutex_lock(&port->dmaqueue_lock); + list_move_tail(&ubuf->list, &port->list_buf_free.list); + mutex_unlock(&port->dmaqueue_lock); + + /* Dequeue next */ + if ((file->f_flags & O_NONBLOCK) == 0) { + if (wait_event_interruptible(port->wait_read, + saa7164_vbi_next_buf(port))) { + break; + } + } + ubuf = saa7164_vbi_next_buf(port); + } + } +err: + if (!ret && !ubuf) { + printk(KERN_ERR "%s() EAGAIN\n", __func__); + ret = -EAGAIN; + } + + return ret; +} + +static __poll_t fops_poll(struct file *file, poll_table *wait) +{ + struct saa7164_vbi_fh *fh = to_saa7164_vbi_fh(file); + struct saa7164_port *port = fh->port; + __poll_t mask = 0; + + port->last_poll_msecs_diff = port->last_poll_msecs; + port->last_poll_msecs = jiffies_to_msecs(jiffies); + port->last_poll_msecs_diff = port->last_poll_msecs - + port->last_poll_msecs_diff; + + saa7164_histogram_update(&port->poll_interval, + port->last_poll_msecs_diff); + + if (!video_is_registered(port->v4l_device)) + return EPOLLERR; + + if (atomic_cmpxchg(&fh->v4l_reading, 0, 1) == 0) { + if (atomic_inc_return(&port->v4l_reader_count) == 1) { + if (saa7164_vbi_initialize(port) < 0) + return EPOLLERR; + saa7164_vbi_start_streaming(port); + msleep(200); + } + } + + /* blocking wait for buffer */ + if ((file->f_flags & O_NONBLOCK) == 0) { + if (wait_event_interruptible(port->wait_read, + saa7164_vbi_next_buf(port))) { + return EPOLLERR; + } + } + + /* Pull the first buffer from the used list */ + if (!list_empty(&port->list_buf_used.list)) + mask |= EPOLLIN | EPOLLRDNORM; + + return mask; +} +static const struct v4l2_file_operations vbi_fops = { + .owner = THIS_MODULE, + .open = fops_open, + .release = fops_release, + .read = fops_read, + .poll = fops_poll, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops vbi_ioctl_ops = { + .vidioc_s_std = vidioc_s_std, + .vidioc_g_std = vidioc_g_std, + .vidioc_enum_input = saa7164_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_g_tuner = saa7164_g_tuner, + .vidioc_s_tuner = saa7164_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_querycap = vidioc_querycap, + .vidioc_g_fmt_vbi_cap = saa7164_vbi_fmt, + .vidioc_try_fmt_vbi_cap = saa7164_vbi_fmt, + .vidioc_s_fmt_vbi_cap = saa7164_vbi_fmt, +}; + +static struct video_device saa7164_vbi_template = { + .name = "saa7164", + .fops = &vbi_fops, + .ioctl_ops = &vbi_ioctl_ops, + .minor = -1, + .tvnorms = SAA7164_NORMS, + .device_caps = V4L2_CAP_VBI_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_TUNER, +}; + +static struct video_device *saa7164_vbi_alloc( + struct saa7164_port *port, + struct pci_dev *pci, + struct video_device *template, + char *type) +{ + struct video_device *vfd; + struct saa7164_dev *dev = port->dev; + + dprintk(DBGLVL_VBI, "%s()\n", __func__); + + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + + *vfd = *template; + snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)", dev->name, + type, saa7164_boards[dev->board].name); + + vfd->v4l2_dev = &dev->v4l2_dev; + vfd->release = video_device_release; + return vfd; +} + +int saa7164_vbi_register(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + int result = -ENODEV; + + dprintk(DBGLVL_VBI, "%s()\n", __func__); + + BUG_ON(port->type != SAA7164_MPEG_VBI); + + /* Sanity check that the PCI configuration space is active */ + if (port->hwcfg.BARLocation == 0) { + printk(KERN_ERR "%s() failed (errno = %d), NO PCI configuration\n", + __func__, result); + result = -ENOMEM; + goto failed; + } + + /* Establish VBI defaults here */ + + /* Allocate and register the video device node */ + port->v4l_device = saa7164_vbi_alloc(port, + dev->pci, &saa7164_vbi_template, "vbi"); + + if (!port->v4l_device) { + printk(KERN_INFO "%s: can't allocate vbi device\n", + dev->name); + result = -ENOMEM; + goto failed; + } + + port->enc_port = &dev->ports[port->nr - 2]; + video_set_drvdata(port->v4l_device, port); + result = video_register_device(port->v4l_device, + VFL_TYPE_VBI, -1); + if (result < 0) { + printk(KERN_INFO "%s: can't register vbi device\n", + dev->name); + /* TODO: We're going to leak here if we don't dealloc + The buffers above. The unreg function can't deal wit it. + */ + goto failed; + } + + printk(KERN_INFO "%s: registered device vbi%d [vbi]\n", + dev->name, port->v4l_device->num); + + /* Configure the hardware defaults */ + + result = 0; +failed: + return result; +} + +void saa7164_vbi_unregister(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + + dprintk(DBGLVL_VBI, "%s(port=%d)\n", __func__, port->nr); + + BUG_ON(port->type != SAA7164_MPEG_VBI); + + if (port->v4l_device) { + if (port->v4l_device->minor != -1) + video_unregister_device(port->v4l_device); + else + video_device_release(port->v4l_device); + + port->v4l_device = NULL; + } + +} diff --git a/drivers/media/pci/saa7164/saa7164.h b/drivers/media/pci/saa7164/saa7164.h new file mode 100644 index 000000000..94e987e7b --- /dev/null +++ b/drivers/media/pci/saa7164/saa7164.h @@ -0,0 +1,629 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for the NXP SAA7164 PCIe bridge + * + * Copyright (c) 2010-2015 Steven Toth + */ + +/* + Driver architecture + ******************* + + saa7164_core.c/buffer.c/cards.c/i2c.c/dvb.c + | : Standard Linux driver framework for creating + | : exposing and managing interfaces to the rest + | : of the kernel or userland. Also uses _fw.c to load + | : firmware direct into the PCIe bus, bypassing layers. + V + saa7164_api..() : Translate kernel specific functions/features + | : into command buffers. + V + saa7164_cmd..() : Manages the flow of command packets on/off, + | : the bus. Deal with bus errors, timeouts etc. + V + saa7164_bus..() : Manage a read/write memory ring buffer in the + | : PCIe Address space. + | + | saa7164_fw...() : Load any firmware + | | : direct into the device + V V + <- ----------------- PCIe address space -------------------- -> +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "saa7164-reg.h" +#include "saa7164-types.h" + +#define SAA7164_MAXBOARDS 8 + +#define UNSET (-1U) +#define SAA7164_BOARD_NOAUTO UNSET +#define SAA7164_BOARD_UNKNOWN 0 +#define SAA7164_BOARD_UNKNOWN_REV2 1 +#define SAA7164_BOARD_UNKNOWN_REV3 2 +#define SAA7164_BOARD_HAUPPAUGE_HVR2250 3 +#define SAA7164_BOARD_HAUPPAUGE_HVR2200 4 +#define SAA7164_BOARD_HAUPPAUGE_HVR2200_2 5 +#define SAA7164_BOARD_HAUPPAUGE_HVR2200_3 6 +#define SAA7164_BOARD_HAUPPAUGE_HVR2250_2 7 +#define SAA7164_BOARD_HAUPPAUGE_HVR2250_3 8 +#define SAA7164_BOARD_HAUPPAUGE_HVR2200_4 9 +#define SAA7164_BOARD_HAUPPAUGE_HVR2200_5 10 +#define SAA7164_BOARD_HAUPPAUGE_HVR2255proto 11 +#define SAA7164_BOARD_HAUPPAUGE_HVR2255 12 +#define SAA7164_BOARD_HAUPPAUGE_HVR2205 13 + +#define SAA7164_MAX_UNITS 8 +#define SAA7164_TS_NUMBER_OF_LINES 312 +#define SAA7164_PS_NUMBER_OF_LINES 256 +#define SAA7164_PT_ENTRIES 16 /* (312 * 188) / 4096 */ +#define SAA7164_MAX_ENCODER_BUFFERS 64 /* max 5secs of latency at 6Mbps */ +#define SAA7164_MAX_VBI_BUFFERS 64 + +/* Port related defines */ +#define SAA7164_PORT_TS1 (0) +#define SAA7164_PORT_TS2 (SAA7164_PORT_TS1 + 1) +#define SAA7164_PORT_ENC1 (SAA7164_PORT_TS2 + 1) +#define SAA7164_PORT_ENC2 (SAA7164_PORT_ENC1 + 1) +#define SAA7164_PORT_VBI1 (SAA7164_PORT_ENC2 + 1) +#define SAA7164_PORT_VBI2 (SAA7164_PORT_VBI1 + 1) +#define SAA7164_MAX_PORTS (SAA7164_PORT_VBI2 + 1) + +#define DBGLVL_FW 4 +#define DBGLVL_DVB 8 +#define DBGLVL_I2C 16 +#define DBGLVL_API 32 +#define DBGLVL_CMD 64 +#define DBGLVL_BUS 128 +#define DBGLVL_IRQ 256 +#define DBGLVL_BUF 512 +#define DBGLVL_ENC 1024 +#define DBGLVL_VBI 2048 +#define DBGLVL_THR 4096 +#define DBGLVL_CPU 8192 + +#define SAA7164_NORMS \ + (V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_JP) + +/* TV frequency range copied from tuner-core.c */ +#define SAA7164_TV_MIN_FREQ (44U * 16U) +#define SAA7164_TV_MAX_FREQ (958U * 16U) + +enum port_t { + SAA7164_MPEG_UNDEFINED = 0, + SAA7164_MPEG_DVB, + SAA7164_MPEG_ENCODER, + SAA7164_MPEG_VBI, +}; + +enum saa7164_i2c_bus_nr { + SAA7164_I2C_BUS_0 = 0, + SAA7164_I2C_BUS_1, + SAA7164_I2C_BUS_2, +}; + +enum saa7164_buffer_flags { + SAA7164_BUFFER_UNDEFINED = 0, + SAA7164_BUFFER_FREE, + SAA7164_BUFFER_BUSY, + SAA7164_BUFFER_FULL +}; + +enum saa7164_unit_type { + SAA7164_UNIT_UNDEFINED = 0, + SAA7164_UNIT_DIGITAL_DEMODULATOR, + SAA7164_UNIT_ANALOG_DEMODULATOR, + SAA7164_UNIT_TUNER, + SAA7164_UNIT_EEPROM, + SAA7164_UNIT_ZILOG_IRBLASTER, + SAA7164_UNIT_ENCODER, +}; + +/* The PCIe bridge doesn't grant direct access to i2c. + * Instead, you address i2c devices using a uniqely + * allocated 'unitid' value via a messaging API. This + * is a problem. The kernel and existing demod/tuner + * drivers expect to talk 'i2c', so we have to maintain + * a translation layer, and a series of functions to + * convert i2c bus + device address into a unit id. + */ +struct saa7164_unit { + enum saa7164_unit_type type; + u8 id; + char *name; + enum saa7164_i2c_bus_nr i2c_bus_nr; + u8 i2c_bus_addr; + u8 i2c_reg_len; +}; + +struct saa7164_board { + char *name; + enum port_t porta, portb, portc, + portd, porte, portf; + enum { + SAA7164_CHIP_UNDEFINED = 0, + SAA7164_CHIP_REV2, + SAA7164_CHIP_REV3, + } chiprev; + struct saa7164_unit unit[SAA7164_MAX_UNITS]; +}; + +struct saa7164_subid { + u16 subvendor; + u16 subdevice; + u32 card; +}; + +struct saa7164_encoder_fh { + struct v4l2_fh fh; + struct saa7164_port *port; + atomic_t v4l_reading; +}; + +static inline struct saa7164_encoder_fh *to_saa7164_encoder_fh(struct file *filp) +{ + return container_of(file_to_v4l2_fh(filp), struct saa7164_encoder_fh, fh); +} + +struct saa7164_vbi_fh { + struct v4l2_fh fh; + struct saa7164_port *port; + atomic_t v4l_reading; +}; + +static inline struct saa7164_vbi_fh *to_saa7164_vbi_fh(struct file *filp) +{ + return container_of(file_to_v4l2_fh(filp), struct saa7164_vbi_fh, fh); +} + +struct saa7164_histogram_bucket { + u32 val; + u32 count; + u64 update_time; +}; + +struct saa7164_histogram { + char name[32]; + struct saa7164_histogram_bucket counter1[64]; +}; + +struct saa7164_user_buffer { + struct list_head list; + + /* Attributes */ + u8 *data; + u32 pos; + u32 actual_size; + + u32 crc; +}; + +struct saa7164_fw_status { + + /* RISC Core details */ + u32 status; + u32 mode; + u32 spec; + u32 inst; + u32 cpuload; + u32 remainheap; + + /* Firmware version */ + u32 version; + u32 major; + u32 sub; + u32 rel; + u32 buildnr; +}; + +struct saa7164_dvb { + struct mutex lock; + struct dvb_adapter adapter; + struct dvb_frontend *frontend; + struct dvb_demux demux; + struct dmxdev dmxdev; + struct dmx_frontend fe_hw; + struct dmx_frontend fe_mem; + struct dvb_net net; + int feeding; +}; + +struct saa7164_i2c { + struct saa7164_dev *dev; + + enum saa7164_i2c_bus_nr nr; + + /* I2C I/O */ + struct i2c_adapter i2c_adap; + struct i2c_client i2c_client; + u32 i2c_rc; +}; + +struct saa7164_tvnorm { + char *name; + v4l2_std_id id; +}; + +struct saa7164_encoder_params { + struct saa7164_tvnorm encodernorm; + u32 height; + u32 width; + u32 is_50hz; + u32 bitrate; /* bps */ + u32 bitrate_peak; /* bps */ + u32 bitrate_mode; + u32 stream_type; /* V4L2_MPEG_STREAM_TYPE_MPEG2_TS */ + + u32 audio_sampling_freq; + u32 ctl_mute; + u32 ctl_aspect; + u32 refdist; + u32 gop_size; +}; + +struct saa7164_vbi_params { + struct saa7164_tvnorm encodernorm; + u32 height; + u32 width; + u32 is_50hz; + u32 bitrate; /* bps */ + u32 bitrate_peak; /* bps */ + u32 bitrate_mode; + u32 stream_type; /* V4L2_MPEG_STREAM_TYPE_MPEG2_TS */ + + u32 audio_sampling_freq; + u32 ctl_mute; + u32 ctl_aspect; + u32 refdist; + u32 gop_size; +}; + +struct saa7164_port; + +struct saa7164_buffer { + struct list_head list; + + /* Note of which h/w buffer list index position we occupy */ + int idx; + + struct saa7164_port *port; + + /* Hardware Specific */ + /* PCI Memory allocations */ + enum saa7164_buffer_flags flags; /* Free, Busy, Full */ + + /* A block of page align PCI memory */ + u32 pci_size; /* PCI allocation size in bytes */ + u64 *cpu; /* Virtual address */ + dma_addr_t dma; /* Physical address */ + u32 crc; /* Checksum for the entire buffer data */ + + /* A page table that splits the block into a number of entries */ + u32 pt_size; /* PCI allocation size in bytes */ + u64 *pt_cpu; /* Virtual address */ + dma_addr_t pt_dma; /* Physical address */ + + /* Encoder fops */ + u32 pos; + u32 actual_size; +}; + +struct saa7164_port { + + struct saa7164_dev *dev; + enum port_t type; + int nr; + + /* --- Generic port attributes --- */ + + /* HW stream parameters */ + struct tmHWStreamParameters hw_streamingparams; + + /* DMA configuration values, is seeded during initialization */ + struct tmComResDMATermDescrHeader hwcfg; + + /* hardware specific registers */ + u32 bufcounter; + u32 pitch; + u32 bufsize; + u32 bufoffset; + u32 bufptr32l; + u32 bufptr32h; + u64 bufptr64; + + u32 numpte; /* Number of entries in array, only valid in head */ + + struct mutex dmaqueue_lock; + struct saa7164_buffer dmaqueue; + + u64 last_irq_msecs, last_svc_msecs; + u64 last_irq_msecs_diff, last_svc_msecs_diff; + u32 last_svc_wp; + u32 last_svc_rp; + u64 last_irq_svc_msecs_diff; + u64 last_read_msecs, last_read_msecs_diff; + u64 last_poll_msecs, last_poll_msecs_diff; + + struct saa7164_histogram irq_interval; + struct saa7164_histogram svc_interval; + struct saa7164_histogram irq_svc_interval; + struct saa7164_histogram read_interval; + struct saa7164_histogram poll_interval; + + /* --- DVB Transport Specific --- */ + struct saa7164_dvb dvb; + struct i2c_client *i2c_client_demod; + struct i2c_client *i2c_client_tuner; + + /* --- Encoder/V4L related attributes --- */ + /* Encoder */ + /* Defaults established in saa7164-encoder.c */ + struct saa7164_tvnorm encodernorm; + struct v4l2_ctrl_handler ctrl_handler; + v4l2_std_id std; + u32 height; + u32 width; + u32 freq; + u8 mux_input; + u8 encoder_profile; + u8 video_format; + u8 audio_format; + u8 video_resolution; + u16 ctl_brightness; + u16 ctl_contrast; + u16 ctl_hue; + u16 ctl_saturation; + u16 ctl_sharpness; + s8 ctl_volume; + + struct tmComResAFeatureDescrHeader audfeat; + struct tmComResEncoderDescrHeader encunit; + struct tmComResProcDescrHeader vidproc; + struct tmComResExtDevDescrHeader ifunit; + struct tmComResTunerDescrHeader tunerunit; + + struct work_struct workenc; + + /* V4L Encoder Video */ + struct saa7164_encoder_params encoder_params; + struct video_device *v4l_device; + atomic_t v4l_reader_count; + + struct saa7164_buffer list_buf_used; + struct saa7164_buffer list_buf_free; + wait_queue_head_t wait_read; + + /* V4L VBI */ + struct tmComResVBIFormatDescrHeader vbi_fmt_ntsc; + struct saa7164_vbi_params vbi_params; + struct saa7164_port *enc_port; + + /* Debug */ + u32 sync_errors; + u32 v_cc_errors; + u32 a_cc_errors; + u8 last_v_cc; + u8 last_a_cc; + u32 done_first_interrupt; +}; + +struct saa7164_dev { + struct list_head devlist; + atomic_t refcount; + + struct v4l2_device v4l2_dev; + + /* pci stuff */ + struct pci_dev *pci; + unsigned char pci_rev, pci_lat; + int pci_bus, pci_slot; + u32 __iomem *lmmio; + u8 __iomem *bmmio; + u32 __iomem *lmmio2; + u8 __iomem *bmmio2; + int pci_irqmask; + + /* board details */ + int nr; + int hwrevision; + u32 board; + char name[16]; + + /* firmware status */ + struct saa7164_fw_status fw_status; + u32 firmwareloaded; + + struct tmComResHWDescr hwdesc; + struct tmComResInterfaceDescr intfdesc; + struct tmComResBusDescr busdesc; + + struct tmComResBusInfo bus; + + /* Interrupt status and ack registers */ + u32 int_status; + u32 int_ack; + bool msi; + + struct cmd cmds[SAA_CMD_MAX_MSG_UNITS]; + struct mutex lock; + + /* I2c related */ + struct saa7164_i2c i2c_bus[3]; + + /* Transport related */ + struct saa7164_port ports[SAA7164_MAX_PORTS]; + + /* Deferred command/api interrupts handling */ + struct work_struct workcmd; + + /* A kernel thread to monitor the firmware log, used + * only in debug mode. + */ + struct task_struct *kthread; + +}; + +extern struct list_head saa7164_devlist; +extern unsigned int waitsecs; +extern unsigned int encoder_buffers; +extern unsigned int vbi_buffers; + +/* ----------------------------------------------------------- */ +/* saa7164-core.c */ +void saa7164_dumpregs(struct saa7164_dev *dev, u32 addr); +void saa7164_getfirmwarestatus(struct saa7164_dev *dev); +u32 saa7164_getcurrentfirmwareversion(struct saa7164_dev *dev); +void saa7164_histogram_update(struct saa7164_histogram *hg, u32 val); + +/* ----------------------------------------------------------- */ +/* saa7164-fw.c */ +int saa7164_downloadfirmware(struct saa7164_dev *dev); + +/* ----------------------------------------------------------- */ +/* saa7164-i2c.c */ +extern int saa7164_i2c_register(struct saa7164_i2c *bus); +extern int saa7164_i2c_unregister(struct saa7164_i2c *bus); + +/* ----------------------------------------------------------- */ +/* saa7164-bus.c */ +int saa7164_bus_setup(struct saa7164_dev *dev); +void saa7164_bus_dump(struct saa7164_dev *dev); +int saa7164_bus_set(struct saa7164_dev *dev, struct tmComResInfo* msg, + void *buf); +int saa7164_bus_get(struct saa7164_dev *dev, struct tmComResInfo* msg, + void *buf, int peekonly); + +/* ----------------------------------------------------------- */ +/* saa7164-cmd.c */ +int saa7164_cmd_send(struct saa7164_dev *dev, + u8 id, enum tmComResCmd command, u16 controlselector, + u16 size, void *buf); +int saa7164_irq_dequeue(struct saa7164_dev *dev); + +/* ----------------------------------------------------------- */ +/* saa7164-api.c */ +int saa7164_api_get_fw_version(struct saa7164_dev *dev, u32 *version); +int saa7164_api_enum_subdevs(struct saa7164_dev *dev); +int saa7164_api_i2c_read(struct saa7164_i2c *bus, u8 addr, u32 reglen, u8 *reg, + u32 datalen, u8 *data); +int saa7164_api_i2c_write(struct saa7164_i2c *bus, u8 addr, + u32 datalen, u8 *data); +int saa7164_api_dif_write(struct saa7164_i2c *bus, u8 addr, + u32 datalen, u8 *data); +int saa7164_api_read_eeprom(struct saa7164_dev *dev, u8 *buf, int buflen); +int saa7164_api_set_gpiobit(struct saa7164_dev *dev, u8 unitid, u8 pin); +int saa7164_api_clear_gpiobit(struct saa7164_dev *dev, u8 unitid, u8 pin); +int saa7164_api_transition_port(struct saa7164_port *port, u8 mode); +int saa7164_api_initialize_dif(struct saa7164_port *port); +int saa7164_api_configure_dif(struct saa7164_port *port, u32 std); +int saa7164_api_set_encoder(struct saa7164_port *port); +int saa7164_api_get_encoder(struct saa7164_port *port); +int saa7164_api_set_aspect_ratio(struct saa7164_port *port); +int saa7164_api_set_usercontrol(struct saa7164_port *port, u8 ctl); +int saa7164_api_get_usercontrol(struct saa7164_port *port, u8 ctl); +int saa7164_api_set_videomux(struct saa7164_port *port); +int saa7164_api_audio_mute(struct saa7164_port *port, int mute); +int saa7164_api_set_audio_volume(struct saa7164_port *port, s8 level); +int saa7164_api_set_audio_std(struct saa7164_port *port); +int saa7164_api_set_audio_detection(struct saa7164_port *port, int autodetect); +int saa7164_api_get_videomux(struct saa7164_port *port); +int saa7164_api_set_vbi_format(struct saa7164_port *port); +int saa7164_api_set_debug(struct saa7164_dev *dev, u8 level); +int saa7164_api_collect_debug(struct saa7164_dev *dev); +int saa7164_api_get_load_info(struct saa7164_dev *dev, + struct tmFwInfoStruct *i); + +/* ----------------------------------------------------------- */ +/* saa7164-cards.c */ +extern struct saa7164_board saa7164_boards[]; +extern const unsigned int saa7164_bcount; + +extern struct saa7164_subid saa7164_subids[]; +extern const unsigned int saa7164_idcount; + +extern void saa7164_card_list(struct saa7164_dev *dev); +extern void saa7164_gpio_setup(struct saa7164_dev *dev); +extern void saa7164_card_setup(struct saa7164_dev *dev); + +extern int saa7164_i2caddr_to_reglen(struct saa7164_i2c *bus, int addr); +extern int saa7164_i2caddr_to_unitid(struct saa7164_i2c *bus, int addr); +extern char *saa7164_unitid_name(struct saa7164_dev *dev, u8 unitid); + +/* ----------------------------------------------------------- */ +/* saa7164-dvb.c */ +extern int saa7164_dvb_register(struct saa7164_port *port); +extern int saa7164_dvb_unregister(struct saa7164_port *port); + +/* ----------------------------------------------------------- */ +/* saa7164-buffer.c */ +extern struct saa7164_buffer *saa7164_buffer_alloc( + struct saa7164_port *port, u32 len); +extern int saa7164_buffer_dealloc(struct saa7164_buffer *buf); +extern int saa7164_buffer_activate(struct saa7164_buffer *buf, int i); +extern int saa7164_buffer_cfg_port(struct saa7164_port *port); +extern struct saa7164_user_buffer *saa7164_buffer_alloc_user( + struct saa7164_dev *dev, u32 len); +extern void saa7164_buffer_dealloc_user(struct saa7164_user_buffer *buf); +extern int saa7164_buffer_zero_offsets(struct saa7164_port *port, int i); + +/* ----------------------------------------------------------- */ +/* saa7164-encoder.c */ +int saa7164_s_std(struct saa7164_port *port, v4l2_std_id id); +int saa7164_g_std(struct saa7164_port *port, v4l2_std_id *id); +int saa7164_enum_input(struct file *file, void *priv, struct v4l2_input *i); +int saa7164_g_input(struct saa7164_port *port, unsigned int *i); +int saa7164_s_input(struct saa7164_port *port, unsigned int i); +int saa7164_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t); +int saa7164_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *t); +int saa7164_g_frequency(struct saa7164_port *port, struct v4l2_frequency *f); +int saa7164_s_frequency(struct saa7164_port *port, + const struct v4l2_frequency *f); +int saa7164_encoder_register(struct saa7164_port *port); +void saa7164_encoder_unregister(struct saa7164_port *port); + +/* ----------------------------------------------------------- */ +/* saa7164-vbi.c */ +int saa7164_vbi_register(struct saa7164_port *port); +void saa7164_vbi_unregister(struct saa7164_port *port); + +/* ----------------------------------------------------------- */ + +extern unsigned int crc_checking; + +extern unsigned int saa_debug; +#define dprintk(level, fmt, arg...)\ + do { if (saa_debug & level)\ + printk(KERN_DEBUG "%s: " fmt, dev->name, ## arg);\ + } while (0) + +#define log_warn(fmt, arg...)\ + do { \ + printk(KERN_WARNING "%s: " fmt, dev->name, ## arg);\ + } while (0) + +#define saa7164_readl(reg) readl(dev->lmmio + ((reg) >> 2)) +#define saa7164_writel(reg, value) writel((value), dev->lmmio + ((reg) >> 2)) + +#define saa7164_readb(reg) readl(dev->bmmio + (reg)) +#define saa7164_writeb(reg, value) writel((value), dev->bmmio + (reg)) + diff --git a/drivers/media/pci/smipcie/Kconfig b/drivers/media/pci/smipcie/Kconfig new file mode 100644 index 000000000..407711c0f --- /dev/null +++ b/drivers/media/pci/smipcie/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DVB_SMIPCIE + tristate "SMI PCIe DVBSky cards" + depends on DVB_CORE && PCI && I2C + select I2C_ALGOBIT + select DVB_M88DS3103 if MEDIA_SUBDRV_AUTOSELECT + select DVB_SI2168 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TS2020 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_M88RS6000T if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_SI2157 if MEDIA_SUBDRV_AUTOSELECT + depends on RC_CORE + help + Support for cards with SMI PCIe bridge: + - DVBSky S950 V3 + - DVBSky S952 V3 + - DVBSky T9580 V3 + + Say Y or M if you own such a device and want to use it. + If unsure say N. diff --git a/drivers/media/pci/smipcie/Makefile b/drivers/media/pci/smipcie/Makefile new file mode 100644 index 000000000..2426b7566 --- /dev/null +++ b/drivers/media/pci/smipcie/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 + +smipcie-objs := smipcie-main.o smipcie-ir.o + +obj-$(CONFIG_DVB_SMIPCIE) += smipcie.o + +ccflags-y += -I $(srctree)/drivers/media/tuners +ccflags-y += -I $(srctree)/drivers/media/dvb-frontends diff --git a/drivers/media/pci/smipcie/smipcie-ir.c b/drivers/media/pci/smipcie/smipcie-ir.c new file mode 100644 index 000000000..0bbe4fa2d --- /dev/null +++ b/drivers/media/pci/smipcie/smipcie-ir.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SMI PCIe driver for DVBSky cards. + * + * Copyright (C) 2014 Max nibble + */ + +#include "smipcie.h" + +#define SMI_SAMPLE_PERIOD 83 +#define SMI_SAMPLE_IDLEMIN (10000 / SMI_SAMPLE_PERIOD) + +static void smi_ir_enableInterrupt(struct smi_rc *ir) +{ + struct smi_dev *dev = ir->dev; + + smi_write(MSI_INT_ENA_SET, IR_X_INT); +} + +static void smi_ir_disableInterrupt(struct smi_rc *ir) +{ + struct smi_dev *dev = ir->dev; + + smi_write(MSI_INT_ENA_CLR, IR_X_INT); +} + +static void smi_ir_clearInterrupt(struct smi_rc *ir) +{ + struct smi_dev *dev = ir->dev; + + smi_write(MSI_INT_STATUS_CLR, IR_X_INT); +} + +static void smi_ir_stop(struct smi_rc *ir) +{ + struct smi_dev *dev = ir->dev; + + smi_ir_disableInterrupt(ir); + smi_clear(IR_Init_Reg, rbIRen); +} + +static void smi_raw_process(struct rc_dev *rc_dev, const u8 *buffer, + const u8 length) +{ + struct ir_raw_event rawir = {}; + int cnt; + + for (cnt = 0; cnt < length; cnt++) { + if (buffer[cnt] & 0x7f) { + rawir.pulse = (buffer[cnt] & 0x80) == 0; + rawir.duration = ((buffer[cnt] & 0x7f) + + (rawir.pulse ? 0 : -1)) * + rc_dev->rx_resolution; + ir_raw_event_store_with_filter(rc_dev, &rawir); + } + } +} + +static void smi_ir_decode(struct smi_rc *ir) +{ + struct smi_dev *dev = ir->dev; + struct rc_dev *rc_dev = ir->rc_dev; + u32 control, data; + u8 index, ir_count, read_loop; + + control = smi_read(IR_Init_Reg); + + dev_dbg(&rc_dev->dev, "ircontrol: 0x%08x\n", control); + + if (control & rbIRVld) { + ir_count = (u8)smi_read(IR_Data_Cnt); + + dev_dbg(&rc_dev->dev, "ircount %d\n", ir_count); + + read_loop = ir_count / 4; + if (ir_count % 4) + read_loop += 1; + for (index = 0; index < read_loop; index++) { + data = smi_read(IR_DATA_BUFFER_BASE + (index * 4)); + dev_dbg(&rc_dev->dev, "IRData 0x%08x\n", data); + + ir->irData[index * 4 + 0] = (u8)(data); + ir->irData[index * 4 + 1] = (u8)(data >> 8); + ir->irData[index * 4 + 2] = (u8)(data >> 16); + ir->irData[index * 4 + 3] = (u8)(data >> 24); + } + smi_raw_process(rc_dev, ir->irData, ir_count); + } + + if (control & rbIRhighidle) { + struct ir_raw_event rawir = {}; + + dev_dbg(&rc_dev->dev, "high idle\n"); + + rawir.pulse = 0; + rawir.duration = SMI_SAMPLE_PERIOD * SMI_SAMPLE_IDLEMIN; + ir_raw_event_store_with_filter(rc_dev, &rawir); + } + + smi_set(IR_Init_Reg, rbIRVld); + ir_raw_event_handle(rc_dev); +} + +/* ir functions call by main driver.*/ +int smi_ir_irq(struct smi_rc *ir, u32 int_status) +{ + int handled = 0; + + if (int_status & IR_X_INT) { + smi_ir_disableInterrupt(ir); + smi_ir_clearInterrupt(ir); + smi_ir_decode(ir); + smi_ir_enableInterrupt(ir); + handled = 1; + } + return handled; +} + +void smi_ir_start(struct smi_rc *ir) +{ + struct smi_dev *dev = ir->dev; + + smi_write(IR_Idle_Cnt_Low, + (((SMI_SAMPLE_PERIOD - 1) & 0xFFFF) << 16) | + (SMI_SAMPLE_IDLEMIN & 0xFFFF)); + msleep(20); + smi_set(IR_Init_Reg, rbIRen | rbIRhighidle); + + smi_ir_enableInterrupt(ir); +} + +int smi_ir_init(struct smi_dev *dev) +{ + int ret; + struct rc_dev *rc_dev; + struct smi_rc *ir = &dev->ir; + + rc_dev = rc_allocate_device(RC_DRIVER_IR_RAW); + if (!rc_dev) + return -ENOMEM; + + /* init input device */ + snprintf(ir->device_name, sizeof(ir->device_name), "IR (%s)", + dev->info->name); + snprintf(ir->input_phys, sizeof(ir->input_phys), "pci-%s/ir0", + pci_name(dev->pci_dev)); + + rc_dev->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER; + rc_dev->driver_name = "SMI_PCIe"; + rc_dev->input_phys = ir->input_phys; + rc_dev->device_name = ir->device_name; + rc_dev->input_id.bustype = BUS_PCI; + rc_dev->input_id.version = 1; + rc_dev->input_id.vendor = dev->pci_dev->subsystem_vendor; + rc_dev->input_id.product = dev->pci_dev->subsystem_device; + rc_dev->dev.parent = &dev->pci_dev->dev; + + rc_dev->map_name = dev->info->rc_map; + rc_dev->timeout = SMI_SAMPLE_PERIOD * SMI_SAMPLE_IDLEMIN; + rc_dev->rx_resolution = SMI_SAMPLE_PERIOD; + + ir->rc_dev = rc_dev; + ir->dev = dev; + + smi_ir_disableInterrupt(ir); + + ret = rc_register_device(rc_dev); + if (ret) + goto ir_err; + + return 0; +ir_err: + rc_free_device(rc_dev); + return ret; +} + +void smi_ir_exit(struct smi_dev *dev) +{ + struct smi_rc *ir = &dev->ir; + struct rc_dev *rc_dev = ir->rc_dev; + + rc_unregister_device(rc_dev); + smi_ir_stop(ir); + rc_free_device(rc_dev); + ir->rc_dev = NULL; +} diff --git a/drivers/media/pci/smipcie/smipcie-main.c b/drivers/media/pci/smipcie/smipcie-main.c new file mode 100644 index 000000000..5f18abb48 --- /dev/null +++ b/drivers/media/pci/smipcie/smipcie-main.c @@ -0,0 +1,1123 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SMI PCIe driver for DVBSky cards. + * + * Copyright (C) 2014 Max nibble + */ + +#include "smipcie.h" +#include "m88ds3103.h" +#include "ts2020.h" +#include "m88rs6000t.h" +#include "si2168.h" +#include "si2157.h" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static int smi_hw_init(struct smi_dev *dev) +{ + u32 port_mux, port_ctrl, int_stat; + + /* set port mux.*/ + port_mux = smi_read(MUX_MODE_CTRL); + port_mux &= ~(rbPaMSMask); + port_mux |= rbPaMSDtvNoGpio; + port_mux &= ~(rbPbMSMask); + port_mux |= rbPbMSDtvNoGpio; + port_mux &= ~(0x0f0000); + port_mux |= 0x50000; + smi_write(MUX_MODE_CTRL, port_mux); + + /* set DTV register.*/ + /* Port A */ + port_ctrl = smi_read(VIDEO_CTRL_STATUS_A); + port_ctrl &= ~0x01; + smi_write(VIDEO_CTRL_STATUS_A, port_ctrl); + port_ctrl = smi_read(MPEG2_CTRL_A); + port_ctrl &= ~0x40; + port_ctrl |= 0x80; + smi_write(MPEG2_CTRL_A, port_ctrl); + /* Port B */ + port_ctrl = smi_read(VIDEO_CTRL_STATUS_B); + port_ctrl &= ~0x01; + smi_write(VIDEO_CTRL_STATUS_B, port_ctrl); + port_ctrl = smi_read(MPEG2_CTRL_B); + port_ctrl &= ~0x40; + port_ctrl |= 0x80; + smi_write(MPEG2_CTRL_B, port_ctrl); + + /* disable and clear interrupt.*/ + smi_write(MSI_INT_ENA_CLR, ALL_INT); + int_stat = smi_read(MSI_INT_STATUS); + smi_write(MSI_INT_STATUS_CLR, int_stat); + + /* reset demod.*/ + smi_clear(PERIPHERAL_CTRL, 0x0303); + msleep(50); + smi_set(PERIPHERAL_CTRL, 0x0101); + return 0; +} + +/* i2c bit bus.*/ +static void smi_i2c_cfg(struct smi_dev *dev, u32 sw_ctl) +{ + u32 dwCtrl; + + dwCtrl = smi_read(sw_ctl); + dwCtrl &= ~0x18; /* disable output.*/ + dwCtrl |= 0x21; /* reset and software mode.*/ + dwCtrl &= ~0xff00; + dwCtrl |= 0x6400; + smi_write(sw_ctl, dwCtrl); + msleep(20); + dwCtrl = smi_read(sw_ctl); + dwCtrl &= ~0x20; + smi_write(sw_ctl, dwCtrl); +} + +static void smi_i2c_setsda(struct smi_dev *dev, int state, u32 sw_ctl) +{ + if (state) { + /* set as input.*/ + smi_clear(sw_ctl, SW_I2C_MSK_DAT_EN); + } else { + smi_clear(sw_ctl, SW_I2C_MSK_DAT_OUT); + /* set as output.*/ + smi_set(sw_ctl, SW_I2C_MSK_DAT_EN); + } +} + +static void smi_i2c_setscl(void *data, int state, u32 sw_ctl) +{ + struct smi_dev *dev = data; + + if (state) { + /* set as input.*/ + smi_clear(sw_ctl, SW_I2C_MSK_CLK_EN); + } else { + smi_clear(sw_ctl, SW_I2C_MSK_CLK_OUT); + /* set as output.*/ + smi_set(sw_ctl, SW_I2C_MSK_CLK_EN); + } +} + +static int smi_i2c_getsda(void *data, u32 sw_ctl) +{ + struct smi_dev *dev = data; + /* set as input.*/ + smi_clear(sw_ctl, SW_I2C_MSK_DAT_EN); + udelay(1); + return (smi_read(sw_ctl) & SW_I2C_MSK_DAT_IN) ? 1 : 0; +} + +static int smi_i2c_getscl(void *data, u32 sw_ctl) +{ + struct smi_dev *dev = data; + /* set as input.*/ + smi_clear(sw_ctl, SW_I2C_MSK_CLK_EN); + udelay(1); + return (smi_read(sw_ctl) & SW_I2C_MSK_CLK_IN) ? 1 : 0; +} +/* i2c 0.*/ +static void smi_i2c0_setsda(void *data, int state) +{ + struct smi_dev *dev = data; + + smi_i2c_setsda(dev, state, I2C_A_SW_CTL); +} + +static void smi_i2c0_setscl(void *data, int state) +{ + struct smi_dev *dev = data; + + smi_i2c_setscl(dev, state, I2C_A_SW_CTL); +} + +static int smi_i2c0_getsda(void *data) +{ + struct smi_dev *dev = data; + + return smi_i2c_getsda(dev, I2C_A_SW_CTL); +} + +static int smi_i2c0_getscl(void *data) +{ + struct smi_dev *dev = data; + + return smi_i2c_getscl(dev, I2C_A_SW_CTL); +} +/* i2c 1.*/ +static void smi_i2c1_setsda(void *data, int state) +{ + struct smi_dev *dev = data; + + smi_i2c_setsda(dev, state, I2C_B_SW_CTL); +} + +static void smi_i2c1_setscl(void *data, int state) +{ + struct smi_dev *dev = data; + + smi_i2c_setscl(dev, state, I2C_B_SW_CTL); +} + +static int smi_i2c1_getsda(void *data) +{ + struct smi_dev *dev = data; + + return smi_i2c_getsda(dev, I2C_B_SW_CTL); +} + +static int smi_i2c1_getscl(void *data) +{ + struct smi_dev *dev = data; + + return smi_i2c_getscl(dev, I2C_B_SW_CTL); +} + +static int smi_i2c_init(struct smi_dev *dev) +{ + int ret; + + /* i2c bus 0 */ + smi_i2c_cfg(dev, I2C_A_SW_CTL); + i2c_set_adapdata(&dev->i2c_bus[0], dev); + strscpy(dev->i2c_bus[0].name, "SMI-I2C0", sizeof(dev->i2c_bus[0].name)); + dev->i2c_bus[0].owner = THIS_MODULE; + dev->i2c_bus[0].dev.parent = &dev->pci_dev->dev; + dev->i2c_bus[0].algo_data = &dev->i2c_bit[0]; + dev->i2c_bit[0].data = dev; + dev->i2c_bit[0].setsda = smi_i2c0_setsda; + dev->i2c_bit[0].setscl = smi_i2c0_setscl; + dev->i2c_bit[0].getsda = smi_i2c0_getsda; + dev->i2c_bit[0].getscl = smi_i2c0_getscl; + dev->i2c_bit[0].udelay = 12; + dev->i2c_bit[0].timeout = 10; + /* Raise SCL and SDA */ + smi_i2c0_setsda(dev, 1); + smi_i2c0_setscl(dev, 1); + + ret = i2c_bit_add_bus(&dev->i2c_bus[0]); + if (ret < 0) + return ret; + + /* i2c bus 1 */ + smi_i2c_cfg(dev, I2C_B_SW_CTL); + i2c_set_adapdata(&dev->i2c_bus[1], dev); + strscpy(dev->i2c_bus[1].name, "SMI-I2C1", sizeof(dev->i2c_bus[1].name)); + dev->i2c_bus[1].owner = THIS_MODULE; + dev->i2c_bus[1].dev.parent = &dev->pci_dev->dev; + dev->i2c_bus[1].algo_data = &dev->i2c_bit[1]; + dev->i2c_bit[1].data = dev; + dev->i2c_bit[1].setsda = smi_i2c1_setsda; + dev->i2c_bit[1].setscl = smi_i2c1_setscl; + dev->i2c_bit[1].getsda = smi_i2c1_getsda; + dev->i2c_bit[1].getscl = smi_i2c1_getscl; + dev->i2c_bit[1].udelay = 12; + dev->i2c_bit[1].timeout = 10; + /* Raise SCL and SDA */ + smi_i2c1_setsda(dev, 1); + smi_i2c1_setscl(dev, 1); + + ret = i2c_bit_add_bus(&dev->i2c_bus[1]); + if (ret < 0) + i2c_del_adapter(&dev->i2c_bus[0]); + + return ret; +} + +static void smi_i2c_exit(struct smi_dev *dev) +{ + i2c_del_adapter(&dev->i2c_bus[0]); + i2c_del_adapter(&dev->i2c_bus[1]); +} + +static int smi_read_eeprom(struct i2c_adapter *i2c, u16 reg, u8 *data, u16 size) +{ + int ret; + u8 b0[2] = { (reg >> 8) & 0xff, reg & 0xff }; + + struct i2c_msg msg[] = { + { .addr = 0x50, .flags = 0, + .buf = b0, .len = 2 }, + { .addr = 0x50, .flags = I2C_M_RD, + .buf = data, .len = size } + }; + + ret = i2c_transfer(i2c, msg, 2); + + if (ret != 2) { + dev_err(&i2c->dev, "%s: reg=0x%x (error=%d)\n", + __func__, reg, ret); + return ret; + } + return ret; +} + +/* ts port interrupt operations */ +static void smi_port_disableInterrupt(struct smi_port *port) +{ + struct smi_dev *dev = port->dev; + + smi_write(MSI_INT_ENA_CLR, + (port->_dmaInterruptCH0 | port->_dmaInterruptCH1)); +} + +static void smi_port_enableInterrupt(struct smi_port *port) +{ + struct smi_dev *dev = port->dev; + + smi_write(MSI_INT_ENA_SET, + (port->_dmaInterruptCH0 | port->_dmaInterruptCH1)); +} + +static void smi_port_clearInterrupt(struct smi_port *port) +{ + struct smi_dev *dev = port->dev; + + smi_write(MSI_INT_STATUS_CLR, + (port->_dmaInterruptCH0 | port->_dmaInterruptCH1)); +} + +/* BH work handler: DMA data to dmx.*/ +static void smi_dma_xfer(struct work_struct *t) +{ + struct smi_port *port = from_work(port, t, bh_work); + struct smi_dev *dev = port->dev; + u32 intr_status, finishedData, dmaManagement; + u8 dmaChan0State, dmaChan1State; + + intr_status = port->_int_status; + dmaManagement = smi_read(port->DMA_MANAGEMENT); + dmaChan0State = (u8)((dmaManagement & 0x00000030) >> 4); + dmaChan1State = (u8)((dmaManagement & 0x00300000) >> 20); + + /* CH-0 DMA interrupt.*/ + if ((intr_status & port->_dmaInterruptCH0) && (dmaChan0State == 0x01)) { + dev_dbg(&dev->pci_dev->dev, + "Port[%d]-DMA CH0 engine complete successful !\n", + port->idx); + finishedData = smi_read(port->DMA_CHAN0_TRANS_STATE); + finishedData &= 0x003FFFFF; + /* value of DMA_PORT0_CHAN0_TRANS_STATE register [21:0] + * indicate dma total transfer length and + * zero of [21:0] indicate dma total transfer length + * equal to 0x400000 (4MB)*/ + if (finishedData == 0) + finishedData = 0x00400000; + if (finishedData != SMI_TS_DMA_BUF_SIZE) { + dev_dbg(&dev->pci_dev->dev, + "DMA CH0 engine complete length mismatched, finish data=%d !\n", + finishedData); + } + dvb_dmx_swfilter_packets(&port->demux, + port->cpu_addr[0], (finishedData / 188)); + /*dvb_dmx_swfilter(&port->demux, + port->cpu_addr[0], finishedData);*/ + } + /* CH-1 DMA interrupt.*/ + if ((intr_status & port->_dmaInterruptCH1) && (dmaChan1State == 0x01)) { + dev_dbg(&dev->pci_dev->dev, + "Port[%d]-DMA CH1 engine complete successful !\n", + port->idx); + finishedData = smi_read(port->DMA_CHAN1_TRANS_STATE); + finishedData &= 0x003FFFFF; + /* value of DMA_PORT0_CHAN0_TRANS_STATE register [21:0] + * indicate dma total transfer length and + * zero of [21:0] indicate dma total transfer length + * equal to 0x400000 (4MB)*/ + if (finishedData == 0) + finishedData = 0x00400000; + if (finishedData != SMI_TS_DMA_BUF_SIZE) { + dev_dbg(&dev->pci_dev->dev, + "DMA CH1 engine complete length mismatched, finish data=%d !\n", + finishedData); + } + dvb_dmx_swfilter_packets(&port->demux, + port->cpu_addr[1], (finishedData / 188)); + /*dvb_dmx_swfilter(&port->demux, + port->cpu_addr[1], finishedData);*/ + } + /* restart DMA.*/ + if (intr_status & port->_dmaInterruptCH0) + dmaManagement |= 0x00000002; + if (intr_status & port->_dmaInterruptCH1) + dmaManagement |= 0x00020000; + smi_write(port->DMA_MANAGEMENT, dmaManagement); + /* Re-enable interrupts */ + smi_port_enableInterrupt(port); +} + +static void smi_port_dma_free(struct smi_port *port) +{ + if (port->cpu_addr[0]) { + dma_free_coherent(&port->dev->pci_dev->dev, + SMI_TS_DMA_BUF_SIZE, port->cpu_addr[0], + port->dma_addr[0]); + port->cpu_addr[0] = NULL; + } + if (port->cpu_addr[1]) { + dma_free_coherent(&port->dev->pci_dev->dev, + SMI_TS_DMA_BUF_SIZE, port->cpu_addr[1], + port->dma_addr[1]); + port->cpu_addr[1] = NULL; + } +} + +static int smi_port_init(struct smi_port *port, int dmaChanUsed) +{ + dev_dbg(&port->dev->pci_dev->dev, + "%s, port %d, dmaused %d\n", __func__, port->idx, dmaChanUsed); + port->enable = 0; + if (port->idx == 0) { + /* Port A */ + port->_dmaInterruptCH0 = dmaChanUsed & 0x01; + port->_dmaInterruptCH1 = dmaChanUsed & 0x02; + + port->DMA_CHAN0_ADDR_LOW = DMA_PORTA_CHAN0_ADDR_LOW; + port->DMA_CHAN0_ADDR_HI = DMA_PORTA_CHAN0_ADDR_HI; + port->DMA_CHAN0_TRANS_STATE = DMA_PORTA_CHAN0_TRANS_STATE; + port->DMA_CHAN0_CONTROL = DMA_PORTA_CHAN0_CONTROL; + port->DMA_CHAN1_ADDR_LOW = DMA_PORTA_CHAN1_ADDR_LOW; + port->DMA_CHAN1_ADDR_HI = DMA_PORTA_CHAN1_ADDR_HI; + port->DMA_CHAN1_TRANS_STATE = DMA_PORTA_CHAN1_TRANS_STATE; + port->DMA_CHAN1_CONTROL = DMA_PORTA_CHAN1_CONTROL; + port->DMA_MANAGEMENT = DMA_PORTA_MANAGEMENT; + } else { + /* Port B */ + port->_dmaInterruptCH0 = (dmaChanUsed << 2) & 0x04; + port->_dmaInterruptCH1 = (dmaChanUsed << 2) & 0x08; + + port->DMA_CHAN0_ADDR_LOW = DMA_PORTB_CHAN0_ADDR_LOW; + port->DMA_CHAN0_ADDR_HI = DMA_PORTB_CHAN0_ADDR_HI; + port->DMA_CHAN0_TRANS_STATE = DMA_PORTB_CHAN0_TRANS_STATE; + port->DMA_CHAN0_CONTROL = DMA_PORTB_CHAN0_CONTROL; + port->DMA_CHAN1_ADDR_LOW = DMA_PORTB_CHAN1_ADDR_LOW; + port->DMA_CHAN1_ADDR_HI = DMA_PORTB_CHAN1_ADDR_HI; + port->DMA_CHAN1_TRANS_STATE = DMA_PORTB_CHAN1_TRANS_STATE; + port->DMA_CHAN1_CONTROL = DMA_PORTB_CHAN1_CONTROL; + port->DMA_MANAGEMENT = DMA_PORTB_MANAGEMENT; + } + + if (port->_dmaInterruptCH0) { + port->cpu_addr[0] = dma_alloc_coherent(&port->dev->pci_dev->dev, + SMI_TS_DMA_BUF_SIZE, + &port->dma_addr[0], + GFP_KERNEL); + if (!port->cpu_addr[0]) { + dev_err(&port->dev->pci_dev->dev, + "Port[%d] DMA CH0 memory allocation failed!\n", + port->idx); + goto err; + } + } + + if (port->_dmaInterruptCH1) { + port->cpu_addr[1] = dma_alloc_coherent(&port->dev->pci_dev->dev, + SMI_TS_DMA_BUF_SIZE, + &port->dma_addr[1], + GFP_KERNEL); + if (!port->cpu_addr[1]) { + dev_err(&port->dev->pci_dev->dev, + "Port[%d] DMA CH1 memory allocation failed!\n", + port->idx); + goto err; + } + } + + smi_port_disableInterrupt(port); + INIT_WORK(&port->bh_work, smi_dma_xfer); + disable_work_sync(&port->bh_work); + port->enable = 1; + return 0; +err: + smi_port_dma_free(port); + return -ENOMEM; +} + +static void smi_port_exit(struct smi_port *port) +{ + smi_port_disableInterrupt(port); + cancel_work_sync(&port->bh_work); + smi_port_dma_free(port); + port->enable = 0; +} + +static int smi_port_irq(struct smi_port *port, u32 int_status) +{ + u32 port_req_irq = port->_dmaInterruptCH0 | port->_dmaInterruptCH1; + int handled = 0; + + if (int_status & port_req_irq) { + smi_port_disableInterrupt(port); + port->_int_status = int_status; + smi_port_clearInterrupt(port); + queue_work(system_bh_wq, &port->bh_work); + handled = 1; + } + return handled; +} + +static irqreturn_t smi_irq_handler(int irq, void *dev_id) +{ + struct smi_dev *dev = dev_id; + struct smi_port *port0 = &dev->ts_port[0]; + struct smi_port *port1 = &dev->ts_port[1]; + struct smi_rc *ir = &dev->ir; + int handled = 0; + + u32 intr_status = smi_read(MSI_INT_STATUS); + + /* ts0 interrupt.*/ + if (dev->info->ts_0) + handled += smi_port_irq(port0, intr_status); + + /* ts1 interrupt.*/ + if (dev->info->ts_1) + handled += smi_port_irq(port1, intr_status); + + /* ir interrupt.*/ + handled += smi_ir_irq(ir, intr_status); + + return IRQ_RETVAL(handled); +} + +static struct i2c_client *smi_add_i2c_client(struct i2c_adapter *adapter, + struct i2c_board_info *info) +{ + struct i2c_client *client; + + request_module(info->type); + client = i2c_new_client_device(adapter, info); + if (!i2c_client_has_driver(client)) + goto err_add_i2c_client; + + if (!try_module_get(client->dev.driver->owner)) { + i2c_unregister_device(client); + goto err_add_i2c_client; + } + return client; + +err_add_i2c_client: + client = NULL; + return client; +} + +static void smi_del_i2c_client(struct i2c_client *client) +{ + module_put(client->dev.driver->owner); + i2c_unregister_device(client); +} + +static const struct m88ds3103_config smi_dvbsky_m88ds3103_cfg = { + .i2c_addr = 0x68, + .clock = 27000000, + .i2c_wr_max = 33, + .clock_out = 0, + .ts_mode = M88DS3103_TS_PARALLEL, + .ts_clk = 16000, + .ts_clk_pol = 1, + .agc = 0x99, + .lnb_hv_pol = 0, + .lnb_en_pol = 1, +}; + +static int smi_dvbsky_m88ds3103_fe_attach(struct smi_port *port) +{ + int ret = 0; + struct smi_dev *dev = port->dev; + struct i2c_adapter *i2c; + /* tuner I2C module */ + struct i2c_adapter *tuner_i2c_adapter; + struct i2c_client *tuner_client; + struct i2c_board_info tuner_info; + struct ts2020_config ts2020_config = {}; + memset(&tuner_info, 0, sizeof(struct i2c_board_info)); + i2c = (port->idx == 0) ? &dev->i2c_bus[0] : &dev->i2c_bus[1]; + + /* attach demod */ + port->fe = dvb_attach(m88ds3103_attach, + &smi_dvbsky_m88ds3103_cfg, i2c, &tuner_i2c_adapter); + if (!port->fe) { + ret = -ENODEV; + return ret; + } + /* attach tuner */ + ts2020_config.fe = port->fe; + strscpy(tuner_info.type, "ts2020", I2C_NAME_SIZE); + tuner_info.addr = 0x60; + tuner_info.platform_data = &ts2020_config; + tuner_client = smi_add_i2c_client(tuner_i2c_adapter, &tuner_info); + if (!tuner_client) { + ret = -ENODEV; + goto err_tuner_i2c_device; + } + + /* delegate signal strength measurement to tuner */ + port->fe->ops.read_signal_strength = + port->fe->ops.tuner_ops.get_rf_strength; + + port->i2c_client_tuner = tuner_client; + return ret; + +err_tuner_i2c_device: + dvb_frontend_detach(port->fe); + return ret; +} + +static const struct m88ds3103_config smi_dvbsky_m88rs6000_cfg = { + .i2c_addr = 0x69, + .clock = 27000000, + .i2c_wr_max = 33, + .ts_mode = M88DS3103_TS_PARALLEL, + .ts_clk = 16000, + .ts_clk_pol = 1, + .agc = 0x99, + .lnb_hv_pol = 0, + .lnb_en_pol = 1, +}; + +static int smi_dvbsky_m88rs6000_fe_attach(struct smi_port *port) +{ + int ret = 0; + struct smi_dev *dev = port->dev; + struct i2c_adapter *i2c; + /* tuner I2C module */ + struct i2c_adapter *tuner_i2c_adapter; + struct i2c_client *tuner_client; + struct i2c_board_info tuner_info; + struct m88rs6000t_config m88rs6000t_config; + + memset(&tuner_info, 0, sizeof(struct i2c_board_info)); + i2c = (port->idx == 0) ? &dev->i2c_bus[0] : &dev->i2c_bus[1]; + + /* attach demod */ + port->fe = dvb_attach(m88ds3103_attach, + &smi_dvbsky_m88rs6000_cfg, i2c, &tuner_i2c_adapter); + if (!port->fe) { + ret = -ENODEV; + return ret; + } + /* attach tuner */ + m88rs6000t_config.fe = port->fe; + strscpy(tuner_info.type, "m88rs6000t", I2C_NAME_SIZE); + tuner_info.addr = 0x21; + tuner_info.platform_data = &m88rs6000t_config; + tuner_client = smi_add_i2c_client(tuner_i2c_adapter, &tuner_info); + if (!tuner_client) { + ret = -ENODEV; + goto err_tuner_i2c_device; + } + + /* delegate signal strength measurement to tuner */ + port->fe->ops.read_signal_strength = + port->fe->ops.tuner_ops.get_rf_strength; + + port->i2c_client_tuner = tuner_client; + return ret; + +err_tuner_i2c_device: + dvb_frontend_detach(port->fe); + return ret; +} + +static int smi_dvbsky_sit2_fe_attach(struct smi_port *port) +{ + int ret = 0; + struct smi_dev *dev = port->dev; + struct i2c_adapter *i2c; + struct i2c_adapter *tuner_i2c_adapter; + struct i2c_client *client_tuner, *client_demod; + struct i2c_board_info client_info; + struct si2168_config si2168_config; + struct si2157_config si2157_config; + + /* select i2c bus */ + i2c = (port->idx == 0) ? &dev->i2c_bus[0] : &dev->i2c_bus[1]; + + /* attach demod */ + memset(&si2168_config, 0, sizeof(si2168_config)); + si2168_config.i2c_adapter = &tuner_i2c_adapter; + si2168_config.fe = &port->fe; + si2168_config.ts_mode = SI2168_TS_PARALLEL; + + memset(&client_info, 0, sizeof(struct i2c_board_info)); + strscpy(client_info.type, "si2168", I2C_NAME_SIZE); + client_info.addr = 0x64; + client_info.platform_data = &si2168_config; + + client_demod = smi_add_i2c_client(i2c, &client_info); + if (!client_demod) { + ret = -ENODEV; + return ret; + } + port->i2c_client_demod = client_demod; + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = port->fe; + si2157_config.if_port = 1; + + memset(&client_info, 0, sizeof(struct i2c_board_info)); + strscpy(client_info.type, "si2157", I2C_NAME_SIZE); + client_info.addr = 0x60; + client_info.platform_data = &si2157_config; + + client_tuner = smi_add_i2c_client(tuner_i2c_adapter, &client_info); + if (!client_tuner) { + smi_del_i2c_client(port->i2c_client_demod); + port->i2c_client_demod = NULL; + ret = -ENODEV; + return ret; + } + port->i2c_client_tuner = client_tuner; + return ret; +} + +static int smi_fe_init(struct smi_port *port) +{ + int ret = 0; + struct smi_dev *dev = port->dev; + struct dvb_adapter *adap = &port->dvb_adapter; + u8 mac_ee[16]; + + dev_dbg(&port->dev->pci_dev->dev, + "%s: port %d, fe_type = %d\n", + __func__, port->idx, port->fe_type); + switch (port->fe_type) { + case DVBSKY_FE_M88DS3103: + ret = smi_dvbsky_m88ds3103_fe_attach(port); + break; + case DVBSKY_FE_M88RS6000: + ret = smi_dvbsky_m88rs6000_fe_attach(port); + break; + case DVBSKY_FE_SIT2: + ret = smi_dvbsky_sit2_fe_attach(port); + break; + } + if (ret < 0) + return ret; + + /* register dvb frontend */ + ret = dvb_register_frontend(adap, port->fe); + if (ret < 0) { + if (port->i2c_client_tuner) + smi_del_i2c_client(port->i2c_client_tuner); + if (port->i2c_client_demod) + smi_del_i2c_client(port->i2c_client_demod); + dvb_frontend_detach(port->fe); + return ret; + } + /* init MAC.*/ + ret = smi_read_eeprom(&dev->i2c_bus[0], 0xc0, mac_ee, 16); + dev_info(&port->dev->pci_dev->dev, + "%s port %d MAC: %pM\n", dev->info->name, + port->idx, mac_ee + (port->idx)*8); + memcpy(adap->proposed_mac, mac_ee + (port->idx)*8, 6); + return ret; +} + +static void smi_fe_exit(struct smi_port *port) +{ + dvb_unregister_frontend(port->fe); + /* remove I2C demod and tuner */ + if (port->i2c_client_tuner) + smi_del_i2c_client(port->i2c_client_tuner); + if (port->i2c_client_demod) + smi_del_i2c_client(port->i2c_client_demod); + dvb_frontend_detach(port->fe); +} + +static int my_dvb_dmx_ts_card_init(struct dvb_demux *dvbdemux, char *id, + int (*start_feed)(struct dvb_demux_feed *), + int (*stop_feed)(struct dvb_demux_feed *), + void *priv) +{ + dvbdemux->priv = priv; + + dvbdemux->filternum = 256; + dvbdemux->feednum = 256; + dvbdemux->start_feed = start_feed; + dvbdemux->stop_feed = stop_feed; + dvbdemux->write_to_decoder = NULL; + dvbdemux->dmx.capabilities = (DMX_TS_FILTERING | + DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING); + return dvb_dmx_init(dvbdemux); +} + +static int my_dvb_dmxdev_ts_card_init(struct dmxdev *dmxdev, + struct dvb_demux *dvbdemux, + struct dmx_frontend *hw_frontend, + struct dmx_frontend *mem_frontend, + struct dvb_adapter *dvb_adapter) +{ + int ret; + + dmxdev->filternum = 256; + dmxdev->demux = &dvbdemux->dmx; + dmxdev->capabilities = 0; + ret = dvb_dmxdev_init(dmxdev, dvb_adapter); + if (ret < 0) + return ret; + + hw_frontend->source = DMX_FRONTEND_0; + dvbdemux->dmx.add_frontend(&dvbdemux->dmx, hw_frontend); + mem_frontend->source = DMX_MEMORY_FE; + dvbdemux->dmx.add_frontend(&dvbdemux->dmx, mem_frontend); + return dvbdemux->dmx.connect_frontend(&dvbdemux->dmx, hw_frontend); +} + +static u32 smi_config_DMA(struct smi_port *port) +{ + struct smi_dev *dev = port->dev; + u32 totalLength = 0, dmaMemPtrLow, dmaMemPtrHi, dmaCtlReg; + u8 chanLatencyTimer = 0, dmaChanEnable = 1, dmaTransStart = 1; + u32 dmaManagement = 0, tlpTransUnit = DMA_TRANS_UNIT_188; + u8 tlpTc = 0, tlpTd = 1, tlpEp = 0, tlpAttr = 0; + u64 mem; + + dmaManagement = smi_read(port->DMA_MANAGEMENT); + /* Setup Channel-0 */ + if (port->_dmaInterruptCH0) { + totalLength = SMI_TS_DMA_BUF_SIZE; + mem = port->dma_addr[0]; + dmaMemPtrLow = mem & 0xffffffff; + dmaMemPtrHi = mem >> 32; + dmaCtlReg = (totalLength) | (tlpTransUnit << 22) | (tlpTc << 25) + | (tlpTd << 28) | (tlpEp << 29) | (tlpAttr << 30); + dmaManagement |= dmaChanEnable | (dmaTransStart << 1) + | (chanLatencyTimer << 8); + /* write DMA register, start DMA engine */ + smi_write(port->DMA_CHAN0_ADDR_LOW, dmaMemPtrLow); + smi_write(port->DMA_CHAN0_ADDR_HI, dmaMemPtrHi); + smi_write(port->DMA_CHAN0_CONTROL, dmaCtlReg); + } + /* Setup Channel-1 */ + if (port->_dmaInterruptCH1) { + totalLength = SMI_TS_DMA_BUF_SIZE; + mem = port->dma_addr[1]; + dmaMemPtrLow = mem & 0xffffffff; + dmaMemPtrHi = mem >> 32; + dmaCtlReg = (totalLength) | (tlpTransUnit << 22) | (tlpTc << 25) + | (tlpTd << 28) | (tlpEp << 29) | (tlpAttr << 30); + dmaManagement |= (dmaChanEnable << 16) | (dmaTransStart << 17) + | (chanLatencyTimer << 24); + /* write DMA register, start DMA engine */ + smi_write(port->DMA_CHAN1_ADDR_LOW, dmaMemPtrLow); + smi_write(port->DMA_CHAN1_ADDR_HI, dmaMemPtrHi); + smi_write(port->DMA_CHAN1_CONTROL, dmaCtlReg); + } + return dmaManagement; +} + +static int smi_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct smi_port *port = dvbdmx->priv; + struct smi_dev *dev = port->dev; + u32 dmaManagement; + + if (port->users++ == 0) { + dmaManagement = smi_config_DMA(port); + smi_port_clearInterrupt(port); + smi_port_enableInterrupt(port); + smi_write(port->DMA_MANAGEMENT, dmaManagement); + enable_and_queue_work(system_bh_wq, &port->bh_work); + } + return port->users; +} + +static int smi_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct smi_port *port = dvbdmx->priv; + struct smi_dev *dev = port->dev; + + if (--port->users) + return port->users; + + disable_work_sync(&port->bh_work); + smi_port_disableInterrupt(port); + smi_clear(port->DMA_MANAGEMENT, 0x30003); + return 0; +} + +static int smi_dvb_init(struct smi_port *port) +{ + int ret; + struct dvb_adapter *adap = &port->dvb_adapter; + struct dvb_demux *dvbdemux = &port->demux; + + dev_dbg(&port->dev->pci_dev->dev, + "%s, port %d\n", __func__, port->idx); + + ret = dvb_register_adapter(adap, "SMI_DVB", THIS_MODULE, + &port->dev->pci_dev->dev, + adapter_nr); + if (ret < 0) { + dev_err(&port->dev->pci_dev->dev, "Fail to register DVB adapter.\n"); + return ret; + } + ret = my_dvb_dmx_ts_card_init(dvbdemux, "SW demux", + smi_start_feed, + smi_stop_feed, port); + if (ret < 0) + goto err_del_dvb_register_adapter; + + ret = my_dvb_dmxdev_ts_card_init(&port->dmxdev, &port->demux, + &port->hw_frontend, + &port->mem_frontend, adap); + if (ret < 0) + goto err_del_dvb_dmx; + + ret = dvb_net_init(adap, &port->dvbnet, port->dmxdev.demux); + if (ret < 0) + goto err_del_dvb_dmxdev; + return 0; +err_del_dvb_dmxdev: + dvbdemux->dmx.close(&dvbdemux->dmx); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &port->hw_frontend); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &port->mem_frontend); + dvb_dmxdev_release(&port->dmxdev); +err_del_dvb_dmx: + dvb_dmx_release(&port->demux); +err_del_dvb_register_adapter: + dvb_unregister_adapter(&port->dvb_adapter); + return ret; +} + +static void smi_dvb_exit(struct smi_port *port) +{ + struct dvb_demux *dvbdemux = &port->demux; + + dvb_net_release(&port->dvbnet); + + dvbdemux->dmx.close(&dvbdemux->dmx); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &port->hw_frontend); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &port->mem_frontend); + dvb_dmxdev_release(&port->dmxdev); + dvb_dmx_release(&port->demux); + + dvb_unregister_adapter(&port->dvb_adapter); +} + +static int smi_port_attach(struct smi_dev *dev, + struct smi_port *port, int index) +{ + int ret, dmachs; + + port->dev = dev; + port->idx = index; + port->fe_type = (index == 0) ? dev->info->fe_0 : dev->info->fe_1; + dmachs = (index == 0) ? dev->info->ts_0 : dev->info->ts_1; + /* port init.*/ + ret = smi_port_init(port, dmachs); + if (ret < 0) + return ret; + /* dvb init.*/ + ret = smi_dvb_init(port); + if (ret < 0) + goto err_del_port_init; + /* fe init.*/ + ret = smi_fe_init(port); + if (ret < 0) + goto err_del_dvb_init; + return 0; +err_del_dvb_init: + smi_dvb_exit(port); +err_del_port_init: + smi_port_exit(port); + return ret; +} + +static void smi_port_detach(struct smi_port *port) +{ + smi_fe_exit(port); + smi_dvb_exit(port); + smi_port_exit(port); +} + +static int smi_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct smi_dev *dev; + int ret = -ENOMEM; + + if (pci_enable_device(pdev) < 0) + return -ENODEV; + + dev = kzalloc_obj(struct smi_dev); + if (!dev) { + ret = -ENOMEM; + goto err_pci_disable_device; + } + + dev->pci_dev = pdev; + pci_set_drvdata(pdev, dev); + dev->info = (struct smi_cfg_info *) id->driver_data; + dev_info(&dev->pci_dev->dev, + "card detected: %s\n", dev->info->name); + + dev->nr = dev->info->type; + dev->lmmio = ioremap(pci_resource_start(dev->pci_dev, 0), + pci_resource_len(dev->pci_dev, 0)); + if (!dev->lmmio) { + ret = -ENOMEM; + goto err_kfree; + } + + /* should we set to 32bit DMA? */ + ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (ret < 0) + goto err_pci_iounmap; + + pci_set_master(pdev); + + ret = smi_hw_init(dev); + if (ret < 0) + goto err_pci_iounmap; + + ret = smi_i2c_init(dev); + if (ret < 0) + goto err_pci_iounmap; + + if (dev->info->ts_0) { + ret = smi_port_attach(dev, &dev->ts_port[0], 0); + if (ret < 0) + goto err_del_i2c_adaptor; + } + + if (dev->info->ts_1) { + ret = smi_port_attach(dev, &dev->ts_port[1], 1); + if (ret < 0) + goto err_del_port0_attach; + } + + ret = smi_ir_init(dev); + if (ret < 0) + goto err_del_port1_attach; + +#ifdef CONFIG_PCI_MSI /* to do msi interrupt.???*/ + if (pci_msi_enabled()) + ret = pci_enable_msi(dev->pci_dev); + if (ret) + dev_info(&dev->pci_dev->dev, "MSI not available.\n"); +#endif + + ret = request_irq(dev->pci_dev->irq, smi_irq_handler, + IRQF_SHARED, "SMI_PCIE", dev); + if (ret < 0) + goto err_del_ir; + + smi_ir_start(&dev->ir); + return 0; + +err_del_ir: + smi_ir_exit(dev); +err_del_port1_attach: + if (dev->info->ts_1) + smi_port_detach(&dev->ts_port[1]); +err_del_port0_attach: + if (dev->info->ts_0) + smi_port_detach(&dev->ts_port[0]); +err_del_i2c_adaptor: + smi_i2c_exit(dev); +err_pci_iounmap: + iounmap(dev->lmmio); +err_kfree: + pci_set_drvdata(pdev, NULL); + kfree(dev); +err_pci_disable_device: + pci_disable_device(pdev); + return ret; +} + +static void smi_remove(struct pci_dev *pdev) +{ + struct smi_dev *dev = pci_get_drvdata(pdev); + + smi_write(MSI_INT_ENA_CLR, ALL_INT); + free_irq(dev->pci_dev->irq, dev); +#ifdef CONFIG_PCI_MSI + pci_disable_msi(dev->pci_dev); +#endif + if (dev->info->ts_1) + smi_port_detach(&dev->ts_port[1]); + if (dev->info->ts_0) + smi_port_detach(&dev->ts_port[0]); + + smi_ir_exit(dev); + smi_i2c_exit(dev); + iounmap(dev->lmmio); + pci_set_drvdata(pdev, NULL); + pci_disable_device(pdev); + kfree(dev); +} + +/* DVBSky cards */ +static const struct smi_cfg_info dvbsky_s950_cfg = { + .type = SMI_DVBSKY_S950, + .name = "DVBSky S950 V3", + .ts_0 = SMI_TS_NULL, + .ts_1 = SMI_TS_DMA_BOTH, + .fe_0 = DVBSKY_FE_NULL, + .fe_1 = DVBSKY_FE_M88DS3103, + .rc_map = RC_MAP_DVBSKY, +}; + +static const struct smi_cfg_info dvbsky_s952_cfg = { + .type = SMI_DVBSKY_S952, + .name = "DVBSky S952 V3", + .ts_0 = SMI_TS_DMA_BOTH, + .ts_1 = SMI_TS_DMA_BOTH, + .fe_0 = DVBSKY_FE_M88RS6000, + .fe_1 = DVBSKY_FE_M88RS6000, + .rc_map = RC_MAP_DVBSKY, +}; + +static const struct smi_cfg_info dvbsky_t9580_cfg = { + .type = SMI_DVBSKY_T9580, + .name = "DVBSky T9580 V3", + .ts_0 = SMI_TS_DMA_BOTH, + .ts_1 = SMI_TS_DMA_BOTH, + .fe_0 = DVBSKY_FE_SIT2, + .fe_1 = DVBSKY_FE_M88DS3103, + .rc_map = RC_MAP_DVBSKY, +}; + +static const struct smi_cfg_info technotrend_s2_4200_cfg = { + .type = SMI_TECHNOTREND_S2_4200, + .name = "TechnoTrend TT-budget S2-4200 Twin", + .ts_0 = SMI_TS_DMA_BOTH, + .ts_1 = SMI_TS_DMA_BOTH, + .fe_0 = DVBSKY_FE_M88RS6000, + .fe_1 = DVBSKY_FE_M88RS6000, + .rc_map = RC_MAP_TT_1500, +}; + +/* PCI IDs */ +#define SMI_ID(_subvend, _subdev, _driverdata) { \ + PCI_DEVICE_SUB(SMI_VID, SMI_PID, (_subvend), (_subdev)), \ + .driver_data = (unsigned long)&_driverdata } + +static const struct pci_device_id smi_id_table[] = { + SMI_ID(0x4254, 0x0550, dvbsky_s950_cfg), + SMI_ID(0x4254, 0x0552, dvbsky_s952_cfg), + SMI_ID(0x4254, 0x5580, dvbsky_t9580_cfg), + SMI_ID(0x13c2, 0x3016, technotrend_s2_4200_cfg), + { } +}; +MODULE_DEVICE_TABLE(pci, smi_id_table); + +static struct pci_driver smipcie_driver = { + .name = "SMI PCIe driver", + .id_table = smi_id_table, + .probe = smi_probe, + .remove = smi_remove, +}; + +module_pci_driver(smipcie_driver); + +MODULE_AUTHOR("Max nibble "); +MODULE_DESCRIPTION("SMI PCIe driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/smipcie/smipcie.h b/drivers/media/pci/smipcie/smipcie.h new file mode 100644 index 000000000..98e44edaa --- /dev/null +++ b/drivers/media/pci/smipcie/smipcie.h @@ -0,0 +1,310 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * SMI PCIe driver for DVBSky cards. + * + * Copyright (C) 2014 Max nibble + */ + +#ifndef _SMI_PCIE_H_ +#define _SMI_PCIE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* -------- Register Base -------- */ +#define MSI_CONTROL_REG_BASE 0x0800 +#define SYSTEM_CONTROL_REG_BASE 0x0880 +#define PCIE_EP_DEBUG_REG_BASE 0x08C0 +#define IR_CONTROL_REG_BASE 0x0900 +#define I2C_A_CONTROL_REG_BASE 0x0940 +#define I2C_B_CONTROL_REG_BASE 0x0980 +#define ATV_PORTA_CONTROL_REG_BASE 0x09C0 +#define DTV_PORTA_CONTROL_REG_BASE 0x0A00 +#define AES_PORTA_CONTROL_REG_BASE 0x0A80 +#define DMA_PORTA_CONTROL_REG_BASE 0x0AC0 +#define ATV_PORTB_CONTROL_REG_BASE 0x0B00 +#define DTV_PORTB_CONTROL_REG_BASE 0x0B40 +#define AES_PORTB_CONTROL_REG_BASE 0x0BC0 +#define DMA_PORTB_CONTROL_REG_BASE 0x0C00 +#define UART_A_REGISTER_BASE 0x0C40 +#define UART_B_REGISTER_BASE 0x0C80 +#define GPS_CONTROL_REG_BASE 0x0CC0 +#define DMA_PORTC_CONTROL_REG_BASE 0x0D00 +#define DMA_PORTD_CONTROL_REG_BASE 0x0D00 +#define AES_RANDOM_DATA_BASE 0x0D80 +#define AES_KEY_IN_BASE 0x0D90 +#define RANDOM_DATA_LIB_BASE 0x0E00 +#define IR_DATA_BUFFER_BASE 0x0F00 +#define PORTA_TS_BUFFER_BASE 0x1000 +#define PORTA_I2S_BUFFER_BASE 0x1400 +#define PORTB_TS_BUFFER_BASE 0x1800 +#define PORTB_I2S_BUFFER_BASE 0x1C00 + +/* -------- MSI control and state register -------- */ +#define MSI_DELAY_TIMER (MSI_CONTROL_REG_BASE + 0x00) +#define MSI_INT_STATUS (MSI_CONTROL_REG_BASE + 0x08) +#define MSI_INT_STATUS_CLR (MSI_CONTROL_REG_BASE + 0x0C) +#define MSI_INT_STATUS_SET (MSI_CONTROL_REG_BASE + 0x10) +#define MSI_INT_ENA (MSI_CONTROL_REG_BASE + 0x14) +#define MSI_INT_ENA_CLR (MSI_CONTROL_REG_BASE + 0x18) +#define MSI_INT_ENA_SET (MSI_CONTROL_REG_BASE + 0x1C) +#define MSI_SOFT_RESET (MSI_CONTROL_REG_BASE + 0x20) +#define MSI_CFG_SRC0 (MSI_CONTROL_REG_BASE + 0x24) + +/* -------- Hybird Controller System Control register -------- */ +#define MUX_MODE_CTRL (SYSTEM_CONTROL_REG_BASE + 0x00) + #define rbPaMSMask 0x07 + #define rbPaMSDtvNoGpio 0x00 /*[2:0], DTV Simple mode */ + #define rbPaMSDtv4bitGpio 0x01 /*[2:0], DTV TS2 Serial mode)*/ + #define rbPaMSDtv7bitGpio 0x02 /*[2:0], DTV TS0 Serial mode*/ + #define rbPaMS8bitGpio 0x03 /*[2:0], GPIO mode selected;(8bit GPIO)*/ + #define rbPaMSAtv 0x04 /*[2:0], 3'b1xx: ATV mode select*/ + #define rbPbMSMask 0x38 + #define rbPbMSDtvNoGpio 0x00 /*[5:3], DTV Simple mode */ + #define rbPbMSDtv4bitGpio 0x08 /*[5:3], DTV TS2 Serial mode*/ + #define rbPbMSDtv7bitGpio 0x10 /*[5:3], DTV TS0 Serial mode*/ + #define rbPbMS8bitGpio 0x18 /*[5:3], GPIO mode selected;(8bit GPIO)*/ + #define rbPbMSAtv 0x20 /*[5:3], 3'b1xx: ATV mode select*/ + #define rbPaAESEN 0x40 /*[6], port A AES enable bit*/ + #define rbPbAESEN 0x80 /*[7], port B AES enable bit*/ + +#define INTERNAL_RST (SYSTEM_CONTROL_REG_BASE + 0x04) +#define PERIPHERAL_CTRL (SYSTEM_CONTROL_REG_BASE + 0x08) +#define GPIO_0to7_CTRL (SYSTEM_CONTROL_REG_BASE + 0x0C) +#define GPIO_8to15_CTRL (SYSTEM_CONTROL_REG_BASE + 0x10) +#define GPIO_16to24_CTRL (SYSTEM_CONTROL_REG_BASE + 0x14) +#define GPIO_INT_SRC_CFG (SYSTEM_CONTROL_REG_BASE + 0x18) +#define SYS_BUF_STATUS (SYSTEM_CONTROL_REG_BASE + 0x1C) +#define PCIE_IP_REG_ACS (SYSTEM_CONTROL_REG_BASE + 0x20) +#define PCIE_IP_REG_ACS_ADDR (SYSTEM_CONTROL_REG_BASE + 0x24) +#define PCIE_IP_REG_ACS_DATA (SYSTEM_CONTROL_REG_BASE + 0x28) + +/* -------- IR Control register -------- */ +#define IR_Init_Reg (IR_CONTROL_REG_BASE + 0x00) +#define IR_Idle_Cnt_Low (IR_CONTROL_REG_BASE + 0x04) +#define IR_Idle_Cnt_High (IR_CONTROL_REG_BASE + 0x05) +#define IR_Unit_Cnt_Low (IR_CONTROL_REG_BASE + 0x06) +#define IR_Unit_Cnt_High (IR_CONTROL_REG_BASE + 0x07) +#define IR_Data_Cnt (IR_CONTROL_REG_BASE + 0x08) +#define rbIRen 0x80 +#define rbIRhighidle 0x10 +#define rbIRlowidle 0x00 +#define rbIRVld 0x04 + +/* -------- I2C A control and state register -------- */ +#define I2C_A_CTL_STATUS (I2C_A_CONTROL_REG_BASE + 0x00) +#define I2C_A_ADDR (I2C_A_CONTROL_REG_BASE + 0x04) +#define I2C_A_SW_CTL (I2C_A_CONTROL_REG_BASE + 0x08) +#define I2C_A_TIME_OUT_CNT (I2C_A_CONTROL_REG_BASE + 0x0C) +#define I2C_A_FIFO_STATUS (I2C_A_CONTROL_REG_BASE + 0x10) +#define I2C_A_FS_EN (I2C_A_CONTROL_REG_BASE + 0x14) +#define I2C_A_FIFO_DATA (I2C_A_CONTROL_REG_BASE + 0x20) + +/* -------- I2C B control and state register -------- */ +#define I2C_B_CTL_STATUS (I2C_B_CONTROL_REG_BASE + 0x00) +#define I2C_B_ADDR (I2C_B_CONTROL_REG_BASE + 0x04) +#define I2C_B_SW_CTL (I2C_B_CONTROL_REG_BASE + 0x08) +#define I2C_B_TIME_OUT_CNT (I2C_B_CONTROL_REG_BASE + 0x0C) +#define I2C_B_FIFO_STATUS (I2C_B_CONTROL_REG_BASE + 0x10) +#define I2C_B_FS_EN (I2C_B_CONTROL_REG_BASE + 0x14) +#define I2C_B_FIFO_DATA (I2C_B_CONTROL_REG_BASE + 0x20) + +#define VIDEO_CTRL_STATUS_A (ATV_PORTA_CONTROL_REG_BASE + 0x04) + +/* -------- Digital TV control register, Port A -------- */ +#define MPEG2_CTRL_A (DTV_PORTA_CONTROL_REG_BASE + 0x00) +#define SERIAL_IN_ADDR_A (DTV_PORTA_CONTROL_REG_BASE + 0x4C) +#define VLD_CNT_ADDR_A (DTV_PORTA_CONTROL_REG_BASE + 0x60) +#define ERR_CNT_ADDR_A (DTV_PORTA_CONTROL_REG_BASE + 0x64) +#define BRD_CNT_ADDR_A (DTV_PORTA_CONTROL_REG_BASE + 0x68) + +/* -------- DMA Control Register, Port A -------- */ +#define DMA_PORTA_CHAN0_ADDR_LOW (DMA_PORTA_CONTROL_REG_BASE + 0x00) +#define DMA_PORTA_CHAN0_ADDR_HI (DMA_PORTA_CONTROL_REG_BASE + 0x04) +#define DMA_PORTA_CHAN0_TRANS_STATE (DMA_PORTA_CONTROL_REG_BASE + 0x08) +#define DMA_PORTA_CHAN0_CONTROL (DMA_PORTA_CONTROL_REG_BASE + 0x0C) +#define DMA_PORTA_CHAN1_ADDR_LOW (DMA_PORTA_CONTROL_REG_BASE + 0x10) +#define DMA_PORTA_CHAN1_ADDR_HI (DMA_PORTA_CONTROL_REG_BASE + 0x14) +#define DMA_PORTA_CHAN1_TRANS_STATE (DMA_PORTA_CONTROL_REG_BASE + 0x18) +#define DMA_PORTA_CHAN1_CONTROL (DMA_PORTA_CONTROL_REG_BASE + 0x1C) +#define DMA_PORTA_MANAGEMENT (DMA_PORTA_CONTROL_REG_BASE + 0x20) +#define VIDEO_CTRL_STATUS_B (ATV_PORTB_CONTROL_REG_BASE + 0x04) + +/* -------- Digital TV control register, Port B -------- */ +#define MPEG2_CTRL_B (DTV_PORTB_CONTROL_REG_BASE + 0x00) +#define SERIAL_IN_ADDR_B (DTV_PORTB_CONTROL_REG_BASE + 0x4C) +#define VLD_CNT_ADDR_B (DTV_PORTB_CONTROL_REG_BASE + 0x60) +#define ERR_CNT_ADDR_B (DTV_PORTB_CONTROL_REG_BASE + 0x64) +#define BRD_CNT_ADDR_B (DTV_PORTB_CONTROL_REG_BASE + 0x68) + +/* -------- AES control register, Port B -------- */ +#define AES_CTRL_B (AES_PORTB_CONTROL_REG_BASE + 0x00) +#define AES_KEY_BASE_B (AES_PORTB_CONTROL_REG_BASE + 0x04) + +/* -------- DMA Control Register, Port B -------- */ +#define DMA_PORTB_CHAN0_ADDR_LOW (DMA_PORTB_CONTROL_REG_BASE + 0x00) +#define DMA_PORTB_CHAN0_ADDR_HI (DMA_PORTB_CONTROL_REG_BASE + 0x04) +#define DMA_PORTB_CHAN0_TRANS_STATE (DMA_PORTB_CONTROL_REG_BASE + 0x08) +#define DMA_PORTB_CHAN0_CONTROL (DMA_PORTB_CONTROL_REG_BASE + 0x0C) +#define DMA_PORTB_CHAN1_ADDR_LOW (DMA_PORTB_CONTROL_REG_BASE + 0x10) +#define DMA_PORTB_CHAN1_ADDR_HI (DMA_PORTB_CONTROL_REG_BASE + 0x14) +#define DMA_PORTB_CHAN1_TRANS_STATE (DMA_PORTB_CONTROL_REG_BASE + 0x18) +#define DMA_PORTB_CHAN1_CONTROL (DMA_PORTB_CONTROL_REG_BASE + 0x1C) +#define DMA_PORTB_MANAGEMENT (DMA_PORTB_CONTROL_REG_BASE + 0x20) + +#define DMA_TRANS_UNIT_188 (0x00000007) + +/* -------- Macro define of 24 interrupt resource --------*/ +#define DMA_A_CHAN0_DONE_INT (0x00000001) +#define DMA_A_CHAN1_DONE_INT (0x00000002) +#define DMA_B_CHAN0_DONE_INT (0x00000004) +#define DMA_B_CHAN1_DONE_INT (0x00000008) +#define DMA_C_CHAN0_DONE_INT (0x00000010) +#define DMA_C_CHAN1_DONE_INT (0x00000020) +#define DMA_D_CHAN0_DONE_INT (0x00000040) +#define DMA_D_CHAN1_DONE_INT (0x00000080) +#define DATA_BUF_OVERFLOW_INT (0x00000100) +#define UART_0_X_INT (0x00000200) +#define UART_1_X_INT (0x00000400) +#define IR_X_INT (0x00000800) +#define GPIO_0_INT (0x00001000) +#define GPIO_1_INT (0x00002000) +#define GPIO_2_INT (0x00004000) +#define GPIO_3_INT (0x00008000) +#define ALL_INT (0x0000FFFF) + +/* software I2C bit mask */ +#define SW_I2C_MSK_MODE 0x01 +#define SW_I2C_MSK_CLK_OUT 0x02 +#define SW_I2C_MSK_DAT_OUT 0x04 +#define SW_I2C_MSK_CLK_EN 0x08 +#define SW_I2C_MSK_DAT_EN 0x10 +#define SW_I2C_MSK_DAT_IN 0x40 +#define SW_I2C_MSK_CLK_IN 0x80 + +#define SMI_VID 0x1ADE +#define SMI_PID 0x3038 +#define SMI_TS_DMA_BUF_SIZE (1024 * 188) + +struct smi_cfg_info { +#define SMI_DVBSKY_S952 0 +#define SMI_DVBSKY_S950 1 +#define SMI_DVBSKY_T9580 2 +#define SMI_DVBSKY_T982 3 +#define SMI_TECHNOTREND_S2_4200 4 + int type; + char *name; +#define SMI_TS_NULL 0 +#define SMI_TS_DMA_SINGLE 1 +#define SMI_TS_DMA_BOTH 3 +/* SMI_TS_NULL: not use; + * SMI_TS_DMA_SINGLE: use DMA 0 only; + * SMI_TS_DMA_BOTH:use DMA 0 and 1.*/ + int ts_0; + int ts_1; +#define DVBSKY_FE_NULL 0 +#define DVBSKY_FE_M88RS6000 1 +#define DVBSKY_FE_M88DS3103 2 +#define DVBSKY_FE_SIT2 3 + int fe_0; + int fe_1; + char *rc_map; +}; + +struct smi_rc { + struct smi_dev *dev; + struct rc_dev *rc_dev; + char input_phys[64]; + char device_name[64]; + u8 irData[256]; + + int users; +}; + +struct smi_port { + struct smi_dev *dev; + int idx; + int enable; + int fe_type; + /* regs */ + u32 DMA_CHAN0_ADDR_LOW; + u32 DMA_CHAN0_ADDR_HI; + u32 DMA_CHAN0_TRANS_STATE; + u32 DMA_CHAN0_CONTROL; + u32 DMA_CHAN1_ADDR_LOW; + u32 DMA_CHAN1_ADDR_HI; + u32 DMA_CHAN1_TRANS_STATE; + u32 DMA_CHAN1_CONTROL; + u32 DMA_MANAGEMENT; + /* dma */ + dma_addr_t dma_addr[2]; + u8 *cpu_addr[2]; + u32 _dmaInterruptCH0; + u32 _dmaInterruptCH1; + u32 _int_status; + struct work_struct bh_work; + /* dvb */ + struct dmx_frontend hw_frontend; + struct dmx_frontend mem_frontend; + struct dmxdev dmxdev; + struct dvb_adapter dvb_adapter; + struct dvb_demux demux; + struct dvb_net dvbnet; + int users; + struct dvb_frontend *fe; + /* frontend i2c module */ + struct i2c_client *i2c_client_demod; + struct i2c_client *i2c_client_tuner; +}; + +struct smi_dev { + int nr; + struct smi_cfg_info *info; + + /* pcie */ + struct pci_dev *pci_dev; + u32 __iomem *lmmio; + + /* ts port */ + struct smi_port ts_port[2]; + + /* i2c */ + struct i2c_adapter i2c_bus[2]; + struct i2c_algo_bit_data i2c_bit[2]; + + /* ir */ + struct smi_rc ir; +}; + +#define smi_read(reg) readl(dev->lmmio + ((reg)>>2)) +#define smi_write(reg, value) writel((value), dev->lmmio + ((reg)>>2)) + +#define smi_andor(reg, mask, value) \ + writel((readl(dev->lmmio+((reg)>>2)) & ~(mask)) |\ + ((value) & (mask)), dev->lmmio+((reg)>>2)) + +#define smi_set(reg, bit) smi_andor((reg), (bit), (bit)) +#define smi_clear(reg, bit) smi_andor((reg), (bit), 0) + +int smi_ir_irq(struct smi_rc *ir, u32 int_status); +void smi_ir_start(struct smi_rc *ir); +void smi_ir_exit(struct smi_dev *dev); +int smi_ir_init(struct smi_dev *dev); + +#endif /* #ifndef _SMI_PCIE_H_ */ diff --git a/drivers/media/pci/solo6x10/Kconfig b/drivers/media/pci/solo6x10/Kconfig new file mode 100644 index 000000000..cc4a7b088 --- /dev/null +++ b/drivers/media/pci/solo6x10/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_SOLO6X10 + tristate "Bluecherry / Softlogic 6x10 capture cards (MPEG-4/H.264)" + depends on PCI && VIDEO_DEV && SND && I2C + select BITREVERSE + select FONT_SUPPORT + select FONT_8x16 + select VIDEOBUF2_DMA_SG + select VIDEOBUF2_DMA_CONTIG + select SND_PCM + help + This driver supports the Bluecherry H.264 and MPEG-4 hardware + compression capture cards and other Softlogic-based ones. + + Following cards have been tested: + * Bluecherry BC-H16480A (PCIe, 16 port, H.264) + * Bluecherry BC-H04120A (PCIe, 4 port, H.264) + * Bluecherry BC-H04120A-MPCI (Mini-PCI, 4 port, H.264) + * Bluecherry BC-04120A (PCIe, 4 port, MPEG-4) diff --git a/drivers/media/pci/solo6x10/Makefile b/drivers/media/pci/solo6x10/Makefile new file mode 100644 index 000000000..308387c86 --- /dev/null +++ b/drivers/media/pci/solo6x10/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +solo6x10-y := solo6x10-core.o solo6x10-i2c.o solo6x10-p2m.o solo6x10-v4l2.o \ + solo6x10-tw28.o solo6x10-gpio.o solo6x10-disp.o solo6x10-enc.o \ + solo6x10-v4l2-enc.o solo6x10-g723.o solo6x10-eeprom.o + +obj-$(CONFIG_VIDEO_SOLO6X10) += solo6x10.o diff --git a/drivers/media/pci/solo6x10/solo6x10-core.c b/drivers/media/pci/solo6x10/solo6x10-core.c new file mode 100644 index 000000000..282c2c871 --- /dev/null +++ b/drivers/media/pci/solo6x10/solo6x10-core.c @@ -0,0 +1,671 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2010-2013 Bluecherry, LLC + * + * Original author: + * Ben Collins + * + * Additional work by: + * John Brooks + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "solo6x10.h" +#include "solo6x10-tw28.h" + +MODULE_DESCRIPTION("Softlogic 6x10 MPEG4/H.264/G.723 CODEC V4L2/ALSA Driver"); +MODULE_AUTHOR("Bluecherry "); +MODULE_VERSION(SOLO6X10_VERSION); +MODULE_LICENSE("GPL"); + +static unsigned video_nr = -1; +module_param(video_nr, uint, 0644); +MODULE_PARM_DESC(video_nr, "videoX start number, -1 is autodetect (default)"); + +static int full_eeprom; /* default is only top 64B */ +module_param(full_eeprom, uint, 0644); +MODULE_PARM_DESC(full_eeprom, "Allow access to full 128B EEPROM (dangerous)"); + + +static void solo_set_time(struct solo_dev *solo_dev) +{ + struct timespec64 ts; + + ktime_get_ts64(&ts); + + /* no overflow because we use monotonic timestamps */ + solo_reg_write(solo_dev, SOLO_TIMER_SEC, (u32)ts.tv_sec); + solo_reg_write(solo_dev, SOLO_TIMER_USEC, (u32)ts.tv_nsec / NSEC_PER_USEC); +} + +static void solo_timer_sync(struct solo_dev *solo_dev) +{ + u32 sec, usec; + struct timespec64 ts; + long diff; + + if (solo_dev->type != SOLO_DEV_6110) + return; + + if (++solo_dev->time_sync < 60) + return; + + solo_dev->time_sync = 0; + + sec = solo_reg_read(solo_dev, SOLO_TIMER_SEC); + usec = solo_reg_read(solo_dev, SOLO_TIMER_USEC); + + ktime_get_ts64(&ts); + + diff = (s32)ts.tv_sec - (s32)sec; + diff = (diff * 1000000) + + ((s32)(ts.tv_nsec / NSEC_PER_USEC) - (s32)usec); + + if (diff > 1000 || diff < -1000) { + solo_set_time(solo_dev); + } else if (diff) { + long usec_lsb = solo_dev->usec_lsb; + + usec_lsb -= diff / 4; + if (usec_lsb < 0) + usec_lsb = 0; + else if (usec_lsb > 255) + usec_lsb = 255; + + solo_dev->usec_lsb = usec_lsb; + solo_reg_write(solo_dev, SOLO_TIMER_USEC_LSB, + solo_dev->usec_lsb); + } +} + +static irqreturn_t solo_isr(int irq, void *data) +{ + struct solo_dev *solo_dev = data; + u32 status; + int i; + + status = solo_reg_read(solo_dev, SOLO_IRQ_STAT); + if (!status) + return IRQ_NONE; + + /* Acknowledge all interrupts immediately */ + solo_reg_write(solo_dev, SOLO_IRQ_STAT, status); + + if (status & SOLO_IRQ_PCI_ERR) + solo_p2m_error_isr(solo_dev); + + for (i = 0; i < SOLO_NR_P2M; i++) + if (status & SOLO_IRQ_P2M(i)) + solo_p2m_isr(solo_dev, i); + + if (status & SOLO_IRQ_IIC) + solo_i2c_isr(solo_dev); + + if (status & SOLO_IRQ_VIDEO_IN) { + solo_video_in_isr(solo_dev); + solo_timer_sync(solo_dev); + } + + if (status & SOLO_IRQ_ENCODER) + solo_enc_v4l2_isr(solo_dev); + + if (status & SOLO_IRQ_G723) + solo_g723_isr(solo_dev); + + return IRQ_HANDLED; +} + +static void free_solo_dev(struct solo_dev *solo_dev) +{ + struct pci_dev *pdev = solo_dev->pdev; + + if (solo_dev->dev.parent) + device_unregister(&solo_dev->dev); + + if (solo_dev->reg_base) { + /* Bring down the sub-devices first */ + solo_g723_exit(solo_dev); + solo_enc_v4l2_exit(solo_dev); + solo_enc_exit(solo_dev); + solo_v4l2_exit(solo_dev); + solo_disp_exit(solo_dev); + solo_gpio_exit(solo_dev); + solo_p2m_exit(solo_dev); + solo_i2c_exit(solo_dev); + + /* Now cleanup the PCI device */ + solo_irq_off(solo_dev, ~0); + } + + pci_disable_device(pdev); + v4l2_device_unregister(&solo_dev->v4l2_dev); + pci_set_drvdata(pdev, NULL); + + kfree(solo_dev); +} + +static ssize_t eeprom_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + u16 *p = (u16 *)buf; + int i; + + if (count & 0x1) + dev_warn(dev, "EEPROM Write not aligned (truncating)\n"); + + if (!full_eeprom && count > 64) { + dev_warn(dev, "EEPROM Write truncated to 64 bytes\n"); + count = 64; + } else if (full_eeprom && count > 128) { + dev_warn(dev, "EEPROM Write truncated to 128 bytes\n"); + count = 128; + } + + solo_eeprom_ewen(solo_dev, 1); + + for (i = full_eeprom ? 0 : 32; i < min((int)(full_eeprom ? 64 : 32), + (int)(count / 2)); i++) + solo_eeprom_write(solo_dev, i, cpu_to_be16(p[i])); + + solo_eeprom_ewen(solo_dev, 0); + + return count; +} + +static ssize_t eeprom_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + u16 *p = (u16 *)buf; + int count = (full_eeprom ? 128 : 64); + int i; + + for (i = (full_eeprom ? 0 : 32); i < (count / 2); i++) + p[i] = be16_to_cpu(solo_eeprom_read(solo_dev, i)); + + return count; +} + +static ssize_t p2m_timeouts_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + + return sprintf(buf, "%d\n", solo_dev->p2m_timeouts); +} + +static ssize_t sdram_size_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + + return sprintf(buf, "%dMegs\n", solo_dev->sdram_size >> 20); +} + +static ssize_t tw28xx_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + + return sprintf(buf, "tw2815[%d] tw2864[%d] tw2865[%d]\n", + hweight32(solo_dev->tw2815), + hweight32(solo_dev->tw2864), + hweight32(solo_dev->tw2865)); +} + +static ssize_t input_map_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + unsigned int val; + char *out = buf; + + val = solo_reg_read(solo_dev, SOLO_VI_CH_SWITCH_0); + out += sprintf(out, "Channel 0 => Input %d\n", val & 0x1f); + out += sprintf(out, "Channel 1 => Input %d\n", (val >> 5) & 0x1f); + out += sprintf(out, "Channel 2 => Input %d\n", (val >> 10) & 0x1f); + out += sprintf(out, "Channel 3 => Input %d\n", (val >> 15) & 0x1f); + out += sprintf(out, "Channel 4 => Input %d\n", (val >> 20) & 0x1f); + out += sprintf(out, "Channel 5 => Input %d\n", (val >> 25) & 0x1f); + + val = solo_reg_read(solo_dev, SOLO_VI_CH_SWITCH_1); + out += sprintf(out, "Channel 6 => Input %d\n", val & 0x1f); + out += sprintf(out, "Channel 7 => Input %d\n", (val >> 5) & 0x1f); + out += sprintf(out, "Channel 8 => Input %d\n", (val >> 10) & 0x1f); + out += sprintf(out, "Channel 9 => Input %d\n", (val >> 15) & 0x1f); + out += sprintf(out, "Channel 10 => Input %d\n", (val >> 20) & 0x1f); + out += sprintf(out, "Channel 11 => Input %d\n", (val >> 25) & 0x1f); + + val = solo_reg_read(solo_dev, SOLO_VI_CH_SWITCH_2); + out += sprintf(out, "Channel 12 => Input %d\n", val & 0x1f); + out += sprintf(out, "Channel 13 => Input %d\n", (val >> 5) & 0x1f); + out += sprintf(out, "Channel 14 => Input %d\n", (val >> 10) & 0x1f); + out += sprintf(out, "Channel 15 => Input %d\n", (val >> 15) & 0x1f); + out += sprintf(out, "Spot Output => Input %d\n", (val >> 20) & 0x1f); + + return out - buf; +} + +static ssize_t p2m_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + unsigned long ms; + int ret = kstrtoul(buf, 10, &ms); + + if (ret < 0 || ms > 200) + return -EINVAL; + solo_dev->p2m_jiffies = msecs_to_jiffies(ms); + + return count; +} + +static ssize_t p2m_timeout_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + + return sprintf(buf, "%ums\n", jiffies_to_msecs(solo_dev->p2m_jiffies)); +} + +static ssize_t intervals_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + char *out = buf; + int fps = solo_dev->fps; + int i; + + for (i = 0; i < solo_dev->nr_chans; i++) { + out += sprintf(out, "Channel %d: %d/%d (0x%08x)\n", + i, solo_dev->v4l2_enc[i]->interval, fps, + solo_reg_read(solo_dev, SOLO_CAP_CH_INTV(i))); + } + + return out - buf; +} + +static ssize_t sdram_offsets_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + char *out = buf; + + out += sprintf(out, "DISP: 0x%08x @ 0x%08x\n", + SOLO_DISP_EXT_ADDR, + SOLO_DISP_EXT_SIZE); + + out += sprintf(out, "EOSD: 0x%08x @ 0x%08x (0x%08x * %d)\n", + SOLO_EOSD_EXT_ADDR, + SOLO_EOSD_EXT_AREA(solo_dev), + SOLO_EOSD_EXT_SIZE(solo_dev), + SOLO_EOSD_EXT_AREA(solo_dev) / + SOLO_EOSD_EXT_SIZE(solo_dev)); + + out += sprintf(out, "MOTI: 0x%08x @ 0x%08x\n", + SOLO_MOTION_EXT_ADDR(solo_dev), + SOLO_MOTION_EXT_SIZE); + + out += sprintf(out, "G723: 0x%08x @ 0x%08x\n", + SOLO_G723_EXT_ADDR(solo_dev), + SOLO_G723_EXT_SIZE); + + out += sprintf(out, "CAPT: 0x%08x @ 0x%08x (0x%08x * %d)\n", + SOLO_CAP_EXT_ADDR(solo_dev), + SOLO_CAP_EXT_SIZE(solo_dev), + SOLO_CAP_PAGE_SIZE, + SOLO_CAP_EXT_SIZE(solo_dev) / SOLO_CAP_PAGE_SIZE); + + out += sprintf(out, "EREF: 0x%08x @ 0x%08x (0x%08x * %d)\n", + SOLO_EREF_EXT_ADDR(solo_dev), + SOLO_EREF_EXT_AREA(solo_dev), + SOLO_EREF_EXT_SIZE, + SOLO_EREF_EXT_AREA(solo_dev) / SOLO_EREF_EXT_SIZE); + + out += sprintf(out, "MPEG: 0x%08x @ 0x%08x\n", + SOLO_MP4E_EXT_ADDR(solo_dev), + SOLO_MP4E_EXT_SIZE(solo_dev)); + + out += sprintf(out, "JPEG: 0x%08x @ 0x%08x\n", + SOLO_JPEG_EXT_ADDR(solo_dev), + SOLO_JPEG_EXT_SIZE(solo_dev)); + + return out - buf; +} + +static ssize_t sdram_show(struct file *file, struct kobject *kobj, + const struct bin_attribute *a, char *buf, + loff_t off, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + const int size = solo_dev->sdram_size; + + if (off >= size) + return 0; + + if (off + count > size) + count = size - off; + + if (solo_p2m_dma(solo_dev, 0, buf, off, count, 0, 0)) + return -EIO; + + return count; +} + +static const struct device_attribute solo_dev_attrs[] = { + __ATTR(eeprom, 0640, eeprom_show, eeprom_store), + __ATTR(p2m_timeout, 0644, p2m_timeout_show, p2m_timeout_store), + __ATTR_RO(p2m_timeouts), + __ATTR_RO(sdram_size), + __ATTR_RO(tw28xx), + __ATTR_RO(input_map), + __ATTR_RO(intervals), + __ATTR_RO(sdram_offsets), +}; + +static void solo_device_release(struct device *dev) +{ + /* Do nothing */ +} + +static int solo_sysfs_init(struct solo_dev *solo_dev) +{ + struct bin_attribute *sdram_attr = &solo_dev->sdram_attr; + struct device *dev = &solo_dev->dev; + const char *driver; + int i; + + if (solo_dev->type == SOLO_DEV_6110) + driver = "solo6110"; + else + driver = "solo6010"; + + dev->release = solo_device_release; + dev->parent = &solo_dev->pdev->dev; + set_dev_node(dev, dev_to_node(&solo_dev->pdev->dev)); + dev_set_name(dev, "%s-%d-%d", driver, solo_dev->vfd->num, + solo_dev->nr_chans); + + if (device_register(dev)) { + put_device(dev); + dev->parent = NULL; + return -ENOMEM; + } + + for (i = 0; i < ARRAY_SIZE(solo_dev_attrs); i++) { + if (device_create_file(dev, &solo_dev_attrs[i])) { + device_unregister(dev); + return -ENOMEM; + } + } + + sysfs_attr_init(&sdram_attr->attr); + sdram_attr->attr.name = "sdram"; + sdram_attr->attr.mode = 0440; + sdram_attr->read = sdram_show; + sdram_attr->size = solo_dev->sdram_size; + + if (device_create_bin_file(dev, sdram_attr)) { + device_unregister(dev); + return -ENOMEM; + } + + return 0; +} + +static int solo_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct solo_dev *solo_dev; + int ret; + u8 chip_id; + + solo_dev = kzalloc_obj(*solo_dev); + if (solo_dev == NULL) + return -ENOMEM; + + if (id->driver_data == SOLO_DEV_6010) + dev_info(&pdev->dev, "Probing Softlogic 6010\n"); + else + dev_info(&pdev->dev, "Probing Softlogic 6110\n"); + + solo_dev->type = id->driver_data; + solo_dev->pdev = pdev; + ret = v4l2_device_register(&pdev->dev, &solo_dev->v4l2_dev); + if (ret) + goto fail_probe; + + /* Only for during init */ + solo_dev->p2m_jiffies = msecs_to_jiffies(100); + + ret = pci_enable_device(pdev); + if (ret) + goto fail_probe; + + pci_set_master(pdev); + + /* RETRY/TRDY Timeout disabled */ + pci_write_config_byte(pdev, 0x40, 0x00); + pci_write_config_byte(pdev, 0x41, 0x00); + + solo_dev->reg_base = pcim_iomap_region(pdev, 0, SOLO6X10_NAME); + ret = PTR_ERR_OR_ZERO(solo_dev->reg_base); + if (ret) + goto fail_probe; + + chip_id = solo_reg_read(solo_dev, SOLO_CHIP_OPTION) & + SOLO_CHIP_ID_MASK; + switch (chip_id) { + case 7: + solo_dev->nr_chans = 16; + solo_dev->nr_ext = 5; + break; + case 6: + solo_dev->nr_chans = 8; + solo_dev->nr_ext = 2; + break; + default: + dev_warn(&pdev->dev, "Invalid chip_id 0x%02x, assuming 4 ch\n", + chip_id); + fallthrough; + case 5: + solo_dev->nr_chans = 4; + solo_dev->nr_ext = 1; + } + + /* Disable all interrupts to start */ + solo_irq_off(solo_dev, ~0); + + /* Initial global settings */ + if (solo_dev->type == SOLO_DEV_6010) { + solo_dev->clock_mhz = 108; + solo_dev->sys_config = SOLO_SYS_CFG_SDRAM64BIT + | SOLO_SYS_CFG_INPUTDIV(25) + | SOLO_SYS_CFG_FEEDBACKDIV(solo_dev->clock_mhz * 2 - 2) + | SOLO_SYS_CFG_OUTDIV(3); + solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config); + } else { + u32 divq, divf; + + solo_dev->clock_mhz = 135; + + if (solo_dev->clock_mhz < 125) { + divq = 3; + divf = (solo_dev->clock_mhz * 4) / 3 - 1; + } else { + divq = 2; + divf = (solo_dev->clock_mhz * 2) / 3 - 1; + } + + solo_reg_write(solo_dev, SOLO_PLL_CONFIG, + (1 << 20) | /* PLL_RANGE */ + (8 << 15) | /* PLL_DIVR */ + (divq << 12) | + (divf << 4) | + (1 << 1) /* PLL_FSEN */); + + solo_dev->sys_config = SOLO_SYS_CFG_SDRAM64BIT; + } + + solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config); + solo_reg_write(solo_dev, SOLO_TIMER_CLOCK_NUM, + solo_dev->clock_mhz - 1); + + /* PLL locking time of 1ms */ + mdelay(1); + + ret = devm_request_irq(&pdev->dev, pdev->irq, solo_isr, IRQF_SHARED, + SOLO6X10_NAME, solo_dev); + if (ret) + goto fail_probe; + + /* Handle this from the start */ + solo_irq_on(solo_dev, SOLO_IRQ_PCI_ERR); + + ret = solo_i2c_init(solo_dev); + if (ret) + goto fail_probe; + + /* Setup the DMA engine */ + solo_reg_write(solo_dev, SOLO_DMA_CTRL, + SOLO_DMA_CTRL_REFRESH_CYCLE(1) | + SOLO_DMA_CTRL_SDRAM_SIZE(2) | + SOLO_DMA_CTRL_SDRAM_CLK_INVERT | + SOLO_DMA_CTRL_READ_CLK_SELECT | + SOLO_DMA_CTRL_LATENCY(1)); + + /* Undocumented crap */ + solo_reg_write(solo_dev, SOLO_DMA_CTRL1, + solo_dev->type == SOLO_DEV_6010 ? 0x100 : 0x300); + + if (solo_dev->type != SOLO_DEV_6010) { + solo_dev->usec_lsb = 0x3f; + solo_set_time(solo_dev); + } + + /* Disable watchdog */ + solo_reg_write(solo_dev, SOLO_WATCHDOG, 0); + + /* Initialize sub components */ + + ret = solo_p2m_init(solo_dev); + if (ret) + goto fail_probe; + + ret = solo_disp_init(solo_dev); + if (ret) + goto fail_probe; + + ret = solo_gpio_init(solo_dev); + if (ret) + goto fail_probe; + + ret = solo_tw28_init(solo_dev); + if (ret) + goto fail_probe; + + ret = solo_v4l2_init(solo_dev, video_nr); + if (ret) + goto fail_probe; + + ret = solo_enc_init(solo_dev); + if (ret) + goto fail_probe; + + ret = solo_enc_v4l2_init(solo_dev, video_nr); + if (ret) + goto fail_probe; + + ret = solo_g723_init(solo_dev); + if (ret) + goto fail_probe; + + ret = solo_sysfs_init(solo_dev); + if (ret) + goto fail_probe; + + /* Now that init is over, set this lower */ + solo_dev->p2m_jiffies = msecs_to_jiffies(20); + + return 0; + +fail_probe: + free_solo_dev(solo_dev); + return ret; +} + +static void solo_pci_remove(struct pci_dev *pdev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev); + struct solo_dev *solo_dev = container_of(v4l2_dev, struct solo_dev, v4l2_dev); + + free_solo_dev(solo_dev); +} + +static const struct pci_device_id solo_id_table[] = { + /* 6010 based cards */ + { PCI_DEVICE(PCI_VENDOR_ID_SOFTLOGIC, PCI_DEVICE_ID_SOLO6010), + .driver_data = SOLO_DEV_6010 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_4), + .driver_data = SOLO_DEV_6010 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_9), + .driver_data = SOLO_DEV_6010 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_16), + .driver_data = SOLO_DEV_6010 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_4), + .driver_data = SOLO_DEV_6010 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_9), + .driver_data = SOLO_DEV_6010 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_16), + .driver_data = SOLO_DEV_6010 }, + /* 6110 based cards */ + { PCI_DEVICE(PCI_VENDOR_ID_SOFTLOGIC, PCI_DEVICE_ID_SOLO6110), + .driver_data = SOLO_DEV_6110 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_4), + .driver_data = SOLO_DEV_6110 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_8), + .driver_data = SOLO_DEV_6110 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_16), + .driver_data = SOLO_DEV_6110 }, + { } +}; + +MODULE_DEVICE_TABLE(pci, solo_id_table); + +static struct pci_driver solo_pci_driver = { + .name = SOLO6X10_NAME, + .id_table = solo_id_table, + .probe = solo_pci_probe, + .remove = solo_pci_remove, +}; + +module_pci_driver(solo_pci_driver); diff --git a/drivers/media/pci/solo6x10/solo6x10-disp.c b/drivers/media/pci/solo6x10/solo6x10-disp.c new file mode 100644 index 000000000..ad98ca7fb --- /dev/null +++ b/drivers/media/pci/solo6x10/solo6x10-disp.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2010-2013 Bluecherry, LLC + * + * Original author: + * Ben Collins + * + * Additional work by: + * John Brooks + */ + +#include +#include +#include +#include + +#include "solo6x10.h" + +#define SOLO_VCLK_DELAY 3 +#define SOLO_PROGRESSIVE_VSIZE 1024 + +#define SOLO_MOT_THRESH_W 64 +#define SOLO_MOT_THRESH_H 64 +#define SOLO_MOT_THRESH_SIZE 8192 +#define SOLO_MOT_THRESH_REAL (SOLO_MOT_THRESH_W * SOLO_MOT_THRESH_H) +#define SOLO_MOT_FLAG_SIZE 1024 +#define SOLO_MOT_FLAG_AREA (SOLO_MOT_FLAG_SIZE * 16) + +static void solo_vin_config(struct solo_dev *solo_dev) +{ + solo_dev->vin_hstart = 8; + solo_dev->vin_vstart = 2; + + solo_reg_write(solo_dev, SOLO_SYS_VCLK, + SOLO_VCLK_SELECT(2) | + SOLO_VCLK_VIN1415_DELAY(SOLO_VCLK_DELAY) | + SOLO_VCLK_VIN1213_DELAY(SOLO_VCLK_DELAY) | + SOLO_VCLK_VIN1011_DELAY(SOLO_VCLK_DELAY) | + SOLO_VCLK_VIN0809_DELAY(SOLO_VCLK_DELAY) | + SOLO_VCLK_VIN0607_DELAY(SOLO_VCLK_DELAY) | + SOLO_VCLK_VIN0405_DELAY(SOLO_VCLK_DELAY) | + SOLO_VCLK_VIN0203_DELAY(SOLO_VCLK_DELAY) | + SOLO_VCLK_VIN0001_DELAY(SOLO_VCLK_DELAY)); + + solo_reg_write(solo_dev, SOLO_VI_ACT_I_P, + SOLO_VI_H_START(solo_dev->vin_hstart) | + SOLO_VI_V_START(solo_dev->vin_vstart) | + SOLO_VI_V_STOP(solo_dev->vin_vstart + + solo_dev->video_vsize)); + + solo_reg_write(solo_dev, SOLO_VI_ACT_I_S, + SOLO_VI_H_START(solo_dev->vout_hstart) | + SOLO_VI_V_START(solo_dev->vout_vstart) | + SOLO_VI_V_STOP(solo_dev->vout_vstart + + solo_dev->video_vsize)); + + solo_reg_write(solo_dev, SOLO_VI_ACT_P, + SOLO_VI_H_START(0) | + SOLO_VI_V_START(1) | + SOLO_VI_V_STOP(SOLO_PROGRESSIVE_VSIZE)); + + solo_reg_write(solo_dev, SOLO_VI_CH_FORMAT, + SOLO_VI_FD_SEL_MASK(0) | SOLO_VI_PROG_MASK(0)); + + /* On 6110, initialize mozaic darkness strength */ + if (solo_dev->type == SOLO_DEV_6010) + solo_reg_write(solo_dev, SOLO_VI_FMT_CFG, 0); + else + solo_reg_write(solo_dev, SOLO_VI_FMT_CFG, 16 << 22); + + solo_reg_write(solo_dev, SOLO_VI_PAGE_SW, 2); + + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) { + solo_reg_write(solo_dev, SOLO_VI_PB_CONFIG, + SOLO_VI_PB_USER_MODE); + solo_reg_write(solo_dev, SOLO_VI_PB_RANGE_HV, + SOLO_VI_PB_HSIZE(858) | SOLO_VI_PB_VSIZE(246)); + solo_reg_write(solo_dev, SOLO_VI_PB_ACT_V, + SOLO_VI_PB_VSTART(4) | + SOLO_VI_PB_VSTOP(4 + 240)); + } else { + solo_reg_write(solo_dev, SOLO_VI_PB_CONFIG, + SOLO_VI_PB_USER_MODE | SOLO_VI_PB_PAL); + solo_reg_write(solo_dev, SOLO_VI_PB_RANGE_HV, + SOLO_VI_PB_HSIZE(864) | SOLO_VI_PB_VSIZE(294)); + solo_reg_write(solo_dev, SOLO_VI_PB_ACT_V, + SOLO_VI_PB_VSTART(4) | + SOLO_VI_PB_VSTOP(4 + 288)); + } + solo_reg_write(solo_dev, SOLO_VI_PB_ACT_H, SOLO_VI_PB_HSTART(16) | + SOLO_VI_PB_HSTOP(16 + 720)); +} + +static void solo_vout_config_cursor(struct solo_dev *dev) +{ + int i; + + /* Load (blank) cursor bitmap mask (2bpp) */ + for (i = 0; i < 20; i++) + solo_reg_write(dev, SOLO_VO_CURSOR_MASK(i), 0); + + solo_reg_write(dev, SOLO_VO_CURSOR_POS, 0); + + solo_reg_write(dev, SOLO_VO_CURSOR_CLR, + (0x80 << 24) | (0x80 << 16) | (0x10 << 8) | 0x80); + solo_reg_write(dev, SOLO_VO_CURSOR_CLR2, (0xe0 << 8) | 0x80); +} + +static void solo_vout_config(struct solo_dev *solo_dev) +{ + solo_dev->vout_hstart = 6; + solo_dev->vout_vstart = 8; + + solo_reg_write(solo_dev, SOLO_VO_FMT_ENC, + solo_dev->video_type | + SOLO_VO_USER_COLOR_SET_NAV | + SOLO_VO_USER_COLOR_SET_NAH | + SOLO_VO_NA_COLOR_Y(0) | + SOLO_VO_NA_COLOR_CB(0) | + SOLO_VO_NA_COLOR_CR(0)); + + solo_reg_write(solo_dev, SOLO_VO_ACT_H, + SOLO_VO_H_START(solo_dev->vout_hstart) | + SOLO_VO_H_STOP(solo_dev->vout_hstart + + solo_dev->video_hsize)); + + solo_reg_write(solo_dev, SOLO_VO_ACT_V, + SOLO_VO_V_START(solo_dev->vout_vstart) | + SOLO_VO_V_STOP(solo_dev->vout_vstart + + solo_dev->video_vsize)); + + solo_reg_write(solo_dev, SOLO_VO_RANGE_HV, + SOLO_VO_H_LEN(solo_dev->video_hsize) | + SOLO_VO_V_LEN(solo_dev->video_vsize)); + + /* Border & background colors */ + solo_reg_write(solo_dev, SOLO_VO_BORDER_LINE_COLOR, + (0xa0 << 24) | (0x88 << 16) | (0xa0 << 8) | 0x88); + solo_reg_write(solo_dev, SOLO_VO_BORDER_FILL_COLOR, + (0x10 << 24) | (0x8f << 16) | (0x10 << 8) | 0x8f); + solo_reg_write(solo_dev, SOLO_VO_BKG_COLOR, + (16 << 24) | (128 << 16) | (16 << 8) | 128); + + solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, SOLO_VO_DISP_ERASE_ON); + + solo_reg_write(solo_dev, SOLO_VI_WIN_SW, 0); + + solo_reg_write(solo_dev, SOLO_VO_ZOOM_CTRL, 0); + solo_reg_write(solo_dev, SOLO_VO_FREEZE_CTRL, 0); + + solo_reg_write(solo_dev, SOLO_VO_DISP_CTRL, SOLO_VO_DISP_ON | + SOLO_VO_DISP_ERASE_COUNT(8) | + SOLO_VO_DISP_BASE(SOLO_DISP_EXT_ADDR)); + + + solo_vout_config_cursor(solo_dev); + + /* Enable channels we support */ + solo_reg_write(solo_dev, SOLO_VI_CH_ENA, + (1 << solo_dev->nr_chans) - 1); +} + +static int solo_dma_vin_region(struct solo_dev *solo_dev, u32 off, + u16 val, int reg_size) +{ + __le16 *buf; + const int n = 64, size = n * sizeof(*buf); + int i, ret = 0; + + buf = kmalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < n; i++) + buf[i] = cpu_to_le16(val); + + for (i = 0; i < reg_size; i += size) { + ret = solo_p2m_dma(solo_dev, 1, buf, + SOLO_MOTION_EXT_ADDR(solo_dev) + off + i, + size, 0, 0); + + if (ret) + break; + } + + kfree(buf); + return ret; +} + +int solo_set_motion_threshold(struct solo_dev *solo_dev, u8 ch, u16 val) +{ + if (ch > solo_dev->nr_chans) + return -EINVAL; + + return solo_dma_vin_region(solo_dev, SOLO_MOT_FLAG_AREA + + (ch * SOLO_MOT_THRESH_SIZE * 2), + val, SOLO_MOT_THRESH_SIZE); +} + +int solo_set_motion_block(struct solo_dev *solo_dev, u8 ch, + const u16 *thresholds) +{ + const unsigned size = sizeof(u16) * 64; + u32 off = SOLO_MOT_FLAG_AREA + ch * SOLO_MOT_THRESH_SIZE * 2; + __le16 *buf; + int x, y; + int ret = 0; + + buf = kzalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + for (y = 0; y < SOLO_MOTION_SZ; y++) { + for (x = 0; x < SOLO_MOTION_SZ; x++) + buf[x] = cpu_to_le16(thresholds[y * SOLO_MOTION_SZ + x]); + ret |= solo_p2m_dma(solo_dev, 1, buf, + SOLO_MOTION_EXT_ADDR(solo_dev) + off + y * size, + size, 0, 0); + } + kfree(buf); + return ret; +} + +/* First 8k is motion flag (512 bytes * 16). Following that is an 8k+8k + * threshold and working table for each channel. At least that's what the + * spec says. However, this code (taken from rdk) has some mystery 8k + * block right after the flag area, before the first thresh table. */ +static void solo_motion_config(struct solo_dev *solo_dev) +{ + int i; + + for (i = 0; i < solo_dev->nr_chans; i++) { + /* Clear motion flag area */ + solo_dma_vin_region(solo_dev, i * SOLO_MOT_FLAG_SIZE, 0x0000, + SOLO_MOT_FLAG_SIZE); + + /* Clear working cache table */ + solo_dma_vin_region(solo_dev, SOLO_MOT_FLAG_AREA + + (i * SOLO_MOT_THRESH_SIZE * 2) + + SOLO_MOT_THRESH_SIZE, 0x0000, + SOLO_MOT_THRESH_SIZE); + + /* Set default threshold table */ + solo_set_motion_threshold(solo_dev, i, SOLO_DEF_MOT_THRESH); + } + + /* Default motion settings */ + solo_reg_write(solo_dev, SOLO_VI_MOT_ADR, SOLO_VI_MOTION_EN(0) | + (SOLO_MOTION_EXT_ADDR(solo_dev) >> 16)); + solo_reg_write(solo_dev, SOLO_VI_MOT_CTRL, + SOLO_VI_MOTION_FRAME_COUNT(3) | + SOLO_VI_MOTION_SAMPLE_LENGTH(solo_dev->video_hsize / 16) + /* | SOLO_VI_MOTION_INTR_START_STOP */ + | SOLO_VI_MOTION_SAMPLE_COUNT(10)); + + solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER, 0); + solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR, 0); +} + +int solo_disp_init(struct solo_dev *solo_dev) +{ + int i; + + solo_dev->video_hsize = 704; + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) { + solo_dev->video_vsize = 240; + solo_dev->fps = 30; + } else { + solo_dev->video_vsize = 288; + solo_dev->fps = 25; + } + + solo_vin_config(solo_dev); + solo_motion_config(solo_dev); + solo_vout_config(solo_dev); + + for (i = 0; i < solo_dev->nr_chans; i++) + solo_reg_write(solo_dev, SOLO_VI_WIN_ON(i), 1); + + return 0; +} + +void solo_disp_exit(struct solo_dev *solo_dev) +{ + int i; + + solo_reg_write(solo_dev, SOLO_VO_DISP_CTRL, 0); + solo_reg_write(solo_dev, SOLO_VO_ZOOM_CTRL, 0); + solo_reg_write(solo_dev, SOLO_VO_FREEZE_CTRL, 0); + + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL0(i), 0); + solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL1(i), 0); + solo_reg_write(solo_dev, SOLO_VI_WIN_ON(i), 0); + } + + /* Set default border */ + for (i = 0; i < 5; i++) + solo_reg_write(solo_dev, SOLO_VO_BORDER_X(i), 0); + + for (i = 0; i < 5; i++) + solo_reg_write(solo_dev, SOLO_VO_BORDER_Y(i), 0); + + solo_reg_write(solo_dev, SOLO_VO_BORDER_LINE_MASK, 0); + solo_reg_write(solo_dev, SOLO_VO_BORDER_FILL_MASK, 0); + + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_CTRL(0), 0); + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_START(0), 0); + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_STOP(0), 0); + + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_CTRL(1), 0); + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_START(1), 0); + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_STOP(1), 0); +} diff --git a/drivers/media/pci/solo6x10/solo6x10-eeprom.c b/drivers/media/pci/solo6x10/solo6x10-eeprom.c new file mode 100644 index 000000000..0d864b8ca --- /dev/null +++ b/drivers/media/pci/solo6x10/solo6x10-eeprom.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2010-2013 Bluecherry, LLC + * + * Original author: + * Ben Collins + * + * Additional work by: + * John Brooks + */ + +#include +#include + +#include "solo6x10.h" + +/* Control */ +#define EE_SHIFT_CLK 0x04 +#define EE_CS 0x08 +#define EE_DATA_WRITE 0x02 +#define EE_DATA_READ 0x01 +#define EE_ENB (0x80 | EE_CS) + +#define eeprom_delay() udelay(100) +#if 0 +#define eeprom_delay() solo_reg_read(solo_dev, SOLO_EEPROM_CTRL) +#define eeprom_delay() ({ \ + int i, ret; \ + udelay(100); \ + for (i = ret = 0; i < 1000 && !ret; i++) \ + ret = solo_eeprom_reg_read(solo_dev); \ +}) +#endif +#define ADDR_LEN 6 + +/* Commands */ +#define EE_EWEN_CMD 4 +#define EE_EWDS_CMD 4 +#define EE_WRITE_CMD 5 +#define EE_READ_CMD 6 +#define EE_ERASE_CMD 7 + +static unsigned int solo_eeprom_reg_read(struct solo_dev *solo_dev) +{ + return solo_reg_read(solo_dev, SOLO_EEPROM_CTRL) & EE_DATA_READ; +} + +static void solo_eeprom_reg_write(struct solo_dev *solo_dev, u32 data) +{ + solo_reg_write(solo_dev, SOLO_EEPROM_CTRL, data); + eeprom_delay(); +} + +static void solo_eeprom_cmd(struct solo_dev *solo_dev, int cmd) +{ + int i; + + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ACCESS_EN); + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE); + + for (i = 4 + ADDR_LEN; i >= 0; i--) { + int dataval = (cmd & (1 << i)) ? EE_DATA_WRITE : 0; + + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE | dataval); + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE | + EE_SHIFT_CLK | dataval); + } + + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE); +} + +unsigned int solo_eeprom_ewen(struct solo_dev *solo_dev, int w_en) +{ + int ewen_cmd = (w_en ? 0x3f : 0) | (EE_EWEN_CMD << ADDR_LEN); + unsigned int retval = 0; + int i; + + solo_eeprom_cmd(solo_dev, ewen_cmd); + + for (i = 0; i < 16; i++) { + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE | + EE_SHIFT_CLK); + retval = (retval << 1) | solo_eeprom_reg_read(solo_dev); + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE); + retval = (retval << 1) | solo_eeprom_reg_read(solo_dev); + } + + solo_eeprom_reg_write(solo_dev, ~EE_CS); + retval = (retval << 1) | solo_eeprom_reg_read(solo_dev); + + return retval; +} + +__be16 solo_eeprom_read(struct solo_dev *solo_dev, int loc) +{ + int read_cmd = loc | (EE_READ_CMD << ADDR_LEN); + u16 retval = 0; + int i; + + solo_eeprom_cmd(solo_dev, read_cmd); + + for (i = 0; i < 16; i++) { + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE | + EE_SHIFT_CLK); + retval = (retval << 1) | solo_eeprom_reg_read(solo_dev); + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE); + } + + solo_eeprom_reg_write(solo_dev, ~EE_CS); + + return (__force __be16)retval; +} + +int solo_eeprom_write(struct solo_dev *solo_dev, int loc, + __be16 data) +{ + int write_cmd = loc | (EE_WRITE_CMD << ADDR_LEN); + unsigned int retval; + int i; + + solo_eeprom_cmd(solo_dev, write_cmd); + + for (i = 15; i >= 0; i--) { + unsigned int dataval = ((__force unsigned)data >> i) & 1; + + solo_eeprom_reg_write(solo_dev, EE_ENB); + solo_eeprom_reg_write(solo_dev, + EE_ENB | (dataval << 1) | EE_SHIFT_CLK); + } + + solo_eeprom_reg_write(solo_dev, EE_ENB); + solo_eeprom_reg_write(solo_dev, ~EE_CS); + solo_eeprom_reg_write(solo_dev, EE_ENB); + + for (i = retval = 0; i < 10000 && !retval; i++) + retval = solo_eeprom_reg_read(solo_dev); + + solo_eeprom_reg_write(solo_dev, ~EE_CS); + + return !retval; +} diff --git a/drivers/media/pci/solo6x10/solo6x10-enc.c b/drivers/media/pci/solo6x10/solo6x10-enc.c new file mode 100644 index 000000000..14a1d51cf --- /dev/null +++ b/drivers/media/pci/solo6x10/solo6x10-enc.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2010-2013 Bluecherry, LLC + * + * Original author: + * Ben Collins + * + * Additional work by: + * John Brooks + */ + +#include +#include +#include +#include + +#include "solo6x10.h" + +#define VI_PROG_HSIZE (1280 - 16) +#define VI_PROG_VSIZE (1024 - 16) + +#define IRQ_LEVEL 2 + +static void solo_capture_config(struct solo_dev *solo_dev) +{ + unsigned long height; + unsigned long width; + void *buf; + int i; + + solo_reg_write(solo_dev, SOLO_CAP_BASE, + SOLO_CAP_MAX_PAGE((SOLO_CAP_EXT_SIZE(solo_dev) + - SOLO_CAP_PAGE_SIZE) >> 16) + | SOLO_CAP_BASE_ADDR(SOLO_CAP_EXT_ADDR(solo_dev) >> 16)); + + /* XXX: Undocumented bits at b17 and b24 */ + if (solo_dev->type == SOLO_DEV_6110) { + /* NOTE: Ref driver has (62 << 24) here as well, but it causes + * wacked out frame timing on 4-port 6110. */ + solo_reg_write(solo_dev, SOLO_CAP_BTW, + (1 << 17) | SOLO_CAP_PROG_BANDWIDTH(2) | + SOLO_CAP_MAX_BANDWIDTH(36)); + } else { + solo_reg_write(solo_dev, SOLO_CAP_BTW, + (1 << 17) | SOLO_CAP_PROG_BANDWIDTH(2) | + SOLO_CAP_MAX_BANDWIDTH(32)); + } + + /* Set scale 1, 9 dimension */ + width = solo_dev->video_hsize; + height = solo_dev->video_vsize; + solo_reg_write(solo_dev, SOLO_DIM_SCALE1, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 8) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Set scale 2, 10 dimension */ + width = solo_dev->video_hsize / 2; + height = solo_dev->video_vsize; + solo_reg_write(solo_dev, SOLO_DIM_SCALE2, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 8) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Set scale 3, 11 dimension */ + width = solo_dev->video_hsize / 2; + height = solo_dev->video_vsize / 2; + solo_reg_write(solo_dev, SOLO_DIM_SCALE3, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 8) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Set scale 4, 12 dimension */ + width = solo_dev->video_hsize / 3; + height = solo_dev->video_vsize / 3; + solo_reg_write(solo_dev, SOLO_DIM_SCALE4, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 8) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Set scale 5, 13 dimension */ + width = solo_dev->video_hsize / 4; + height = solo_dev->video_vsize / 2; + solo_reg_write(solo_dev, SOLO_DIM_SCALE5, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 8) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Progressive */ + width = VI_PROG_HSIZE; + height = VI_PROG_VSIZE; + solo_reg_write(solo_dev, SOLO_DIM_PROG, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 16) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Clear OSD */ + solo_reg_write(solo_dev, SOLO_VE_OSD_CH, 0); + solo_reg_write(solo_dev, SOLO_VE_OSD_BASE, SOLO_EOSD_EXT_ADDR >> 16); + solo_reg_write(solo_dev, SOLO_VE_OSD_CLR, + 0xF0 << 16 | 0x80 << 8 | 0x80); + + if (solo_dev->type == SOLO_DEV_6010) + solo_reg_write(solo_dev, SOLO_VE_OSD_OPT, + SOLO_VE_OSD_H_SHADOW | SOLO_VE_OSD_V_SHADOW); + else + solo_reg_write(solo_dev, SOLO_VE_OSD_OPT, SOLO_VE_OSD_V_DOUBLE + | SOLO_VE_OSD_H_SHADOW | SOLO_VE_OSD_V_SHADOW); + + /* Clear OSG buffer */ + buf = kzalloc(SOLO_EOSD_EXT_SIZE(solo_dev), GFP_KERNEL); + if (!buf) + return; + + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_p2m_dma(solo_dev, 1, buf, + SOLO_EOSD_EXT_ADDR + + (SOLO_EOSD_EXT_SIZE(solo_dev) * i), + SOLO_EOSD_EXT_SIZE(solo_dev), 0, 0); + } + kfree(buf); +} + +#define SOLO_OSD_WRITE_SIZE (16 * OSD_TEXT_MAX) + +/* Should be called with enable_lock held */ +int solo_osd_print(struct solo_enc_dev *solo_enc) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + u8 *str = solo_enc->osd_text; + u8 *buf = solo_enc->osd_buf; + u32 reg; + const struct font_desc *vga = find_font("VGA8x16"); + const u8 *vga_data; + int i, j; + + if (WARN_ON_ONCE(!vga)) + return -ENODEV; + + reg = solo_reg_read(solo_dev, SOLO_VE_OSD_CH); + if (!*str) { + /* Disable OSD on this channel */ + reg &= ~(1 << solo_enc->ch); + goto out; + } + + memset(buf, 0, SOLO_OSD_WRITE_SIZE); + vga_data = (const u8 *)vga->data; + + for (i = 0; *str; i++, str++) { + for (j = 0; j < 16; j++) { + buf[(j << 1) | (i & 1) | ((i & ~1) << 4)] = + bitrev8(vga_data[(*str << 4) | j]); + } + } + + solo_p2m_dma(solo_dev, 1, buf, + SOLO_EOSD_EXT_ADDR_CHAN(solo_dev, solo_enc->ch), + SOLO_OSD_WRITE_SIZE, 0, 0); + + /* Enable OSD on this channel */ + reg |= (1 << solo_enc->ch); + +out: + solo_reg_write(solo_dev, SOLO_VE_OSD_CH, reg); + return 0; +} + +/* + * Set channel Quality Profile (0-3). + */ +void solo_s_jpeg_qp(struct solo_dev *solo_dev, unsigned int ch, + unsigned int qp) +{ + unsigned long flags; + unsigned int idx, reg; + + if ((ch > 31) || (qp > 3)) + return; + + if (solo_dev->type == SOLO_DEV_6010) + return; + + if (ch < 16) { + idx = 0; + reg = SOLO_VE_JPEG_QP_CH_L; + } else { + ch -= 16; + idx = 1; + reg = SOLO_VE_JPEG_QP_CH_H; + } + ch *= 2; + + spin_lock_irqsave(&solo_dev->jpeg_qp_lock, flags); + + solo_dev->jpeg_qp[idx] &= ~(3 << ch); + solo_dev->jpeg_qp[idx] |= (qp & 3) << ch; + + solo_reg_write(solo_dev, reg, solo_dev->jpeg_qp[idx]); + + spin_unlock_irqrestore(&solo_dev->jpeg_qp_lock, flags); +} + +int solo_g_jpeg_qp(struct solo_dev *solo_dev, unsigned int ch) +{ + int idx; + + if (solo_dev->type == SOLO_DEV_6010) + return 2; + + if (WARN_ON_ONCE(ch > 31)) + return 2; + + if (ch < 16) { + idx = 0; + } else { + ch -= 16; + idx = 1; + } + ch *= 2; + + return (solo_dev->jpeg_qp[idx] >> ch) & 3; +} + +#define SOLO_QP_INIT 0xaaaaaaaa + +static void solo_jpeg_config(struct solo_dev *solo_dev) +{ + if (solo_dev->type == SOLO_DEV_6010) { + solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_TBL, + (2 << 24) | (2 << 16) | (2 << 8) | 2); + } else { + solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_TBL, + (4 << 24) | (3 << 16) | (2 << 8) | 1); + } + + spin_lock_init(&solo_dev->jpeg_qp_lock); + + /* Initialize Quality Profile for all channels */ + solo_dev->jpeg_qp[0] = solo_dev->jpeg_qp[1] = SOLO_QP_INIT; + solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_CH_L, SOLO_QP_INIT); + solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_CH_H, SOLO_QP_INIT); + + solo_reg_write(solo_dev, SOLO_VE_JPEG_CFG, + (SOLO_JPEG_EXT_SIZE(solo_dev) & 0xffff0000) | + ((SOLO_JPEG_EXT_ADDR(solo_dev) >> 16) & 0x0000ffff)); + solo_reg_write(solo_dev, SOLO_VE_JPEG_CTRL, 0xffffffff); + if (solo_dev->type == SOLO_DEV_6110) { + solo_reg_write(solo_dev, SOLO_VE_JPEG_CFG1, + (0 << 16) | (30 << 8) | 60); + } +} + +static void solo_mp4e_config(struct solo_dev *solo_dev) +{ + int i; + u32 cfg; + + solo_reg_write(solo_dev, SOLO_VE_CFG0, + SOLO_VE_INTR_CTRL(IRQ_LEVEL) | + SOLO_VE_BLOCK_SIZE(SOLO_MP4E_EXT_SIZE(solo_dev) >> 16) | + SOLO_VE_BLOCK_BASE(SOLO_MP4E_EXT_ADDR(solo_dev) >> 16)); + + + cfg = SOLO_VE_BYTE_ALIGN(2) | SOLO_VE_INSERT_INDEX + | SOLO_VE_MOTION_MODE(0); + if (solo_dev->type != SOLO_DEV_6010) { + cfg |= SOLO_VE_MPEG_SIZE_H( + (SOLO_MP4E_EXT_SIZE(solo_dev) >> 24) & 0x0f); + cfg |= SOLO_VE_JPEG_SIZE_H( + (SOLO_JPEG_EXT_SIZE(solo_dev) >> 24) & 0x0f); + } + solo_reg_write(solo_dev, SOLO_VE_CFG1, cfg); + + solo_reg_write(solo_dev, SOLO_VE_WMRK_POLY, 0); + solo_reg_write(solo_dev, SOLO_VE_VMRK_INIT_KEY, 0); + solo_reg_write(solo_dev, SOLO_VE_WMRK_STRL, 0); + if (solo_dev->type == SOLO_DEV_6110) + solo_reg_write(solo_dev, SOLO_VE_WMRK_ENABLE, 0); + solo_reg_write(solo_dev, SOLO_VE_ENCRYP_POLY, 0); + solo_reg_write(solo_dev, SOLO_VE_ENCRYP_INIT, 0); + + solo_reg_write(solo_dev, SOLO_VE_ATTR, + SOLO_VE_LITTLE_ENDIAN | + SOLO_COMP_ATTR_FCODE(1) | + SOLO_COMP_TIME_INC(0) | + SOLO_COMP_TIME_WIDTH(15) | + SOLO_DCT_INTERVAL(solo_dev->type == SOLO_DEV_6010 ? 9 : 10)); + + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_reg_write(solo_dev, SOLO_VE_CH_REF_BASE(i), + (SOLO_EREF_EXT_ADDR(solo_dev) + + (i * SOLO_EREF_EXT_SIZE)) >> 16); + solo_reg_write(solo_dev, SOLO_VE_CH_REF_BASE_E(i), + (SOLO_EREF_EXT_ADDR(solo_dev) + + ((i + 16) * SOLO_EREF_EXT_SIZE)) >> 16); + } + + if (solo_dev->type == SOLO_DEV_6110) { + solo_reg_write(solo_dev, SOLO_VE_COMPT_MOT, 0x00040008); + } else { + for (i = 0; i < solo_dev->nr_chans; i++) + solo_reg_write(solo_dev, SOLO_VE_CH_MOT(i), 0x100); + } +} + +int solo_enc_init(struct solo_dev *solo_dev) +{ + int i; + + solo_capture_config(solo_dev); + solo_mp4e_config(solo_dev); + solo_jpeg_config(solo_dev); + + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(i), 0); + solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(i), 0); + } + + return 0; +} + +void solo_enc_exit(struct solo_dev *solo_dev) +{ + int i; + + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(i), 0); + solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(i), 0); + } +} diff --git a/drivers/media/pci/solo6x10/solo6x10-g723.c b/drivers/media/pci/solo6x10/solo6x10-g723.c new file mode 100644 index 000000000..e41b8d90a --- /dev/null +++ b/drivers/media/pci/solo6x10/solo6x10-g723.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2010-2013 Bluecherry, LLC + * + * Original author: + * Ben Collins + * + * Additional work by: + * John Brooks + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "solo6x10.h" +#include "solo6x10-tw28.h" + +#define G723_FDMA_PAGES 32 +#define G723_PERIOD_BYTES 48 +#define G723_PERIOD_BLOCK 1024 +#define G723_FRAMES_PER_PAGE 48 + +/* Sets up channels 16-19 for decoding and 0-15 for encoding */ +#define OUTMODE_MASK 0x300 + +#define SAMPLERATE 8000 +#define BITRATE 25 + +/* The solo writes to 1k byte pages, 32 pages, in the dma. Each 1k page + * is broken down to 20 * 48 byte regions (one for each channel possible) + * with the rest of the page being dummy data. */ +#define PERIODS G723_FDMA_PAGES +#define G723_INTR_ORDER 4 /* 0 - 4 */ + +struct solo_snd_pcm { + int on; + spinlock_t lock; + struct solo_dev *solo_dev; + u8 *g723_buf; + dma_addr_t g723_dma; +}; + +static void solo_g723_config(struct solo_dev *solo_dev) +{ + int clk_div; + + clk_div = (solo_dev->clock_mhz * 1000000) + / (SAMPLERATE * (BITRATE * 2) * 2); + + solo_reg_write(solo_dev, SOLO_AUDIO_SAMPLE, + SOLO_AUDIO_BITRATE(BITRATE) + | SOLO_AUDIO_CLK_DIV(clk_div)); + + solo_reg_write(solo_dev, SOLO_AUDIO_FDMA_INTR, + SOLO_AUDIO_FDMA_INTERVAL(1) + | SOLO_AUDIO_INTR_ORDER(G723_INTR_ORDER) + | SOLO_AUDIO_FDMA_BASE(SOLO_G723_EXT_ADDR(solo_dev) >> 16)); + + solo_reg_write(solo_dev, SOLO_AUDIO_CONTROL, + SOLO_AUDIO_ENABLE + | SOLO_AUDIO_I2S_MODE + | SOLO_AUDIO_I2S_MULTI(3) + | SOLO_AUDIO_MODE(OUTMODE_MASK)); +} + +void solo_g723_isr(struct solo_dev *solo_dev) +{ + struct snd_pcm_str *pstr = + &solo_dev->snd_pcm->streams[SNDRV_PCM_STREAM_CAPTURE]; + struct snd_pcm_substream *ss; + struct solo_snd_pcm *solo_pcm; + + for (ss = pstr->substream; ss != NULL; ss = ss->next) { + if (snd_pcm_substream_chip(ss) == NULL) + continue; + + /* This means open() hasn't been called on this one */ + if (snd_pcm_substream_chip(ss) == solo_dev) + continue; + + /* Haven't triggered a start yet */ + solo_pcm = snd_pcm_substream_chip(ss); + if (!solo_pcm->on) + continue; + + snd_pcm_period_elapsed(ss); + } +} + +static const struct snd_pcm_hardware snd_solo_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8, + .rates = SNDRV_PCM_RATE_8000, + .rate_min = SAMPLERATE, + .rate_max = SAMPLERATE, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = G723_PERIOD_BYTES * PERIODS, + .period_bytes_min = G723_PERIOD_BYTES, + .period_bytes_max = G723_PERIOD_BYTES, + .periods_min = PERIODS, + .periods_max = PERIODS, +}; + +static int snd_solo_pcm_open(struct snd_pcm_substream *ss) +{ + struct solo_dev *solo_dev = snd_pcm_substream_chip(ss); + struct solo_snd_pcm *solo_pcm; + + solo_pcm = kzalloc_obj(*solo_pcm); + if (solo_pcm == NULL) + goto oom; + + solo_pcm->g723_buf = dma_alloc_coherent(&solo_dev->pdev->dev, + G723_PERIOD_BYTES, + &solo_pcm->g723_dma, + GFP_KERNEL); + if (solo_pcm->g723_buf == NULL) + goto oom; + + spin_lock_init(&solo_pcm->lock); + solo_pcm->solo_dev = solo_dev; + ss->runtime->hw = snd_solo_pcm_hw; + + snd_pcm_substream_chip(ss) = solo_pcm; + + return 0; + +oom: + kfree(solo_pcm); + return -ENOMEM; +} + +static int snd_solo_pcm_close(struct snd_pcm_substream *ss) +{ + struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); + + snd_pcm_substream_chip(ss) = solo_pcm->solo_dev; + dma_free_coherent(&solo_pcm->solo_dev->pdev->dev, G723_PERIOD_BYTES, + solo_pcm->g723_buf, solo_pcm->g723_dma); + kfree(solo_pcm); + + return 0; +} + +static int snd_solo_pcm_trigger(struct snd_pcm_substream *ss, int cmd) +{ + struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); + struct solo_dev *solo_dev = solo_pcm->solo_dev; + int ret = 0; + + spin_lock(&solo_pcm->lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (solo_pcm->on == 0) { + /* If this is the first user, switch on interrupts */ + if (atomic_inc_return(&solo_dev->snd_users) == 1) + solo_irq_on(solo_dev, SOLO_IRQ_G723); + solo_pcm->on = 1; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + if (solo_pcm->on) { + /* If this was our last user, switch them off */ + if (atomic_dec_return(&solo_dev->snd_users) == 0) + solo_irq_off(solo_dev, SOLO_IRQ_G723); + solo_pcm->on = 0; + } + break; + default: + ret = -EINVAL; + } + + spin_unlock(&solo_pcm->lock); + + return ret; +} + +static int snd_solo_pcm_prepare(struct snd_pcm_substream *ss) +{ + return 0; +} + +static snd_pcm_uframes_t snd_solo_pcm_pointer(struct snd_pcm_substream *ss) +{ + struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); + struct solo_dev *solo_dev = solo_pcm->solo_dev; + snd_pcm_uframes_t idx = solo_reg_read(solo_dev, SOLO_AUDIO_STA) & 0x1f; + + return idx * G723_FRAMES_PER_PAGE; +} + +static int snd_solo_pcm_copy(struct snd_pcm_substream *ss, int channel, + unsigned long pos, struct iov_iter *dst, + unsigned long count) +{ + struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); + struct solo_dev *solo_dev = solo_pcm->solo_dev; + int err, i; + + for (i = 0; i < (count / G723_FRAMES_PER_PAGE); i++) { + int page = (pos / G723_FRAMES_PER_PAGE) + i; + + err = solo_p2m_dma_t(solo_dev, 0, solo_pcm->g723_dma, + SOLO_G723_EXT_ADDR(solo_dev) + + (page * G723_PERIOD_BLOCK) + + (ss->number * G723_PERIOD_BYTES), + G723_PERIOD_BYTES, 0, 0); + if (err) + return err; + + if (copy_to_iter(solo_pcm->g723_buf, G723_PERIOD_BYTES, dst) != + G723_PERIOD_BYTES) + return -EFAULT; + } + + return 0; +} + +static const struct snd_pcm_ops snd_solo_pcm_ops = { + .open = snd_solo_pcm_open, + .close = snd_solo_pcm_close, + .prepare = snd_solo_pcm_prepare, + .trigger = snd_solo_pcm_trigger, + .pointer = snd_solo_pcm_pointer, + .copy = snd_solo_pcm_copy, +}; + +static int snd_solo_capture_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 1; + info->value.integer.min = 0; + info->value.integer.max = 15; + info->value.integer.step = 1; + + return 0; +} + +static int snd_solo_capture_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct solo_dev *solo_dev = snd_kcontrol_chip(kcontrol); + u8 ch = value->id.numid - 1; + + value->value.integer.value[0] = tw28_get_audio_gain(solo_dev, ch); + + return 0; +} + +static int snd_solo_capture_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct solo_dev *solo_dev = snd_kcontrol_chip(kcontrol); + u8 ch = value->id.numid - 1; + u8 old_val; + + old_val = tw28_get_audio_gain(solo_dev, ch); + if (old_val == value->value.integer.value[0]) + return 0; + + tw28_set_audio_gain(solo_dev, ch, value->value.integer.value[0]); + + return 1; +} + +static const struct snd_kcontrol_new snd_solo_capture_volume = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Volume", + .info = snd_solo_capture_volume_info, + .get = snd_solo_capture_volume_get, + .put = snd_solo_capture_volume_put, +}; + +static int solo_snd_pcm_init(struct solo_dev *solo_dev) +{ + struct snd_card *card = solo_dev->snd_card; + struct snd_pcm *pcm; + struct snd_pcm_substream *ss; + int ret; + int i; + + ret = snd_pcm_new(card, card->driver, 0, 0, solo_dev->nr_chans, + &pcm); + if (ret < 0) + return ret; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_solo_pcm_ops); + + snd_pcm_chip(pcm) = solo_dev; + pcm->info_flags = 0; + strscpy(pcm->name, card->shortname, sizeof(pcm->name)); + + for (i = 0, ss = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + ss; ss = ss->next, i++) + sprintf(ss->name, "Camera #%d Audio", i); + + snd_pcm_set_managed_buffer_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, + G723_PERIOD_BYTES * PERIODS, + G723_PERIOD_BYTES * PERIODS); + + solo_dev->snd_pcm = pcm; + + return 0; +} + +int solo_g723_init(struct solo_dev *solo_dev) +{ + static struct snd_device_ops ops = { }; + struct snd_card *card; + struct snd_kcontrol_new kctl; + char name[32]; + int ret; + + atomic_set(&solo_dev->snd_users, 0); + + /* Allows for easier mapping between video and audio */ + sprintf(name, "Softlogic%d", solo_dev->vfd->num); + + ret = snd_card_new(&solo_dev->pdev->dev, + SNDRV_DEFAULT_IDX1, name, THIS_MODULE, 0, + &solo_dev->snd_card); + if (ret < 0) + return ret; + + card = solo_dev->snd_card; + + strscpy(card->driver, SOLO6X10_NAME, sizeof(card->driver)); + strscpy(card->shortname, "SOLO-6x10 Audio", sizeof(card->shortname)); + sprintf(card->longname, "%s on %s IRQ %d", card->shortname, + pci_name(solo_dev->pdev), solo_dev->pdev->irq); + + ret = snd_device_new(card, SNDRV_DEV_LOWLEVEL, solo_dev, &ops); + if (ret < 0) + goto snd_error; + + /* Mixer controls */ + strscpy(card->mixername, "SOLO-6x10", sizeof(card->mixername)); + kctl = snd_solo_capture_volume; + kctl.count = solo_dev->nr_chans; + + ret = snd_ctl_add(card, snd_ctl_new1(&kctl, solo_dev)); + if (ret < 0) + goto snd_error; + + ret = solo_snd_pcm_init(solo_dev); + if (ret < 0) + goto snd_error; + + ret = snd_card_register(card); + if (ret < 0) + goto snd_error; + + solo_g723_config(solo_dev); + + dev_info(&solo_dev->pdev->dev, "Alsa sound card as %s\n", name); + + return 0; + +snd_error: + snd_card_free(card); + return ret; +} + +void solo_g723_exit(struct solo_dev *solo_dev) +{ + if (!solo_dev->snd_card) + return; + + solo_reg_write(solo_dev, SOLO_AUDIO_CONTROL, 0); + solo_irq_off(solo_dev, SOLO_IRQ_G723); + + snd_card_free(solo_dev->snd_card); + solo_dev->snd_card = NULL; +} diff --git a/drivers/media/pci/solo6x10/solo6x10-gpio.c b/drivers/media/pci/solo6x10/solo6x10-gpio.c new file mode 100644 index 000000000..71848741c --- /dev/null +++ b/drivers/media/pci/solo6x10/solo6x10-gpio.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2010-2013 Bluecherry, LLC + * + * Original author: + * Ben Collins + * + * Additional work by: + * John Brooks + */ + +#include +#include +#include +#include + +#include "solo6x10.h" + +static void solo_gpio_mode(struct solo_dev *solo_dev, + unsigned int port_mask, unsigned int mode) +{ + int port; + unsigned int ret; + + ret = solo_reg_read(solo_dev, SOLO_GPIO_CONFIG_0); + + /* To set gpio */ + for (port = 0; port < 16; port++) { + if (!((1 << port) & port_mask)) + continue; + + ret &= (~(3 << (port << 1))); + ret |= ((mode & 3) << (port << 1)); + } + + solo_reg_write(solo_dev, SOLO_GPIO_CONFIG_0, ret); + + /* To set extended gpio - sensor */ + ret = solo_reg_read(solo_dev, SOLO_GPIO_CONFIG_1); + + for (port = 0; port < 16; port++) { + if (!((1UL << (port + 16)) & port_mask)) + continue; + + if (!mode) + ret &= ~(1UL << port); + else + ret |= 1UL << port; + } + + /* Enable GPIO[31:16] */ + ret |= 0xffff0000; + + solo_reg_write(solo_dev, SOLO_GPIO_CONFIG_1, ret); +} + +static void solo_gpio_set(struct solo_dev *solo_dev, unsigned int value) +{ + solo_reg_write(solo_dev, SOLO_GPIO_DATA_OUT, + solo_reg_read(solo_dev, SOLO_GPIO_DATA_OUT) | value); +} + +static void solo_gpio_clear(struct solo_dev *solo_dev, unsigned int value) +{ + solo_reg_write(solo_dev, SOLO_GPIO_DATA_OUT, + solo_reg_read(solo_dev, SOLO_GPIO_DATA_OUT) & ~value); +} + +static void solo_gpio_config(struct solo_dev *solo_dev) +{ + /* Video reset */ + solo_gpio_mode(solo_dev, 0x30, 1); + solo_gpio_clear(solo_dev, 0x30); + udelay(100); + solo_gpio_set(solo_dev, 0x30); + udelay(100); + + /* Warning: Don't touch the next line unless you're sure of what + * you're doing: first four gpio [0-3] are used for video. */ + solo_gpio_mode(solo_dev, 0x0f, 2); + + /* We use bit 8-15 of SOLO_GPIO_CONFIG_0 for relay purposes */ + solo_gpio_mode(solo_dev, 0xff00, 1); + + /* Initially set relay status to 0 */ + solo_gpio_clear(solo_dev, 0xff00); + + /* Set input pins direction */ + solo_gpio_mode(solo_dev, 0xffff0000, 0); +} + +#ifdef CONFIG_GPIOLIB +/* Pins 0-7 are not exported, because it seems from code above they are + * used for internal purposes. So offset 0 corresponds to pin 8, therefore + * offsets 0-7 are relay GPIOs, 8-23 - input GPIOs. + */ +static int solo_gpiochip_get_direction(struct gpio_chip *chip, + unsigned int offset) +{ + int ret, mode; + struct solo_dev *solo_dev = gpiochip_get_data(chip); + + if (offset < 8) { + ret = solo_reg_read(solo_dev, SOLO_GPIO_CONFIG_0); + mode = 3 & (ret >> ((offset + 8) * 2)); + } else { + ret = solo_reg_read(solo_dev, SOLO_GPIO_CONFIG_1); + mode = 1 & (ret >> (offset - 8)); + } + + if (!mode) + return 1; + else if (mode == 1) + return 0; + + return -1; +} + +static int solo_gpiochip_get(struct gpio_chip *chip, + unsigned int offset) +{ + int ret; + struct solo_dev *solo_dev = gpiochip_get_data(chip); + + ret = solo_reg_read(solo_dev, SOLO_GPIO_DATA_IN); + + return 1 & (ret >> (offset + 8)); +} + +static int solo_gpiochip_set(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct solo_dev *solo_dev = gpiochip_get_data(chip); + + if (value) + solo_gpio_set(solo_dev, 1 << (offset + 8)); + else + solo_gpio_clear(solo_dev, 1 << (offset + 8)); + + return 0; +} +#endif + +int solo_gpio_init(struct solo_dev *solo_dev) +{ +#ifdef CONFIG_GPIOLIB + int ret; +#endif + + solo_gpio_config(solo_dev); +#ifdef CONFIG_GPIOLIB + solo_dev->gpio_dev.label = SOLO6X10_NAME"_gpio"; + solo_dev->gpio_dev.parent = &solo_dev->pdev->dev; + solo_dev->gpio_dev.owner = THIS_MODULE; + solo_dev->gpio_dev.base = -1; + solo_dev->gpio_dev.ngpio = 24; + solo_dev->gpio_dev.can_sleep = 0; + + solo_dev->gpio_dev.get_direction = solo_gpiochip_get_direction; + solo_dev->gpio_dev.get = solo_gpiochip_get; + solo_dev->gpio_dev.set = solo_gpiochip_set; + + ret = gpiochip_add_data(&solo_dev->gpio_dev, solo_dev); + + if (ret) { + solo_dev->gpio_dev.label = NULL; + return -1; + } +#endif + return 0; +} + +void solo_gpio_exit(struct solo_dev *solo_dev) +{ +#ifdef CONFIG_GPIOLIB + if (solo_dev->gpio_dev.label) { + gpiochip_remove(&solo_dev->gpio_dev); + solo_dev->gpio_dev.label = NULL; + } +#endif + solo_gpio_clear(solo_dev, 0x30); + solo_gpio_config(solo_dev); +} diff --git a/drivers/media/pci/solo6x10/solo6x10-i2c.c b/drivers/media/pci/solo6x10/solo6x10-i2c.c new file mode 100644 index 000000000..7db785e9c --- /dev/null +++ b/drivers/media/pci/solo6x10/solo6x10-i2c.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2010-2013 Bluecherry, LLC + * + * Original author: + * Ben Collins + * + * Additional work by: + * John Brooks + */ + +/* XXX: The SOLO6x10 i2c does not have separate interrupts for each i2c + * channel. The bus can only handle one i2c event at a time. The below handles + * this all wrong. We should be using the status registers to see if the bus + * is in use, and have a global lock to check the status register. Also, + * the bulk of the work should be handled out-of-interrupt. The ugly loops + * that occur during interrupt scare me. The ISR should merely signal + * thread context, ACK the interrupt, and move on. -- BenC */ + +#include +#include + +#include "solo6x10.h" + +u8 solo_i2c_readbyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off) +{ + struct i2c_msg msgs[2]; + u8 data; + + msgs[0].flags = 0; + msgs[0].addr = addr; + msgs[0].len = 1; + msgs[0].buf = &off; + + msgs[1].flags = I2C_M_RD; + msgs[1].addr = addr; + msgs[1].len = 1; + msgs[1].buf = &data; + + i2c_transfer(&solo_dev->i2c_adap[id], msgs, 2); + + return data; +} + +void solo_i2c_writebyte(struct solo_dev *solo_dev, int id, u8 addr, + u8 off, u8 data) +{ + struct i2c_msg msgs; + u8 buf[2]; + + buf[0] = off; + buf[1] = data; + msgs.flags = 0; + msgs.addr = addr; + msgs.len = 2; + msgs.buf = buf; + + i2c_transfer(&solo_dev->i2c_adap[id], &msgs, 1); +} + +static void solo_i2c_flush(struct solo_dev *solo_dev, int wr) +{ + u32 ctrl; + + ctrl = SOLO_IIC_CH_SET(solo_dev->i2c_id); + + if (solo_dev->i2c_state == IIC_STATE_START) + ctrl |= SOLO_IIC_START; + + if (wr) { + ctrl |= SOLO_IIC_WRITE; + } else { + ctrl |= SOLO_IIC_READ; + if (!(solo_dev->i2c_msg->flags & I2C_M_NO_RD_ACK)) + ctrl |= SOLO_IIC_ACK_EN; + } + + if (solo_dev->i2c_msg_ptr == solo_dev->i2c_msg->len) + ctrl |= SOLO_IIC_STOP; + + solo_reg_write(solo_dev, SOLO_IIC_CTRL, ctrl); +} + +static void solo_i2c_start(struct solo_dev *solo_dev) +{ + u32 addr = solo_dev->i2c_msg->addr << 1; + + if (solo_dev->i2c_msg->flags & I2C_M_RD) + addr |= 1; + + solo_dev->i2c_state = IIC_STATE_START; + solo_reg_write(solo_dev, SOLO_IIC_TXD, addr); + solo_i2c_flush(solo_dev, 1); +} + +static void solo_i2c_stop(struct solo_dev *solo_dev) +{ + solo_irq_off(solo_dev, SOLO_IRQ_IIC); + solo_reg_write(solo_dev, SOLO_IIC_CTRL, 0); + solo_dev->i2c_state = IIC_STATE_STOP; + wake_up(&solo_dev->i2c_wait); +} + +static int solo_i2c_handle_read(struct solo_dev *solo_dev) +{ +prepare_read: + if (solo_dev->i2c_msg_ptr != solo_dev->i2c_msg->len) { + solo_i2c_flush(solo_dev, 0); + return 0; + } + + solo_dev->i2c_msg_ptr = 0; + solo_dev->i2c_msg++; + solo_dev->i2c_msg_num--; + + if (solo_dev->i2c_msg_num == 0) { + solo_i2c_stop(solo_dev); + return 0; + } + + if (!(solo_dev->i2c_msg->flags & I2C_M_NOSTART)) { + solo_i2c_start(solo_dev); + } else { + if (solo_dev->i2c_msg->flags & I2C_M_RD) + goto prepare_read; + else + solo_i2c_stop(solo_dev); + } + + return 0; +} + +static int solo_i2c_handle_write(struct solo_dev *solo_dev) +{ +retry_write: + if (solo_dev->i2c_msg_ptr != solo_dev->i2c_msg->len) { + solo_reg_write(solo_dev, SOLO_IIC_TXD, + solo_dev->i2c_msg->buf[solo_dev->i2c_msg_ptr]); + solo_dev->i2c_msg_ptr++; + solo_i2c_flush(solo_dev, 1); + return 0; + } + + solo_dev->i2c_msg_ptr = 0; + solo_dev->i2c_msg++; + solo_dev->i2c_msg_num--; + + if (solo_dev->i2c_msg_num == 0) { + solo_i2c_stop(solo_dev); + return 0; + } + + if (!(solo_dev->i2c_msg->flags & I2C_M_NOSTART)) { + solo_i2c_start(solo_dev); + } else { + if (solo_dev->i2c_msg->flags & I2C_M_RD) + solo_i2c_stop(solo_dev); + else + goto retry_write; + } + + return 0; +} + +int solo_i2c_isr(struct solo_dev *solo_dev) +{ + u32 status = solo_reg_read(solo_dev, SOLO_IIC_CTRL); + int ret = -EINVAL; + + + if (CHK_FLAGS(status, SOLO_IIC_STATE_TRNS | SOLO_IIC_STATE_SIG_ERR) + || solo_dev->i2c_id < 0) { + solo_i2c_stop(solo_dev); + return -ENXIO; + } + + switch (solo_dev->i2c_state) { + case IIC_STATE_START: + if (solo_dev->i2c_msg->flags & I2C_M_RD) { + solo_dev->i2c_state = IIC_STATE_READ; + ret = solo_i2c_handle_read(solo_dev); + break; + } + + solo_dev->i2c_state = IIC_STATE_WRITE; + fallthrough; + case IIC_STATE_WRITE: + ret = solo_i2c_handle_write(solo_dev); + break; + + case IIC_STATE_READ: + solo_dev->i2c_msg->buf[solo_dev->i2c_msg_ptr] = + solo_reg_read(solo_dev, SOLO_IIC_RXD); + solo_dev->i2c_msg_ptr++; + + ret = solo_i2c_handle_read(solo_dev); + break; + + default: + solo_i2c_stop(solo_dev); + } + + return ret; +} + +static int solo_i2c_master_xfer(struct i2c_adapter *adap, + struct i2c_msg msgs[], int num) +{ + struct solo_dev *solo_dev = adap->algo_data; + unsigned long timeout; + int ret; + int i; + DEFINE_WAIT(wait); + + for (i = 0; i < SOLO_I2C_ADAPTERS; i++) { + if (&solo_dev->i2c_adap[i] == adap) + break; + } + + if (i == SOLO_I2C_ADAPTERS) + return num; /* XXX Right return value for failure? */ + + mutex_lock(&solo_dev->i2c_mutex); + solo_dev->i2c_id = i; + solo_dev->i2c_msg = msgs; + solo_dev->i2c_msg_num = num; + solo_dev->i2c_msg_ptr = 0; + + solo_reg_write(solo_dev, SOLO_IIC_CTRL, 0); + solo_irq_on(solo_dev, SOLO_IRQ_IIC); + solo_i2c_start(solo_dev); + + timeout = HZ / 2; + + for (;;) { + prepare_to_wait(&solo_dev->i2c_wait, &wait, + TASK_INTERRUPTIBLE); + + if (solo_dev->i2c_state == IIC_STATE_STOP) + break; + + timeout = schedule_timeout(timeout); + if (!timeout) + break; + + if (signal_pending(current)) + break; + } + + finish_wait(&solo_dev->i2c_wait, &wait); + ret = num - solo_dev->i2c_msg_num; + solo_dev->i2c_state = IIC_STATE_IDLE; + solo_dev->i2c_id = -1; + + mutex_unlock(&solo_dev->i2c_mutex); + + return ret; +} + +static u32 solo_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C; +} + +static const struct i2c_algorithm solo_i2c_algo = { + .master_xfer = solo_i2c_master_xfer, + .functionality = solo_i2c_functionality, +}; + +int solo_i2c_init(struct solo_dev *solo_dev) +{ + int i; + int ret; + + solo_reg_write(solo_dev, SOLO_IIC_CFG, + SOLO_IIC_PRESCALE(8) | SOLO_IIC_ENABLE); + + solo_dev->i2c_id = -1; + solo_dev->i2c_state = IIC_STATE_IDLE; + init_waitqueue_head(&solo_dev->i2c_wait); + mutex_init(&solo_dev->i2c_mutex); + + for (i = 0; i < SOLO_I2C_ADAPTERS; i++) { + struct i2c_adapter *adap = &solo_dev->i2c_adap[i]; + + snprintf(adap->name, I2C_NAME_SIZE, "%s I2C %d", + SOLO6X10_NAME, i); + adap->algo = &solo_i2c_algo; + adap->algo_data = solo_dev; + adap->retries = 1; + adap->dev.parent = &solo_dev->pdev->dev; + + ret = i2c_add_adapter(adap); + if (ret) { + adap->algo_data = NULL; + break; + } + } + + if (ret) { + for (i = 0; i < SOLO_I2C_ADAPTERS; i++) { + if (!solo_dev->i2c_adap[i].algo_data) + break; + i2c_del_adapter(&solo_dev->i2c_adap[i]); + solo_dev->i2c_adap[i].algo_data = NULL; + } + return ret; + } + + return 0; +} + +void solo_i2c_exit(struct solo_dev *solo_dev) +{ + int i; + + for (i = 0; i < SOLO_I2C_ADAPTERS; i++) { + if (!solo_dev->i2c_adap[i].algo_data) + continue; + i2c_del_adapter(&solo_dev->i2c_adap[i]); + solo_dev->i2c_adap[i].algo_data = NULL; + } +} diff --git a/drivers/media/pci/solo6x10/solo6x10-jpeg.h b/drivers/media/pci/solo6x10/solo6x10-jpeg.h new file mode 100644 index 000000000..e212f4828 --- /dev/null +++ b/drivers/media/pci/solo6x10/solo6x10-jpeg.h @@ -0,0 +1,180 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC + * + * Original author: + * Ben Collins + * + * Additional work by: + * John Brooks + */ + +#ifndef __SOLO6X10_JPEG_H +#define __SOLO6X10_JPEG_H + +static const u8 jpeg_header[] = { + 0xff, 0xd8, 0xff, 0xfe, 0x00, 0x0d, 0x42, 0x6c, + 0x75, 0x65, 0x63, 0x68, 0x65, 0x72, 0x72, 0x79, + 0x20, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x20, 0x16, + 0x18, 0x1c, 0x18, 0x14, 0x20, 0x1c, 0x1a, 0x1c, + 0x24, 0x22, 0x20, 0x26, 0x30, 0x50, 0x34, 0x30, + 0x2c, 0x2c, 0x30, 0x62, 0x46, 0x4a, 0x3a, 0x50, + 0x74, 0x66, 0x7a, 0x78, 0x72, 0x66, 0x70, 0x6e, + 0x80, 0x90, 0xb8, 0x9c, 0x80, 0x88, 0xae, 0x8a, + 0x6e, 0x70, 0xa0, 0xda, 0xa2, 0xae, 0xbe, 0xc4, + 0xce, 0xd0, 0xce, 0x7c, 0x9a, 0xe2, 0xf2, 0xe0, + 0xc8, 0xf0, 0xb8, 0xca, 0xce, 0xc6, 0xff, 0xdb, + 0x00, 0x43, 0x01, 0x22, 0x24, 0x24, 0x30, 0x2a, + 0x30, 0x5e, 0x34, 0x34, 0x5e, 0xc6, 0x84, 0x70, + 0x84, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xff, 0xc4, 0x01, 0xa2, 0x00, + 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, + 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, + 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01, 0x02, 0x03, + 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, + 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, + 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, + 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, + 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, + 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, + 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, + 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, + 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, + 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, + 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, + 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, + 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, + 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, + 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, + 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, + 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, + 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, + 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, + 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, + 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, + 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, + 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, + 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, + 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, + 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, + 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, + 0xc0, 0x00, 0x11, 0x08, 0x00, 0xf0, 0x02, 0xc0, + 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, + 0x11, 0x01, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, + 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00 +}; + +/* This is the byte marker for the start of SOF0: 0xffc0 marker */ +#define SOF0_START 575 + +/* This is the byte marker for the start of the DQT */ +#define DQT_START 17 +#define DQT_LEN 138 +static const u8 jpeg_dqt[4][DQT_LEN] = { + { + 0xff, 0xdb, 0x00, 0x43, 0x00, + 0x08, 0x06, 0x06, 0x07, 0x06, 0x05, 0x08, 0x07, + 0x07, 0x07, 0x09, 0x09, 0x08, 0x0a, 0x0c, 0x14, + 0x0d, 0x0c, 0x0b, 0x0b, 0x0c, 0x19, 0x12, 0x13, + 0x0f, 0x14, 0x1d, 0x1a, 0x1f, 0x1e, 0x1d, 0x1a, + 0x1c, 0x1c, 0x20, 0x24, 0x2e, 0x27, 0x20, 0x22, + 0x2c, 0x23, 0x1c, 0x1c, 0x28, 0x37, 0x29, 0x2c, + 0x30, 0x31, 0x34, 0x34, 0x34, 0x1f, 0x27, 0x39, + 0x3d, 0x38, 0x32, 0x3c, 0x2e, 0x33, 0x34, 0x32, + 0xff, 0xdb, 0x00, 0x43, 0x01, + 0x09, 0x09, 0x09, 0x0c, 0x0b, 0x0c, 0x18, 0x0d, + 0x0d, 0x18, 0x32, 0x21, 0x1c, 0x21, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32 + }, { + 0xff, 0xdb, 0x00, 0x43, 0x00, + 0x10, 0x0b, 0x0c, 0x0e, 0x0c, 0x0a, 0x10, 0x0e, + 0x0d, 0x0e, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28, + 0x1a, 0x18, 0x16, 0x16, 0x18, 0x31, 0x23, 0x25, + 0x1d, 0x28, 0x3a, 0x33, 0x3d, 0x3c, 0x39, 0x33, + 0x38, 0x37, 0x40, 0x48, 0x5c, 0x4e, 0x40, 0x44, + 0x57, 0x45, 0x37, 0x38, 0x50, 0x6d, 0x51, 0x57, + 0x5f, 0x62, 0x67, 0x68, 0x67, 0x3e, 0x4d, 0x71, + 0x79, 0x70, 0x64, 0x78, 0x5c, 0x65, 0x67, 0x63, + 0xff, 0xdb, 0x00, 0x43, 0x01, + 0x11, 0x12, 0x12, 0x18, 0x15, 0x18, 0x2f, 0x1a, + 0x1a, 0x2f, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63 + }, { + 0xff, 0xdb, 0x00, 0x43, 0x00, + 0x20, 0x16, 0x18, 0x1c, 0x18, 0x14, 0x20, 0x1c, + 0x1a, 0x1c, 0x24, 0x22, 0x20, 0x26, 0x30, 0x50, + 0x34, 0x30, 0x2c, 0x2c, 0x30, 0x62, 0x46, 0x4a, + 0x3a, 0x50, 0x74, 0x66, 0x7a, 0x78, 0x72, 0x66, + 0x70, 0x6e, 0x80, 0x90, 0xb8, 0x9c, 0x80, 0x88, + 0xae, 0x8a, 0x6e, 0x70, 0xa0, 0xda, 0xa2, 0xae, + 0xbe, 0xc4, 0xce, 0xd0, 0xce, 0x7c, 0x9a, 0xe2, + 0xf2, 0xe0, 0xc8, 0xf0, 0xb8, 0xca, 0xce, 0xc6, + 0xff, 0xdb, 0x00, 0x43, 0x01, + 0x22, 0x24, 0x24, 0x30, 0x2a, 0x30, 0x5e, 0x34, + 0x34, 0x5e, 0xc6, 0x84, 0x70, 0x84, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6 + }, { + 0xff, 0xdb, 0x00, 0x43, 0x00, + 0x30, 0x21, 0x24, 0x2a, 0x24, 0x1e, 0x30, 0x2a, + 0x27, 0x2a, 0x36, 0x33, 0x30, 0x39, 0x48, 0x78, + 0x4e, 0x48, 0x42, 0x42, 0x48, 0x93, 0x69, 0x6f, + 0x57, 0x78, 0xae, 0x99, 0xb7, 0xb4, 0xab, 0x99, + 0xa8, 0xa5, 0xc0, 0xd8, 0xff, 0xea, 0xc0, 0xcc, + 0xff, 0xcf, 0xa5, 0xa8, 0xf0, 0xff, 0xf3, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xba, 0xe7, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xdb, 0x00, 0x43, 0x01, + 0x33, 0x36, 0x36, 0x48, 0x3f, 0x48, 0x8d, 0x4e, + 0x4e, 0x8d, 0xff, 0xc6, 0xa8, 0xc6, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + } +}; + +#endif /* __SOLO6X10_JPEG_H */ diff --git a/drivers/media/pci/solo6x10/solo6x10-offsets.h b/drivers/media/pci/solo6x10/solo6x10-offsets.h new file mode 100644 index 000000000..fdbb817e6 --- /dev/null +++ b/drivers/media/pci/solo6x10/solo6x10-offsets.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC + * + * Original author: + * Ben Collins + * + * Additional work by: + * John Brooks + */ + +#ifndef __SOLO6X10_OFFSETS_H +#define __SOLO6X10_OFFSETS_H + +#define SOLO_DISP_EXT_ADDR 0x00000000 +#define SOLO_DISP_EXT_SIZE 0x00480000 + +#define SOLO_EOSD_EXT_ADDR \ + (SOLO_DISP_EXT_ADDR + SOLO_DISP_EXT_SIZE) +#define SOLO_EOSD_EXT_SIZE(__solo) \ + (__solo->type == SOLO_DEV_6010 ? 0x10000 : 0x20000) +#define SOLO_EOSD_EXT_SIZE_MAX 0x20000 +#define SOLO_EOSD_EXT_AREA(__solo) \ + (SOLO_EOSD_EXT_SIZE(__solo) * 32) +#define SOLO_EOSD_EXT_ADDR_CHAN(__solo, ch) \ + (SOLO_EOSD_EXT_ADDR + SOLO_EOSD_EXT_SIZE(__solo) * (ch)) + +#define SOLO_MOTION_EXT_ADDR(__solo) \ + (SOLO_EOSD_EXT_ADDR + SOLO_EOSD_EXT_AREA(__solo)) +#define SOLO_MOTION_EXT_SIZE 0x00080000 + +#define SOLO_G723_EXT_ADDR(__solo) \ + (SOLO_MOTION_EXT_ADDR(__solo) + SOLO_MOTION_EXT_SIZE) +#define SOLO_G723_EXT_SIZE 0x00010000 + +#define SOLO_CAP_EXT_ADDR(__solo) \ + (SOLO_G723_EXT_ADDR(__solo) + SOLO_G723_EXT_SIZE) + +/* 18 is the maximum number of pages required for PAL@D1, the largest frame + * possible */ +#define SOLO_CAP_PAGE_SIZE (18 << 16) + +/* Always allow the encoder enough for 16 channels, even if we have less. The + * exception is if we have card with only 32Megs of memory. */ +#define SOLO_CAP_EXT_SIZE(__solo) \ + ((((__solo->sdram_size <= (32 << 20)) ? 4 : 16) + 1) \ + * SOLO_CAP_PAGE_SIZE) + +#define SOLO_EREF_EXT_ADDR(__solo) \ + (SOLO_CAP_EXT_ADDR(__solo) + SOLO_CAP_EXT_SIZE(__solo)) +#define SOLO_EREF_EXT_SIZE 0x00140000 +#define SOLO_EREF_EXT_AREA(__solo) \ + (SOLO_EREF_EXT_SIZE * __solo->nr_chans * 2) + +#define __SOLO_JPEG_MIN_SIZE(__solo) (__solo->nr_chans * 0x00080000) + +#define SOLO_MP4E_EXT_ADDR(__solo) \ + (SOLO_EREF_EXT_ADDR(__solo) + SOLO_EREF_EXT_AREA(__solo)) +#define SOLO_MP4E_EXT_SIZE(__solo) \ + clamp(__solo->sdram_size - SOLO_MP4E_EXT_ADDR(__solo) - \ + __SOLO_JPEG_MIN_SIZE(__solo), \ + __solo->nr_chans * 0x00080000, 0x00ff0000) + +#define __SOLO_JPEG_MIN_SIZE(__solo) (__solo->nr_chans * 0x00080000) +#define SOLO_JPEG_EXT_ADDR(__solo) \ + (SOLO_MP4E_EXT_ADDR(__solo) + SOLO_MP4E_EXT_SIZE(__solo)) +#define SOLO_JPEG_EXT_SIZE(__solo) \ + clamp(__solo->sdram_size - SOLO_JPEG_EXT_ADDR(__solo), \ + __SOLO_JPEG_MIN_SIZE(__solo), 0x00ff0000) + +#define SOLO_SDRAM_END(__solo) \ + (SOLO_JPEG_EXT_ADDR(__solo) + SOLO_JPEG_EXT_SIZE(__solo)) + +#endif /* __SOLO6X10_OFFSETS_H */ diff --git a/drivers/media/pci/solo6x10/solo6x10-p2m.c b/drivers/media/pci/solo6x10/solo6x10-p2m.c new file mode 100644 index 000000000..5f100e5e0 --- /dev/null +++ b/drivers/media/pci/solo6x10/solo6x10-p2m.c @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2010-2013 Bluecherry, LLC + * + * Original author: + * Ben Collins + * + * Additional work by: + * John Brooks + */ + +#include +#include +#include + +#include "solo6x10.h" + +static int multi_p2m; +module_param(multi_p2m, uint, 0644); +MODULE_PARM_DESC(multi_p2m, + "Use multiple P2M DMA channels (default: no, 6010-only)"); + +static int desc_mode; +module_param(desc_mode, uint, 0644); +MODULE_PARM_DESC(desc_mode, + "Allow use of descriptor mode DMA (default: no, 6010-only)"); + +int solo_p2m_dma(struct solo_dev *solo_dev, int wr, + void *sys_addr, u32 ext_addr, u32 size, + int repeat, u32 ext_size) +{ + dma_addr_t dma_addr; + int ret; + + if (WARN_ON_ONCE((unsigned long)sys_addr & 0x03)) + return -EINVAL; + if (WARN_ON_ONCE(!size)) + return -EINVAL; + + dma_addr = dma_map_single(&solo_dev->pdev->dev, sys_addr, size, + wr ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + if (dma_mapping_error(&solo_dev->pdev->dev, dma_addr)) + return -ENOMEM; + + ret = solo_p2m_dma_t(solo_dev, wr, dma_addr, ext_addr, size, + repeat, ext_size); + + dma_unmap_single(&solo_dev->pdev->dev, dma_addr, size, + wr ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + + return ret; +} + +/* Mutex must be held for p2m_id before calling this!! */ +int solo_p2m_dma_desc(struct solo_dev *solo_dev, + struct solo_p2m_desc *desc, dma_addr_t desc_dma, + int desc_cnt) +{ + struct solo_p2m_dev *p2m_dev; + unsigned long time_left; + unsigned int config = 0; + int ret = 0; + unsigned int p2m_id = 0; + + /* Get next ID. According to Softlogic, 6110 has problems on !=0 P2M */ + if (solo_dev->type != SOLO_DEV_6110 && multi_p2m) + p2m_id = atomic_inc_return(&solo_dev->p2m_count) % SOLO_NR_P2M; + + p2m_dev = &solo_dev->p2m_dev[p2m_id]; + + if (mutex_lock_interruptible(&p2m_dev->mutex)) + return -EINTR; + + reinit_completion(&p2m_dev->completion); + p2m_dev->error = 0; + + if (desc_cnt > 1 && solo_dev->type != SOLO_DEV_6110 && desc_mode) { + /* For 6010 with more than one desc, we can do a one-shot */ + p2m_dev->desc_count = p2m_dev->desc_idx = 0; + config = solo_reg_read(solo_dev, SOLO_P2M_CONFIG(p2m_id)); + + solo_reg_write(solo_dev, SOLO_P2M_DES_ADR(p2m_id), desc_dma); + solo_reg_write(solo_dev, SOLO_P2M_DESC_ID(p2m_id), desc_cnt); + solo_reg_write(solo_dev, SOLO_P2M_CONFIG(p2m_id), config | + SOLO_P2M_DESC_MODE); + } else { + /* For single descriptors and 6110, we need to run each desc */ + p2m_dev->desc_count = desc_cnt; + p2m_dev->desc_idx = 1; + p2m_dev->descs = desc; + + solo_reg_write(solo_dev, SOLO_P2M_TAR_ADR(p2m_id), + desc[1].dma_addr); + solo_reg_write(solo_dev, SOLO_P2M_EXT_ADR(p2m_id), + desc[1].ext_addr); + solo_reg_write(solo_dev, SOLO_P2M_EXT_CFG(p2m_id), + desc[1].cfg); + solo_reg_write(solo_dev, SOLO_P2M_CONTROL(p2m_id), + desc[1].ctrl); + } + + time_left = wait_for_completion_timeout(&p2m_dev->completion, + solo_dev->p2m_jiffies); + + if (WARN_ON_ONCE(p2m_dev->error)) + ret = -EIO; + else if (time_left == 0) { + solo_dev->p2m_timeouts++; + ret = -EAGAIN; + } + + solo_reg_write(solo_dev, SOLO_P2M_CONTROL(p2m_id), 0); + + /* Don't write here for the no_desc_mode case, because config is 0. + * We can't test no_desc_mode again, it might race. */ + if (desc_cnt > 1 && solo_dev->type != SOLO_DEV_6110 && config) + solo_reg_write(solo_dev, SOLO_P2M_CONFIG(p2m_id), config); + + mutex_unlock(&p2m_dev->mutex); + + return ret; +} + +void solo_p2m_fill_desc(struct solo_p2m_desc *desc, int wr, + dma_addr_t dma_addr, u32 ext_addr, u32 size, + int repeat, u32 ext_size) +{ + WARN_ON_ONCE(dma_addr & 0x03); + WARN_ON_ONCE(!size); + + desc->cfg = SOLO_P2M_COPY_SIZE(size >> 2); + desc->ctrl = SOLO_P2M_BURST_SIZE(SOLO_P2M_BURST_256) | + (wr ? SOLO_P2M_WRITE : 0) | SOLO_P2M_TRANS_ON; + + if (repeat) { + desc->cfg |= SOLO_P2M_EXT_INC(ext_size >> 2); + desc->ctrl |= SOLO_P2M_PCI_INC(size >> 2) | + SOLO_P2M_REPEAT(repeat); + } + + desc->dma_addr = dma_addr; + desc->ext_addr = ext_addr; +} + +int solo_p2m_dma_t(struct solo_dev *solo_dev, int wr, + dma_addr_t dma_addr, u32 ext_addr, u32 size, + int repeat, u32 ext_size) +{ + struct solo_p2m_desc desc[2]; + + solo_p2m_fill_desc(&desc[1], wr, dma_addr, ext_addr, size, repeat, + ext_size); + + /* No need for desc_dma since we know it is a single-shot */ + return solo_p2m_dma_desc(solo_dev, desc, 0, 1); +} + +void solo_p2m_isr(struct solo_dev *solo_dev, int id) +{ + struct solo_p2m_dev *p2m_dev = &solo_dev->p2m_dev[id]; + struct solo_p2m_desc *desc; + + if (p2m_dev->desc_count <= p2m_dev->desc_idx) { + complete(&p2m_dev->completion); + return; + } + + /* Setup next descriptor */ + p2m_dev->desc_idx++; + desc = &p2m_dev->descs[p2m_dev->desc_idx]; + + solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id), 0); + solo_reg_write(solo_dev, SOLO_P2M_TAR_ADR(id), desc->dma_addr); + solo_reg_write(solo_dev, SOLO_P2M_EXT_ADR(id), desc->ext_addr); + solo_reg_write(solo_dev, SOLO_P2M_EXT_CFG(id), desc->cfg); + solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id), desc->ctrl); +} + +void solo_p2m_error_isr(struct solo_dev *solo_dev) +{ + unsigned int err = solo_reg_read(solo_dev, SOLO_PCI_ERR); + struct solo_p2m_dev *p2m_dev; + int i; + + if (!(err & (SOLO_PCI_ERR_P2M | SOLO_PCI_ERR_P2M_DESC))) + return; + + for (i = 0; i < SOLO_NR_P2M; i++) { + p2m_dev = &solo_dev->p2m_dev[i]; + p2m_dev->error = 1; + solo_reg_write(solo_dev, SOLO_P2M_CONTROL(i), 0); + complete(&p2m_dev->completion); + } +} + +void solo_p2m_exit(struct solo_dev *solo_dev) +{ + int i; + + for (i = 0; i < SOLO_NR_P2M; i++) + solo_irq_off(solo_dev, SOLO_IRQ_P2M(i)); +} + +static int solo_p2m_test(struct solo_dev *solo_dev, int base, int size) +{ + u32 *wr_buf; + u32 *rd_buf; + int i; + int ret = -EIO; + int order = get_order(size); + + wr_buf = (u32 *)__get_free_pages(GFP_KERNEL, order); + if (wr_buf == NULL) + return -1; + + rd_buf = (u32 *)__get_free_pages(GFP_KERNEL, order); + if (rd_buf == NULL) { + free_pages((unsigned long)wr_buf, order); + return -1; + } + + for (i = 0; i < (size >> 3); i++) + *(wr_buf + i) = (i << 16) | (i + 1); + + for (i = (size >> 3); i < (size >> 2); i++) + *(wr_buf + i) = ~((i << 16) | (i + 1)); + + memset(rd_buf, 0x55, size); + + if (solo_p2m_dma(solo_dev, 1, wr_buf, base, size, 0, 0)) + goto test_fail; + + if (solo_p2m_dma(solo_dev, 0, rd_buf, base, size, 0, 0)) + goto test_fail; + + for (i = 0; i < (size >> 2); i++) { + if (*(wr_buf + i) != *(rd_buf + i)) + goto test_fail; + } + + ret = 0; + +test_fail: + free_pages((unsigned long)wr_buf, order); + free_pages((unsigned long)rd_buf, order); + + return ret; +} + +int solo_p2m_init(struct solo_dev *solo_dev) +{ + struct solo_p2m_dev *p2m_dev; + int i; + + for (i = 0; i < SOLO_NR_P2M; i++) { + p2m_dev = &solo_dev->p2m_dev[i]; + + mutex_init(&p2m_dev->mutex); + init_completion(&p2m_dev->completion); + + solo_reg_write(solo_dev, SOLO_P2M_CONTROL(i), 0); + solo_reg_write(solo_dev, SOLO_P2M_CONFIG(i), + SOLO_P2M_CSC_16BIT_565 | + SOLO_P2M_DESC_INTR_OPT | + SOLO_P2M_DMA_INTERVAL(0) | + SOLO_P2M_PCI_MASTER_MODE); + solo_irq_on(solo_dev, SOLO_IRQ_P2M(i)); + } + + /* Find correct SDRAM size */ + for (solo_dev->sdram_size = 0, i = 2; i >= 0; i--) { + solo_reg_write(solo_dev, SOLO_DMA_CTRL, + SOLO_DMA_CTRL_REFRESH_CYCLE(1) | + SOLO_DMA_CTRL_SDRAM_SIZE(i) | + SOLO_DMA_CTRL_SDRAM_CLK_INVERT | + SOLO_DMA_CTRL_READ_CLK_SELECT | + SOLO_DMA_CTRL_LATENCY(1)); + + solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config | + SOLO_SYS_CFG_RESET); + solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config); + + switch (i) { + case 2: + if (solo_p2m_test(solo_dev, 0x07ff0000, 0x00010000) || + solo_p2m_test(solo_dev, 0x05ff0000, 0x00010000)) + continue; + break; + + case 1: + if (solo_p2m_test(solo_dev, 0x03ff0000, 0x00010000)) + continue; + break; + + default: + if (solo_p2m_test(solo_dev, 0x01ff0000, 0x00010000)) + continue; + } + + solo_dev->sdram_size = (32 << 20) << i; + break; + } + + if (!solo_dev->sdram_size) { + dev_err(&solo_dev->pdev->dev, "Error detecting SDRAM size\n"); + return -EIO; + } + + if (SOLO_SDRAM_END(solo_dev) > solo_dev->sdram_size) { + dev_err(&solo_dev->pdev->dev, + "SDRAM is not large enough (%u < %u)\n", + solo_dev->sdram_size, SOLO_SDRAM_END(solo_dev)); + return -EIO; + } + + return 0; +} diff --git a/drivers/media/pci/solo6x10/solo6x10-regs.h b/drivers/media/pci/solo6x10/solo6x10-regs.h new file mode 100644 index 000000000..12e0ac190 --- /dev/null +++ b/drivers/media/pci/solo6x10/solo6x10-regs.h @@ -0,0 +1,628 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC + * + * Original author: + * Ben Collins + * + * Additional work by: + * John Brooks + */ + +#ifndef __SOLO6X10_REGISTERS_H +#define __SOLO6X10_REGISTERS_H + +#include + +#include "solo6x10-offsets.h" + +/* Global 6010 system configuration */ +#define SOLO_SYS_CFG 0x0000 +#define SOLO_SYS_CFG_FOUT_EN 0x00000001 +#define SOLO_SYS_CFG_PLL_BYPASS 0x00000002 +#define SOLO_SYS_CFG_PLL_PWDN 0x00000004 +#define SOLO_SYS_CFG_OUTDIV(__n) (((__n) & 0x003) << 3) +#define SOLO_SYS_CFG_FEEDBACKDIV(__n) (((__n) & 0x1ff) << 5) +#define SOLO_SYS_CFG_INPUTDIV(__n) (((__n) & 0x01f) << 14) +#define SOLO_SYS_CFG_CLOCK_DIV 0x00080000 +#define SOLO_SYS_CFG_NCLK_DELAY(__n) (((__n) & 0x003) << 24) +#define SOLO_SYS_CFG_PCLK_DELAY(__n) (((__n) & 0x00f) << 26) +#define SOLO_SYS_CFG_SDRAM64BIT 0x40000000 +#define SOLO_SYS_CFG_RESET 0x80000000 + +#define SOLO_DMA_CTRL 0x0004 +#define SOLO_DMA_CTRL_REFRESH_CYCLE(n) ((n)<<8) +/* 0=16/32MB, 1=32/64MB, 2=64/128MB, 3=128/256MB */ +#define SOLO_DMA_CTRL_SDRAM_SIZE(n) ((n)<<6) +#define SOLO_DMA_CTRL_SDRAM_CLK_INVERT BIT(5) +#define SOLO_DMA_CTRL_STROBE_SELECT BIT(4) +#define SOLO_DMA_CTRL_READ_DATA_SELECT BIT(3) +#define SOLO_DMA_CTRL_READ_CLK_SELECT BIT(2) +#define SOLO_DMA_CTRL_LATENCY(n) ((n)<<0) + +/* Some things we set in this are undocumented. Why Softlogic?!?! */ +#define SOLO_DMA_CTRL1 0x0008 + +#define SOLO_SYS_VCLK 0x000C +#define SOLO_VCLK_INVERT BIT(22) +/* 0=sys_clk/4, 1=sys_clk/2, 2=clk_in/2 of system input */ +#define SOLO_VCLK_SELECT(n) ((n)<<20) +#define SOLO_VCLK_VIN1415_DELAY(n) ((n)<<14) +#define SOLO_VCLK_VIN1213_DELAY(n) ((n)<<12) +#define SOLO_VCLK_VIN1011_DELAY(n) ((n)<<10) +#define SOLO_VCLK_VIN0809_DELAY(n) ((n)<<8) +#define SOLO_VCLK_VIN0607_DELAY(n) ((n)<<6) +#define SOLO_VCLK_VIN0405_DELAY(n) ((n)<<4) +#define SOLO_VCLK_VIN0203_DELAY(n) ((n)<<2) +#define SOLO_VCLK_VIN0001_DELAY(n) ((n)<<0) + +#define SOLO_IRQ_STAT 0x0010 +#define SOLO_IRQ_MASK 0x0014 +#define SOLO_IRQ_P2M(n) BIT((n) + 17) +#define SOLO_IRQ_GPIO BIT(16) +#define SOLO_IRQ_VIDEO_LOSS BIT(15) +#define SOLO_IRQ_VIDEO_IN BIT(14) +#define SOLO_IRQ_MOTION BIT(13) +#define SOLO_IRQ_ATA_CMD BIT(12) +#define SOLO_IRQ_ATA_DIR BIT(11) +#define SOLO_IRQ_PCI_ERR BIT(10) +#define SOLO_IRQ_PS2_1 BIT(9) +#define SOLO_IRQ_PS2_0 BIT(8) +#define SOLO_IRQ_SPI BIT(7) +#define SOLO_IRQ_IIC BIT(6) +#define SOLO_IRQ_UART(n) BIT((n) + 4) +#define SOLO_IRQ_G723 BIT(3) +#define SOLO_IRQ_DECODER BIT(1) +#define SOLO_IRQ_ENCODER BIT(0) + +#define SOLO_CHIP_OPTION 0x001C +#define SOLO_CHIP_ID_MASK 0x00000007 + +#define SOLO_PLL_CONFIG 0x0020 /* 6110 Only */ + +#define SOLO_EEPROM_CTRL 0x0060 +#define SOLO_EEPROM_ACCESS_EN BIT(7) +#define SOLO_EEPROM_CS BIT(3) +#define SOLO_EEPROM_CLK BIT(2) +#define SOLO_EEPROM_DO BIT(1) +#define SOLO_EEPROM_DI BIT(0) +#define SOLO_EEPROM_ENABLE (SOLO_EEPROM_ACCESS_EN | SOLO_EEPROM_CS) + +#define SOLO_PCI_ERR 0x0070 +#define SOLO_PCI_ERR_FATAL 0x00000001 +#define SOLO_PCI_ERR_PARITY 0x00000002 +#define SOLO_PCI_ERR_TARGET 0x00000004 +#define SOLO_PCI_ERR_TIMEOUT 0x00000008 +#define SOLO_PCI_ERR_P2M 0x00000010 +#define SOLO_PCI_ERR_ATA 0x00000020 +#define SOLO_PCI_ERR_P2M_DESC 0x00000040 +#define SOLO_PCI_ERR_FSM0(__s) (((__s) >> 16) & 0x0f) +#define SOLO_PCI_ERR_FSM1(__s) (((__s) >> 20) & 0x0f) +#define SOLO_PCI_ERR_FSM2(__s) (((__s) >> 24) & 0x1f) + +#define SOLO_P2M_BASE 0x0080 + +#define SOLO_P2M_CONFIG(n) (0x0080 + ((n)*0x20)) +#define SOLO_P2M_DMA_INTERVAL(n) ((n)<<6)/* N*32 clocks */ +#define SOLO_P2M_CSC_BYTE_REORDER BIT(5) /* BGR -> RGB */ +/* 0:r=[14:10] g=[9:5] b=[4:0], 1:r=[15:11] g=[10:5] b=[4:0] */ +#define SOLO_P2M_CSC_16BIT_565 BIT(4) +#define SOLO_P2M_UV_SWAP BIT(3) +#define SOLO_P2M_PCI_MASTER_MODE BIT(2) +#define SOLO_P2M_DESC_INTR_OPT BIT(1) /* 1:Empty, 0:Each */ +#define SOLO_P2M_DESC_MODE BIT(0) + +#define SOLO_P2M_DES_ADR(n) (0x0084 + ((n)*0x20)) + +#define SOLO_P2M_DESC_ID(n) (0x0088 + ((n)*0x20)) +#define SOLO_P2M_UPDATE_ID(n) ((n)<<0) + +#define SOLO_P2M_STATUS(n) (0x008C + ((n)*0x20)) +#define SOLO_P2M_COMMAND_DONE BIT(8) +#define SOLO_P2M_CURRENT_ID(stat) (0xff & (stat)) + +#define SOLO_P2M_CONTROL(n) (0x0090 + ((n)*0x20)) +#define SOLO_P2M_PCI_INC(n) ((n)<<20) +#define SOLO_P2M_REPEAT(n) ((n)<<10) +/* 0:512, 1:256, 2:128, 3:64, 4:32, 5:128(2page) */ +#define SOLO_P2M_BURST_SIZE(n) ((n)<<7) +#define SOLO_P2M_BURST_512 0 +#define SOLO_P2M_BURST_256 1 +#define SOLO_P2M_BURST_128 2 +#define SOLO_P2M_BURST_64 3 +#define SOLO_P2M_BURST_32 4 +#define SOLO_P2M_CSC_16BIT BIT(6) /* 0:24bit, 1:16bit */ +/* 0:Y[0]<-0(OFF), 1:Y[0]<-1(ON), 2:Y[0]<-G[0], 3:Y[0]<-Bit[15] */ +#define SOLO_P2M_ALPHA_MODE(n) ((n)<<4) +#define SOLO_P2M_CSC_ON BIT(3) +#define SOLO_P2M_INTERRUPT_REQ BIT(2) +#define SOLO_P2M_WRITE BIT(1) +#define SOLO_P2M_TRANS_ON BIT(0) + +#define SOLO_P2M_EXT_CFG(n) (0x0094 + ((n)*0x20)) +#define SOLO_P2M_EXT_INC(n) ((n)<<20) +#define SOLO_P2M_COPY_SIZE(n) ((n)<<0) + +#define SOLO_P2M_TAR_ADR(n) (0x0098 + ((n)*0x20)) + +#define SOLO_P2M_EXT_ADR(n) (0x009C + ((n)*0x20)) + +#define SOLO_P2M_BUFFER(i) (0x2000 + ((i)*4)) + +#define SOLO_VI_CH_SWITCH_0 0x0100 +#define SOLO_VI_CH_SWITCH_1 0x0104 +#define SOLO_VI_CH_SWITCH_2 0x0108 + +#define SOLO_VI_CH_ENA 0x010C +#define SOLO_VI_CH_FORMAT 0x0110 +#define SOLO_VI_FD_SEL_MASK(n) ((n)<<16) +#define SOLO_VI_PROG_MASK(n) ((n)<<0) + +#define SOLO_VI_FMT_CFG 0x0114 +#define SOLO_VI_FMT_CHECK_VCOUNT BIT(31) +#define SOLO_VI_FMT_CHECK_HCOUNT BIT(30) +#define SOLO_VI_FMT_TEST_SIGNAL BIT(28) + +#define SOLO_VI_PAGE_SW 0x0118 +#define SOLO_FI_INV_DISP_LIVE(n) ((n)<<8) +#define SOLO_FI_INV_DISP_OUT(n) ((n)<<7) +#define SOLO_DISP_SYNC_FI(n) ((n)<<6) +#define SOLO_PIP_PAGE_ADD(n) ((n)<<3) +#define SOLO_NORMAL_PAGE_ADD(n) ((n)<<0) + +#define SOLO_VI_ACT_I_P 0x011C +#define SOLO_VI_ACT_I_S 0x0120 +#define SOLO_VI_ACT_P 0x0124 +#define SOLO_VI_FI_INVERT BIT(31) +#define SOLO_VI_H_START(n) ((n)<<21) +#define SOLO_VI_V_START(n) ((n)<<11) +#define SOLO_VI_V_STOP(n) ((n)<<0) + +#define SOLO_VI_STATUS0 0x0128 +#define SOLO_VI_STATUS0_PAGE(__n) ((__n) & 0x07) +#define SOLO_VI_STATUS1 0x012C + +/* XXX: Might be better off in kernel level disp.h */ +#define DISP_PAGE(stat) ((stat) & 0x07) + +#define SOLO_VI_PB_CONFIG 0x0130 +#define SOLO_VI_PB_USER_MODE BIT(1) +#define SOLO_VI_PB_PAL BIT(0) +#define SOLO_VI_PB_RANGE_HV 0x0134 +#define SOLO_VI_PB_HSIZE(h) ((h)<<12) +#define SOLO_VI_PB_VSIZE(v) ((v)<<0) +#define SOLO_VI_PB_ACT_H 0x0138 +#define SOLO_VI_PB_HSTART(n) ((n)<<12) +#define SOLO_VI_PB_HSTOP(n) ((n)<<0) +#define SOLO_VI_PB_ACT_V 0x013C +#define SOLO_VI_PB_VSTART(n) ((n)<<12) +#define SOLO_VI_PB_VSTOP(n) ((n)<<0) + +#define SOLO_VI_MOSAIC(ch) (0x0140 + ((ch)*4)) +#define SOLO_VI_MOSAIC_SX(x) ((x)<<24) +#define SOLO_VI_MOSAIC_EX(x) ((x)<<16) +#define SOLO_VI_MOSAIC_SY(x) ((x)<<8) +#define SOLO_VI_MOSAIC_EY(x) ((x)<<0) + +#define SOLO_VI_WIN_CTRL0(ch) (0x0180 + ((ch)*4)) +#define SOLO_VI_WIN_CTRL1(ch) (0x01C0 + ((ch)*4)) + +#define SOLO_VI_WIN_CHANNEL(n) ((n)<<28) + +#define SOLO_VI_WIN_PIP(n) ((n)<<27) +#define SOLO_VI_WIN_SCALE(n) ((n)<<24) + +#define SOLO_VI_WIN_SX(x) ((x)<<12) +#define SOLO_VI_WIN_EX(x) ((x)<<0) + +#define SOLO_VI_WIN_SY(x) ((x)<<12) +#define SOLO_VI_WIN_EY(x) ((x)<<0) + +#define SOLO_VI_WIN_ON(ch) (0x0200 + ((ch)*4)) + +#define SOLO_VI_WIN_SW 0x0240 +#define SOLO_VI_WIN_LIVE_AUTO_MUTE 0x0244 + +#define SOLO_VI_MOT_ADR 0x0260 +#define SOLO_VI_MOTION_EN(mask) ((mask)<<16) +#define SOLO_VI_MOT_CTRL 0x0264 +#define SOLO_VI_MOTION_FRAME_COUNT(n) ((n)<<24) +#define SOLO_VI_MOTION_SAMPLE_LENGTH(n) ((n)<<16) +#define SOLO_VI_MOTION_INTR_START_STOP BIT(15) +#define SOLO_VI_MOTION_FREEZE_DATA BIT(14) +#define SOLO_VI_MOTION_SAMPLE_COUNT(n) ((n)<<0) +#define SOLO_VI_MOT_CLEAR 0x0268 +#define SOLO_VI_MOT_STATUS 0x026C +#define SOLO_VI_MOTION_CNT(n) ((n)<<0) +#define SOLO_VI_MOTION_BORDER 0x0270 +#define SOLO_VI_MOTION_BAR 0x0274 +#define SOLO_VI_MOTION_Y_SET BIT(29) +#define SOLO_VI_MOTION_Y_ADD BIT(28) +#define SOLO_VI_MOTION_CB_SET BIT(27) +#define SOLO_VI_MOTION_CB_ADD BIT(26) +#define SOLO_VI_MOTION_CR_SET BIT(25) +#define SOLO_VI_MOTION_CR_ADD BIT(24) +#define SOLO_VI_MOTION_Y_VALUE(v) ((v)<<16) +#define SOLO_VI_MOTION_CB_VALUE(v) ((v)<<8) +#define SOLO_VI_MOTION_CR_VALUE(v) ((v)<<0) + +#define SOLO_VO_FMT_ENC 0x0300 +#define SOLO_VO_SCAN_MODE_PROGRESSIVE BIT(31) +#define SOLO_VO_FMT_TYPE_PAL BIT(30) +#define SOLO_VO_FMT_TYPE_NTSC 0 +#define SOLO_VO_USER_SET BIT(29) + +#define SOLO_VO_FI_CHANGE BIT(20) +#define SOLO_VO_USER_COLOR_SET_VSYNC BIT(19) +#define SOLO_VO_USER_COLOR_SET_HSYNC BIT(18) +#define SOLO_VO_USER_COLOR_SET_NAH BIT(17) +#define SOLO_VO_USER_COLOR_SET_NAV BIT(16) +#define SOLO_VO_NA_COLOR_Y(Y) ((Y)<<8) +#define SOLO_VO_NA_COLOR_CB(CB) (((CB)/16)<<4) +#define SOLO_VO_NA_COLOR_CR(CR) (((CR)/16)<<0) + +#define SOLO_VO_ACT_H 0x0304 +#define SOLO_VO_H_BLANK(n) ((n)<<22) +#define SOLO_VO_H_START(n) ((n)<<11) +#define SOLO_VO_H_STOP(n) ((n)<<0) + +#define SOLO_VO_ACT_V 0x0308 +#define SOLO_VO_V_BLANK(n) ((n)<<22) +#define SOLO_VO_V_START(n) ((n)<<11) +#define SOLO_VO_V_STOP(n) ((n)<<0) + +#define SOLO_VO_RANGE_HV 0x030C +#define SOLO_VO_SYNC_INVERT BIT(24) +#define SOLO_VO_HSYNC_INVERT BIT(23) +#define SOLO_VO_VSYNC_INVERT BIT(22) +#define SOLO_VO_H_LEN(n) ((n)<<11) +#define SOLO_VO_V_LEN(n) ((n)<<0) + +#define SOLO_VO_DISP_CTRL 0x0310 +#define SOLO_VO_DISP_ON BIT(31) +#define SOLO_VO_DISP_ERASE_COUNT(n) ((n&0xf)<<24) +#define SOLO_VO_DISP_DOUBLE_SCAN BIT(22) +#define SOLO_VO_DISP_SINGLE_PAGE BIT(21) +#define SOLO_VO_DISP_BASE(n) (((n)>>16) & 0xffff) + +#define SOLO_VO_DISP_ERASE 0x0314 +#define SOLO_VO_DISP_ERASE_ON BIT(0) + +#define SOLO_VO_ZOOM_CTRL 0x0318 +#define SOLO_VO_ZOOM_VER_ON BIT(24) +#define SOLO_VO_ZOOM_HOR_ON BIT(23) +#define SOLO_VO_ZOOM_V_COMP BIT(22) +#define SOLO_VO_ZOOM_SX(h) (((h)/2)<<11) +#define SOLO_VO_ZOOM_SY(v) (((v)/2)<<0) + +#define SOLO_VO_FREEZE_CTRL 0x031C +#define SOLO_VO_FREEZE_ON BIT(1) +#define SOLO_VO_FREEZE_INTERPOLATION BIT(0) + +#define SOLO_VO_BKG_COLOR 0x0320 +#define SOLO_BG_Y(y) ((y)<<16) +#define SOLO_BG_U(u) ((u)<<8) +#define SOLO_BG_V(v) ((v)<<0) + +#define SOLO_VO_DEINTERLACE 0x0324 +#define SOLO_VO_DEINTERLACE_THRESHOLD(n) ((n)<<8) +#define SOLO_VO_DEINTERLACE_EDGE_VALUE(n) ((n)<<0) + +#define SOLO_VO_BORDER_LINE_COLOR 0x0330 +#define SOLO_VO_BORDER_FILL_COLOR 0x0334 +#define SOLO_VO_BORDER_LINE_MASK 0x0338 +#define SOLO_VO_BORDER_FILL_MASK 0x033c + +#define SOLO_VO_BORDER_X(n) (0x0340+((n)*4)) +#define SOLO_VO_BORDER_Y(n) (0x0354+((n)*4)) + +#define SOLO_VO_CELL_EXT_SET 0x0368 +#define SOLO_VO_CELL_EXT_START 0x036c +#define SOLO_VO_CELL_EXT_STOP 0x0370 + +#define SOLO_VO_CELL_EXT_SET2 0x0374 +#define SOLO_VO_CELL_EXT_START2 0x0378 +#define SOLO_VO_CELL_EXT_STOP2 0x037c + +#define SOLO_VO_RECTANGLE_CTRL(n) (0x0368+((n)*12)) +#define SOLO_VO_RECTANGLE_START(n) (0x036c+((n)*12)) +#define SOLO_VO_RECTANGLE_STOP(n) (0x0370+((n)*12)) + +#define SOLO_VO_CURSOR_POS (0x0380) +#define SOLO_VO_CURSOR_CLR (0x0384) +#define SOLO_VO_CURSOR_CLR2 (0x0388) +#define SOLO_VO_CURSOR_MASK(id) (0x0390+((id)*4)) + +#define SOLO_VO_EXPANSION(id) (0x0250+((id)*4)) + +#define SOLO_OSG_CONFIG 0x03E0 +#define SOLO_VO_OSG_ON BIT(31) +#define SOLO_VO_OSG_COLOR_MUTE BIT(28) +#define SOLO_VO_OSG_ALPHA_RATE(n) ((n)<<22) +#define SOLO_VO_OSG_ALPHA_BG_RATE(n) ((n)<<16) +#define SOLO_VO_OSG_BASE(offset) (((offset)>>16)&0xffff) + +#define SOLO_OSG_ERASE 0x03E4 +#define SOLO_OSG_ERASE_ON (0x80) +#define SOLO_OSG_ERASE_OFF (0x00) + +#define SOLO_VO_OSG_BLINK 0x03E8 +#define SOLO_VO_OSG_BLINK_ON BIT(1) +#define SOLO_VO_OSG_BLINK_INTREVAL18 BIT(0) + +#define SOLO_CAP_BASE 0x0400 +#define SOLO_CAP_MAX_PAGE(n) ((n)<<16) +#define SOLO_CAP_BASE_ADDR(n) ((n)<<0) +#define SOLO_CAP_BTW 0x0404 +#define SOLO_CAP_PROG_BANDWIDTH(n) ((n)<<8) +#define SOLO_CAP_MAX_BANDWIDTH(n) ((n)<<0) + +#define SOLO_DIM_SCALE1 0x0408 +#define SOLO_DIM_SCALE2 0x040C +#define SOLO_DIM_SCALE3 0x0410 +#define SOLO_DIM_SCALE4 0x0414 +#define SOLO_DIM_SCALE5 0x0418 +#define SOLO_DIM_V_MB_NUM_FRAME(n) ((n)<<16) +#define SOLO_DIM_V_MB_NUM_FIELD(n) ((n)<<8) +#define SOLO_DIM_H_MB_NUM(n) ((n)<<0) + +#define SOLO_DIM_PROG 0x041C +#define SOLO_CAP_STATUS 0x0420 + +#define SOLO_CAP_CH_SCALE(ch) (0x0440+((ch)*4)) +#define SOLO_CAP_CH_COMP_ENA_E(ch) (0x0480+((ch)*4)) +#define SOLO_CAP_CH_INTV(ch) (0x04C0+((ch)*4)) +#define SOLO_CAP_CH_INTV_E(ch) (0x0500+((ch)*4)) + + +#define SOLO_VE_CFG0 0x0610 +#define SOLO_VE_TWO_PAGE_MODE BIT(31) +#define SOLO_VE_INTR_CTRL(n) ((n)<<24) +#define SOLO_VE_BLOCK_SIZE(n) ((n)<<16) +#define SOLO_VE_BLOCK_BASE(n) ((n)<<0) + +#define SOLO_VE_CFG1 0x0614 +#define SOLO_VE_BYTE_ALIGN(n) ((n)<<24) +#define SOLO_VE_INSERT_INDEX BIT(18) +#define SOLO_VE_MOTION_MODE(n) ((n)<<16) +#define SOLO_VE_MOTION_BASE(n) ((n)<<0) +#define SOLO_VE_MPEG_SIZE_H(n) ((n)<<28) /* 6110 Only */ +#define SOLO_VE_JPEG_SIZE_H(n) ((n)<<20) /* 6110 Only */ +#define SOLO_VE_INSERT_INDEX_JPEG BIT(19) /* 6110 Only */ + +#define SOLO_VE_WMRK_POLY 0x061C +#define SOLO_VE_VMRK_INIT_KEY 0x0620 +#define SOLO_VE_WMRK_STRL 0x0624 +#define SOLO_VE_ENCRYP_POLY 0x0628 +#define SOLO_VE_ENCRYP_INIT 0x062C +#define SOLO_VE_ATTR 0x0630 +#define SOLO_VE_LITTLE_ENDIAN BIT(31) +#define SOLO_COMP_ATTR_RN BIT(30) +#define SOLO_COMP_ATTR_FCODE(n) ((n)<<27) +#define SOLO_COMP_TIME_INC(n) ((n)<<25) +#define SOLO_COMP_TIME_WIDTH(n) ((n)<<21) +#define SOLO_DCT_INTERVAL(n) ((n)<<16) +#define SOLO_VE_COMPT_MOT 0x0634 /* 6110 Only */ + +#define SOLO_VE_STATE(n) (0x0640+((n)*4)) + +#define SOLO_VE_JPEG_QP_TBL 0x0670 +#define SOLO_VE_JPEG_QP_CH_L 0x0674 +#define SOLO_VE_JPEG_QP_CH_H 0x0678 +#define SOLO_VE_JPEG_CFG 0x067C +#define SOLO_VE_JPEG_CTRL 0x0680 +#define SOLO_VE_CODE_ENCRYPT 0x0684 /* 6110 Only */ +#define SOLO_VE_JPEG_CFG1 0x0688 /* 6110 Only */ +#define SOLO_VE_WMRK_ENABLE 0x068C /* 6110 Only */ +#define SOLO_VE_OSD_CH 0x0690 +#define SOLO_VE_OSD_BASE 0x0694 +#define SOLO_VE_OSD_CLR 0x0698 +#define SOLO_VE_OSD_OPT 0x069C +#define SOLO_VE_OSD_V_DOUBLE BIT(16) /* 6110 Only */ +#define SOLO_VE_OSD_H_SHADOW BIT(15) +#define SOLO_VE_OSD_V_SHADOW BIT(14) +#define SOLO_VE_OSD_H_OFFSET(n) ((n & 0x7f)<<7) +#define SOLO_VE_OSD_V_OFFSET(n) (n & 0x7f) + +#define SOLO_VE_CH_INTL(ch) (0x0700+((ch)*4)) +#define SOLO_VE_CH_MOT(ch) (0x0740+((ch)*4)) +#define SOLO_VE_CH_QP(ch) (0x0780+((ch)*4)) +#define SOLO_VE_CH_QP_E(ch) (0x07C0+((ch)*4)) +#define SOLO_VE_CH_GOP(ch) (0x0800+((ch)*4)) +#define SOLO_VE_CH_GOP_E(ch) (0x0840+((ch)*4)) +#define SOLO_VE_CH_REF_BASE(ch) (0x0880+((ch)*4)) +#define SOLO_VE_CH_REF_BASE_E(ch) (0x08C0+((ch)*4)) + +#define SOLO_VE_MPEG4_QUE(n) (0x0A00+((n)*8)) +#define SOLO_VE_JPEG_QUE(n) (0x0A04+((n)*8)) + +#define SOLO_VD_CFG0 0x0900 +#define SOLO_VD_CFG_NO_WRITE_NO_WINDOW BIT(24) +#define SOLO_VD_CFG_BUSY_WIAT_CODE BIT(23) +#define SOLO_VD_CFG_BUSY_WIAT_REF BIT(22) +#define SOLO_VD_CFG_BUSY_WIAT_RES BIT(21) +#define SOLO_VD_CFG_BUSY_WIAT_MS BIT(20) +#define SOLO_VD_CFG_SINGLE_MODE BIT(18) +#define SOLO_VD_CFG_SCAL_MANUAL BIT(17) +#define SOLO_VD_CFG_USER_PAGE_CTRL BIT(16) +#define SOLO_VD_CFG_LITTLE_ENDIAN BIT(15) +#define SOLO_VD_CFG_START_FI BIT(14) +#define SOLO_VD_CFG_ERR_LOCK BIT(13) +#define SOLO_VD_CFG_ERR_INT_ENA BIT(12) +#define SOLO_VD_CFG_TIME_WIDTH(n) ((n)<<8) +#define SOLO_VD_CFG_DCT_INTERVAL(n) ((n)<<0) + +#define SOLO_VD_CFG1 0x0904 + +#define SOLO_VD_DEINTERLACE 0x0908 +#define SOLO_VD_DEINTERLACE_THRESHOLD(n) ((n)<<8) +#define SOLO_VD_DEINTERLACE_EDGE_VALUE(n) ((n)<<0) + +#define SOLO_VD_CODE_ADR 0x090C + +#define SOLO_VD_CTRL 0x0910 +#define SOLO_VD_OPER_ON BIT(31) +#define SOLO_VD_MAX_ITEM(n) ((n)<<0) + +#define SOLO_VD_STATUS0 0x0920 +#define SOLO_VD_STATUS0_INTR_ACK BIT(22) +#define SOLO_VD_STATUS0_INTR_EMPTY BIT(21) +#define SOLO_VD_STATUS0_INTR_ERR BIT(20) + +#define SOLO_VD_STATUS1 0x0924 + +#define SOLO_VD_IDX0 0x0930 +#define SOLO_VD_IDX_INTERLACE BIT(30) +#define SOLO_VD_IDX_CHANNEL(n) ((n)<<24) +#define SOLO_VD_IDX_SIZE(n) ((n)<<0) + +#define SOLO_VD_IDX1 0x0934 +#define SOLO_VD_IDX_SRC_SCALE(n) ((n)<<28) +#define SOLO_VD_IDX_WINDOW(n) ((n)<<24) +#define SOLO_VD_IDX_DEINTERLACE BIT(16) +#define SOLO_VD_IDX_H_BLOCK(n) ((n)<<8) +#define SOLO_VD_IDX_V_BLOCK(n) ((n)<<0) + +#define SOLO_VD_IDX2 0x0938 +#define SOLO_VD_IDX_REF_BASE_SIDE BIT(31) +#define SOLO_VD_IDX_REF_BASE(n) (((n)>>16)&0xffff) + +#define SOLO_VD_IDX3 0x093C +#define SOLO_VD_IDX_DISP_SCALE(n) ((n)<<28) +#define SOLO_VD_IDX_INTERLACE_WR BIT(27) +#define SOLO_VD_IDX_INTERPOL BIT(26) +#define SOLO_VD_IDX_HOR2X BIT(25) +#define SOLO_VD_IDX_OFFSET_X(n) ((n)<<12) +#define SOLO_VD_IDX_OFFSET_Y(n) ((n)<<0) + +#define SOLO_VD_IDX4 0x0940 +#define SOLO_VD_IDX_DEC_WR_PAGE(n) ((n)<<8) +#define SOLO_VD_IDX_DISP_RD_PAGE(n) ((n)<<0) + +#define SOLO_VD_WR_PAGE(n) (0x03F0 + ((n) * 4)) + + +#define SOLO_GPIO_CONFIG_0 0x0B00 +#define SOLO_GPIO_CONFIG_1 0x0B04 +#define SOLO_GPIO_DATA_OUT 0x0B08 +#define SOLO_GPIO_DATA_IN 0x0B0C +#define SOLO_GPIO_INT_ACK_STA 0x0B10 +#define SOLO_GPIO_INT_ENA 0x0B14 +#define SOLO_GPIO_INT_CFG_0 0x0B18 +#define SOLO_GPIO_INT_CFG_1 0x0B1C + + +#define SOLO_IIC_CFG 0x0B20 +#define SOLO_IIC_ENABLE BIT(8) +#define SOLO_IIC_PRESCALE(n) ((n)<<0) + +#define SOLO_IIC_CTRL 0x0B24 +#define SOLO_IIC_AUTO_CLEAR BIT(20) +#define SOLO_IIC_STATE_RX_ACK BIT(19) +#define SOLO_IIC_STATE_BUSY BIT(18) +#define SOLO_IIC_STATE_SIG_ERR BIT(17) +#define SOLO_IIC_STATE_TRNS BIT(16) +#define SOLO_IIC_CH_SET(n) ((n)<<5) +#define SOLO_IIC_ACK_EN BIT(4) +#define SOLO_IIC_START BIT(3) +#define SOLO_IIC_STOP BIT(2) +#define SOLO_IIC_READ BIT(1) +#define SOLO_IIC_WRITE BIT(0) + +#define SOLO_IIC_TXD 0x0B28 +#define SOLO_IIC_RXD 0x0B2C + +/* + * UART REGISTER + */ +#define SOLO_UART_CONTROL(n) (0x0BA0 + ((n)*0x20)) +#define SOLO_UART_CLK_DIV(n) ((n)<<24) +#define SOLO_MODEM_CTRL_EN BIT(20) +#define SOLO_PARITY_ERROR_DROP BIT(18) +#define SOLO_IRQ_ERR_EN BIT(17) +#define SOLO_IRQ_RX_EN BIT(16) +#define SOLO_IRQ_TX_EN BIT(15) +#define SOLO_RX_EN BIT(14) +#define SOLO_TX_EN BIT(13) +#define SOLO_UART_HALF_DUPLEX BIT(12) +#define SOLO_UART_LOOPBACK BIT(11) + +#define SOLO_BAUDRATE_230400 ((0<<9)|(0<<6)) +#define SOLO_BAUDRATE_115200 ((0<<9)|(1<<6)) +#define SOLO_BAUDRATE_57600 ((0<<9)|(2<<6)) +#define SOLO_BAUDRATE_38400 ((0<<9)|(3<<6)) +#define SOLO_BAUDRATE_19200 ((0<<9)|(4<<6)) +#define SOLO_BAUDRATE_9600 ((0<<9)|(5<<6)) +#define SOLO_BAUDRATE_4800 ((0<<9)|(6<<6)) +#define SOLO_BAUDRATE_2400 ((1<<9)|(6<<6)) +#define SOLO_BAUDRATE_1200 ((2<<9)|(6<<6)) +#define SOLO_BAUDRATE_300 ((3<<9)|(6<<6)) + +#define SOLO_UART_DATA_BIT_8 (3<<4) +#define SOLO_UART_DATA_BIT_7 (2<<4) +#define SOLO_UART_DATA_BIT_6 (1<<4) +#define SOLO_UART_DATA_BIT_5 (0<<4) + +#define SOLO_UART_STOP_BIT_1 (0<<2) +#define SOLO_UART_STOP_BIT_2 (1<<2) + +#define SOLO_UART_PARITY_NONE (0<<0) +#define SOLO_UART_PARITY_EVEN (2<<0) +#define SOLO_UART_PARITY_ODD (3<<0) + +#define SOLO_UART_STATUS(n) (0x0BA4 + ((n)*0x20)) +#define SOLO_UART_CTS BIT(15) +#define SOLO_UART_RX_BUSY BIT(14) +#define SOLO_UART_OVERRUN BIT(13) +#define SOLO_UART_FRAME_ERR BIT(12) +#define SOLO_UART_PARITY_ERR BIT(11) +#define SOLO_UART_TX_BUSY BIT(5) + +#define SOLO_UART_RX_BUFF_CNT(stat) (((stat)>>6) & 0x1f) +#define SOLO_UART_RX_BUFF_SIZE 8 +#define SOLO_UART_TX_BUFF_CNT(stat) (((stat)>>0) & 0x1f) +#define SOLO_UART_TX_BUFF_SIZE 8 + +#define SOLO_UART_TX_DATA(n) (0x0BA8 + ((n)*0x20)) +#define SOLO_UART_TX_DATA_PUSH BIT(8) +#define SOLO_UART_RX_DATA(n) (0x0BAC + ((n)*0x20)) +#define SOLO_UART_RX_DATA_POP BIT(8) + +#define SOLO_TIMER_CLOCK_NUM 0x0be0 +#define SOLO_TIMER_USEC 0x0be8 +#define SOLO_TIMER_SEC 0x0bec +#define SOLO_TIMER_USEC_LSB 0x0d20 /* 6110 Only */ + +#define SOLO_AUDIO_CONTROL 0x0D00 +#define SOLO_AUDIO_ENABLE BIT(31) +#define SOLO_AUDIO_MASTER_MODE BIT(30) +#define SOLO_AUDIO_I2S_MODE BIT(29) +#define SOLO_AUDIO_I2S_LR_SWAP BIT(27) +#define SOLO_AUDIO_I2S_8BIT BIT(26) +#define SOLO_AUDIO_I2S_MULTI(n) ((n)<<24) +#define SOLO_AUDIO_MIX_9TO0 BIT(23) +#define SOLO_AUDIO_DEC_9TO0_VOL(n) ((n)<<20) +#define SOLO_AUDIO_MIX_19TO10 BIT(19) +#define SOLO_AUDIO_DEC_19TO10_VOL(n) ((n)<<16) +#define SOLO_AUDIO_MODE(n) ((n)<<0) +#define SOLO_AUDIO_SAMPLE 0x0D04 +#define SOLO_AUDIO_EE_MODE_ON BIT(30) +#define SOLO_AUDIO_EE_ENC_CH(ch) ((ch)<<25) +#define SOLO_AUDIO_BITRATE(n) ((n)<<16) +#define SOLO_AUDIO_CLK_DIV(n) ((n)<<0) +#define SOLO_AUDIO_FDMA_INTR 0x0D08 +#define SOLO_AUDIO_FDMA_INTERVAL(n) ((n)<<19) +#define SOLO_AUDIO_INTR_ORDER(n) ((n)<<16) +#define SOLO_AUDIO_FDMA_BASE(n) ((n)<<0) +#define SOLO_AUDIO_EVOL_0 0x0D0C +#define SOLO_AUDIO_EVOL_1 0x0D10 +#define SOLO_AUDIO_EVOL(ch, value) ((value)<<((ch)%10)) +#define SOLO_AUDIO_STA 0x0D14 + +/* + * Watchdog configuration + */ +#define SOLO_WATCHDOG 0x0be4 +#define SOLO_WATCHDOG_SET(status, sec) (status << 8 | (sec & 0xff)) + +#endif /* __SOLO6X10_REGISTERS_H */ diff --git a/drivers/media/pci/solo6x10/solo6x10-tw28.c b/drivers/media/pci/solo6x10/solo6x10-tw28.c new file mode 100644 index 000000000..8f53946c6 --- /dev/null +++ b/drivers/media/pci/solo6x10/solo6x10-tw28.c @@ -0,0 +1,869 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2010-2013 Bluecherry, LLC + * + * Original author: + * Ben Collins + * + * Additional work by: + * John Brooks + */ + +#include +#include + +#include "solo6x10.h" +#include "solo6x10-tw28.h" + +#define DEFAULT_HDELAY_NTSC (32 - 8) +#define DEFAULT_HACTIVE_NTSC (720 + 16) +#define DEFAULT_VDELAY_NTSC (7 - 2) +#define DEFAULT_VACTIVE_NTSC (240 + 4) + +#define DEFAULT_HDELAY_PAL (32 + 4) +#define DEFAULT_HACTIVE_PAL (864-DEFAULT_HDELAY_PAL) +#define DEFAULT_VDELAY_PAL (6) +#define DEFAULT_VACTIVE_PAL (312-DEFAULT_VDELAY_PAL) + + +static const u8 tbl_tw2864_ntsc_template[] = { + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x00 */ + 0x12, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x10 */ + 0x12, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x20 */ + 0x12, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x30 */ + 0x12, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x40 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x70 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA3, 0x00, + 0x00, 0x02, 0x00, 0xcc, 0x00, 0x80, 0x44, 0x50, /* 0x80 */ + 0x22, 0x01, 0xd8, 0xbc, 0xb8, 0x44, 0x38, 0x00, + 0x00, 0x78, 0x72, 0x3e, 0x14, 0xa5, 0xe4, 0x05, /* 0x90 */ + 0x00, 0x28, 0x44, 0x44, 0xa0, 0x88, 0x5a, 0x01, + 0x08, 0x08, 0x08, 0x08, 0x1a, 0x1a, 0x1a, 0x1a, /* 0xa0 */ + 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x44, + 0x44, 0x0a, 0x00, 0xff, 0xef, 0xef, 0xef, 0xef, /* 0xb0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */ + 0x00, 0x00, 0x55, 0x00, 0xb1, 0xe4, 0x40, 0x00, + 0x77, 0x77, 0x01, 0x13, 0x57, 0x9b, 0xdf, 0x20, /* 0xd0 */ + 0x64, 0xa8, 0xec, 0xc1, 0x0f, 0x11, 0x11, 0x81, + 0x00, 0xe0, 0xbb, 0xbb, 0x00, 0x11, 0x00, 0x00, /* 0xe0 */ + 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0x11, 0x00, + 0x83, 0xb5, 0x09, 0x78, 0x85, 0x00, 0x01, 0x20, /* 0xf0 */ + 0x64, 0x11, 0x40, 0xaf, 0xff, 0x00, 0x00, 0x00, +}; + +static const u8 tbl_tw2864_pal_template[] = { + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x00 */ + 0x18, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x01, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x10 */ + 0x18, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x01, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x20 */ + 0x18, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x01, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x30 */ + 0x18, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x01, 0x7f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x40 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x70 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA3, 0x00, + 0x00, 0x02, 0x00, 0xcc, 0x00, 0x80, 0x44, 0x50, /* 0x80 */ + 0x22, 0x01, 0xd8, 0xbc, 0xb8, 0x44, 0x38, 0x00, + 0x00, 0x78, 0x72, 0x3e, 0x14, 0xa5, 0xe4, 0x05, /* 0x90 */ + 0x00, 0x28, 0x44, 0x44, 0xa0, 0x90, 0x5a, 0x01, + 0x0a, 0x0a, 0x0a, 0x0a, 0x1a, 0x1a, 0x1a, 0x1a, /* 0xa0 */ + 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x44, + 0x44, 0x0a, 0x00, 0xff, 0xef, 0xef, 0xef, 0xef, /* 0xb0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */ + 0x00, 0x00, 0x55, 0x00, 0xb1, 0xe4, 0x40, 0x00, + 0x77, 0x77, 0x01, 0x13, 0x57, 0x9b, 0xdf, 0x20, /* 0xd0 */ + 0x64, 0xa8, 0xec, 0xc1, 0x0f, 0x11, 0x11, 0x81, + 0x00, 0xe0, 0xbb, 0xbb, 0x00, 0x11, 0x00, 0x00, /* 0xe0 */ + 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0x11, 0x00, + 0x83, 0xb5, 0x09, 0x00, 0xa0, 0x00, 0x01, 0x20, /* 0xf0 */ + 0x64, 0x11, 0x40, 0xaf, 0xff, 0x00, 0x00, 0x00, +}; + +static const u8 tbl_tw2865_ntsc_template[] = { + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x00 */ + 0x12, 0xff, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x10 */ + 0x12, 0xff, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x20 */ + 0x12, 0xff, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0xf0, 0x70, 0x48, 0x80, 0x80, 0x00, 0x02, /* 0x30 */ + 0x12, 0xff, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x00, 0x90, 0x68, 0x00, 0x38, 0x80, 0x80, /* 0x40 */ + 0x80, 0x80, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x45, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, + 0x08, 0x00, 0x00, 0x01, 0xf1, 0x03, 0xEF, 0x03, /* 0x70 */ + 0xE9, 0x03, 0xD9, 0x15, 0x15, 0xE4, 0xA3, 0x80, + 0x00, 0x02, 0x00, 0xCC, 0x00, 0x80, 0x44, 0x50, /* 0x80 */ + 0x22, 0x01, 0xD8, 0xBC, 0xB8, 0x44, 0x38, 0x00, + 0x00, 0x78, 0x44, 0x3D, 0x14, 0xA5, 0xE0, 0x05, /* 0x90 */ + 0x00, 0x28, 0x44, 0x44, 0xA0, 0x90, 0x52, 0x13, + 0x08, 0x08, 0x08, 0x08, 0x1A, 0x1A, 0x1B, 0x1A, /* 0xa0 */ + 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, 0x44, + 0x44, 0x4A, 0x00, 0xFF, 0xEF, 0xEF, 0xEF, 0xEF, /* 0xb0 */ + 0xFF, 0xE7, 0xE9, 0xE9, 0xEB, 0xFF, 0xD6, 0xD8, + 0xD8, 0xD7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */ + 0x00, 0x00, 0x55, 0x00, 0xE4, 0x39, 0x00, 0x80, + 0x77, 0x77, 0x03, 0x20, 0x57, 0x9b, 0xdf, 0x31, /* 0xd0 */ + 0x64, 0xa8, 0xec, 0xd1, 0x0f, 0x11, 0x11, 0x81, + 0x10, 0xC0, 0xAA, 0xAA, 0x00, 0x11, 0x00, 0x00, /* 0xe0 */ + 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0x11, 0x00, + 0x83, 0xB5, 0x09, 0x78, 0x85, 0x00, 0x01, 0x20, /* 0xf0 */ + 0x64, 0x51, 0x40, 0xaf, 0xFF, 0xF0, 0x00, 0xC0, +}; + +static const u8 tbl_tw2865_pal_template[] = { + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x00 */ + 0x11, 0xff, 0x01, 0xc3, 0x00, 0x00, 0x01, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x10 */ + 0x11, 0xff, 0x01, 0xc3, 0x00, 0x00, 0x01, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x20 */ + 0x11, 0xff, 0x01, 0xc3, 0x00, 0x00, 0x01, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x30 */ + 0x11, 0xff, 0x01, 0xc3, 0x00, 0x00, 0x01, 0x7f, + 0x00, 0x94, 0x90, 0x48, 0x00, 0x38, 0x7F, 0x80, /* 0x40 */ + 0x80, 0x80, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x45, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, + 0x08, 0x00, 0x00, 0x01, 0xf1, 0x03, 0xEF, 0x03, /* 0x70 */ + 0xEA, 0x03, 0xD9, 0x15, 0x15, 0xE4, 0xA3, 0x80, + 0x00, 0x02, 0x00, 0xCC, 0x00, 0x80, 0x44, 0x50, /* 0x80 */ + 0x22, 0x01, 0xD8, 0xBC, 0xB8, 0x44, 0x38, 0x00, + 0x00, 0x78, 0x44, 0x3D, 0x14, 0xA5, 0xE0, 0x05, /* 0x90 */ + 0x00, 0x28, 0x44, 0x44, 0xA0, 0x90, 0x52, 0x13, + 0x08, 0x08, 0x08, 0x08, 0x1A, 0x1A, 0x1A, 0x1A, /* 0xa0 */ + 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, 0x44, + 0x44, 0x4A, 0x00, 0xFF, 0xEF, 0xEF, 0xEF, 0xEF, /* 0xb0 */ + 0xFF, 0xE7, 0xE9, 0xE9, 0xE9, 0xFF, 0xD7, 0xD8, + 0xD9, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */ + 0x00, 0x00, 0x55, 0x00, 0xE4, 0x39, 0x00, 0x80, + 0x77, 0x77, 0x03, 0x20, 0x57, 0x9b, 0xdf, 0x31, /* 0xd0 */ + 0x64, 0xa8, 0xec, 0xd1, 0x0f, 0x11, 0x11, 0x81, + 0x10, 0xC0, 0xAA, 0xAA, 0x00, 0x11, 0x00, 0x00, /* 0xe0 */ + 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0x11, 0x00, + 0x83, 0xB5, 0x09, 0x00, 0xA0, 0x00, 0x01, 0x20, /* 0xf0 */ + 0x64, 0x51, 0x40, 0xaf, 0xFF, 0xF0, 0x00, 0xC0, +}; + +#define is_tw286x(__solo, __id) (!((__solo)->tw2815 & (1U << (__id)))) + +static u8 tw_readbyte(struct solo_dev *solo_dev, int chip_id, u8 tw6x_off, + u8 tw_off) +{ + if (is_tw286x(solo_dev, chip_id)) + return solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_id), + tw6x_off); + else + return solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_id), + tw_off); +} + +static void tw_writebyte(struct solo_dev *solo_dev, int chip_id, + u8 tw6x_off, u8 tw_off, u8 val) +{ + if (is_tw286x(solo_dev, chip_id)) + solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_id), + tw6x_off, val); + else + solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_id), + tw_off, val); +} + +static void tw_write_and_verify(struct solo_dev *solo_dev, u8 addr, u8 off, + u8 val) +{ + int i; + + for (i = 0; i < 5; i++) { + u8 rval = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, addr, off); + + if (rval == val) + return; + + solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, addr, off, val); + msleep_interruptible(1); + } + +/* printk("solo6x10/tw28: Error writing register: %02x->%02x [%02x]\n", */ +/* addr, off, val); */ +} + +static int tw2865_setup(struct solo_dev *solo_dev, u8 dev_addr) +{ + u8 tbl_tw2865_common[256]; + int i; + + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_PAL) + memcpy(tbl_tw2865_common, tbl_tw2865_pal_template, + sizeof(tbl_tw2865_common)); + else + memcpy(tbl_tw2865_common, tbl_tw2865_ntsc_template, + sizeof(tbl_tw2865_common)); + + /* ALINK Mode */ + if (solo_dev->nr_chans == 4) { + tbl_tw2865_common[0xd2] = 0x01; + tbl_tw2865_common[0xcf] = 0x00; + } else if (solo_dev->nr_chans == 8) { + tbl_tw2865_common[0xd2] = 0x02; + if (dev_addr == TW_CHIP_OFFSET_ADDR(1)) + tbl_tw2865_common[0xcf] = 0x80; + } else if (solo_dev->nr_chans == 16) { + tbl_tw2865_common[0xd2] = 0x03; + if (dev_addr == TW_CHIP_OFFSET_ADDR(1)) + tbl_tw2865_common[0xcf] = 0x83; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(2)) + tbl_tw2865_common[0xcf] = 0x83; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(3)) + tbl_tw2865_common[0xcf] = 0x80; + } + + for (i = 0; i < 0xff; i++) { + /* Skip read only registers */ + switch (i) { + case 0xb8 ... 0xc1: + case 0xc4 ... 0xc7: + case 0xfd: + continue; + } + switch (i & ~0x30) { + case 0x00: + case 0x0c ... 0x0d: + continue; + } + + tw_write_and_verify(solo_dev, dev_addr, i, + tbl_tw2865_common[i]); + } + + return 0; +} + +static int tw2864_setup(struct solo_dev *solo_dev, u8 dev_addr) +{ + u8 tbl_tw2864_common[256]; + int i; + + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_PAL) + memcpy(tbl_tw2864_common, tbl_tw2864_pal_template, + sizeof(tbl_tw2864_common)); + else + memcpy(tbl_tw2864_common, tbl_tw2864_ntsc_template, + sizeof(tbl_tw2864_common)); + + if (solo_dev->tw2865 == 0) { + /* IRQ Mode */ + if (solo_dev->nr_chans == 4) { + tbl_tw2864_common[0xd2] = 0x01; + tbl_tw2864_common[0xcf] = 0x00; + } else if (solo_dev->nr_chans == 8) { + tbl_tw2864_common[0xd2] = 0x02; + if (dev_addr == TW_CHIP_OFFSET_ADDR(0)) + tbl_tw2864_common[0xcf] = 0x43; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(1)) + tbl_tw2864_common[0xcf] = 0x40; + } else if (solo_dev->nr_chans == 16) { + tbl_tw2864_common[0xd2] = 0x03; + if (dev_addr == TW_CHIP_OFFSET_ADDR(0)) + tbl_tw2864_common[0xcf] = 0x43; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(1)) + tbl_tw2864_common[0xcf] = 0x43; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(2)) + tbl_tw2864_common[0xcf] = 0x43; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(3)) + tbl_tw2864_common[0xcf] = 0x40; + } + } else { + /* ALINK Mode. Assumes that the first tw28xx is a + * 2865 and these are in cascade. */ + for (i = 0; i <= 4; i++) + tbl_tw2864_common[0x08 | i << 4] = 0x12; + + if (solo_dev->nr_chans == 8) { + tbl_tw2864_common[0xd2] = 0x02; + if (dev_addr == TW_CHIP_OFFSET_ADDR(1)) + tbl_tw2864_common[0xcf] = 0x80; + } else if (solo_dev->nr_chans == 16) { + tbl_tw2864_common[0xd2] = 0x03; + if (dev_addr == TW_CHIP_OFFSET_ADDR(1)) + tbl_tw2864_common[0xcf] = 0x83; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(2)) + tbl_tw2864_common[0xcf] = 0x83; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(3)) + tbl_tw2864_common[0xcf] = 0x80; + } + } + + for (i = 0; i < 0xff; i++) { + /* Skip read only registers */ + switch (i) { + case 0xb8 ... 0xc1: + case 0xfd: + continue; + } + switch (i & ~0x30) { + case 0x00: + case 0x0c: + case 0x0d: + continue; + } + + tw_write_and_verify(solo_dev, dev_addr, i, + tbl_tw2864_common[i]); + } + + return 0; +} + +static int tw2815_setup(struct solo_dev *solo_dev, u8 dev_addr) +{ + u8 tbl_ntsc_tw2815_common[] = { + 0x00, 0xc8, 0x20, 0xd0, 0x06, 0xf0, 0x08, 0x80, + 0x80, 0x80, 0x80, 0x02, 0x06, 0x00, 0x11, + }; + + u8 tbl_pal_tw2815_common[] = { + 0x00, 0x88, 0x20, 0xd0, 0x05, 0x20, 0x28, 0x80, + 0x80, 0x80, 0x80, 0x82, 0x06, 0x00, 0x11, + }; + + u8 tbl_tw2815_sfr[] = { + 0x00, 0x00, 0x00, 0xc0, 0x45, 0xa0, 0xd0, 0x2f, /* 0x00 */ + 0x64, 0x80, 0x80, 0x82, 0x82, 0x00, 0x00, 0x00, + 0x00, 0x0f, 0x05, 0x00, 0x00, 0x80, 0x06, 0x00, /* 0x10 */ + 0x00, 0x00, 0x00, 0xff, 0x8f, 0x00, 0x00, 0x00, + 0x88, 0x88, 0xc0, 0x00, 0x20, 0x64, 0xa8, 0xec, /* 0x20 */ + 0x31, 0x75, 0xb9, 0xfd, 0x00, 0x00, 0x88, 0x88, + 0x88, 0x11, 0x00, 0x88, 0x88, 0x00, /* 0x30 */ + }; + u8 *tbl_tw2815_common; + int i; + int ch; + + tbl_ntsc_tw2815_common[0x06] = 0; + + /* Horizontal Delay Control */ + tbl_ntsc_tw2815_common[0x02] = DEFAULT_HDELAY_NTSC & 0xff; + tbl_ntsc_tw2815_common[0x06] |= 0x03 & (DEFAULT_HDELAY_NTSC >> 8); + + /* Horizontal Active Control */ + tbl_ntsc_tw2815_common[0x03] = DEFAULT_HACTIVE_NTSC & 0xff; + tbl_ntsc_tw2815_common[0x06] |= + ((0x03 & (DEFAULT_HACTIVE_NTSC >> 8)) << 2); + + /* Vertical Delay Control */ + tbl_ntsc_tw2815_common[0x04] = DEFAULT_VDELAY_NTSC & 0xff; + tbl_ntsc_tw2815_common[0x06] |= + ((0x01 & (DEFAULT_VDELAY_NTSC >> 8)) << 4); + + /* Vertical Active Control */ + tbl_ntsc_tw2815_common[0x05] = DEFAULT_VACTIVE_NTSC & 0xff; + tbl_ntsc_tw2815_common[0x06] |= + ((0x01 & (DEFAULT_VACTIVE_NTSC >> 8)) << 5); + + tbl_pal_tw2815_common[0x06] = 0; + + /* Horizontal Delay Control */ + tbl_pal_tw2815_common[0x02] = DEFAULT_HDELAY_PAL & 0xff; + tbl_pal_tw2815_common[0x06] |= 0x03 & (DEFAULT_HDELAY_PAL >> 8); + + /* Horizontal Active Control */ + tbl_pal_tw2815_common[0x03] = DEFAULT_HACTIVE_PAL & 0xff; + tbl_pal_tw2815_common[0x06] |= + ((0x03 & (DEFAULT_HACTIVE_PAL >> 8)) << 2); + + /* Vertical Delay Control */ + tbl_pal_tw2815_common[0x04] = DEFAULT_VDELAY_PAL & 0xff; + tbl_pal_tw2815_common[0x06] |= + ((0x01 & (DEFAULT_VDELAY_PAL >> 8)) << 4); + + /* Vertical Active Control */ + tbl_pal_tw2815_common[0x05] = DEFAULT_VACTIVE_PAL & 0xff; + tbl_pal_tw2815_common[0x06] |= + ((0x01 & (DEFAULT_VACTIVE_PAL >> 8)) << 5); + + tbl_tw2815_common = + (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) ? + tbl_ntsc_tw2815_common : tbl_pal_tw2815_common; + + /* Dual ITU-R BT.656 format */ + tbl_tw2815_common[0x0d] |= 0x04; + + /* Audio configuration */ + tbl_tw2815_sfr[0x62 - 0x40] &= ~(3 << 6); + + if (solo_dev->nr_chans == 4) { + tbl_tw2815_sfr[0x63 - 0x40] |= 1; + tbl_tw2815_sfr[0x62 - 0x40] |= 3 << 6; + } else if (solo_dev->nr_chans == 8) { + tbl_tw2815_sfr[0x63 - 0x40] |= 2; + if (dev_addr == TW_CHIP_OFFSET_ADDR(0)) + tbl_tw2815_sfr[0x62 - 0x40] |= 1 << 6; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(1)) + tbl_tw2815_sfr[0x62 - 0x40] |= 2 << 6; + } else if (solo_dev->nr_chans == 16) { + tbl_tw2815_sfr[0x63 - 0x40] |= 3; + if (dev_addr == TW_CHIP_OFFSET_ADDR(0)) + tbl_tw2815_sfr[0x62 - 0x40] |= 1 << 6; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(1)) + tbl_tw2815_sfr[0x62 - 0x40] |= 0 << 6; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(2)) + tbl_tw2815_sfr[0x62 - 0x40] |= 0 << 6; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(3)) + tbl_tw2815_sfr[0x62 - 0x40] |= 2 << 6; + } + + /* Output mode of R_ADATM pin (0 mixing, 1 record) */ + /* tbl_tw2815_sfr[0x63 - 0x40] |= 0 << 2; */ + + /* 8KHz, used to be 16KHz, but changed for remote client compat */ + tbl_tw2815_sfr[0x62 - 0x40] |= 0 << 2; + tbl_tw2815_sfr[0x6c - 0x40] |= 0 << 2; + + /* Playback of right channel */ + tbl_tw2815_sfr[0x6c - 0x40] |= 1 << 5; + + /* Reserved value (XXX ??) */ + tbl_tw2815_sfr[0x5c - 0x40] |= 1 << 5; + + /* Analog output gain and mix ratio playback on full */ + tbl_tw2815_sfr[0x70 - 0x40] |= 0xff; + /* Select playback audio and mute all except */ + tbl_tw2815_sfr[0x71 - 0x40] |= 0x10; + tbl_tw2815_sfr[0x6d - 0x40] |= 0x0f; + + /* End of audio configuration */ + + for (ch = 0; ch < 4; ch++) { + tbl_tw2815_common[0x0d] &= ~3; + switch (ch) { + case 0: + tbl_tw2815_common[0x0d] |= 0x21; + break; + case 1: + tbl_tw2815_common[0x0d] |= 0x20; + break; + case 2: + tbl_tw2815_common[0x0d] |= 0x23; + break; + case 3: + tbl_tw2815_common[0x0d] |= 0x22; + break; + } + + for (i = 0; i < 0x0f; i++) { + if (i == 0x00) + continue; /* read-only */ + solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, + dev_addr, (ch * 0x10) + i, + tbl_tw2815_common[i]); + } + } + + for (i = 0x40; i < 0x76; i++) { + /* Skip read-only and nop registers */ + if (i == 0x40 || i == 0x59 || i == 0x5a || + i == 0x5d || i == 0x5e || i == 0x5f) + continue; + + solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, dev_addr, i, + tbl_tw2815_sfr[i - 0x40]); + } + + return 0; +} + +#define FIRST_ACTIVE_LINE 0x0008 +#define LAST_ACTIVE_LINE 0x0102 + +static void saa712x_write_regs(struct solo_dev *dev, const u8 *vals, + int start, int n) +{ + for (; start < n; start++, vals++) { + /* Skip read-only registers */ + switch (start) { + /* case 0x00 ... 0x25: */ + case 0x2e ... 0x37: + case 0x60: + case 0x7d: + continue; + } + solo_i2c_writebyte(dev, SOLO_I2C_SAA, 0x46, start, *vals); + } +} + +#define SAA712x_reg7c (0x80 | ((LAST_ACTIVE_LINE & 0x100) >> 2) \ + | ((FIRST_ACTIVE_LINE & 0x100) >> 4)) + +static void saa712x_setup(struct solo_dev *dev) +{ + const int reg_start = 0x26; + static const u8 saa7128_regs_ntsc[] = { + /* :0x26 */ + 0x0d, 0x00, + /* :0x28 */ + 0x59, 0x1d, 0x75, 0x3f, 0x06, 0x3f, + /* :0x2e XXX: read-only */ + 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* :0x38 */ + 0x1a, 0x1a, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, + /* :0x40 */ + 0x00, 0x00, 0x00, 0x68, 0x10, 0x97, 0x4c, 0x18, + 0x9b, 0x93, 0x9f, 0xff, 0x7c, 0x34, 0x3f, 0x3f, + /* :0x50 */ + 0x3f, 0x83, 0x83, 0x80, 0x0d, 0x0f, 0xc3, 0x06, + 0x02, 0x80, 0x71, 0x77, 0xa7, 0x67, 0x66, 0x2e, + /* :0x60 */ + 0x7b, 0x11, 0x4f, 0x1f, 0x7c, 0xf0, 0x21, 0x77, + 0x41, 0x88, 0x41, 0x52, 0xed, 0x10, 0x10, 0x00, + /* :0x70 */ + 0x41, 0xc3, 0x00, 0x3e, 0xb8, 0x02, 0x00, 0x00, + 0x00, 0x00, FIRST_ACTIVE_LINE, LAST_ACTIVE_LINE & 0xff, + SAA712x_reg7c, 0x00, 0xff, 0xff, + }, saa7128_regs_pal[] = { + /* :0x26 */ + 0x0d, 0x00, + /* :0x28 */ + 0xe1, 0x1d, 0x75, 0x3f, 0x06, 0x3f, + /* :0x2e XXX: read-only */ + 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* :0x38 */ + 0x1a, 0x1a, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, + /* :0x40 */ + 0x00, 0x00, 0x00, 0x68, 0x10, 0x97, 0x4c, 0x18, + 0x9b, 0x93, 0x9f, 0xff, 0x7c, 0x34, 0x3f, 0x3f, + /* :0x50 */ + 0x3f, 0x83, 0x83, 0x80, 0x0d, 0x0f, 0xc3, 0x06, + 0x02, 0x80, 0x0f, 0x77, 0xa7, 0x67, 0x66, 0x2e, + /* :0x60 */ + 0x7b, 0x02, 0x35, 0xcb, 0x8a, 0x09, 0x2a, 0x77, + 0x41, 0x88, 0x41, 0x52, 0xf1, 0x10, 0x20, 0x00, + /* :0x70 */ + 0x41, 0xc3, 0x00, 0x3e, 0xb8, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x12, 0x30, + SAA712x_reg7c | 0x40, 0x00, 0xff, 0xff, + }; + + if (dev->video_type == SOLO_VO_FMT_TYPE_PAL) + saa712x_write_regs(dev, saa7128_regs_pal, reg_start, + sizeof(saa7128_regs_pal)); + else + saa712x_write_regs(dev, saa7128_regs_ntsc, reg_start, + sizeof(saa7128_regs_ntsc)); +} + +int solo_tw28_init(struct solo_dev *solo_dev) +{ + int i; + u8 value; + + solo_dev->tw28_cnt = 0; + + /* Detect techwell chip type(s) */ + for (i = 0; i < solo_dev->nr_chans / 4; i++) { + value = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(i), 0xFF); + + switch (value >> 3) { + case 0x18: + solo_dev->tw2865 |= 1 << i; + solo_dev->tw28_cnt++; + break; + case 0x0c: + case 0x0d: + solo_dev->tw2864 |= 1 << i; + solo_dev->tw28_cnt++; + break; + default: + value = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(i), + 0x59); + if ((value >> 3) == 0x04) { + solo_dev->tw2815 |= 1 << i; + solo_dev->tw28_cnt++; + } + } + } + + if (solo_dev->tw28_cnt != (solo_dev->nr_chans >> 2)) { + dev_err(&solo_dev->pdev->dev, + "Could not initialize any techwell chips\n"); + return -EINVAL; + } + + saa712x_setup(solo_dev); + + for (i = 0; i < solo_dev->tw28_cnt; i++) { + if ((solo_dev->tw2865 & (1 << i))) + tw2865_setup(solo_dev, TW_CHIP_OFFSET_ADDR(i)); + else if ((solo_dev->tw2864 & (1 << i))) + tw2864_setup(solo_dev, TW_CHIP_OFFSET_ADDR(i)); + else + tw2815_setup(solo_dev, TW_CHIP_OFFSET_ADDR(i)); + } + + return 0; +} + +/* + * We accessed the video status signal in the Techwell chip through + * iic/i2c because the video status reported by register REG_VI_STATUS1 + * (address 0x012C) of the SOLO6010 chip doesn't give the correct video + * status signal values. + */ +int tw28_get_video_status(struct solo_dev *solo_dev, u8 ch) +{ + u8 val, chip_num; + + /* Get the right chip and on-chip channel */ + chip_num = ch / 4; + ch %= 4; + + val = tw_readbyte(solo_dev, chip_num, TW286x_AV_STAT_ADDR, + TW_AV_STAT_ADDR) & 0x0f; + + return val & (1 << ch) ? 1 : 0; +} + +#if 0 +/* Status of audio from up to 4 techwell chips are combined into 1 variable. + * See techwell datasheet for details. */ +u16 tw28_get_audio_status(struct solo_dev *solo_dev) +{ + u8 val; + u16 status = 0; + int i; + + for (i = 0; i < solo_dev->tw28_cnt; i++) { + val = (tw_readbyte(solo_dev, i, TW286x_AV_STAT_ADDR, + TW_AV_STAT_ADDR) & 0xf0) >> 4; + status |= val << (i * 4); + } + + return status; +} +#endif + +bool tw28_has_sharpness(struct solo_dev *solo_dev, u8 ch) +{ + return is_tw286x(solo_dev, ch / 4); +} + +int tw28_set_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch, + s32 val) +{ + char sval; + u8 chip_num; + + /* Get the right chip and on-chip channel */ + chip_num = ch / 4; + ch %= 4; + + if (chip_num >= TW_NUM_CHIP) + return -EINVAL; + + if (val > 255 || val < 0) + return -ERANGE; + + switch (ctrl) { + case V4L2_CID_SHARPNESS: + /* Only 286x has sharpness */ + if (is_tw286x(solo_dev, chip_num)) { + u8 v = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_num), + TW286x_SHARPNESS(chip_num)); + v &= 0xf0; + v |= val; + solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_num), + TW286x_SHARPNESS(chip_num), v); + } else { + return -EINVAL; + } + break; + + case V4L2_CID_HUE: + if (is_tw286x(solo_dev, chip_num)) + sval = val - 128; + else + sval = (char)val; + tw_writebyte(solo_dev, chip_num, TW286x_HUE_ADDR(ch), + TW_HUE_ADDR(ch), sval); + + break; + + case V4L2_CID_SATURATION: + /* 286x chips have a U and V component for saturation */ + if (is_tw286x(solo_dev, chip_num)) { + solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_num), + TW286x_SATURATIONU_ADDR(ch), val); + } + tw_writebyte(solo_dev, chip_num, TW286x_SATURATIONV_ADDR(ch), + TW_SATURATION_ADDR(ch), val); + + break; + + case V4L2_CID_CONTRAST: + tw_writebyte(solo_dev, chip_num, TW286x_CONTRAST_ADDR(ch), + TW_CONTRAST_ADDR(ch), val); + break; + + case V4L2_CID_BRIGHTNESS: + if (is_tw286x(solo_dev, chip_num)) + sval = val - 128; + else + sval = (char)val; + tw_writebyte(solo_dev, chip_num, TW286x_BRIGHTNESS_ADDR(ch), + TW_BRIGHTNESS_ADDR(ch), sval); + + break; + default: + return -EINVAL; + } + + return 0; +} + +int tw28_get_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch, + s32 *val) +{ + u8 rval, chip_num; + + /* Get the right chip and on-chip channel */ + chip_num = ch / 4; + ch %= 4; + + if (chip_num >= TW_NUM_CHIP) + return -EINVAL; + + switch (ctrl) { + case V4L2_CID_SHARPNESS: + /* Only 286x has sharpness */ + if (is_tw286x(solo_dev, chip_num)) { + rval = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_num), + TW286x_SHARPNESS(chip_num)); + *val = rval & 0x0f; + } else + *val = 0; + break; + case V4L2_CID_HUE: + rval = tw_readbyte(solo_dev, chip_num, TW286x_HUE_ADDR(ch), + TW_HUE_ADDR(ch)); + if (is_tw286x(solo_dev, chip_num)) + *val = (s32)((char)rval) + 128; + else + *val = rval; + break; + case V4L2_CID_SATURATION: + *val = tw_readbyte(solo_dev, chip_num, + TW286x_SATURATIONU_ADDR(ch), + TW_SATURATION_ADDR(ch)); + break; + case V4L2_CID_CONTRAST: + *val = tw_readbyte(solo_dev, chip_num, + TW286x_CONTRAST_ADDR(ch), + TW_CONTRAST_ADDR(ch)); + break; + case V4L2_CID_BRIGHTNESS: + rval = tw_readbyte(solo_dev, chip_num, + TW286x_BRIGHTNESS_ADDR(ch), + TW_BRIGHTNESS_ADDR(ch)); + if (is_tw286x(solo_dev, chip_num)) + *val = (s32)((char)rval) + 128; + else + *val = rval; + break; + default: + return -EINVAL; + } + + return 0; +} + +#if 0 +/* + * For audio output volume, the output channel is only 1. In this case we + * don't need to offset TW_CHIP_OFFSET_ADDR. The TW_CHIP_OFFSET_ADDR used + * is the base address of the techwell chip. + */ +void tw2815_Set_AudioOutVol(struct solo_dev *solo_dev, unsigned int u_val) +{ + unsigned int val; + unsigned int chip_num; + + chip_num = (solo_dev->nr_chans - 1) / 4; + + val = tw_readbyte(solo_dev, chip_num, TW286x_AUDIO_OUTPUT_VOL_ADDR, + TW_AUDIO_OUTPUT_VOL_ADDR); + + u_val = (val & 0x0f) | (u_val << 4); + + tw_writebyte(solo_dev, chip_num, TW286x_AUDIO_OUTPUT_VOL_ADDR, + TW_AUDIO_OUTPUT_VOL_ADDR, u_val); +} +#endif + +u8 tw28_get_audio_gain(struct solo_dev *solo_dev, u8 ch) +{ + u8 val; + u8 chip_num; + + /* Get the right chip and on-chip channel */ + chip_num = ch / 4; + ch %= 4; + + val = tw_readbyte(solo_dev, chip_num, + TW286x_AUDIO_INPUT_GAIN_ADDR(ch), + TW_AUDIO_INPUT_GAIN_ADDR(ch)); + + return (ch % 2) ? (val >> 4) : (val & 0x0f); +} + +void tw28_set_audio_gain(struct solo_dev *solo_dev, u8 ch, u8 val) +{ + u8 old_val; + u8 chip_num; + + /* Get the right chip and on-chip channel */ + chip_num = ch / 4; + ch %= 4; + + old_val = tw_readbyte(solo_dev, chip_num, + TW286x_AUDIO_INPUT_GAIN_ADDR(ch), + TW_AUDIO_INPUT_GAIN_ADDR(ch)); + + val = (old_val & ((ch % 2) ? 0x0f : 0xf0)) | + ((ch % 2) ? (val << 4) : val); + + tw_writebyte(solo_dev, chip_num, TW286x_AUDIO_INPUT_GAIN_ADDR(ch), + TW_AUDIO_INPUT_GAIN_ADDR(ch), val); +} diff --git a/drivers/media/pci/solo6x10/solo6x10-tw28.h b/drivers/media/pci/solo6x10/solo6x10-tw28.h new file mode 100644 index 000000000..4a8ede313 --- /dev/null +++ b/drivers/media/pci/solo6x10/solo6x10-tw28.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC + * + * Original author: + * Ben Collins + * + * Additional work by: + * John Brooks + */ + +#ifndef __SOLO6X10_TW28_H +#define __SOLO6X10_TW28_H + +#include "solo6x10.h" + +#define TW_NUM_CHIP 4 +#define TW_BASE_ADDR 0x28 +#define TW_CHIP_OFFSET_ADDR(n) (TW_BASE_ADDR + (n)) + +/* tw2815 */ +#define TW_AV_STAT_ADDR 0x5a +#define TW_HUE_ADDR(n) (0x07 | ((n) << 4)) +#define TW_SATURATION_ADDR(n) (0x08 | ((n) << 4)) +#define TW_CONTRAST_ADDR(n) (0x09 | ((n) << 4)) +#define TW_BRIGHTNESS_ADDR(n) (0x0a | ((n) << 4)) +#define TW_AUDIO_OUTPUT_VOL_ADDR 0x70 +#define TW_AUDIO_INPUT_GAIN_ADDR(n) (0x60 + ((n > 1) ? 1 : 0)) + +/* tw286x */ +#define TW286x_AV_STAT_ADDR 0xfd +#define TW286x_HUE_ADDR(n) (0x06 | ((n) << 4)) +#define TW286x_SATURATIONU_ADDR(n) (0x04 | ((n) << 4)) +#define TW286x_SATURATIONV_ADDR(n) (0x05 | ((n) << 4)) +#define TW286x_CONTRAST_ADDR(n) (0x02 | ((n) << 4)) +#define TW286x_BRIGHTNESS_ADDR(n) (0x01 | ((n) << 4)) +#define TW286x_SHARPNESS(n) (0x03 | ((n) << 4)) +#define TW286x_AUDIO_OUTPUT_VOL_ADDR 0xdf +#define TW286x_AUDIO_INPUT_GAIN_ADDR(n) (0xD0 + ((n > 1) ? 1 : 0)) + +int solo_tw28_init(struct solo_dev *solo_dev); + +int tw28_set_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch, s32 val); +int tw28_get_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch, s32 *val); +bool tw28_has_sharpness(struct solo_dev *solo_dev, u8 ch); + +u8 tw28_get_audio_gain(struct solo_dev *solo_dev, u8 ch); +void tw28_set_audio_gain(struct solo_dev *solo_dev, u8 ch, u8 val); +int tw28_get_video_status(struct solo_dev *solo_dev, u8 ch); + +#if 0 +unsigned int tw2815_get_audio_status(struct SOLO *solo); +void tw2815_Set_AudioOutVol(struct SOLO *solo, unsigned int u_val); +#endif + +#endif /* __SOLO6X10_TW28_H */ diff --git a/drivers/media/pci/solo6x10/solo6x10-v4l2-enc.c b/drivers/media/pci/solo6x10/solo6x10-v4l2-enc.c new file mode 100644 index 000000000..91b5c4161 --- /dev/null +++ b/drivers/media/pci/solo6x10/solo6x10-v4l2-enc.c @@ -0,0 +1,1392 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2010-2013 Bluecherry, LLC + * + * Original author: + * Ben Collins + * + * Additional work by: + * John Brooks + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "solo6x10.h" +#include "solo6x10-tw28.h" +#include "solo6x10-jpeg.h" + +#define MIN_VID_BUFFERS 2 +#define FRAME_BUF_SIZE (400 * 1024) +#define MP4_QS 16 +#define DMA_ALIGN 4096 + +/* 6010 M4V */ +static u8 vop_6010_ntsc_d1[] = { + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20, + 0x02, 0x48, 0x1d, 0xc0, 0x00, 0x40, 0x00, 0x40, + 0x00, 0x40, 0x00, 0x80, 0x00, 0x97, 0x53, 0x04, + 0x1f, 0x4c, 0x58, 0x10, 0xf0, 0x71, 0x18, 0x3f, +}; + +static u8 vop_6010_ntsc_cif[] = { + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20, + 0x02, 0x48, 0x1d, 0xc0, 0x00, 0x40, 0x00, 0x40, + 0x00, 0x40, 0x00, 0x80, 0x00, 0x97, 0x53, 0x04, + 0x1f, 0x4c, 0x2c, 0x10, 0x78, 0x51, 0x18, 0x3f, +}; + +static u8 vop_6010_pal_d1[] = { + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20, + 0x02, 0x48, 0x15, 0xc0, 0x00, 0x40, 0x00, 0x40, + 0x00, 0x40, 0x00, 0x80, 0x00, 0x97, 0x53, 0x04, + 0x1f, 0x4c, 0x58, 0x11, 0x20, 0x71, 0x18, 0x3f, +}; + +static u8 vop_6010_pal_cif[] = { + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20, + 0x02, 0x48, 0x15, 0xc0, 0x00, 0x40, 0x00, 0x40, + 0x00, 0x40, 0x00, 0x80, 0x00, 0x97, 0x53, 0x04, + 0x1f, 0x4c, 0x2c, 0x10, 0x90, 0x51, 0x18, 0x3f, +}; + +/* 6110 h.264 */ +static u8 vop_6110_ntsc_d1[] = { + 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e, + 0x9a, 0x74, 0x05, 0x81, 0xec, 0x80, 0x00, 0x00, + 0x00, 0x01, 0x68, 0xce, 0x32, 0x28, 0x00, 0x00, +}; + +static u8 vop_6110_ntsc_cif[] = { + 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e, + 0x9a, 0x74, 0x0b, 0x0f, 0xc8, 0x00, 0x00, 0x00, + 0x01, 0x68, 0xce, 0x32, 0x28, 0x00, 0x00, 0x00, +}; + +static u8 vop_6110_pal_d1[] = { + 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e, + 0x9a, 0x74, 0x05, 0x80, 0x93, 0x20, 0x00, 0x00, + 0x00, 0x01, 0x68, 0xce, 0x32, 0x28, 0x00, 0x00, +}; + +static u8 vop_6110_pal_cif[] = { + 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e, + 0x9a, 0x74, 0x0b, 0x04, 0xb2, 0x00, 0x00, 0x00, + 0x01, 0x68, 0xce, 0x32, 0x28, 0x00, 0x00, 0x00, +}; + +typedef __le32 vop_header[16]; + +struct solo_enc_buf { + enum solo_enc_types type; + const vop_header *vh; + int motion; +}; + +static int solo_is_motion_on(struct solo_enc_dev *solo_enc) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + + return (solo_dev->motion_mask >> solo_enc->ch) & 1; +} + +static int solo_motion_detected(struct solo_enc_dev *solo_enc) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + unsigned long flags; + u32 ch_mask = 1 << solo_enc->ch; + int ret = 0; + + spin_lock_irqsave(&solo_enc->motion_lock, flags); + if (solo_reg_read(solo_dev, SOLO_VI_MOT_STATUS) & ch_mask) { + solo_reg_write(solo_dev, SOLO_VI_MOT_CLEAR, ch_mask); + ret = 1; + } + spin_unlock_irqrestore(&solo_enc->motion_lock, flags); + + return ret; +} + +static void solo_motion_toggle(struct solo_enc_dev *solo_enc, int on) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + u32 mask = 1 << solo_enc->ch; + unsigned long flags; + + spin_lock_irqsave(&solo_enc->motion_lock, flags); + + if (on) + solo_dev->motion_mask |= mask; + else + solo_dev->motion_mask &= ~mask; + + solo_reg_write(solo_dev, SOLO_VI_MOT_CLEAR, mask); + + solo_reg_write(solo_dev, SOLO_VI_MOT_ADR, + SOLO_VI_MOTION_EN(solo_dev->motion_mask) | + (SOLO_MOTION_EXT_ADDR(solo_dev) >> 16)); + + spin_unlock_irqrestore(&solo_enc->motion_lock, flags); +} + +void solo_update_mode(struct solo_enc_dev *solo_enc) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + int vop_len; + u8 *vop; + + solo_enc->interlaced = (solo_enc->mode & 0x08) ? 1 : 0; + solo_enc->bw_weight = max(solo_dev->fps / solo_enc->interval, 1); + + if (solo_enc->mode == SOLO_ENC_MODE_CIF) { + solo_enc->width = solo_dev->video_hsize >> 1; + solo_enc->height = solo_dev->video_vsize; + if (solo_dev->type == SOLO_DEV_6110) { + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) { + vop = vop_6110_ntsc_cif; + vop_len = sizeof(vop_6110_ntsc_cif); + } else { + vop = vop_6110_pal_cif; + vop_len = sizeof(vop_6110_pal_cif); + } + } else { + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) { + vop = vop_6010_ntsc_cif; + vop_len = sizeof(vop_6010_ntsc_cif); + } else { + vop = vop_6010_pal_cif; + vop_len = sizeof(vop_6010_pal_cif); + } + } + } else { + solo_enc->width = solo_dev->video_hsize; + solo_enc->height = solo_dev->video_vsize << 1; + solo_enc->bw_weight <<= 2; + if (solo_dev->type == SOLO_DEV_6110) { + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) { + vop = vop_6110_ntsc_d1; + vop_len = sizeof(vop_6110_ntsc_d1); + } else { + vop = vop_6110_pal_d1; + vop_len = sizeof(vop_6110_pal_d1); + } + } else { + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) { + vop = vop_6010_ntsc_d1; + vop_len = sizeof(vop_6010_ntsc_d1); + } else { + vop = vop_6010_pal_d1; + vop_len = sizeof(vop_6010_pal_d1); + } + } + } + + memcpy(solo_enc->vop, vop, vop_len); + + /* Some fixups for 6010/M4V */ + if (solo_dev->type == SOLO_DEV_6010) { + u16 fps = solo_dev->fps * 1000; + u16 interval = solo_enc->interval * 1000; + + vop = solo_enc->vop; + + /* Frame rate and interval */ + vop[22] = fps >> 4; + vop[23] = ((fps << 4) & 0xf0) | 0x0c + | ((interval >> 13) & 0x3); + vop[24] = (interval >> 5) & 0xff; + vop[25] = ((interval << 3) & 0xf8) | 0x04; + } + + solo_enc->vop_len = vop_len; + + /* Now handle the jpeg header */ + vop = solo_enc->jpeg_header; + vop[SOF0_START + 5] = 0xff & (solo_enc->height >> 8); + vop[SOF0_START + 6] = 0xff & solo_enc->height; + vop[SOF0_START + 7] = 0xff & (solo_enc->width >> 8); + vop[SOF0_START + 8] = 0xff & solo_enc->width; + + memcpy(vop + DQT_START, + jpeg_dqt[solo_g_jpeg_qp(solo_dev, solo_enc->ch)], DQT_LEN); +} + +static int solo_enc_on(struct solo_enc_dev *solo_enc) +{ + u8 ch = solo_enc->ch; + struct solo_dev *solo_dev = solo_enc->solo_dev; + u8 interval; + + solo_update_mode(solo_enc); + + /* Make sure to do a bandwidth check */ + if (solo_enc->bw_weight > solo_dev->enc_bw_remain) + return -EBUSY; + solo_enc->sequence = 0; + solo_dev->enc_bw_remain -= solo_enc->bw_weight; + + if (solo_enc->type == SOLO_ENC_TYPE_EXT) + solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(ch), 1); + + /* Disable all encoding for this channel */ + solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(ch), 0); + + /* Common for both std and ext encoding */ + solo_reg_write(solo_dev, SOLO_VE_CH_INTL(ch), + solo_enc->interlaced ? 1 : 0); + + if (solo_enc->interlaced) + interval = solo_enc->interval - 1; + else + interval = solo_enc->interval; + + /* Standard encoding only */ + solo_reg_write(solo_dev, SOLO_VE_CH_GOP(ch), solo_enc->gop); + solo_reg_write(solo_dev, SOLO_VE_CH_QP(ch), solo_enc->qp); + solo_reg_write(solo_dev, SOLO_CAP_CH_INTV(ch), interval); + + /* Extended encoding only */ + solo_reg_write(solo_dev, SOLO_VE_CH_GOP_E(ch), solo_enc->gop); + solo_reg_write(solo_dev, SOLO_VE_CH_QP_E(ch), solo_enc->qp); + solo_reg_write(solo_dev, SOLO_CAP_CH_INTV_E(ch), interval); + + /* Enables the standard encoder */ + solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(ch), solo_enc->mode); + + return 0; +} + +static void solo_enc_off(struct solo_enc_dev *solo_enc) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + + solo_dev->enc_bw_remain += solo_enc->bw_weight; + + solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(solo_enc->ch), 0); + solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(solo_enc->ch), 0); +} + +static int enc_get_mpeg_dma(struct solo_dev *solo_dev, dma_addr_t dma, + unsigned int off, unsigned int size) +{ + int ret; + + if (off > SOLO_MP4E_EXT_SIZE(solo_dev)) + return -EINVAL; + + /* Single shot */ + if (off + size <= SOLO_MP4E_EXT_SIZE(solo_dev)) { + return solo_p2m_dma_t(solo_dev, 0, dma, + SOLO_MP4E_EXT_ADDR(solo_dev) + off, size, + 0, 0); + } + + /* Buffer wrap */ + ret = solo_p2m_dma_t(solo_dev, 0, dma, + SOLO_MP4E_EXT_ADDR(solo_dev) + off, + SOLO_MP4E_EXT_SIZE(solo_dev) - off, 0, 0); + + if (!ret) { + ret = solo_p2m_dma_t(solo_dev, 0, + dma + SOLO_MP4E_EXT_SIZE(solo_dev) - off, + SOLO_MP4E_EXT_ADDR(solo_dev), + size + off - SOLO_MP4E_EXT_SIZE(solo_dev), 0, 0); + } + + return ret; +} + +/* Build a descriptor queue out of an SG list and send it to the P2M for + * processing. */ +static int solo_send_desc(struct solo_enc_dev *solo_enc, int skip, + struct sg_table *vbuf, int off, int size, + unsigned int base, unsigned int base_size) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct scatterlist *sg; + int i; + int ret; + + if (WARN_ON_ONCE(size > FRAME_BUF_SIZE)) + return -EINVAL; + + solo_enc->desc_count = 1; + + for_each_sg(vbuf->sgl, sg, vbuf->nents, i) { + struct solo_p2m_desc *desc; + dma_addr_t dma; + int len; + int left = base_size - off; + + desc = &solo_enc->desc_items[solo_enc->desc_count++]; + dma = sg_dma_address(sg); + len = sg_dma_len(sg); + + /* We assume this is smaller than the scatter size */ + BUG_ON(skip >= len); + if (skip) { + len -= skip; + dma += skip; + size -= skip; + skip = 0; + } + + len = min(len, size); + + if (len <= left) { + /* Single descriptor */ + solo_p2m_fill_desc(desc, 0, dma, base + off, + len, 0, 0); + } else { + /* Buffer wrap */ + /* XXX: Do these as separate DMA requests, to avoid + timeout errors triggered by awkwardly sized + descriptors. See + + */ + ret = solo_p2m_dma_t(solo_dev, 0, dma, base + off, + left, 0, 0); + if (ret) + return ret; + + ret = solo_p2m_dma_t(solo_dev, 0, dma + left, base, + len - left, 0, 0); + if (ret) + return ret; + + solo_enc->desc_count--; + } + + size -= len; + if (size <= 0) + break; + + off += len; + if (off >= base_size) + off -= base_size; + + /* Because we may use two descriptors per loop */ + if (solo_enc->desc_count >= (solo_enc->desc_nelts - 1)) { + ret = solo_p2m_dma_desc(solo_dev, solo_enc->desc_items, + solo_enc->desc_dma, + solo_enc->desc_count - 1); + if (ret) + return ret; + solo_enc->desc_count = 1; + } + } + + if (solo_enc->desc_count <= 1) + return 0; + + return solo_p2m_dma_desc(solo_dev, solo_enc->desc_items, + solo_enc->desc_dma, solo_enc->desc_count - 1); +} + +/* Extract values from VOP header - VE_STATUSxx */ +static inline __always_unused int vop_interlaced(const vop_header *vh) +{ + return (__le32_to_cpu((*vh)[0]) >> 30) & 1; +} + +static inline __always_unused u8 vop_channel(const vop_header *vh) +{ + return (__le32_to_cpu((*vh)[0]) >> 24) & 0x1F; +} + +static inline u8 vop_type(const vop_header *vh) +{ + return (__le32_to_cpu((*vh)[0]) >> 22) & 3; +} + +static inline u32 vop_mpeg_size(const vop_header *vh) +{ + return __le32_to_cpu((*vh)[0]) & 0xFFFFF; +} + +static inline u8 __always_unused vop_hsize(const vop_header *vh) +{ + return (__le32_to_cpu((*vh)[1]) >> 8) & 0xFF; +} + +static inline u8 __always_unused vop_vsize(const vop_header *vh) +{ + return __le32_to_cpu((*vh)[1]) & 0xFF; +} + +static inline u32 vop_mpeg_offset(const vop_header *vh) +{ + return __le32_to_cpu((*vh)[2]); +} + +static inline u32 vop_jpeg_offset(const vop_header *vh) +{ + return __le32_to_cpu((*vh)[3]); +} + +static inline u32 vop_jpeg_size(const vop_header *vh) +{ + return __le32_to_cpu((*vh)[4]) & 0xFFFFF; +} + +static inline u32 __always_unused vop_sec(const vop_header *vh) +{ + return __le32_to_cpu((*vh)[5]); +} + +static inline __always_unused u32 vop_usec(const vop_header *vh) +{ + return __le32_to_cpu((*vh)[6]); +} + +static int solo_fill_jpeg(struct solo_enc_dev *solo_enc, + struct vb2_buffer *vb, const vop_header *vh) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, 0); + int frame_size; + + vbuf->flags |= V4L2_BUF_FLAG_KEYFRAME; + + if (vb2_plane_size(vb, 0) < vop_jpeg_size(vh) + solo_enc->jpeg_len) + return -EIO; + + frame_size = ALIGN(vop_jpeg_size(vh) + solo_enc->jpeg_len, DMA_ALIGN); + vb2_set_plane_payload(vb, 0, vop_jpeg_size(vh) + solo_enc->jpeg_len); + + return solo_send_desc(solo_enc, solo_enc->jpeg_len, sgt, + vop_jpeg_offset(vh) - SOLO_JPEG_EXT_ADDR(solo_dev), + frame_size, SOLO_JPEG_EXT_ADDR(solo_dev), + SOLO_JPEG_EXT_SIZE(solo_dev)); +} + +static int solo_fill_mpeg(struct solo_enc_dev *solo_enc, + struct vb2_buffer *vb, const vop_header *vh) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, 0); + int frame_off, frame_size; + int skip = 0; + + if (vb2_plane_size(vb, 0) < vop_mpeg_size(vh)) + return -EIO; + + /* If this is a key frame, add extra header */ + vbuf->flags &= ~(V4L2_BUF_FLAG_KEYFRAME | V4L2_BUF_FLAG_PFRAME | + V4L2_BUF_FLAG_BFRAME); + if (!vop_type(vh)) { + skip = solo_enc->vop_len; + vbuf->flags |= V4L2_BUF_FLAG_KEYFRAME; + vb2_set_plane_payload(vb, 0, vop_mpeg_size(vh) + + solo_enc->vop_len); + } else { + vbuf->flags |= V4L2_BUF_FLAG_PFRAME; + vb2_set_plane_payload(vb, 0, vop_mpeg_size(vh)); + } + + /* Now get the actual mpeg payload */ + frame_off = (vop_mpeg_offset(vh) - SOLO_MP4E_EXT_ADDR(solo_dev) + + sizeof(*vh)) % SOLO_MP4E_EXT_SIZE(solo_dev); + frame_size = ALIGN(vop_mpeg_size(vh) + skip, DMA_ALIGN); + + return solo_send_desc(solo_enc, skip, sgt, frame_off, frame_size, + SOLO_MP4E_EXT_ADDR(solo_dev), + SOLO_MP4E_EXT_SIZE(solo_dev)); +} + +static int solo_enc_fillbuf(struct solo_enc_dev *solo_enc, + struct vb2_buffer *vb, struct solo_enc_buf *enc_buf) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + const vop_header *vh = enc_buf->vh; + int ret; + + switch (solo_enc->fmt) { + case V4L2_PIX_FMT_MPEG4: + case V4L2_PIX_FMT_H264: + ret = solo_fill_mpeg(solo_enc, vb, vh); + break; + default: /* V4L2_PIX_FMT_MJPEG */ + ret = solo_fill_jpeg(solo_enc, vb, vh); + break; + } + + if (!ret) { + vbuf->sequence = solo_enc->sequence++; + vb->timestamp = ktime_get_ns(); + + /* Check for motion flags */ + if (solo_is_motion_on(solo_enc) && enc_buf->motion) { + struct v4l2_event ev = { + .type = V4L2_EVENT_MOTION_DET, + .u.motion_det = { + .flags + = V4L2_EVENT_MD_FL_HAVE_FRAME_SEQ, + .frame_sequence = vbuf->sequence, + .region_mask = enc_buf->motion ? 1 : 0, + }, + }; + + v4l2_event_queue(solo_enc->vfd, &ev); + } + } + + vb2_buffer_done(vb, ret ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); + + return ret; +} + +static void solo_enc_handle_one(struct solo_enc_dev *solo_enc, + struct solo_enc_buf *enc_buf) +{ + struct solo_vb2_buf *vb; + unsigned long flags; + + mutex_lock(&solo_enc->lock); + if (solo_enc->type != enc_buf->type) + goto unlock; + + spin_lock_irqsave(&solo_enc->av_lock, flags); + if (list_empty(&solo_enc->vidq_active)) { + spin_unlock_irqrestore(&solo_enc->av_lock, flags); + goto unlock; + } + vb = list_first_entry(&solo_enc->vidq_active, struct solo_vb2_buf, + list); + list_del(&vb->list); + spin_unlock_irqrestore(&solo_enc->av_lock, flags); + + solo_enc_fillbuf(solo_enc, &vb->vb.vb2_buf, enc_buf); +unlock: + mutex_unlock(&solo_enc->lock); +} + +void solo_enc_v4l2_isr(struct solo_dev *solo_dev) +{ + wake_up_interruptible_all(&solo_dev->ring_thread_wait); +} + +static void solo_handle_ring(struct solo_dev *solo_dev) +{ + for (;;) { + struct solo_enc_dev *solo_enc; + struct solo_enc_buf enc_buf; + u32 mpeg_current, off; + u8 ch; + u8 cur_q; + + /* Check if the hardware has any new ones in the queue */ + cur_q = solo_reg_read(solo_dev, SOLO_VE_STATE(11)) & 0xff; + if (cur_q == solo_dev->enc_idx) + break; + + mpeg_current = solo_reg_read(solo_dev, + SOLO_VE_MPEG4_QUE(solo_dev->enc_idx)); + solo_dev->enc_idx = (solo_dev->enc_idx + 1) % MP4_QS; + + ch = (mpeg_current >> 24) & 0x1f; + off = mpeg_current & 0x00ffffff; + + if (ch >= SOLO_MAX_CHANNELS) { + ch -= SOLO_MAX_CHANNELS; + enc_buf.type = SOLO_ENC_TYPE_EXT; + } else + enc_buf.type = SOLO_ENC_TYPE_STD; + + solo_enc = solo_dev->v4l2_enc[ch]; + if (solo_enc == NULL) { + dev_err(&solo_dev->pdev->dev, + "Got spurious packet for channel %d\n", ch); + continue; + } + + /* FAIL... */ + if (enc_get_mpeg_dma(solo_dev, solo_dev->vh_dma, off, + sizeof(vop_header))) + continue; + + enc_buf.vh = solo_dev->vh_buf; + + /* Sanity check */ + if (vop_mpeg_offset(enc_buf.vh) != + SOLO_MP4E_EXT_ADDR(solo_dev) + off) + continue; + + if (solo_motion_detected(solo_enc)) + enc_buf.motion = 1; + else + enc_buf.motion = 0; + + solo_enc_handle_one(solo_enc, &enc_buf); + } +} + +static int solo_ring_thread(void *data) +{ + struct solo_dev *solo_dev = data; + DECLARE_WAITQUEUE(wait, current); + + set_freezable(); + add_wait_queue(&solo_dev->ring_thread_wait, &wait); + + for (;;) { + long timeout = schedule_timeout_interruptible(HZ); + + if (timeout == -ERESTARTSYS || kthread_should_stop()) + break; + solo_handle_ring(solo_dev); + try_to_freeze(); + } + + remove_wait_queue(&solo_dev->ring_thread_wait, &wait); + + return 0; +} + +static int solo_enc_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + sizes[0] = FRAME_BUF_SIZE; + *num_planes = 1; + + if (*num_buffers < MIN_VID_BUFFERS) + *num_buffers = MIN_VID_BUFFERS; + + return 0; +} + +static void solo_enc_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vb2_queue *vq = vb->vb2_queue; + struct solo_enc_dev *solo_enc = vb2_get_drv_priv(vq); + struct solo_vb2_buf *solo_vb = + container_of(vbuf, struct solo_vb2_buf, vb); + + spin_lock(&solo_enc->av_lock); + list_add_tail(&solo_vb->list, &solo_enc->vidq_active); + spin_unlock(&solo_enc->av_lock); +} + +static int solo_ring_start(struct solo_dev *solo_dev) +{ + solo_dev->ring_thread = kthread_run(solo_ring_thread, solo_dev, + SOLO6X10_NAME "_ring"); + if (IS_ERR(solo_dev->ring_thread)) { + int err = PTR_ERR(solo_dev->ring_thread); + + solo_dev->ring_thread = NULL; + return err; + } + + solo_irq_on(solo_dev, SOLO_IRQ_ENCODER); + + return 0; +} + +static void solo_ring_stop(struct solo_dev *solo_dev) +{ + if (solo_dev->ring_thread) { + kthread_stop(solo_dev->ring_thread); + solo_dev->ring_thread = NULL; + } + + solo_irq_off(solo_dev, SOLO_IRQ_ENCODER); +} + +static int solo_enc_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct solo_enc_dev *solo_enc = vb2_get_drv_priv(q); + + return solo_enc_on(solo_enc); +} + +static void solo_enc_stop_streaming(struct vb2_queue *q) +{ + struct solo_enc_dev *solo_enc = vb2_get_drv_priv(q); + unsigned long flags; + + spin_lock_irqsave(&solo_enc->av_lock, flags); + solo_enc_off(solo_enc); + while (!list_empty(&solo_enc->vidq_active)) { + struct solo_vb2_buf *buf = list_entry( + solo_enc->vidq_active.next, + struct solo_vb2_buf, list); + + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&solo_enc->av_lock, flags); +} + +static void solo_enc_buf_finish(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct solo_enc_dev *solo_enc = vb2_get_drv_priv(vb->vb2_queue); + struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, 0); + + switch (solo_enc->fmt) { + case V4L2_PIX_FMT_MPEG4: + case V4L2_PIX_FMT_H264: + if (vbuf->flags & V4L2_BUF_FLAG_KEYFRAME) + sg_copy_from_buffer(sgt->sgl, sgt->nents, + solo_enc->vop, solo_enc->vop_len); + break; + default: /* V4L2_PIX_FMT_MJPEG */ + sg_copy_from_buffer(sgt->sgl, sgt->nents, + solo_enc->jpeg_header, solo_enc->jpeg_len); + break; + } +} + +static const struct vb2_ops solo_enc_video_qops = { + .queue_setup = solo_enc_queue_setup, + .buf_queue = solo_enc_buf_queue, + .buf_finish = solo_enc_buf_finish, + .start_streaming = solo_enc_start_streaming, + .stop_streaming = solo_enc_stop_streaming, +}; + +static int solo_enc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + + strscpy(cap->driver, SOLO6X10_NAME, sizeof(cap->driver)); + snprintf(cap->card, sizeof(cap->card), "Softlogic 6x10 Enc %d", + solo_enc->ch); + return 0; +} + +static int solo_enc_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct solo_dev *solo_dev = solo_enc->solo_dev; + + if (input->index) + return -EINVAL; + + snprintf(input->name, sizeof(input->name), "Encoder %d", + solo_enc->ch + 1); + input->type = V4L2_INPUT_TYPE_CAMERA; + input->std = solo_enc->vfd->tvnorms; + + if (!tw28_get_video_status(solo_dev, solo_enc->ch)) + input->status = V4L2_IN_ST_NO_SIGNAL; + + return 0; +} + +static int solo_enc_set_input(struct file *file, void *priv, + unsigned int index) +{ + if (index) + return -EINVAL; + + return 0; +} + +static int solo_enc_get_input(struct file *file, void *priv, + unsigned int *index) +{ + *index = 0; + + return 0; +} + +static int solo_enc_enum_fmt_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + int dev_type = solo_enc->solo_dev->type; + + switch (f->index) { + case 0: + switch (dev_type) { + case SOLO_DEV_6010: + f->pixelformat = V4L2_PIX_FMT_MPEG4; + break; + case SOLO_DEV_6110: + f->pixelformat = V4L2_PIX_FMT_H264; + break; + } + break; + case 1: + f->pixelformat = V4L2_PIX_FMT_MJPEG; + break; + default: + return -EINVAL; + } + return 0; +} + +static inline int solo_valid_pixfmt(u32 pixfmt, int dev_type) +{ + return (pixfmt == V4L2_PIX_FMT_H264 && dev_type == SOLO_DEV_6110) + || (pixfmt == V4L2_PIX_FMT_MPEG4 && dev_type == SOLO_DEV_6010) + || pixfmt == V4L2_PIX_FMT_MJPEG ? 0 : -EINVAL; +} + +static int solo_enc_try_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct v4l2_pix_format *pix = &f->fmt.pix; + + if (solo_valid_pixfmt(pix->pixelformat, solo_dev->type)) + return -EINVAL; + + if (pix->width < solo_dev->video_hsize || + pix->height < solo_dev->video_vsize << 1) { + /* Default to CIF 1/2 size */ + pix->width = solo_dev->video_hsize >> 1; + pix->height = solo_dev->video_vsize; + } else { + /* Full frame */ + pix->width = solo_dev->video_hsize; + pix->height = solo_dev->video_vsize << 1; + } + + switch (pix->field) { + case V4L2_FIELD_NONE: + case V4L2_FIELD_INTERLACED: + break; + case V4L2_FIELD_ANY: + default: + pix->field = V4L2_FIELD_INTERLACED; + break; + } + + /* Just set these */ + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + pix->sizeimage = FRAME_BUF_SIZE; + pix->bytesperline = 0; + + return 0; +} + +static int solo_enc_set_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct v4l2_pix_format *pix = &f->fmt.pix; + int ret; + + if (vb2_is_busy(&solo_enc->vidq)) + return -EBUSY; + + ret = solo_enc_try_fmt_cap(file, priv, f); + if (ret) + return ret; + + if (pix->width == solo_dev->video_hsize) + solo_enc->mode = SOLO_ENC_MODE_D1; + else + solo_enc->mode = SOLO_ENC_MODE_CIF; + + /* This does not change the encoder at all */ + solo_enc->fmt = pix->pixelformat; + + /* + * More information is needed about these 'extended' types. As far + * as I can tell these are basically additional video streams with + * different MPEG encoding attributes that can run in parallel with + * the main stream. If so, then this should be implemented as a + * second video node. Abusing priv like this is certainly not the + * right approach. + if (pix->priv) + solo_enc->type = SOLO_ENC_TYPE_EXT; + */ + solo_update_mode(solo_enc); + return 0; +} + +static int solo_enc_get_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct v4l2_pix_format *pix = &f->fmt.pix; + + pix->width = solo_enc->width; + pix->height = solo_enc->height; + pix->pixelformat = solo_enc->fmt; + pix->field = solo_enc->interlaced ? V4L2_FIELD_INTERLACED : + V4L2_FIELD_NONE; + pix->sizeimage = FRAME_BUF_SIZE; + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int solo_enc_g_std(struct file *file, void *priv, v4l2_std_id *i) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct solo_dev *solo_dev = solo_enc->solo_dev; + + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) + *i = V4L2_STD_NTSC_M; + else + *i = V4L2_STD_PAL; + return 0; +} + +static int solo_enc_s_std(struct file *file, void *priv, v4l2_std_id std) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + + return solo_set_video_type(solo_enc->solo_dev, std & V4L2_STD_625_50); +} + +static int solo_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct solo_dev *solo_dev = solo_enc->solo_dev; + + if (solo_valid_pixfmt(fsize->pixel_format, solo_dev->type)) + return -EINVAL; + + switch (fsize->index) { + case 0: + fsize->discrete.width = solo_dev->video_hsize >> 1; + fsize->discrete.height = solo_dev->video_vsize; + break; + case 1: + fsize->discrete.width = solo_dev->video_hsize; + fsize->discrete.height = solo_dev->video_vsize << 1; + break; + default: + return -EINVAL; + } + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + + return 0; +} + +static int solo_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *fintv) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct solo_dev *solo_dev = solo_enc->solo_dev; + + if (solo_valid_pixfmt(fintv->pixel_format, solo_dev->type)) + return -EINVAL; + if (fintv->index) + return -EINVAL; + if ((fintv->width != solo_dev->video_hsize >> 1 || + fintv->height != solo_dev->video_vsize) && + (fintv->width != solo_dev->video_hsize || + fintv->height != solo_dev->video_vsize << 1)) + return -EINVAL; + + fintv->type = V4L2_FRMIVAL_TYPE_STEPWISE; + + fintv->stepwise.min.numerator = 1; + fintv->stepwise.min.denominator = solo_dev->fps; + + fintv->stepwise.max.numerator = 15; + fintv->stepwise.max.denominator = solo_dev->fps; + + fintv->stepwise.step.numerator = 1; + fintv->stepwise.step.denominator = solo_dev->fps; + + return 0; +} + +static int solo_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct v4l2_captureparm *cp = &sp->parm.capture; + + cp->capability = V4L2_CAP_TIMEPERFRAME; + cp->timeperframe.numerator = solo_enc->interval; + cp->timeperframe.denominator = solo_enc->solo_dev->fps; + cp->capturemode = 0; + /* XXX: Shouldn't we be able to get/set this from vb2? */ + cp->readbuffers = 2; + + return 0; +} + +static inline int calc_interval(u8 fps, u32 n, u32 d) +{ + if (!n || !d) + return 1; + if (d == fps) + return n; + n *= fps; + return min(15U, n / d + (n % d >= (fps >> 1))); +} + +static int solo_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct v4l2_fract *t = &sp->parm.capture.timeperframe; + u8 fps = solo_enc->solo_dev->fps; + + if (vb2_is_streaming(&solo_enc->vidq)) + return -EBUSY; + + solo_enc->interval = calc_interval(fps, t->numerator, t->denominator); + solo_update_mode(solo_enc); + return solo_g_parm(file, priv, sp); +} + +static int solo_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct solo_enc_dev *solo_enc = + container_of(ctrl->handler, struct solo_enc_dev, hdl); + struct solo_dev *solo_dev = solo_enc->solo_dev; + int err; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_CONTRAST: + case V4L2_CID_SATURATION: + case V4L2_CID_HUE: + case V4L2_CID_SHARPNESS: + return tw28_set_ctrl_val(solo_dev, ctrl->id, solo_enc->ch, + ctrl->val); + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + solo_enc->gop = ctrl->val; + solo_reg_write(solo_dev, SOLO_VE_CH_GOP(solo_enc->ch), solo_enc->gop); + solo_reg_write(solo_dev, SOLO_VE_CH_GOP_E(solo_enc->ch), solo_enc->gop); + return 0; + case V4L2_CID_MPEG_VIDEO_H264_MIN_QP: + solo_enc->qp = ctrl->val; + solo_reg_write(solo_dev, SOLO_VE_CH_QP(solo_enc->ch), solo_enc->qp); + solo_reg_write(solo_dev, SOLO_VE_CH_QP_E(solo_enc->ch), solo_enc->qp); + return 0; + case V4L2_CID_DETECT_MD_GLOBAL_THRESHOLD: + solo_enc->motion_thresh = ctrl->val << 8; + if (!solo_enc->motion_global || !solo_enc->motion_enabled) + return 0; + return solo_set_motion_threshold(solo_dev, solo_enc->ch, + solo_enc->motion_thresh); + case V4L2_CID_DETECT_MD_MODE: + solo_enc->motion_global = ctrl->val == V4L2_DETECT_MD_MODE_GLOBAL; + solo_enc->motion_enabled = ctrl->val > V4L2_DETECT_MD_MODE_DISABLED; + if (ctrl->val) { + if (solo_enc->motion_global) + err = solo_set_motion_threshold(solo_dev, solo_enc->ch, + solo_enc->motion_thresh); + else + err = solo_set_motion_block(solo_dev, solo_enc->ch, + solo_enc->md_thresholds->p_cur.p_u16); + if (err) + return err; + } + solo_motion_toggle(solo_enc, ctrl->val); + return 0; + case V4L2_CID_DETECT_MD_THRESHOLD_GRID: + if (solo_enc->motion_enabled && !solo_enc->motion_global) + return solo_set_motion_block(solo_dev, solo_enc->ch, + solo_enc->md_thresholds->p_new.p_u16); + break; + case V4L2_CID_OSD_TEXT: + strscpy(solo_enc->osd_text, ctrl->p_new.p_char, + sizeof(solo_enc->osd_text)); + return solo_osd_print(solo_enc); + default: + return -EINVAL; + } + + return 0; +} + +static int solo_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + + switch (sub->type) { + case V4L2_EVENT_MOTION_DET: + /* Allow for up to 30 events (1 second for NTSC) to be + * stored. */ + return v4l2_event_subscribe(fh, sub, 30, NULL); + default: + return v4l2_ctrl_subscribe_event(fh, sub); + } +} + +static const struct v4l2_file_operations solo_enc_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops solo_enc_ioctl_ops = { + .vidioc_querycap = solo_enc_querycap, + .vidioc_s_std = solo_enc_s_std, + .vidioc_g_std = solo_enc_g_std, + /* Input callbacks */ + .vidioc_enum_input = solo_enc_enum_input, + .vidioc_s_input = solo_enc_set_input, + .vidioc_g_input = solo_enc_get_input, + /* Video capture format callbacks */ + .vidioc_enum_fmt_vid_cap = solo_enc_enum_fmt_cap, + .vidioc_try_fmt_vid_cap = solo_enc_try_fmt_cap, + .vidioc_s_fmt_vid_cap = solo_enc_set_fmt_cap, + .vidioc_g_fmt_vid_cap = solo_enc_get_fmt_cap, + /* Streaming I/O */ + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + /* Frame size and interval */ + .vidioc_enum_framesizes = solo_enum_framesizes, + .vidioc_enum_frameintervals = solo_enum_frameintervals, + /* Video capture parameters */ + .vidioc_s_parm = solo_s_parm, + .vidioc_g_parm = solo_g_parm, + /* Logging and events */ + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = solo_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct video_device solo_enc_template = { + .name = SOLO6X10_NAME, + .fops = &solo_enc_fops, + .ioctl_ops = &solo_enc_ioctl_ops, + .minor = -1, + .release = video_device_release, + .tvnorms = V4L2_STD_NTSC_M | V4L2_STD_PAL, + .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING, +}; + +static const struct v4l2_ctrl_ops solo_ctrl_ops = { + .s_ctrl = solo_s_ctrl, +}; + +static const struct v4l2_ctrl_config solo_osd_text_ctrl = { + .ops = &solo_ctrl_ops, + .id = V4L2_CID_OSD_TEXT, + .name = "OSD Text", + .type = V4L2_CTRL_TYPE_STRING, + .max = OSD_TEXT_MAX, + .step = 1, +}; + +/* Motion Detection Threshold matrix */ +static const struct v4l2_ctrl_config solo_md_thresholds = { + .ops = &solo_ctrl_ops, + .id = V4L2_CID_DETECT_MD_THRESHOLD_GRID, + .dims = { SOLO_MOTION_SZ, SOLO_MOTION_SZ }, + .def = SOLO_DEF_MOT_THRESH, + .max = 65535, + .step = 1, +}; + +static struct solo_enc_dev *solo_enc_alloc(struct solo_dev *solo_dev, + u8 ch, unsigned nr) +{ + struct solo_enc_dev *solo_enc; + struct v4l2_ctrl_handler *hdl; + int ret; + + solo_enc = kzalloc_obj(*solo_enc); + if (!solo_enc) + return ERR_PTR(-ENOMEM); + + hdl = &solo_enc->hdl; + v4l2_ctrl_handler_init(hdl, 10); + v4l2_ctrl_new_std(hdl, &solo_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &solo_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &solo_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &solo_ctrl_ops, + V4L2_CID_HUE, 0, 255, 1, 128); + if (tw28_has_sharpness(solo_dev, ch)) + v4l2_ctrl_new_std(hdl, &solo_ctrl_ops, + V4L2_CID_SHARPNESS, 0, 15, 1, 0); + v4l2_ctrl_new_std(hdl, &solo_ctrl_ops, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, 1, 255, 1, solo_dev->fps); + v4l2_ctrl_new_std(hdl, &solo_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_MIN_QP, 0, 31, 1, SOLO_DEFAULT_QP); + v4l2_ctrl_new_std_menu(hdl, &solo_ctrl_ops, + V4L2_CID_DETECT_MD_MODE, + V4L2_DETECT_MD_MODE_THRESHOLD_GRID, 0, + V4L2_DETECT_MD_MODE_DISABLED); + v4l2_ctrl_new_std(hdl, &solo_ctrl_ops, + V4L2_CID_DETECT_MD_GLOBAL_THRESHOLD, 0, 0xff, 1, + SOLO_DEF_MOT_THRESH >> 8); + v4l2_ctrl_new_custom(hdl, &solo_osd_text_ctrl, NULL); + solo_enc->md_thresholds = + v4l2_ctrl_new_custom(hdl, &solo_md_thresholds, NULL); + if (hdl->error) { + ret = hdl->error; + goto hdl_free; + } + + solo_enc->solo_dev = solo_dev; + solo_enc->ch = ch; + mutex_init(&solo_enc->lock); + spin_lock_init(&solo_enc->av_lock); + INIT_LIST_HEAD(&solo_enc->vidq_active); + solo_enc->fmt = (solo_dev->type == SOLO_DEV_6010) ? + V4L2_PIX_FMT_MPEG4 : V4L2_PIX_FMT_H264; + solo_enc->type = SOLO_ENC_TYPE_STD; + + solo_enc->qp = SOLO_DEFAULT_QP; + solo_enc->gop = solo_dev->fps; + solo_enc->interval = 1; + solo_enc->mode = SOLO_ENC_MODE_CIF; + solo_enc->motion_global = true; + solo_enc->motion_thresh = SOLO_DEF_MOT_THRESH; + solo_enc->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + solo_enc->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; + solo_enc->vidq.ops = &solo_enc_video_qops; + solo_enc->vidq.mem_ops = &vb2_dma_sg_memops; + solo_enc->vidq.drv_priv = solo_enc; + solo_enc->vidq.gfp_flags = __GFP_DMA32 | __GFP_KSWAPD_RECLAIM; + solo_enc->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + solo_enc->vidq.buf_struct_size = sizeof(struct solo_vb2_buf); + solo_enc->vidq.lock = &solo_enc->lock; + solo_enc->vidq.dev = &solo_dev->pdev->dev; + ret = vb2_queue_init(&solo_enc->vidq); + if (ret) + goto hdl_free; + solo_update_mode(solo_enc); + + spin_lock_init(&solo_enc->motion_lock); + + /* Initialize this per encoder */ + solo_enc->jpeg_len = sizeof(jpeg_header); + memcpy(solo_enc->jpeg_header, jpeg_header, solo_enc->jpeg_len); + + solo_enc->desc_nelts = 32; + solo_enc->desc_items = dma_alloc_coherent(&solo_dev->pdev->dev, + sizeof(struct solo_p2m_desc) * + solo_enc->desc_nelts, + &solo_enc->desc_dma, + GFP_KERNEL); + ret = -ENOMEM; + if (solo_enc->desc_items == NULL) + goto hdl_free; + + solo_enc->vfd = video_device_alloc(); + if (!solo_enc->vfd) + goto pci_free; + + *solo_enc->vfd = solo_enc_template; + solo_enc->vfd->v4l2_dev = &solo_dev->v4l2_dev; + solo_enc->vfd->ctrl_handler = hdl; + solo_enc->vfd->queue = &solo_enc->vidq; + solo_enc->vfd->lock = &solo_enc->lock; + video_set_drvdata(solo_enc->vfd, solo_enc); + ret = video_register_device(solo_enc->vfd, VFL_TYPE_VIDEO, nr); + if (ret < 0) + goto vdev_release; + + snprintf(solo_enc->vfd->name, sizeof(solo_enc->vfd->name), + "%s-enc (%i/%i)", SOLO6X10_NAME, solo_dev->vfd->num, + solo_enc->vfd->num); + + return solo_enc; + +vdev_release: + video_device_release(solo_enc->vfd); +pci_free: + dma_free_coherent(&solo_enc->solo_dev->pdev->dev, + sizeof(struct solo_p2m_desc) * solo_enc->desc_nelts, + solo_enc->desc_items, solo_enc->desc_dma); +hdl_free: + v4l2_ctrl_handler_free(hdl); + kfree(solo_enc); + return ERR_PTR(ret); +} + +static void solo_enc_free(struct solo_enc_dev *solo_enc) +{ + if (solo_enc == NULL) + return; + + dma_free_coherent(&solo_enc->solo_dev->pdev->dev, + sizeof(struct solo_p2m_desc) * solo_enc->desc_nelts, + solo_enc->desc_items, solo_enc->desc_dma); + video_unregister_device(solo_enc->vfd); + v4l2_ctrl_handler_free(&solo_enc->hdl); + kfree(solo_enc); +} + +int solo_enc_v4l2_init(struct solo_dev *solo_dev, unsigned nr) +{ + int i; + + init_waitqueue_head(&solo_dev->ring_thread_wait); + + solo_dev->vh_size = sizeof(vop_header); + solo_dev->vh_buf = dma_alloc_coherent(&solo_dev->pdev->dev, + solo_dev->vh_size, + &solo_dev->vh_dma, GFP_KERNEL); + if (solo_dev->vh_buf == NULL) + return -ENOMEM; + + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_dev->v4l2_enc[i] = solo_enc_alloc(solo_dev, i, nr); + if (IS_ERR(solo_dev->v4l2_enc[i])) + break; + } + + if (i != solo_dev->nr_chans) { + int ret = PTR_ERR(solo_dev->v4l2_enc[i]); + + while (i--) + solo_enc_free(solo_dev->v4l2_enc[i]); + dma_free_coherent(&solo_dev->pdev->dev, solo_dev->vh_size, + solo_dev->vh_buf, solo_dev->vh_dma); + solo_dev->vh_buf = NULL; + return ret; + } + + if (solo_dev->type == SOLO_DEV_6010) + solo_dev->enc_bw_remain = solo_dev->fps * 4 * 4; + else + solo_dev->enc_bw_remain = solo_dev->fps * 4 * 5; + + dev_info(&solo_dev->pdev->dev, "Encoders as /dev/video%d-%d\n", + solo_dev->v4l2_enc[0]->vfd->num, + solo_dev->v4l2_enc[solo_dev->nr_chans - 1]->vfd->num); + + return solo_ring_start(solo_dev); +} + +void solo_enc_v4l2_exit(struct solo_dev *solo_dev) +{ + int i; + + solo_ring_stop(solo_dev); + + for (i = 0; i < solo_dev->nr_chans; i++) + solo_enc_free(solo_dev->v4l2_enc[i]); + + if (solo_dev->vh_buf) + dma_free_coherent(&solo_dev->pdev->dev, solo_dev->vh_size, + solo_dev->vh_buf, solo_dev->vh_dma); +} diff --git a/drivers/media/pci/solo6x10/solo6x10-v4l2.c b/drivers/media/pci/solo6x10/solo6x10-v4l2.c new file mode 100644 index 000000000..35715b21d --- /dev/null +++ b/drivers/media/pci/solo6x10/solo6x10-v4l2.c @@ -0,0 +1,717 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2010-2013 Bluecherry, LLC + * + * Original author: + * Ben Collins + * + * Additional work by: + * John Brooks + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "solo6x10.h" +#include "solo6x10-tw28.h" + +/* Image size is two fields, SOLO_HW_BPL is one horizontal line in hardware */ +#define SOLO_HW_BPL 2048 +#define solo_vlines(__solo) (__solo->video_vsize * 2) +#define solo_image_size(__solo) (solo_bytesperline(__solo) * \ + solo_vlines(__solo)) +#define solo_bytesperline(__solo) (__solo->video_hsize * 2) + +#define MIN_VID_BUFFERS 2 + +static inline void erase_on(struct solo_dev *solo_dev) +{ + solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, SOLO_VO_DISP_ERASE_ON); + solo_dev->erasing = 1; + solo_dev->frame_blank = 0; +} + +static inline int erase_off(struct solo_dev *solo_dev) +{ + if (!solo_dev->erasing) + return 0; + + /* First time around, assert erase off */ + if (!solo_dev->frame_blank) + solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, 0); + /* Keep the erasing flag on for 8 frames minimum */ + if (solo_dev->frame_blank++ >= 8) + solo_dev->erasing = 0; + + return 1; +} + +void solo_video_in_isr(struct solo_dev *solo_dev) +{ + wake_up_interruptible_all(&solo_dev->disp_thread_wait); +} + +static void solo_win_setup(struct solo_dev *solo_dev, u8 ch, + int sx, int sy, int ex, int ey, int scale) +{ + if (ch >= solo_dev->nr_chans) + return; + + /* Here, we just keep window/channel the same */ + solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL0(ch), + SOLO_VI_WIN_CHANNEL(ch) | + SOLO_VI_WIN_SX(sx) | + SOLO_VI_WIN_EX(ex) | + SOLO_VI_WIN_SCALE(scale)); + + solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL1(ch), + SOLO_VI_WIN_SY(sy) | + SOLO_VI_WIN_EY(ey)); +} + +static int solo_v4l2_ch_ext_4up(struct solo_dev *solo_dev, u8 idx, int on) +{ + u8 ch = idx * 4; + + if (ch >= solo_dev->nr_chans) + return -EINVAL; + + if (!on) { + u8 i; + + for (i = ch; i < ch + 4; i++) + solo_win_setup(solo_dev, i, solo_dev->video_hsize, + solo_vlines(solo_dev), + solo_dev->video_hsize, + solo_vlines(solo_dev), 0); + return 0; + } + + /* Row 1 */ + solo_win_setup(solo_dev, ch, 0, 0, solo_dev->video_hsize / 2, + solo_vlines(solo_dev) / 2, 3); + solo_win_setup(solo_dev, ch + 1, solo_dev->video_hsize / 2, 0, + solo_dev->video_hsize, solo_vlines(solo_dev) / 2, 3); + /* Row 2 */ + solo_win_setup(solo_dev, ch + 2, 0, solo_vlines(solo_dev) / 2, + solo_dev->video_hsize / 2, solo_vlines(solo_dev), 3); + solo_win_setup(solo_dev, ch + 3, solo_dev->video_hsize / 2, + solo_vlines(solo_dev) / 2, solo_dev->video_hsize, + solo_vlines(solo_dev), 3); + + return 0; +} + +static int solo_v4l2_ch_ext_16up(struct solo_dev *solo_dev, int on) +{ + int sy, ysize, hsize, i; + + if (!on) { + for (i = 0; i < 16; i++) + solo_win_setup(solo_dev, i, solo_dev->video_hsize, + solo_vlines(solo_dev), + solo_dev->video_hsize, + solo_vlines(solo_dev), 0); + return 0; + } + + ysize = solo_vlines(solo_dev) / 4; + hsize = solo_dev->video_hsize / 4; + + for (sy = 0, i = 0; i < 4; i++, sy += ysize) { + solo_win_setup(solo_dev, i * 4, 0, sy, hsize, + sy + ysize, 5); + solo_win_setup(solo_dev, (i * 4) + 1, hsize, sy, + hsize * 2, sy + ysize, 5); + solo_win_setup(solo_dev, (i * 4) + 2, hsize * 2, sy, + hsize * 3, sy + ysize, 5); + solo_win_setup(solo_dev, (i * 4) + 3, hsize * 3, sy, + solo_dev->video_hsize, sy + ysize, 5); + } + + return 0; +} + +static int solo_v4l2_ch(struct solo_dev *solo_dev, u8 ch, int on) +{ + u8 ext_ch; + + if (ch < solo_dev->nr_chans) { + solo_win_setup(solo_dev, ch, on ? 0 : solo_dev->video_hsize, + on ? 0 : solo_vlines(solo_dev), + solo_dev->video_hsize, solo_vlines(solo_dev), + on ? 1 : 0); + return 0; + } + + if (ch >= solo_dev->nr_chans + solo_dev->nr_ext) + return -EINVAL; + + ext_ch = ch - solo_dev->nr_chans; + + /* 4up's first */ + if (ext_ch < 4) + return solo_v4l2_ch_ext_4up(solo_dev, ext_ch, on); + + /* Remaining case is 16up for 16-port */ + return solo_v4l2_ch_ext_16up(solo_dev, on); +} + +static int solo_v4l2_set_ch(struct solo_dev *solo_dev, u8 ch) +{ + if (ch >= solo_dev->nr_chans + solo_dev->nr_ext) + return -EINVAL; + + erase_on(solo_dev); + + solo_v4l2_ch(solo_dev, solo_dev->cur_disp_ch, 0); + solo_v4l2_ch(solo_dev, ch, 1); + + solo_dev->cur_disp_ch = ch; + + return 0; +} + +static void solo_fillbuf(struct solo_dev *solo_dev, + struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + dma_addr_t addr; + unsigned int fdma_addr; + int error = -1; + int i; + + addr = vb2_dma_contig_plane_dma_addr(vb, 0); + if (!addr) + goto finish_buf; + + if (erase_off(solo_dev)) { + void *p = vb2_plane_vaddr(vb, 0); + int image_size = solo_image_size(solo_dev); + + for (i = 0; i < image_size; i += 2) { + ((u8 *)p)[i] = 0x80; + ((u8 *)p)[i + 1] = 0x00; + } + error = 0; + } else { + fdma_addr = SOLO_DISP_EXT_ADDR + (solo_dev->old_write * + (SOLO_HW_BPL * solo_vlines(solo_dev))); + + error = solo_p2m_dma_t(solo_dev, 0, addr, fdma_addr, + solo_bytesperline(solo_dev), + solo_vlines(solo_dev), SOLO_HW_BPL); + } + +finish_buf: + if (!error) { + vb2_set_plane_payload(vb, 0, + solo_vlines(solo_dev) * solo_bytesperline(solo_dev)); + vbuf->sequence = solo_dev->sequence++; + vb->timestamp = ktime_get_ns(); + } + + vb2_buffer_done(vb, error ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); +} + +static void solo_thread_try(struct solo_dev *solo_dev) +{ + struct solo_vb2_buf *vb; + + /* Only "break" from this loop if slock is held, otherwise + * just return. */ + for (;;) { + unsigned int cur_write; + + cur_write = SOLO_VI_STATUS0_PAGE( + solo_reg_read(solo_dev, SOLO_VI_STATUS0)); + if (cur_write == solo_dev->old_write) + return; + + spin_lock(&solo_dev->slock); + + if (list_empty(&solo_dev->vidq_active)) + break; + + vb = list_first_entry(&solo_dev->vidq_active, struct solo_vb2_buf, + list); + + solo_dev->old_write = cur_write; + list_del(&vb->list); + + spin_unlock(&solo_dev->slock); + + solo_fillbuf(solo_dev, &vb->vb.vb2_buf); + } + + assert_spin_locked(&solo_dev->slock); + spin_unlock(&solo_dev->slock); +} + +static int solo_thread(void *data) +{ + struct solo_dev *solo_dev = data; + DECLARE_WAITQUEUE(wait, current); + + set_freezable(); + add_wait_queue(&solo_dev->disp_thread_wait, &wait); + + for (;;) { + long timeout = schedule_timeout_interruptible(HZ); + + if (timeout == -ERESTARTSYS || kthread_should_stop()) + break; + solo_thread_try(solo_dev); + try_to_freeze(); + } + + remove_wait_queue(&solo_dev->disp_thread_wait, &wait); + + return 0; +} + +static int solo_start_thread(struct solo_dev *solo_dev) +{ + int ret = 0; + + solo_dev->kthread = kthread_run(solo_thread, solo_dev, SOLO6X10_NAME "_disp"); + + if (IS_ERR(solo_dev->kthread)) { + ret = PTR_ERR(solo_dev->kthread); + solo_dev->kthread = NULL; + return ret; + } + solo_irq_on(solo_dev, SOLO_IRQ_VIDEO_IN); + + return ret; +} + +static void solo_stop_thread(struct solo_dev *solo_dev) +{ + if (!solo_dev->kthread) + return; + + solo_irq_off(solo_dev, SOLO_IRQ_VIDEO_IN); + kthread_stop(solo_dev->kthread); + solo_dev->kthread = NULL; +} + +static int solo_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct solo_dev *solo_dev = vb2_get_drv_priv(q); + + sizes[0] = solo_image_size(solo_dev); + *num_planes = 1; + + if (*num_buffers < MIN_VID_BUFFERS) + *num_buffers = MIN_VID_BUFFERS; + + return 0; +} + +static int solo_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct solo_dev *solo_dev = vb2_get_drv_priv(q); + + solo_dev->sequence = 0; + return solo_start_thread(solo_dev); +} + +static void solo_stop_streaming(struct vb2_queue *q) +{ + struct solo_dev *solo_dev = vb2_get_drv_priv(q); + + solo_stop_thread(solo_dev); + + spin_lock(&solo_dev->slock); + while (!list_empty(&solo_dev->vidq_active)) { + struct solo_vb2_buf *buf = list_entry( + solo_dev->vidq_active.next, + struct solo_vb2_buf, list); + + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock(&solo_dev->slock); + INIT_LIST_HEAD(&solo_dev->vidq_active); +} + +static void solo_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vb2_queue *vq = vb->vb2_queue; + struct solo_dev *solo_dev = vb2_get_drv_priv(vq); + struct solo_vb2_buf *solo_vb = + container_of(vbuf, struct solo_vb2_buf, vb); + + spin_lock(&solo_dev->slock); + list_add_tail(&solo_vb->list, &solo_dev->vidq_active); + spin_unlock(&solo_dev->slock); + wake_up_interruptible(&solo_dev->disp_thread_wait); +} + +static const struct vb2_ops solo_video_qops = { + .queue_setup = solo_queue_setup, + .buf_queue = solo_buf_queue, + .start_streaming = solo_start_streaming, + .stop_streaming = solo_stop_streaming, +}; + +static int solo_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, SOLO6X10_NAME, sizeof(cap->driver)); + strscpy(cap->card, "Softlogic 6x10", sizeof(cap->card)); + return 0; +} + +static int solo_enum_ext_input(struct solo_dev *solo_dev, + struct v4l2_input *input) +{ + int ext = input->index - solo_dev->nr_chans; + unsigned int nup, first; + + if (ext >= solo_dev->nr_ext) + return -EINVAL; + + nup = (ext == 4) ? 16 : 4; + first = (ext & 3) << 2; /* first channel in the n-up */ + snprintf(input->name, sizeof(input->name), + "Multi %d-up (cameras %d-%d)", + nup, first + 1, first + nup); + /* Possible outputs: + * Multi 4-up (cameras 1-4) + * Multi 4-up (cameras 5-8) + * Multi 4-up (cameras 9-12) + * Multi 4-up (cameras 13-16) + * Multi 16-up (cameras 1-16) + */ + return 0; +} + +static int solo_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + struct solo_dev *solo_dev = video_drvdata(file); + + if (input->index >= solo_dev->nr_chans) { + int ret = solo_enum_ext_input(solo_dev, input); + + if (ret < 0) + return ret; + } else { + snprintf(input->name, sizeof(input->name), "Camera %d", + input->index + 1); + + /* We can only check this for normal inputs */ + if (!tw28_get_video_status(solo_dev, input->index)) + input->status = V4L2_IN_ST_NO_SIGNAL; + } + + input->type = V4L2_INPUT_TYPE_CAMERA; + input->std = solo_dev->vfd->tvnorms; + return 0; +} + +static int solo_set_input(struct file *file, void *priv, unsigned int index) +{ + struct solo_dev *solo_dev = video_drvdata(file); + int ret = solo_v4l2_set_ch(solo_dev, index); + + if (!ret) { + while (erase_off(solo_dev)) + /* Do nothing */; + } + + return ret; +} + +static int solo_get_input(struct file *file, void *priv, unsigned int *index) +{ + struct solo_dev *solo_dev = video_drvdata(file); + + *index = solo_dev->cur_disp_ch; + + return 0; +} + +static int solo_enum_fmt_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index) + return -EINVAL; + + f->pixelformat = V4L2_PIX_FMT_UYVY; + return 0; +} + +static int solo_try_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_dev *solo_dev = video_drvdata(file); + struct v4l2_pix_format *pix = &f->fmt.pix; + int image_size = solo_image_size(solo_dev); + + if (pix->pixelformat != V4L2_PIX_FMT_UYVY) + return -EINVAL; + + pix->width = solo_dev->video_hsize; + pix->height = solo_vlines(solo_dev); + pix->sizeimage = image_size; + pix->field = V4L2_FIELD_INTERLACED; + pix->pixelformat = V4L2_PIX_FMT_UYVY; + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + return 0; +} + +static int solo_set_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_dev *solo_dev = video_drvdata(file); + + if (vb2_is_busy(&solo_dev->vidq)) + return -EBUSY; + + /* For right now, if it doesn't match our running config, + * then fail */ + return solo_try_fmt_cap(file, priv, f); +} + +static int solo_get_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_dev *solo_dev = video_drvdata(file); + struct v4l2_pix_format *pix = &f->fmt.pix; + + pix->width = solo_dev->video_hsize; + pix->height = solo_vlines(solo_dev); + pix->pixelformat = V4L2_PIX_FMT_UYVY; + pix->field = V4L2_FIELD_INTERLACED; + pix->sizeimage = solo_image_size(solo_dev); + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + pix->bytesperline = solo_bytesperline(solo_dev); + + return 0; +} + +static int solo_g_std(struct file *file, void *priv, v4l2_std_id *i) +{ + struct solo_dev *solo_dev = video_drvdata(file); + + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) + *i = V4L2_STD_NTSC_M; + else + *i = V4L2_STD_PAL; + return 0; +} + +int solo_set_video_type(struct solo_dev *solo_dev, bool is_50hz) +{ + int i; + + /* Make sure all video nodes are idle */ + if (vb2_is_busy(&solo_dev->vidq)) + return -EBUSY; + for (i = 0; i < solo_dev->nr_chans; i++) + if (vb2_is_busy(&solo_dev->v4l2_enc[i]->vidq)) + return -EBUSY; + solo_dev->video_type = is_50hz ? SOLO_VO_FMT_TYPE_PAL : + SOLO_VO_FMT_TYPE_NTSC; + /* Reconfigure for the new standard */ + solo_disp_init(solo_dev); + solo_enc_init(solo_dev); + solo_tw28_init(solo_dev); + for (i = 0; i < solo_dev->nr_chans; i++) + solo_update_mode(solo_dev->v4l2_enc[i]); + return solo_v4l2_set_ch(solo_dev, solo_dev->cur_disp_ch); +} + +static int solo_s_std(struct file *file, void *priv, v4l2_std_id std) +{ + struct solo_dev *solo_dev = video_drvdata(file); + + return solo_set_video_type(solo_dev, std & V4L2_STD_625_50); +} + +static int solo_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct solo_dev *solo_dev = + container_of(ctrl->handler, struct solo_dev, disp_hdl); + + switch (ctrl->id) { + case V4L2_CID_MOTION_TRACE: + if (ctrl->val) { + solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER, + SOLO_VI_MOTION_Y_ADD | + SOLO_VI_MOTION_Y_VALUE(0x20) | + SOLO_VI_MOTION_CB_VALUE(0x10) | + SOLO_VI_MOTION_CR_VALUE(0x10)); + solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR, + SOLO_VI_MOTION_CR_ADD | + SOLO_VI_MOTION_Y_VALUE(0x10) | + SOLO_VI_MOTION_CB_VALUE(0x80) | + SOLO_VI_MOTION_CR_VALUE(0x10)); + } else { + solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER, 0); + solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR, 0); + } + return 0; + default: + break; + } + return -EINVAL; +} + +static const struct v4l2_file_operations solo_v4l2_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops solo_v4l2_ioctl_ops = { + .vidioc_querycap = solo_querycap, + .vidioc_s_std = solo_s_std, + .vidioc_g_std = solo_g_std, + /* Input callbacks */ + .vidioc_enum_input = solo_enum_input, + .vidioc_s_input = solo_set_input, + .vidioc_g_input = solo_get_input, + /* Video capture format callbacks */ + .vidioc_enum_fmt_vid_cap = solo_enum_fmt_cap, + .vidioc_try_fmt_vid_cap = solo_try_fmt_cap, + .vidioc_s_fmt_vid_cap = solo_set_fmt_cap, + .vidioc_g_fmt_vid_cap = solo_get_fmt_cap, + /* Streaming I/O */ + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + /* Logging and events */ + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct video_device solo_v4l2_template = { + .name = SOLO6X10_NAME, + .fops = &solo_v4l2_fops, + .ioctl_ops = &solo_v4l2_ioctl_ops, + .minor = -1, + .release = video_device_release, + .tvnorms = V4L2_STD_NTSC_M | V4L2_STD_PAL, + .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING, +}; + +static const struct v4l2_ctrl_ops solo_ctrl_ops = { + .s_ctrl = solo_s_ctrl, +}; + +static const struct v4l2_ctrl_config solo_motion_trace_ctrl = { + .ops = &solo_ctrl_ops, + .id = V4L2_CID_MOTION_TRACE, + .name = "Motion Detection Trace", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .max = 1, + .step = 1, +}; + +int solo_v4l2_init(struct solo_dev *solo_dev, unsigned nr) +{ + int ret; + int i; + + init_waitqueue_head(&solo_dev->disp_thread_wait); + spin_lock_init(&solo_dev->slock); + mutex_init(&solo_dev->lock); + INIT_LIST_HEAD(&solo_dev->vidq_active); + + solo_dev->vfd = video_device_alloc(); + if (!solo_dev->vfd) + return -ENOMEM; + + *solo_dev->vfd = solo_v4l2_template; + solo_dev->vfd->v4l2_dev = &solo_dev->v4l2_dev; + solo_dev->vfd->queue = &solo_dev->vidq; + solo_dev->vfd->lock = &solo_dev->lock; + v4l2_ctrl_handler_init(&solo_dev->disp_hdl, 1); + v4l2_ctrl_new_custom(&solo_dev->disp_hdl, &solo_motion_trace_ctrl, NULL); + if (solo_dev->disp_hdl.error) { + ret = solo_dev->disp_hdl.error; + goto fail; + } + solo_dev->vfd->ctrl_handler = &solo_dev->disp_hdl; + + video_set_drvdata(solo_dev->vfd, solo_dev); + + solo_dev->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + solo_dev->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; + solo_dev->vidq.ops = &solo_video_qops; + solo_dev->vidq.mem_ops = &vb2_dma_contig_memops; + solo_dev->vidq.drv_priv = solo_dev; + solo_dev->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + solo_dev->vidq.gfp_flags = __GFP_DMA32 | __GFP_KSWAPD_RECLAIM; + solo_dev->vidq.buf_struct_size = sizeof(struct solo_vb2_buf); + solo_dev->vidq.lock = &solo_dev->lock; + solo_dev->vidq.dev = &solo_dev->pdev->dev; + ret = vb2_queue_init(&solo_dev->vidq); + if (ret < 0) + goto fail; + + /* Cycle all the channels and clear */ + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_v4l2_set_ch(solo_dev, i); + while (erase_off(solo_dev)) + /* Do nothing */; + } + + /* Set the default display channel */ + solo_v4l2_set_ch(solo_dev, 0); + while (erase_off(solo_dev)) + /* Do nothing */; + + ret = video_register_device(solo_dev->vfd, VFL_TYPE_VIDEO, nr); + if (ret < 0) + goto fail; + + snprintf(solo_dev->vfd->name, sizeof(solo_dev->vfd->name), "%s (%i)", + SOLO6X10_NAME, solo_dev->vfd->num); + + dev_info(&solo_dev->pdev->dev, "Display as /dev/video%d with %d inputs (%d extended)\n", + solo_dev->vfd->num, + solo_dev->nr_chans, solo_dev->nr_ext); + + return 0; + +fail: + video_device_release(solo_dev->vfd); + v4l2_ctrl_handler_free(&solo_dev->disp_hdl); + solo_dev->vfd = NULL; + return ret; +} + +void solo_v4l2_exit(struct solo_dev *solo_dev) +{ + if (solo_dev->vfd == NULL) + return; + + video_unregister_device(solo_dev->vfd); + v4l2_ctrl_handler_free(&solo_dev->disp_hdl); + solo_dev->vfd = NULL; +} diff --git a/drivers/media/pci/solo6x10/solo6x10.h b/drivers/media/pci/solo6x10/solo6x10.h new file mode 100644 index 000000000..126f6fb7b --- /dev/null +++ b/drivers/media/pci/solo6x10/solo6x10.h @@ -0,0 +1,380 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC + * + * Original author: + * Ben Collins + * + * Additional work by: + * John Brooks + */ + +#ifndef __SOLO6X10_H +#define __SOLO6X10_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "solo6x10-regs.h" + +#ifndef PCI_VENDOR_ID_SOFTLOGIC +#define PCI_VENDOR_ID_SOFTLOGIC 0x9413 +#define PCI_DEVICE_ID_SOLO6010 0x6010 +#define PCI_DEVICE_ID_SOLO6110 0x6110 +#endif + +#ifndef PCI_VENDOR_ID_BLUECHERRY +#define PCI_VENDOR_ID_BLUECHERRY 0x1BB3 +/* Neugent Softlogic 6010 based cards */ +#define PCI_DEVICE_ID_NEUSOLO_4 0x4304 +#define PCI_DEVICE_ID_NEUSOLO_9 0x4309 +#define PCI_DEVICE_ID_NEUSOLO_16 0x4310 +/* Bluecherry Softlogic 6010 based cards */ +#define PCI_DEVICE_ID_BC_SOLO_4 0x4E04 +#define PCI_DEVICE_ID_BC_SOLO_9 0x4E09 +#define PCI_DEVICE_ID_BC_SOLO_16 0x4E10 +/* Bluecherry Softlogic 6110 based cards */ +#define PCI_DEVICE_ID_BC_6110_4 0x5304 +#define PCI_DEVICE_ID_BC_6110_8 0x5308 +#define PCI_DEVICE_ID_BC_6110_16 0x5310 +#endif /* Bluecherry */ + +/* Used in pci_device_id, and solo_dev->type */ +#define SOLO_DEV_6010 0 +#define SOLO_DEV_6110 1 + +#define SOLO6X10_NAME "solo6x10" + +#define SOLO_MAX_CHANNELS 16 + +#define SOLO6X10_VERSION "3.0.0" + +/* + * The SOLO6x10 actually has 8 i2c channels, but we only use 2. + * 0 - Techwell chip(s) + * 1 - SAA7128 + */ +#define SOLO_I2C_ADAPTERS 2 +#define SOLO_I2C_TW 0 +#define SOLO_I2C_SAA 1 + +/* DMA Engine setup */ +#define SOLO_NR_P2M 4 +#define SOLO_NR_P2M_DESC 256 +#define SOLO_P2M_DESC_SIZE (SOLO_NR_P2M_DESC * 16) + +/* Encoder standard modes */ +#define SOLO_ENC_MODE_CIF 2 +#define SOLO_ENC_MODE_HD1 1 +#define SOLO_ENC_MODE_D1 9 + +#define SOLO_DEFAULT_QP 3 + +#define SOLO_CID_CUSTOM_BASE (V4L2_CID_USER_BASE | 0xf000) +#define V4L2_CID_MOTION_TRACE (SOLO_CID_CUSTOM_BASE+2) +#define V4L2_CID_OSD_TEXT (SOLO_CID_CUSTOM_BASE+3) + +/* + * Motion thresholds are in a table of 64x64 samples, with + * each sample representing 16x16 pixels of the source. In + * effect, 44x30 samples are used for NTSC, and 44x36 for PAL. + * The 5th sample on the 10th row is (10*64)+5 = 645. + * + * Internally it is stored as a 45x45 array (45*16 = 720, which is the + * maximum PAL/NTSC width). + */ +#define SOLO_MOTION_SZ (45) + +enum SOLO_I2C_STATE { + IIC_STATE_IDLE, + IIC_STATE_START, + IIC_STATE_READ, + IIC_STATE_WRITE, + IIC_STATE_STOP +}; + +/* Defined in Table 4-16, Page 68-69 of the 6010 Datasheet */ +struct solo_p2m_desc { + u32 ctrl; + u32 cfg; + u32 dma_addr; + u32 ext_addr; +}; + +struct solo_p2m_dev { + struct mutex mutex; + struct completion completion; + int desc_count; + int desc_idx; + struct solo_p2m_desc *descs; + int error; +}; + +#define OSD_TEXT_MAX 44 + +struct solo_vb2_buf { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +enum solo_enc_types { + SOLO_ENC_TYPE_STD, + SOLO_ENC_TYPE_EXT, +}; + +struct solo_enc_dev { + struct solo_dev *solo_dev; + /* V4L2 Items */ + struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *md_thresholds; + struct video_device *vfd; + /* General accounting */ + struct mutex lock; + spinlock_t motion_lock; + u8 ch; + u8 mode, gop, qp, interlaced, interval; + u8 bw_weight; + u16 motion_thresh; + bool motion_global; + bool motion_enabled; + u16 width; + u16 height; + + /* OSD buffers */ + char osd_text[OSD_TEXT_MAX + 1]; + u8 osd_buf[SOLO_EOSD_EXT_SIZE_MAX] + __aligned(4); + + /* VOP stuff */ + u8 vop[64]; + int vop_len; + u8 jpeg_header[1024]; + int jpeg_len; + + u32 fmt; + enum solo_enc_types type; + u32 sequence; + struct vb2_queue vidq; + struct list_head vidq_active; + int desc_count; + int desc_nelts; + struct solo_p2m_desc *desc_items; + dma_addr_t desc_dma; + spinlock_t av_lock; +}; + +/* The SOLO6x10 PCI Device */ +struct solo_dev { + /* General stuff */ + struct pci_dev *pdev; + int type; + unsigned int time_sync; + unsigned int usec_lsb; + unsigned int clock_mhz; + u8 __iomem *reg_base; + int nr_chans; + int nr_ext; + u32 irq_mask; + u32 motion_mask; + struct v4l2_device v4l2_dev; +#ifdef CONFIG_GPIOLIB + /* GPIO */ + struct gpio_chip gpio_dev; +#endif + + /* tw28xx accounting */ + u8 tw2865, tw2864, tw2815; + u8 tw28_cnt; + + /* i2c related items */ + struct i2c_adapter i2c_adap[SOLO_I2C_ADAPTERS]; + enum SOLO_I2C_STATE i2c_state; + struct mutex i2c_mutex; + int i2c_id; + wait_queue_head_t i2c_wait; + struct i2c_msg *i2c_msg; + unsigned int i2c_msg_num; + unsigned int i2c_msg_ptr; + + /* P2M DMA Engine */ + struct solo_p2m_dev p2m_dev[SOLO_NR_P2M]; + atomic_t p2m_count; + int p2m_jiffies; + unsigned int p2m_timeouts; + + /* V4L2 Display items */ + struct video_device *vfd; + unsigned int erasing; + unsigned int frame_blank; + u8 cur_disp_ch; + wait_queue_head_t disp_thread_wait; + struct v4l2_ctrl_handler disp_hdl; + + /* V4L2 Encoder items */ + struct solo_enc_dev *v4l2_enc[SOLO_MAX_CHANNELS]; + u16 enc_bw_remain; + /* IDX into hw mp4 encoder */ + u8 enc_idx; + + /* Current video settings */ + u32 video_type; + u16 video_hsize, video_vsize; + u16 vout_hstart, vout_vstart; + u16 vin_hstart, vin_vstart; + u8 fps; + + /* JPEG Qp setting */ + spinlock_t jpeg_qp_lock; + u32 jpeg_qp[2]; + + /* Audio components */ + struct snd_card *snd_card; + struct snd_pcm *snd_pcm; + atomic_t snd_users; + int g723_hw_idx; + + /* sysfs stuffs */ + struct device dev; + int sdram_size; + struct bin_attribute sdram_attr; + unsigned int sys_config; + + /* Ring thread */ + struct task_struct *ring_thread; + wait_queue_head_t ring_thread_wait; + + /* VOP_HEADER handling */ + void *vh_buf; + dma_addr_t vh_dma; + int vh_size; + + /* Buffer handling */ + struct vb2_queue vidq; + u32 sequence; + struct task_struct *kthread; + struct mutex lock; + spinlock_t slock; + int old_write; + struct list_head vidq_active; +}; + +static inline u32 solo_reg_read(struct solo_dev *solo_dev, int reg) +{ + return readl(solo_dev->reg_base + reg); +} + +static inline void solo_reg_write(struct solo_dev *solo_dev, int reg, + u32 data) +{ + u16 val; + + writel(data, solo_dev->reg_base + reg); + pci_read_config_word(solo_dev->pdev, PCI_STATUS, &val); +} + +static inline void solo_irq_on(struct solo_dev *dev, u32 mask) +{ + dev->irq_mask |= mask; + solo_reg_write(dev, SOLO_IRQ_MASK, dev->irq_mask); +} + +static inline void solo_irq_off(struct solo_dev *dev, u32 mask) +{ + dev->irq_mask &= ~mask; + solo_reg_write(dev, SOLO_IRQ_MASK, dev->irq_mask); +} + +/* Init/exit routines for subsystems */ +int solo_disp_init(struct solo_dev *solo_dev); +void solo_disp_exit(struct solo_dev *solo_dev); + +int solo_gpio_init(struct solo_dev *solo_dev); +void solo_gpio_exit(struct solo_dev *solo_dev); + +int solo_i2c_init(struct solo_dev *solo_dev); +void solo_i2c_exit(struct solo_dev *solo_dev); + +int solo_p2m_init(struct solo_dev *solo_dev); +void solo_p2m_exit(struct solo_dev *solo_dev); + +int solo_v4l2_init(struct solo_dev *solo_dev, unsigned nr); +void solo_v4l2_exit(struct solo_dev *solo_dev); + +int solo_enc_init(struct solo_dev *solo_dev); +void solo_enc_exit(struct solo_dev *solo_dev); + +int solo_enc_v4l2_init(struct solo_dev *solo_dev, unsigned nr); +void solo_enc_v4l2_exit(struct solo_dev *solo_dev); + +int solo_g723_init(struct solo_dev *solo_dev); +void solo_g723_exit(struct solo_dev *solo_dev); + +/* ISR's */ +int solo_i2c_isr(struct solo_dev *solo_dev); +void solo_p2m_isr(struct solo_dev *solo_dev, int id); +void solo_p2m_error_isr(struct solo_dev *solo_dev); +void solo_enc_v4l2_isr(struct solo_dev *solo_dev); +void solo_g723_isr(struct solo_dev *solo_dev); +void solo_motion_isr(struct solo_dev *solo_dev); +void solo_video_in_isr(struct solo_dev *solo_dev); + +/* i2c read/write */ +u8 solo_i2c_readbyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off); +void solo_i2c_writebyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off, + u8 data); + +/* P2M DMA */ +int solo_p2m_dma_t(struct solo_dev *solo_dev, int wr, + dma_addr_t dma_addr, u32 ext_addr, u32 size, + int repeat, u32 ext_size); +int solo_p2m_dma(struct solo_dev *solo_dev, int wr, + void *sys_addr, u32 ext_addr, u32 size, + int repeat, u32 ext_size); +void solo_p2m_fill_desc(struct solo_p2m_desc *desc, int wr, + dma_addr_t dma_addr, u32 ext_addr, u32 size, + int repeat, u32 ext_size); +int solo_p2m_dma_desc(struct solo_dev *solo_dev, + struct solo_p2m_desc *desc, dma_addr_t desc_dma, + int desc_cnt); + +/* Global s_std ioctl */ +int solo_set_video_type(struct solo_dev *solo_dev, bool is_50hz); +void solo_update_mode(struct solo_enc_dev *solo_enc); + +/* Set the threshold for motion detection */ +int solo_set_motion_threshold(struct solo_dev *solo_dev, u8 ch, u16 val); +int solo_set_motion_block(struct solo_dev *solo_dev, u8 ch, + const u16 *thresholds); +#define SOLO_DEF_MOT_THRESH 0x0300 + +/* Write text on OSD */ +int solo_osd_print(struct solo_enc_dev *solo_enc); + +/* EEPROM commands */ +unsigned int solo_eeprom_ewen(struct solo_dev *solo_dev, int w_en); +__be16 solo_eeprom_read(struct solo_dev *solo_dev, int loc); +int solo_eeprom_write(struct solo_dev *solo_dev, int loc, + __be16 data); + +/* JPEG Qp functions */ +void solo_s_jpeg_qp(struct solo_dev *solo_dev, unsigned int ch, + unsigned int qp); +int solo_g_jpeg_qp(struct solo_dev *solo_dev, unsigned int ch); + +#define CHK_FLAGS(v, flags) (((v) & (flags)) == (flags)) + +#endif /* __SOLO6X10_H */ diff --git a/drivers/media/pci/ttpci/Kconfig b/drivers/media/pci/ttpci/Kconfig new file mode 100644 index 000000000..65a6832a6 --- /dev/null +++ b/drivers/media/pci/ttpci/Kconfig @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DVB_BUDGET_CORE + tristate "SAA7146 DVB cards (aka Budget, Nova-PCI)" + depends on DVB_CORE && PCI && I2C + select VIDEO_SAA7146 + select TTPCI_EEPROM + help + Support for simple SAA7146 based DVB cards + (so called Budget- or Nova-PCI cards) without onboard + MPEG2 decoder. + +config DVB_BUDGET + tristate "Budget cards" + depends on DVB_BUDGET_CORE && I2C + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_VES1X93 if MEDIA_SUBDRV_AUTOSELECT + select DVB_VES1820 if MEDIA_SUBDRV_AUTOSELECT + select DVB_L64781 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA8083 if MEDIA_SUBDRV_AUTOSELECT + select DVB_S5H1420 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10086 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA826X if MEDIA_SUBDRV_AUTOSELECT + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA1004X if MEDIA_SUBDRV_AUTOSELECT + select DVB_ISL6423 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV090x if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV6110x if MEDIA_SUBDRV_AUTOSELECT + help + Support for simple SAA7146 based DVB cards (so called Budget- + or Nova-PCI cards) without onboard MPEG2 decoder, and without + analog inputs or an onboard Common Interface connector. + + Say Y if you own such a card and want to use it. + + To compile this driver as a module, choose M here: the + module will be called budget. + +config DVB_BUDGET_CI + tristate "Budget cards with onboard CI connector" + depends on DVB_BUDGET_CORE && I2C + select DVB_STV0297 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA1004X if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB0899 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0288 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6000 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10023 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA827X if MEDIA_SUBDRV_AUTOSELECT + depends on RC_CORE + help + Support for simple SAA7146 based DVB cards + (so called Budget- or Nova-PCI cards) without onboard + MPEG2 decoder, but with onboard Common Interface connector. + + Note: The Common Interface is not yet supported by this driver + due to lack of information from the vendor. + + Say Y if you own such a card and want to use it. + + To compile this driver as a module, choose M here: the + module will be called budget-ci. + +config DVB_BUDGET_AV + tristate "Budget cards with analog video inputs" + depends on DVB_BUDGET_CORE && I2C + select VIDEO_SAA7146_VV + depends on VIDEO_DEV # dependencies of VIDEO_SAA7146_VV + select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA1004X if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10021 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10023 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB0899 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA8261 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TUA6100 if MEDIA_SUBDRV_AUTOSELECT + help + Support for simple SAA7146 based DVB cards + (so called Budget- or Nova-PCI cards) without onboard + MPEG2 decoder, but with one or more analog video inputs. + + Say Y if you own such a card and want to use it. + + To compile this driver as a module, choose M here: the + module will be called budget-av. diff --git a/drivers/media/pci/ttpci/Makefile b/drivers/media/pci/ttpci/Makefile new file mode 100644 index 000000000..b0708f6e4 --- /dev/null +++ b/drivers/media/pci/ttpci/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the kernel SAA7146 FULL TS DVB device driver +# + +obj-$(CONFIG_DVB_BUDGET_CORE) += budget-core.o +obj-$(CONFIG_DVB_BUDGET) += budget.o +obj-$(CONFIG_DVB_BUDGET_AV) += budget-av.o +obj-$(CONFIG_DVB_BUDGET_CI) += budget-ci.o + +ccflags-y += -I $(srctree)/drivers/media/dvb-frontends/ +ccflags-y += -I $(srctree)/drivers/media/tuners +ccflags-y += -I $(srctree)/drivers/media/common diff --git a/drivers/media/pci/ttpci/budget-av.c b/drivers/media/pci/ttpci/budget-av.c new file mode 100644 index 000000000..55542cfbc --- /dev/null +++ b/drivers/media/pci/ttpci/budget-av.c @@ -0,0 +1,1625 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * budget-av.ko: driver for the SAA7146 based Budget DVB cards + * with analog video input (and optionally with CI) + * + * Compiled from various sources by Michael Hunold + * + * CI interface support (c) 2004 Olivier Gournet & + * Andrew de Quincey + * + * Copyright (C) 2002 Ralph Metzler + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * the project's page is at https://linuxtv.org + */ + + +#include "budget.h" +#include "stv0299.h" +#include "stb0899_drv.h" +#include "stb0899_reg.h" +#include "stb0899_cfg.h" +#include "tda8261.h" +#include "tda8261_cfg.h" +#include "tda1002x.h" +#include "tda1004x.h" +#include "tua6100.h" +#include "dvb-pll.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DEBICICAM 0x02420000 + +#define SLOTSTATUS_NONE 1 +#define SLOTSTATUS_PRESENT 2 +#define SLOTSTATUS_RESET 4 +#define SLOTSTATUS_READY 8 +#define SLOTSTATUS_OCCUPIED (SLOTSTATUS_PRESENT|SLOTSTATUS_RESET|SLOTSTATUS_READY) + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +struct budget_av { + struct budget budget; + struct video_device vd; + int cur_input; + int has_saa7113; + struct work_struct ciintf_irq_bh_work; + int slot_status; + struct dvb_ca_en50221 ca; + u8 reinitialise_demod:1; +}; + +static int ciintf_slot_shutdown(struct dvb_ca_en50221 *ca, int slot); + +/* + * GPIO Connections: + * 0 - Vcc/Reset (Reset is controlled by capacitor). Resets the frontend *AS WELL*! + * 1 - CI memory select 0=>IO memory, 1=>Attribute Memory + * 2 - CI Card Enable (Active Low) + * 3 - CI Card Detect + */ + +/**************************************************************************** + * INITIALIZATION + ****************************************************************************/ + +static u8 i2c_readreg(struct i2c_adapter *i2c, u8 id, u8 reg) +{ + u8 mm1[] = { 0x00 }; + u8 mm2[] = { 0x00 }; + struct i2c_msg msgs[2]; + + msgs[0].flags = 0; + msgs[1].flags = I2C_M_RD; + msgs[0].addr = msgs[1].addr = id / 2; + mm1[0] = reg; + msgs[0].len = 1; + msgs[1].len = 1; + msgs[0].buf = mm1; + msgs[1].buf = mm2; + + i2c_transfer(i2c, msgs, 2); + + return mm2[0]; +} + +static int i2c_readregs(struct i2c_adapter *i2c, u8 id, u8 reg, u8 *buf, u8 len) +{ + u8 mm1[] = { reg }; + struct i2c_msg msgs[2] = { + {.addr = id / 2, .flags = 0, .buf = mm1, .len = 1}, + {.addr = id / 2, .flags = I2C_M_RD, .buf = buf, .len = len} + }; + + if (i2c_transfer(i2c, msgs, 2) != 2) + return -EIO; + + return 0; +} + +static int i2c_writereg(struct i2c_adapter *i2c, u8 id, u8 reg, u8 val) +{ + u8 msg[2] = { reg, val }; + struct i2c_msg msgs; + + msgs.flags = 0; + msgs.addr = id / 2; + msgs.len = 2; + msgs.buf = msg; + return i2c_transfer(i2c, &msgs, 1); +} + +static int ciintf_read_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address) +{ + struct budget_av *budget_av = ca->data; + int result; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(budget_av->budget.dev, 1, SAA7146_GPIO_OUTHI); + udelay(1); + + result = ttpci_budget_debiread(&budget_av->budget, DEBICICAM, address & 0xfff, 1, 0, 1); + if (result == -ETIMEDOUT) { + ciintf_slot_shutdown(ca, slot); + pr_info("cam ejected 1\n"); + } + return result; +} + +static int ciintf_write_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address, u8 value) +{ + struct budget_av *budget_av = ca->data; + int result; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(budget_av->budget.dev, 1, SAA7146_GPIO_OUTHI); + udelay(1); + + result = ttpci_budget_debiwrite(&budget_av->budget, DEBICICAM, address & 0xfff, 1, value, 0, 1); + if (result == -ETIMEDOUT) { + ciintf_slot_shutdown(ca, slot); + pr_info("cam ejected 2\n"); + } + return result; +} + +static int ciintf_read_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address) +{ + struct budget_av *budget_av = ca->data; + int result; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(budget_av->budget.dev, 1, SAA7146_GPIO_OUTLO); + udelay(1); + + result = ttpci_budget_debiread(&budget_av->budget, DEBICICAM, address & 3, 1, 0, 0); + if (result == -ETIMEDOUT) { + ciintf_slot_shutdown(ca, slot); + pr_info("cam ejected 3\n"); + return -ETIMEDOUT; + } + return result; +} + +static int ciintf_write_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address, u8 value) +{ + struct budget_av *budget_av = ca->data; + int result; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(budget_av->budget.dev, 1, SAA7146_GPIO_OUTLO); + udelay(1); + + result = ttpci_budget_debiwrite(&budget_av->budget, DEBICICAM, address & 3, 1, value, 0, 0); + if (result == -ETIMEDOUT) { + ciintf_slot_shutdown(ca, slot); + pr_info("cam ejected 5\n"); + } + return result; +} + +static int ciintf_slot_reset(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_av *budget_av = ca->data; + struct saa7146_dev *saa = budget_av->budget.dev; + + if (slot != 0) + return -EINVAL; + + dprintk(1, "ci slot reset\n"); + budget_av->slot_status = SLOTSTATUS_RESET; + + saa7146_setgpio(saa, 2, SAA7146_GPIO_OUTHI); /* disable card */ + + saa7146_setgpio(saa, 0, SAA7146_GPIO_OUTHI); /* Vcc off */ + msleep(2); + saa7146_setgpio(saa, 0, SAA7146_GPIO_OUTLO); /* Vcc on */ + msleep(20); /* 20 ms Vcc settling time */ + + saa7146_setgpio(saa, 2, SAA7146_GPIO_OUTLO); /* enable card */ + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTB); + msleep(20); + + /* reinitialise the frontend if necessary */ + if (budget_av->reinitialise_demod) + dvb_frontend_reinitialise(budget_av->budget.dvb_frontend); + + return 0; +} + +static int ciintf_slot_shutdown(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_av *budget_av = ca->data; + struct saa7146_dev *saa = budget_av->budget.dev; + + if (slot != 0) + return -EINVAL; + + dprintk(1, "ci slot shutdown\n"); + + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTB); + budget_av->slot_status = SLOTSTATUS_NONE; + + return 0; +} + +static int ciintf_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_av *budget_av = ca->data; + struct saa7146_dev *saa = budget_av->budget.dev; + + if (slot != 0) + return -EINVAL; + + dprintk(1, "ci slot status: %d\n", budget_av->slot_status); + + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTA); + + return 0; +} + +static int ciintf_poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open) +{ + struct budget_av *budget_av = ca->data; + struct saa7146_dev *saa = budget_av->budget.dev; + int result; + + if (slot != 0) + return -EINVAL; + + /* + * test the card detect line - needs to be done carefully + * since it never goes high for some CAMs on this interface (e.g. topuptv) + */ + if (budget_av->slot_status == SLOTSTATUS_NONE) { + saa7146_setgpio(saa, 3, SAA7146_GPIO_INPUT); + udelay(1); + if (saa7146_read(saa, PSR) & MASK_06) { + if (budget_av->slot_status == SLOTSTATUS_NONE) { + budget_av->slot_status = SLOTSTATUS_PRESENT; + pr_info("cam inserted A\n"); + } + } + saa7146_setgpio(saa, 3, SAA7146_GPIO_OUTLO); + } + + /* + * We also try and read from IO memory to work round the above detection bug. If + * there is no CAM, we will get a timeout. Only done if there is no cam + * present, since this test actually breaks some cams :( + * + * if the CI interface is not open, we also do the above test since we + * don't care if the cam has problems - we'll be resetting it on open() anyway + */ + if ((budget_av->slot_status == SLOTSTATUS_NONE) || (!open)) { + saa7146_setgpio(budget_av->budget.dev, 1, SAA7146_GPIO_OUTLO); + result = ttpci_budget_debiread(&budget_av->budget, DEBICICAM, 0, 1, 0, 1); + if ((result >= 0) && (budget_av->slot_status == SLOTSTATUS_NONE)) { + budget_av->slot_status = SLOTSTATUS_PRESENT; + pr_info("cam inserted B\n"); + } else if (result < 0) { + if (budget_av->slot_status != SLOTSTATUS_NONE) { + ciintf_slot_shutdown(ca, slot); + pr_info("cam ejected 5\n"); + return 0; + } + } + } + + /* read from attribute memory in reset/ready state to know when the CAM is ready */ + if (budget_av->slot_status == SLOTSTATUS_RESET) { + result = ciintf_read_attribute_mem(ca, slot, 0); + if (result == 0x1d) + budget_av->slot_status = SLOTSTATUS_READY; + } + + /* work out correct return code */ + if (budget_av->slot_status != SLOTSTATUS_NONE) { + if (budget_av->slot_status & SLOTSTATUS_READY) + return DVB_CA_EN50221_POLL_CAM_PRESENT | DVB_CA_EN50221_POLL_CAM_READY; + return DVB_CA_EN50221_POLL_CAM_PRESENT; + } + return 0; +} + +static int ciintf_init(struct budget_av *budget_av) +{ + struct saa7146_dev *saa = budget_av->budget.dev; + int result; + + memset(&budget_av->ca, 0, sizeof(struct dvb_ca_en50221)); + + saa7146_setgpio(saa, 0, SAA7146_GPIO_OUTLO); + saa7146_setgpio(saa, 1, SAA7146_GPIO_OUTLO); + saa7146_setgpio(saa, 2, SAA7146_GPIO_OUTLO); + saa7146_setgpio(saa, 3, SAA7146_GPIO_OUTLO); + + /* Enable DEBI pins */ + saa7146_write(saa, MC1, MASK_27 | MASK_11); + + /* register CI interface */ + budget_av->ca.owner = THIS_MODULE; + budget_av->ca.read_attribute_mem = ciintf_read_attribute_mem; + budget_av->ca.write_attribute_mem = ciintf_write_attribute_mem; + budget_av->ca.read_cam_control = ciintf_read_cam_control; + budget_av->ca.write_cam_control = ciintf_write_cam_control; + budget_av->ca.slot_reset = ciintf_slot_reset; + budget_av->ca.slot_shutdown = ciintf_slot_shutdown; + budget_av->ca.slot_ts_enable = ciintf_slot_ts_enable; + budget_av->ca.poll_slot_status = ciintf_poll_slot_status; + budget_av->ca.data = budget_av; + budget_av->budget.ci_present = 1; + budget_av->slot_status = SLOTSTATUS_NONE; + + result = dvb_ca_en50221_init(&budget_av->budget.dvb_adapter, + &budget_av->ca, 0, 1); + if (result != 0) { + pr_err("ci initialisation failed\n"); + goto error; + } + + pr_info("ci interface initialised\n"); + return 0; + +error: + saa7146_write(saa, MC1, MASK_27); + return result; +} + +static void ciintf_deinit(struct budget_av *budget_av) +{ + struct saa7146_dev *saa = budget_av->budget.dev; + + saa7146_setgpio(saa, 0, SAA7146_GPIO_INPUT); + saa7146_setgpio(saa, 1, SAA7146_GPIO_INPUT); + saa7146_setgpio(saa, 2, SAA7146_GPIO_INPUT); + saa7146_setgpio(saa, 3, SAA7146_GPIO_INPUT); + + /* release the CA device */ + dvb_ca_en50221_release(&budget_av->ca); + + /* disable DEBI pins */ + saa7146_write(saa, MC1, MASK_27); +} + + +static const u8 saa7113_tab[] = { + 0x01, 0x08, + 0x02, 0xc0, + 0x03, 0x33, + 0x04, 0x00, + 0x05, 0x00, + 0x06, 0xeb, + 0x07, 0xe0, + 0x08, 0x28, + 0x09, 0x00, + 0x0a, 0x80, + 0x0b, 0x47, + 0x0c, 0x40, + 0x0d, 0x00, + 0x0e, 0x01, + 0x0f, 0x44, + + 0x10, 0x08, + 0x11, 0x0c, + 0x12, 0x7b, + 0x13, 0x00, + 0x15, 0x00, 0x16, 0x00, 0x17, 0x00, + + 0x57, 0xff, + 0x40, 0x82, 0x58, 0x00, 0x59, 0x54, 0x5a, 0x07, + 0x5b, 0x83, 0x5e, 0x00, + 0xff +}; + +static int saa7113_init(struct budget_av *budget_av) +{ + struct budget *budget = &budget_av->budget; + struct saa7146_dev *saa = budget->dev; + const u8 *data = saa7113_tab; + + saa7146_setgpio(saa, 0, SAA7146_GPIO_OUTHI); + msleep(200); + + if (i2c_writereg(&budget->i2c_adap, 0x4a, 0x01, 0x08) != 1) { + dprintk(1, "saa7113 not found on KNC card\n"); + return -ENODEV; + } + + dprintk(1, "saa7113 detected and initializing\n"); + + while (*data != 0xff) { + i2c_writereg(&budget->i2c_adap, 0x4a, *data, *(data + 1)); + data += 2; + } + + dprintk(1, "saa7113 status=%02x\n", i2c_readreg(&budget->i2c_adap, 0x4a, 0x1f)); + + return 0; +} + +static int saa7113_setinput(struct budget_av *budget_av, int input) +{ + struct budget *budget = &budget_av->budget; + + if (budget_av->has_saa7113 != 1) + return -ENODEV; + + if (input == 1) { + i2c_writereg(&budget->i2c_adap, 0x4a, 0x02, 0xc7); + i2c_writereg(&budget->i2c_adap, 0x4a, 0x09, 0x80); + } else if (input == 0) { + i2c_writereg(&budget->i2c_adap, 0x4a, 0x02, 0xc0); + i2c_writereg(&budget->i2c_adap, 0x4a, 0x09, 0x00); + } else { + return -EINVAL; + } + + budget_av->cur_input = input; + return 0; +} + + +static int philips_su1278_ty_ci_set_symbol_rate(struct dvb_frontend *fe, u32 srate, u32 ratio) +{ + u8 aclk = 0; + u8 bclk = 0; + u8 m1; + + aclk = 0xb5; + if (srate < 2000000) + bclk = 0x86; + else if (srate < 5000000) + bclk = 0x89; + else if (srate < 15000000) + bclk = 0x8f; + else if (srate < 45000000) + bclk = 0x95; + + m1 = 0x14; + if (srate < 4000000) + m1 = 0x10; + + stv0299_writereg(fe, 0x13, aclk); + stv0299_writereg(fe, 0x14, bclk); + stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg(fe, 0x21, (ratio) & 0xf0); + stv0299_writereg(fe, 0x0f, 0x80 | m1); + + return 0; +} + +static int philips_su1278_ty_ci_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u32 div; + u8 buf[4]; + struct budget *budget = fe->dvb->priv; + struct i2c_msg msg = {.addr = 0x61, .flags = 0, .buf = buf, .len = sizeof(buf) }; + + if ((c->frequency < 950000) || (c->frequency > 2150000)) + return -EINVAL; + + div = (c->frequency + (125 - 1)) / 125; /* round correctly */ + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0x80 | ((div & 0x18000) >> 10) | 4; + buf[3] = 0x20; + + if (c->symbol_rate < 4000000) + buf[3] |= 1; + + if (c->frequency < 1250000) + buf[3] |= 0; + else if (c->frequency < 1550000) + buf[3] |= 0x40; + else if (c->frequency < 2050000) + buf[3] |= 0x80; + else if (c->frequency < 2150000) + buf[3] |= 0xC0; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget->i2c_adap, &msg, 1) != 1) + return -EIO; + return 0; +} + +static u8 typhoon_cinergy1200s_inittab[] = { + 0x01, 0x15, + 0x02, 0x30, + 0x03, 0x00, + 0x04, 0x7d, /* F22FR = 0x7d, F22 = f_VCO / 128 / 0x7d = 22 kHz */ + 0x05, 0x35, /* I2CT = 0, SCLT = 1, SDAT = 1 */ + 0x06, 0x40, /* DAC not used, set to high impendance mode */ + 0x07, 0x00, /* DAC LSB */ + 0x08, 0x40, /* DiSEqC off */ + 0x09, 0x00, /* FIFO */ + 0x0c, 0x51, /* OP1 ctl = Normal, OP1 val = 1 (LNB Power ON) */ + 0x0d, 0x82, /* DC offset compensation = ON, beta_agc1 = 2 */ + 0x0e, 0x23, /* alpha_tmg = 2, beta_tmg = 3 */ + 0x10, 0x3f, // AGC2 0x3d + 0x11, 0x84, + 0x12, 0xb9, + 0x15, 0xc9, // lock detector threshold + 0x16, 0x00, + 0x17, 0x00, + 0x18, 0x00, + 0x19, 0x00, + 0x1a, 0x00, + 0x1f, 0x50, + 0x20, 0x00, + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + 0x28, 0x00, // out imp: normal out type: parallel FEC mode:0 + 0x29, 0x1e, // 1/2 threshold + 0x2a, 0x14, // 2/3 threshold + 0x2b, 0x0f, // 3/4 threshold + 0x2c, 0x09, // 5/6 threshold + 0x2d, 0x05, // 7/8 threshold + 0x2e, 0x01, + 0x31, 0x1f, // test all FECs + 0x32, 0x19, // viterbi and synchro search + 0x33, 0xfc, // rs control + 0x34, 0x93, // error control + 0x0f, 0x92, + 0xff, 0xff +}; + +static const struct stv0299_config typhoon_config = { + .demod_address = 0x68, + .inittab = typhoon_cinergy1200s_inittab, + .mclk = 88000000UL, + .invert = 0, + .skip_reinit = 0, + .lock_output = STV0299_LOCKOUTPUT_1, + .volt13_op0_op1 = STV0299_VOLT13_OP0, + .min_delay_ms = 100, + .set_symbol_rate = philips_su1278_ty_ci_set_symbol_rate, +}; + + +static const struct stv0299_config cinergy_1200s_config = { + .demod_address = 0x68, + .inittab = typhoon_cinergy1200s_inittab, + .mclk = 88000000UL, + .invert = 0, + .skip_reinit = 0, + .lock_output = STV0299_LOCKOUTPUT_0, + .volt13_op0_op1 = STV0299_VOLT13_OP0, + .min_delay_ms = 100, + .set_symbol_rate = philips_su1278_ty_ci_set_symbol_rate, +}; + +static const struct stv0299_config cinergy_1200s_1894_0010_config = { + .demod_address = 0x68, + .inittab = typhoon_cinergy1200s_inittab, + .mclk = 88000000UL, + .invert = 1, + .skip_reinit = 0, + .lock_output = STV0299_LOCKOUTPUT_1, + .volt13_op0_op1 = STV0299_VOLT13_OP0, + .min_delay_ms = 100, + .set_symbol_rate = philips_su1278_ty_ci_set_symbol_rate, +}; + +static int philips_cu1216_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget *budget = fe->dvb->priv; + u8 buf[6]; + struct i2c_msg msg = {.addr = 0x60, .flags = 0, .buf = buf, .len = sizeof(buf) }; + int i; + +#define CU1216_IF 36125000 +#define TUNER_MUL 62500 + + u32 div = (c->frequency + CU1216_IF + TUNER_MUL / 2) / TUNER_MUL; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0xce; + buf[3] = (c->frequency < 150000000 ? 0x01 : + c->frequency < 445000000 ? 0x02 : 0x04); + buf[4] = 0xde; + buf[5] = 0x20; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget->i2c_adap, &msg, 1) != 1) + return -EIO; + + /* wait for the pll lock */ + msg.flags = I2C_M_RD; + msg.len = 1; + for (i = 0; i < 20; i++) { + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget->i2c_adap, &msg, 1) == 1 && (buf[0] & 0x40)) + break; + msleep(10); + } + + /* switch the charge pump to the lower current */ + msg.flags = 0; + msg.len = 2; + msg.buf = &buf[2]; + buf[2] &= ~0x40; + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget->i2c_adap, &msg, 1) != 1) + return -EIO; + + return 0; +} + +static struct tda1002x_config philips_cu1216_config = { + .demod_address = 0x0c, + .invert = 1, +}; + +static struct tda1002x_config philips_cu1216_config_altaddress = { + .demod_address = 0x0d, + .invert = 0, +}; + +static struct tda10023_config philips_cu1216_tda10023_config = { + .demod_address = 0x0c, + .invert = 1, +}; + +static int philips_tu1216_tuner_init(struct dvb_frontend *fe) +{ + struct budget *budget = fe->dvb->priv; + static u8 tu1216_init[] = { 0x0b, 0xf5, 0x85, 0xab }; + struct i2c_msg tuner_msg = {.addr = 0x60, .flags = 0, .buf = tu1216_init, .len = sizeof(tu1216_init) }; + + // setup PLL configuration + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget->i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + msleep(1); + + return 0; +} + +static int philips_tu1216_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget *budget = fe->dvb->priv; + u8 tuner_buf[4]; + struct i2c_msg tuner_msg = {.addr = 0x60, .flags = 0, .buf = tuner_buf, .len = + sizeof(tuner_buf) }; + int tuner_frequency = 0; + u8 band, cp, filter; + + // determine charge pump + tuner_frequency = c->frequency + 36166000; + if (tuner_frequency < 87000000) + return -EINVAL; + else if (tuner_frequency < 130000000) + cp = 3; + else if (tuner_frequency < 160000000) + cp = 5; + else if (tuner_frequency < 200000000) + cp = 6; + else if (tuner_frequency < 290000000) + cp = 3; + else if (tuner_frequency < 420000000) + cp = 5; + else if (tuner_frequency < 480000000) + cp = 6; + else if (tuner_frequency < 620000000) + cp = 3; + else if (tuner_frequency < 830000000) + cp = 5; + else if (tuner_frequency < 895000000) + cp = 7; + else + return -EINVAL; + + // determine band + if (c->frequency < 49000000) + return -EINVAL; + else if (c->frequency < 161000000) + band = 1; + else if (c->frequency < 444000000) + band = 2; + else if (c->frequency < 861000000) + band = 4; + else + return -EINVAL; + + // setup PLL filter + switch (c->bandwidth_hz) { + case 6000000: + filter = 0; + break; + + case 7000000: + filter = 0; + break; + + case 8000000: + filter = 1; + break; + + default: + return -EINVAL; + } + + // calculate divisor + // ((36166000+((1000000/6)/2)) + Finput)/(1000000/6) + tuner_frequency = (((c->frequency / 1000) * 6) + 217496) / 1000; + + // setup tuner buffer + tuner_buf[0] = (tuner_frequency >> 8) & 0x7f; + tuner_buf[1] = tuner_frequency & 0xff; + tuner_buf[2] = 0xca; + tuner_buf[3] = (cp << 5) | (filter << 3) | band; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget->i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + + msleep(1); + return 0; +} + +static int philips_tu1216_request_firmware(struct dvb_frontend *fe, + const struct firmware **fw, char *name) +{ + struct budget *budget = fe->dvb->priv; + + return request_firmware(fw, name, &budget->dev->pci->dev); +} + +static struct tda1004x_config philips_tu1216_config = { + + .demod_address = 0x8, + .invert = 1, + .invert_oclk = 1, + .xtal_freq = TDA10046_XTAL_4M, + .agc_config = TDA10046_AGC_DEFAULT, + .if_freq = TDA10046_FREQ_3617, + .request_firmware = philips_tu1216_request_firmware, +}; + +static u8 philips_sd1878_inittab[] = { + 0x01, 0x15, + 0x02, 0x30, + 0x03, 0x00, + 0x04, 0x7d, + 0x05, 0x35, + 0x06, 0x40, + 0x07, 0x00, + 0x08, 0x43, + 0x09, 0x02, + 0x0C, 0x51, + 0x0D, 0x82, + 0x0E, 0x23, + 0x10, 0x3f, + 0x11, 0x84, + 0x12, 0xb9, + 0x15, 0xc9, + 0x16, 0x19, + 0x17, 0x8c, + 0x18, 0x59, + 0x19, 0xf8, + 0x1a, 0xfe, + 0x1c, 0x7f, + 0x1d, 0x00, + 0x1e, 0x00, + 0x1f, 0x50, + 0x20, 0x00, + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + 0x28, 0x00, + 0x29, 0x28, + 0x2a, 0x14, + 0x2b, 0x0f, + 0x2c, 0x09, + 0x2d, 0x09, + 0x31, 0x1f, + 0x32, 0x19, + 0x33, 0xfc, + 0x34, 0x93, + 0xff, 0xff +}; + +static int philips_sd1878_ci_set_symbol_rate(struct dvb_frontend *fe, + u32 srate, u32 ratio) +{ + u8 aclk = 0; + u8 bclk = 0; + u8 m1; + + aclk = 0xb5; + if (srate < 2000000) + bclk = 0x86; + else if (srate < 5000000) + bclk = 0x89; + else if (srate < 15000000) + bclk = 0x8f; + else if (srate < 45000000) + bclk = 0x95; + + m1 = 0x14; + if (srate < 4000000) + m1 = 0x10; + + stv0299_writereg(fe, 0x0e, 0x23); + stv0299_writereg(fe, 0x0f, 0x94); + stv0299_writereg(fe, 0x10, 0x39); + stv0299_writereg(fe, 0x13, aclk); + stv0299_writereg(fe, 0x14, bclk); + stv0299_writereg(fe, 0x15, 0xc9); + stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg(fe, 0x21, (ratio) & 0xf0); + stv0299_writereg(fe, 0x0f, 0x80 | m1); + + return 0; +} + +static const struct stv0299_config philips_sd1878_config = { + .demod_address = 0x68, + .inittab = philips_sd1878_inittab, + .mclk = 88000000UL, + .invert = 0, + .skip_reinit = 0, + .lock_output = STV0299_LOCKOUTPUT_1, + .volt13_op0_op1 = STV0299_VOLT13_OP0, + .min_delay_ms = 100, + .set_symbol_rate = philips_sd1878_ci_set_symbol_rate, +}; + +/* KNC1 DVB-S (STB0899) Inittab */ +static const struct stb0899_s1_reg knc1_stb0899_s1_init_1[] = { + + { STB0899_DEV_ID, 0x81 }, + { STB0899_DISCNTRL1, 0x32 }, + { STB0899_DISCNTRL2, 0x80 }, + { STB0899_DISRX_ST0, 0x04 }, + { STB0899_DISRX_ST1, 0x00 }, + { STB0899_DISPARITY, 0x00 }, + { STB0899_DISSTATUS, 0x20 }, + { STB0899_DISF22, 0x8c }, + { STB0899_DISF22RX, 0x9a }, + { STB0899_SYSREG, 0x0b }, + { STB0899_ACRPRESC, 0x11 }, + { STB0899_ACRDIV1, 0x0a }, + { STB0899_ACRDIV2, 0x05 }, + { STB0899_DACR1, 0x00 }, + { STB0899_DACR2, 0x00 }, + { STB0899_OUTCFG, 0x00 }, + { STB0899_MODECFG, 0x00 }, + { STB0899_IRQSTATUS_3, 0x30 }, + { STB0899_IRQSTATUS_2, 0x00 }, + { STB0899_IRQSTATUS_1, 0x00 }, + { STB0899_IRQSTATUS_0, 0x00 }, + { STB0899_IRQMSK_3, 0xf3 }, + { STB0899_IRQMSK_2, 0xfc }, + { STB0899_IRQMSK_1, 0xff }, + { STB0899_IRQMSK_0, 0xff }, + { STB0899_IRQCFG, 0x00 }, + { STB0899_I2CCFG, 0x88 }, + { STB0899_I2CRPT, 0x58 }, /* Repeater=8, Stop=disabled */ + { STB0899_IOPVALUE5, 0x00 }, + { STB0899_IOPVALUE4, 0x20 }, + { STB0899_IOPVALUE3, 0xc9 }, + { STB0899_IOPVALUE2, 0x90 }, + { STB0899_IOPVALUE1, 0x40 }, + { STB0899_IOPVALUE0, 0x00 }, + { STB0899_GPIO00CFG, 0x82 }, + { STB0899_GPIO01CFG, 0x82 }, + { STB0899_GPIO02CFG, 0x82 }, + { STB0899_GPIO03CFG, 0x82 }, + { STB0899_GPIO04CFG, 0x82 }, + { STB0899_GPIO05CFG, 0x82 }, + { STB0899_GPIO06CFG, 0x82 }, + { STB0899_GPIO07CFG, 0x82 }, + { STB0899_GPIO08CFG, 0x82 }, + { STB0899_GPIO09CFG, 0x82 }, + { STB0899_GPIO10CFG, 0x82 }, + { STB0899_GPIO11CFG, 0x82 }, + { STB0899_GPIO12CFG, 0x82 }, + { STB0899_GPIO13CFG, 0x82 }, + { STB0899_GPIO14CFG, 0x82 }, + { STB0899_GPIO15CFG, 0x82 }, + { STB0899_GPIO16CFG, 0x82 }, + { STB0899_GPIO17CFG, 0x82 }, + { STB0899_GPIO18CFG, 0x82 }, + { STB0899_GPIO19CFG, 0x82 }, + { STB0899_GPIO20CFG, 0x82 }, + { STB0899_SDATCFG, 0xb8 }, + { STB0899_SCLTCFG, 0xba }, + { STB0899_AGCRFCFG, 0x08 }, /* 0x1c */ + { STB0899_GPIO22, 0x82 }, /* AGCBB2CFG */ + { STB0899_GPIO21, 0x91 }, /* AGCBB1CFG */ + { STB0899_DIRCLKCFG, 0x82 }, + { STB0899_CLKOUT27CFG, 0x7e }, + { STB0899_STDBYCFG, 0x82 }, + { STB0899_CS0CFG, 0x82 }, + { STB0899_CS1CFG, 0x82 }, + { STB0899_DISEQCOCFG, 0x20 }, + { STB0899_GPIO32CFG, 0x82 }, + { STB0899_GPIO33CFG, 0x82 }, + { STB0899_GPIO34CFG, 0x82 }, + { STB0899_GPIO35CFG, 0x82 }, + { STB0899_GPIO36CFG, 0x82 }, + { STB0899_GPIO37CFG, 0x82 }, + { STB0899_GPIO38CFG, 0x82 }, + { STB0899_GPIO39CFG, 0x82 }, + { STB0899_NCOARSE, 0x15 }, /* 0x15 = 27 Mhz Clock, F/3 = 198MHz, F/6 = 99MHz */ + { STB0899_SYNTCTRL, 0x02 }, /* 0x00 = CLK from CLKI, 0x02 = CLK from XTALI */ + { STB0899_FILTCTRL, 0x00 }, + { STB0899_SYSCTRL, 0x00 }, + { STB0899_STOPCLK1, 0x20 }, + { STB0899_STOPCLK2, 0x00 }, + { STB0899_INTBUFSTATUS, 0x00 }, + { STB0899_INTBUFCTRL, 0x0a }, + { 0xffff, 0xff }, +}; + +static const struct stb0899_s1_reg knc1_stb0899_s1_init_3[] = { + { STB0899_DEMOD, 0x00 }, + { STB0899_RCOMPC, 0xc9 }, + { STB0899_AGC1CN, 0x41 }, + { STB0899_AGC1REF, 0x08 }, + { STB0899_RTC, 0x7a }, + { STB0899_TMGCFG, 0x4e }, + { STB0899_AGC2REF, 0x33 }, + { STB0899_TLSR, 0x84 }, + { STB0899_CFD, 0xee }, + { STB0899_ACLC, 0x87 }, + { STB0899_BCLC, 0x94 }, + { STB0899_EQON, 0x41 }, + { STB0899_LDT, 0xdd }, + { STB0899_LDT2, 0xc9 }, + { STB0899_EQUALREF, 0xb4 }, + { STB0899_TMGRAMP, 0x10 }, + { STB0899_TMGTHD, 0x30 }, + { STB0899_IDCCOMP, 0xfb }, + { STB0899_QDCCOMP, 0x03 }, + { STB0899_POWERI, 0x3b }, + { STB0899_POWERQ, 0x3d }, + { STB0899_RCOMP, 0x81 }, + { STB0899_AGCIQIN, 0x80 }, + { STB0899_AGC2I1, 0x04 }, + { STB0899_AGC2I2, 0xf5 }, + { STB0899_TLIR, 0x25 }, + { STB0899_RTF, 0x80 }, + { STB0899_DSTATUS, 0x00 }, + { STB0899_LDI, 0xca }, + { STB0899_CFRM, 0xf1 }, + { STB0899_CFRL, 0xf3 }, + { STB0899_NIRM, 0x2a }, + { STB0899_NIRL, 0x05 }, + { STB0899_ISYMB, 0x17 }, + { STB0899_QSYMB, 0xfa }, + { STB0899_SFRH, 0x2f }, + { STB0899_SFRM, 0x68 }, + { STB0899_SFRL, 0x40 }, + { STB0899_SFRUPH, 0x2f }, + { STB0899_SFRUPM, 0x68 }, + { STB0899_SFRUPL, 0x40 }, + { STB0899_EQUAI1, 0xfd }, + { STB0899_EQUAQ1, 0x04 }, + { STB0899_EQUAI2, 0x0f }, + { STB0899_EQUAQ2, 0xff }, + { STB0899_EQUAI3, 0xdf }, + { STB0899_EQUAQ3, 0xfa }, + { STB0899_EQUAI4, 0x37 }, + { STB0899_EQUAQ4, 0x0d }, + { STB0899_EQUAI5, 0xbd }, + { STB0899_EQUAQ5, 0xf7 }, + { STB0899_DSTATUS2, 0x00 }, + { STB0899_VSTATUS, 0x00 }, + { STB0899_VERROR, 0xff }, + { STB0899_IQSWAP, 0x2a }, + { STB0899_ECNT1M, 0x00 }, + { STB0899_ECNT1L, 0x00 }, + { STB0899_ECNT2M, 0x00 }, + { STB0899_ECNT2L, 0x00 }, + { STB0899_ECNT3M, 0x00 }, + { STB0899_ECNT3L, 0x00 }, + { STB0899_FECAUTO1, 0x06 }, + { STB0899_FECM, 0x01 }, + { STB0899_VTH12, 0xf0 }, + { STB0899_VTH23, 0xa0 }, + { STB0899_VTH34, 0x78 }, + { STB0899_VTH56, 0x4e }, + { STB0899_VTH67, 0x48 }, + { STB0899_VTH78, 0x38 }, + { STB0899_PRVIT, 0xff }, + { STB0899_VITSYNC, 0x19 }, + { STB0899_RSULC, 0xb1 }, /* DVB = 0xb1, DSS = 0xa1 */ + { STB0899_TSULC, 0x42 }, + { STB0899_RSLLC, 0x40 }, + { STB0899_TSLPL, 0x12 }, + { STB0899_TSCFGH, 0x0c }, + { STB0899_TSCFGM, 0x00 }, + { STB0899_TSCFGL, 0x0c }, + { STB0899_TSOUT, 0x4d }, /* 0x0d for CAM */ + { STB0899_RSSYNCDEL, 0x00 }, + { STB0899_TSINHDELH, 0x02 }, + { STB0899_TSINHDELM, 0x00 }, + { STB0899_TSINHDELL, 0x00 }, + { STB0899_TSLLSTKM, 0x00 }, + { STB0899_TSLLSTKL, 0x00 }, + { STB0899_TSULSTKM, 0x00 }, + { STB0899_TSULSTKL, 0xab }, + { STB0899_PCKLENUL, 0x00 }, + { STB0899_PCKLENLL, 0xcc }, + { STB0899_RSPCKLEN, 0xcc }, + { STB0899_TSSTATUS, 0x80 }, + { STB0899_ERRCTRL1, 0xb6 }, + { STB0899_ERRCTRL2, 0x96 }, + { STB0899_ERRCTRL3, 0x89 }, + { STB0899_DMONMSK1, 0x27 }, + { STB0899_DMONMSK0, 0x03 }, + { STB0899_DEMAPVIT, 0x5c }, + { STB0899_PLPARM, 0x1f }, + { STB0899_PDELCTRL, 0x48 }, + { STB0899_PDELCTRL2, 0x00 }, + { STB0899_BBHCTRL1, 0x00 }, + { STB0899_BBHCTRL2, 0x00 }, + { STB0899_HYSTTHRESH, 0x77 }, + { STB0899_MATCSTM, 0x00 }, + { STB0899_MATCSTL, 0x00 }, + { STB0899_UPLCSTM, 0x00 }, + { STB0899_UPLCSTL, 0x00 }, + { STB0899_DFLCSTM, 0x00 }, + { STB0899_DFLCSTL, 0x00 }, + { STB0899_SYNCCST, 0x00 }, + { STB0899_SYNCDCSTM, 0x00 }, + { STB0899_SYNCDCSTL, 0x00 }, + { STB0899_ISI_ENTRY, 0x00 }, + { STB0899_ISI_BIT_EN, 0x00 }, + { STB0899_MATSTRM, 0x00 }, + { STB0899_MATSTRL, 0x00 }, + { STB0899_UPLSTRM, 0x00 }, + { STB0899_UPLSTRL, 0x00 }, + { STB0899_DFLSTRM, 0x00 }, + { STB0899_DFLSTRL, 0x00 }, + { STB0899_SYNCSTR, 0x00 }, + { STB0899_SYNCDSTRM, 0x00 }, + { STB0899_SYNCDSTRL, 0x00 }, + { STB0899_CFGPDELSTATUS1, 0x10 }, + { STB0899_CFGPDELSTATUS2, 0x00 }, + { STB0899_BBFERRORM, 0x00 }, + { STB0899_BBFERRORL, 0x00 }, + { STB0899_UPKTERRORM, 0x00 }, + { STB0899_UPKTERRORL, 0x00 }, + { 0xffff, 0xff }, +}; + +/* STB0899 demodulator config for the KNC1 and clones */ +static struct stb0899_config knc1_dvbs2_config = { + .init_dev = knc1_stb0899_s1_init_1, + .init_s2_demod = stb0899_s2_init_2, + .init_s1_demod = knc1_stb0899_s1_init_3, + .init_s2_fec = stb0899_s2_init_4, + .init_tst = stb0899_s1_init_5, + + .postproc = NULL, + + .demod_address = 0x68, +// .ts_output_mode = STB0899_OUT_PARALLEL, /* types = SERIAL/PARALLEL */ + .block_sync_mode = STB0899_SYNC_FORCED, /* DSS, SYNC_FORCED/UNSYNCED */ +// .ts_pfbit_toggle = STB0899_MPEG_NORMAL, /* DirecTV, MPEG toggling seq */ + + .xtal_freq = 27000000, + .inversion = IQ_SWAP_OFF, + + .lo_clk = 76500000, + .hi_clk = 90000000, + + .esno_ave = STB0899_DVBS2_ESNO_AVE, + .esno_quant = STB0899_DVBS2_ESNO_QUANT, + .avframes_coarse = STB0899_DVBS2_AVFRAMES_COARSE, + .avframes_fine = STB0899_DVBS2_AVFRAMES_FINE, + .miss_threshold = STB0899_DVBS2_MISS_THRESHOLD, + .uwp_threshold_acq = STB0899_DVBS2_UWP_THRESHOLD_ACQ, + .uwp_threshold_track = STB0899_DVBS2_UWP_THRESHOLD_TRACK, + .uwp_threshold_sof = STB0899_DVBS2_UWP_THRESHOLD_SOF, + .sof_search_timeout = STB0899_DVBS2_SOF_SEARCH_TIMEOUT, + + .btr_nco_bits = STB0899_DVBS2_BTR_NCO_BITS, + .btr_gain_shift_offset = STB0899_DVBS2_BTR_GAIN_SHIFT_OFFSET, + .crl_nco_bits = STB0899_DVBS2_CRL_NCO_BITS, + .ldpc_max_iter = STB0899_DVBS2_LDPC_MAX_ITER, + + .tuner_get_frequency = tda8261_get_frequency, + .tuner_set_frequency = tda8261_set_frequency, + .tuner_set_bandwidth = NULL, + .tuner_get_bandwidth = tda8261_get_bandwidth, + .tuner_set_rfsiggain = NULL +}; + +/* + * SD1878/SHA tuner config + * 1F, Single I/P, Horizontal mount, High Sensitivity + */ +static const struct tda8261_config sd1878c_config = { +// .name = "SD1878/SHA", + .addr = 0x60, + .step_size = TDA8261_STEP_1000 /* kHz */ +}; + +static u8 read_pwm(struct budget_av *budget_av) +{ + u8 b = 0xff; + u8 pwm; + struct i2c_msg msg[] = { {.addr = 0x50, .flags = 0, .buf = &b, .len = 1}, + {.addr = 0x50, .flags = I2C_M_RD, .buf = &pwm, .len = 1} + }; + + if ((i2c_transfer(&budget_av->budget.i2c_adap, msg, 2) != 2) + || (pwm == 0xff)) + pwm = 0x48; + + return pwm; +} + +#define SUBID_DVBS_KNC1 0x0010 +#define SUBID_DVBS_KNC1_PLUS 0x0011 +#define SUBID_DVBS_TYPHOON 0x4f56 +#define SUBID_DVBS_CINERGY1200 0x1154 +#define SUBID_DVBS_CYNERGY1200N 0x1155 +#define SUBID_DVBS_TV_STAR 0x0014 +#define SUBID_DVBS_TV_STAR_PLUS_X4 0x0015 +#define SUBID_DVBS_TV_STAR_CI 0x0016 +#define SUBID_DVBS2_KNC1 0x0018 +#define SUBID_DVBS2_KNC1_OEM 0x0019 +#define SUBID_DVBS_EASYWATCH_1 0x001a +#define SUBID_DVBS_EASYWATCH_2 0x001b +#define SUBID_DVBS2_EASYWATCH 0x001d +#define SUBID_DVBS_EASYWATCH 0x001e + +#define SUBID_DVBC_EASYWATCH 0x002a +#define SUBID_DVBC_EASYWATCH_MK3 0x002c +#define SUBID_DVBC_KNC1 0x0020 +#define SUBID_DVBC_KNC1_PLUS 0x0021 +#define SUBID_DVBC_KNC1_MK3 0x0022 +#define SUBID_DVBC_KNC1_TDA10024 0x0028 +#define SUBID_DVBC_KNC1_PLUS_MK3 0x0023 +#define SUBID_DVBC_CINERGY1200 0x1156 +#define SUBID_DVBC_CINERGY1200_MK3 0x1176 + +#define SUBID_DVBT_EASYWATCH 0x003a +#define SUBID_DVBT_KNC1_PLUS 0x0031 +#define SUBID_DVBT_KNC1 0x0030 +#define SUBID_DVBT_CINERGY1200 0x1157 + +static void frontend_init(struct budget_av *budget_av) +{ + struct saa7146_dev *saa = budget_av->budget.dev; + struct dvb_frontend *fe = NULL; + + /* Enable / PowerON Frontend */ + saa7146_setgpio(saa, 0, SAA7146_GPIO_OUTLO); + + /* Wait for PowerON */ + msleep(100); + + /* additional setup necessary for the PLUS cards */ + switch (saa->pci->subsystem_device) { + case SUBID_DVBS_KNC1_PLUS: + case SUBID_DVBC_KNC1_PLUS: + case SUBID_DVBT_KNC1_PLUS: + case SUBID_DVBC_EASYWATCH: + case SUBID_DVBC_KNC1_PLUS_MK3: + case SUBID_DVBS2_KNC1: + case SUBID_DVBS2_KNC1_OEM: + case SUBID_DVBS2_EASYWATCH: + saa7146_setgpio(saa, 3, SAA7146_GPIO_OUTHI); + break; + } + + switch (saa->pci->subsystem_device) { + + case SUBID_DVBS_KNC1: + /* + * maybe that setting is needed for other dvb-s cards as well, + * but so far it has been only confirmed for this type + */ + budget_av->reinitialise_demod = 1; + fallthrough; + case SUBID_DVBS_KNC1_PLUS: + case SUBID_DVBS_EASYWATCH_1: + if (saa->pci->subsystem_vendor == 0x1894) { + fe = dvb_attach(stv0299_attach, &cinergy_1200s_1894_0010_config, + &budget_av->budget.i2c_adap); + if (fe) + dvb_attach(tua6100_attach, fe, 0x60, &budget_av->budget.i2c_adap); + } else { + fe = dvb_attach(stv0299_attach, &typhoon_config, + &budget_av->budget.i2c_adap); + if (fe) + fe->ops.tuner_ops.set_params = philips_su1278_ty_ci_tuner_set_params; + } + break; + + case SUBID_DVBS_TV_STAR: + case SUBID_DVBS_TV_STAR_PLUS_X4: + case SUBID_DVBS_TV_STAR_CI: + case SUBID_DVBS_CYNERGY1200N: + case SUBID_DVBS_EASYWATCH: + case SUBID_DVBS_EASYWATCH_2: + fe = dvb_attach(stv0299_attach, &philips_sd1878_config, + &budget_av->budget.i2c_adap); + if (fe) + dvb_attach(dvb_pll_attach, fe, 0x60, + &budget_av->budget.i2c_adap, + DVB_PLL_PHILIPS_SD1878_TDA8261); + break; + + case SUBID_DVBS_TYPHOON: + fe = dvb_attach(stv0299_attach, &typhoon_config, + &budget_av->budget.i2c_adap); + if (fe) + fe->ops.tuner_ops.set_params = philips_su1278_ty_ci_tuner_set_params; + break; + case SUBID_DVBS2_KNC1: + case SUBID_DVBS2_KNC1_OEM: + case SUBID_DVBS2_EASYWATCH: + budget_av->reinitialise_demod = 1; + fe = dvb_attach(stb0899_attach, &knc1_dvbs2_config, &budget_av->budget.i2c_adap); + if (fe) + dvb_attach(tda8261_attach, fe, &sd1878c_config, &budget_av->budget.i2c_adap); + + break; + case SUBID_DVBS_CINERGY1200: + fe = dvb_attach(stv0299_attach, &cinergy_1200s_config, + &budget_av->budget.i2c_adap); + if (fe) + fe->ops.tuner_ops.set_params = philips_su1278_ty_ci_tuner_set_params; + break; + + case SUBID_DVBC_KNC1: + case SUBID_DVBC_KNC1_PLUS: + case SUBID_DVBC_CINERGY1200: + case SUBID_DVBC_EASYWATCH: + budget_av->reinitialise_demod = 1; + budget_av->budget.dev->i2c_bitrate = SAA7146_I2C_BUS_BIT_RATE_240; + fe = dvb_attach(tda10021_attach, &philips_cu1216_config, + &budget_av->budget.i2c_adap, + read_pwm(budget_av)); + if (fe == NULL) + fe = dvb_attach(tda10021_attach, &philips_cu1216_config_altaddress, + &budget_av->budget.i2c_adap, + read_pwm(budget_av)); + if (fe) + fe->ops.tuner_ops.set_params = philips_cu1216_tuner_set_params; + break; + + case SUBID_DVBC_EASYWATCH_MK3: + case SUBID_DVBC_CINERGY1200_MK3: + case SUBID_DVBC_KNC1_MK3: + case SUBID_DVBC_KNC1_TDA10024: + case SUBID_DVBC_KNC1_PLUS_MK3: + budget_av->reinitialise_demod = 1; + budget_av->budget.dev->i2c_bitrate = SAA7146_I2C_BUS_BIT_RATE_240; + fe = dvb_attach(tda10023_attach, + &philips_cu1216_tda10023_config, + &budget_av->budget.i2c_adap, + read_pwm(budget_av)); + if (fe) + fe->ops.tuner_ops.set_params = philips_cu1216_tuner_set_params; + break; + + case SUBID_DVBT_EASYWATCH: + case SUBID_DVBT_KNC1: + case SUBID_DVBT_KNC1_PLUS: + case SUBID_DVBT_CINERGY1200: + budget_av->reinitialise_demod = 1; + fe = dvb_attach(tda10046_attach, &philips_tu1216_config, + &budget_av->budget.i2c_adap); + if (fe) { + fe->ops.tuner_ops.init = philips_tu1216_tuner_init; + fe->ops.tuner_ops.set_params = philips_tu1216_tuner_set_params; + } + break; + } + + if (fe == NULL) { + pr_err("A frontend driver was not found for device [%04x:%04x] subsystem [%04x:%04x]\n", + saa->pci->vendor, + saa->pci->device, + saa->pci->subsystem_vendor, + saa->pci->subsystem_device); + return; + } + + budget_av->budget.dvb_frontend = fe; + + if (dvb_register_frontend(&budget_av->budget.dvb_adapter, + budget_av->budget.dvb_frontend)) { + pr_err("Frontend registration failed!\n"); + dvb_frontend_detach(budget_av->budget.dvb_frontend); + budget_av->budget.dvb_frontend = NULL; + } +} + + +static void budget_av_irq(struct saa7146_dev *dev, u32 *isr) +{ + struct budget_av *budget_av = dev->ext_priv; + + dprintk(8, "dev: %p, budget_av: %p\n", dev, budget_av); + + if (*isr & MASK_10) + ttpci_budget_irq10_handler(dev, isr); +} + +static int budget_av_detach(struct saa7146_dev *dev) +{ + struct budget_av *budget_av = dev->ext_priv; + int err; + + dprintk(2, "dev: %p\n", dev); + + if (budget_av->has_saa7113 == 1) { + saa7146_setgpio(dev, 0, SAA7146_GPIO_OUTLO); + + msleep(200); + + saa7146_unregister_device(&budget_av->vd, dev); + + saa7146_vv_release(dev); + } + + if (budget_av->budget.ci_present) + ciintf_deinit(budget_av); + + if (budget_av->budget.dvb_frontend != NULL) { + dvb_unregister_frontend(budget_av->budget.dvb_frontend); + dvb_frontend_detach(budget_av->budget.dvb_frontend); + } + err = ttpci_budget_deinit(&budget_av->budget); + + kfree(budget_av); + + return err; +} + +#define KNC1_INPUTS 2 +static struct v4l2_input knc1_inputs[KNC1_INPUTS] = { + { 0, "Composite", V4L2_INPUT_TYPE_TUNER, 1, 0, + V4L2_STD_PAL_BG | V4L2_STD_NTSC_M, 0, V4L2_IN_CAP_STD }, + { 1, "S-Video", V4L2_INPUT_TYPE_CAMERA, 2, 0, + V4L2_STD_PAL_BG | V4L2_STD_NTSC_M, 0, V4L2_IN_CAP_STD }, +}; + +static int vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *i) +{ + dprintk(1, "VIDIOC_ENUMINPUT %d\n", i->index); + if (i->index >= KNC1_INPUTS) + return -EINVAL; + memcpy(i, &knc1_inputs[i->index], sizeof(struct v4l2_input)); + return 0; +} + +static int vidioc_g_input(struct file *file, void *fh, unsigned int *i) +{ + struct saa7146_dev *dev = video_drvdata(file); + struct budget_av *budget_av = dev->ext_priv; + + *i = budget_av->cur_input; + + dprintk(1, "VIDIOC_G_INPUT %d\n", *i); + return 0; +} + +static int vidioc_s_input(struct file *file, void *fh, unsigned int input) +{ + struct saa7146_dev *dev = video_drvdata(file); + struct budget_av *budget_av = dev->ext_priv; + + dprintk(1, "VIDIOC_S_INPUT %d\n", input); + return saa7113_setinput(budget_av, input); +} + +static struct saa7146_ext_vv vv_data; + +static int budget_av_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct budget_av *budget_av; + u8 *mac; + int err; + + dprintk(2, "dev: %p\n", dev); + + budget_av = kzalloc_obj(struct budget_av); + if (!budget_av) + return -ENOMEM; + + budget_av->has_saa7113 = 0; + budget_av->budget.ci_present = 0; + + dev->ext_priv = budget_av; + + err = ttpci_budget_init(&budget_av->budget, dev, info, THIS_MODULE, + adapter_nr); + if (err) { + kfree(budget_av); + return err; + } + + /* knc1 initialization */ + saa7146_write(dev, DD1_STREAM_B, 0x04000000); + saa7146_write(dev, DD1_INIT, 0x07000600); + saa7146_write(dev, MC2, MASK_09 | MASK_25 | MASK_10 | MASK_26); + + if (saa7113_init(budget_av) == 0) { + budget_av->has_saa7113 = 1; + err = saa7146_vv_init(dev, &vv_data); + if (err != 0) { + ttpci_budget_deinit(&budget_av->budget); + kfree(budget_av); + pr_err("cannot init vv subsystem\n"); + return err; + } + vv_data.vid_ops.vidioc_enum_input = vidioc_enum_input; + vv_data.vid_ops.vidioc_g_input = vidioc_g_input; + vv_data.vid_ops.vidioc_s_input = vidioc_s_input; + + err = saa7146_register_device(&budget_av->vd, dev, "knc1", VFL_TYPE_VIDEO); + if (err) { + saa7146_vv_release(dev); + ttpci_budget_deinit(&budget_av->budget); + kfree(budget_av); + pr_err("cannot register capture v4l2 device\n"); + return err; + } + + /* beware: this modifies dev->vv ... */ + saa7146_set_hps_source_and_sync(dev, SAA7146_HPS_SOURCE_PORT_A, + SAA7146_HPS_SYNC_PORT_A); + + saa7113_setinput(budget_av, 0); + } + + /* fixme: find some sane values here... */ + saa7146_write(dev, PCI_BT_V1, 0x1c00101f); + + mac = budget_av->budget.dvb_adapter.proposed_mac; + if (i2c_readregs(&budget_av->budget.i2c_adap, 0xa0, 0x30, mac, 6)) { + pr_err("KNC1-%d: Could not read MAC from KNC1 card\n", + budget_av->budget.dvb_adapter.num); + eth_zero_addr(mac); + } else { + pr_info("KNC1-%d: MAC addr = %pM\n", + budget_av->budget.dvb_adapter.num, mac); + } + + budget_av->budget.dvb_adapter.priv = budget_av; + frontend_init(budget_av); + ciintf_init(budget_av); + + ttpci_budget_init_hooks(&budget_av->budget); + + return 0; +} + +static struct saa7146_standard standard[] = { + {.name = "PAL", .id = V4L2_STD_PAL, + .v_offset = 0x17, .v_field = 288, + .h_offset = 0x14, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768 }, + + {.name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 0x16, .v_field = 240, + .h_offset = 0x06, .h_pixels = 708, + .v_max_out = 480, .h_max_out = 640, }, +}; + +static struct saa7146_ext_vv vv_data = { + .inputs = 2, + .capabilities = 0, // perhaps later: V4L2_CAP_VBI_CAPTURE, but that need tweaking with the saa7113 + .flags = 0, + .stds = &standard[0], + .num_stds = ARRAY_SIZE(standard), +}; + +static struct saa7146_extension budget_extension; + +MAKE_BUDGET_INFO(knc1s, "KNC1 DVB-S", BUDGET_KNC1S); +MAKE_BUDGET_INFO(knc1s2, "KNC1 DVB-S2", BUDGET_KNC1S2); +MAKE_BUDGET_INFO(sates2, "Satelco EasyWatch DVB-S2", BUDGET_KNC1S2); +MAKE_BUDGET_INFO(knc1c, "KNC1 DVB-C", BUDGET_KNC1C); +MAKE_BUDGET_INFO(knc1t, "KNC1 DVB-T", BUDGET_KNC1T); +MAKE_BUDGET_INFO(kncxs, "KNC TV STAR DVB-S", BUDGET_TVSTAR); +MAKE_BUDGET_INFO(satewpls, "Satelco EasyWatch DVB-S light", BUDGET_TVSTAR); +MAKE_BUDGET_INFO(satewpls1, "Satelco EasyWatch DVB-S light", BUDGET_KNC1S); +MAKE_BUDGET_INFO(satewps, "Satelco EasyWatch DVB-S", BUDGET_KNC1S); +MAKE_BUDGET_INFO(satewplc, "Satelco EasyWatch DVB-C", BUDGET_KNC1CP); +MAKE_BUDGET_INFO(satewcmk3, "Satelco EasyWatch DVB-C MK3", BUDGET_KNC1C_MK3); +MAKE_BUDGET_INFO(satewt, "Satelco EasyWatch DVB-T", BUDGET_KNC1T); +MAKE_BUDGET_INFO(knc1sp, "KNC1 DVB-S Plus", BUDGET_KNC1SP); +MAKE_BUDGET_INFO(knc1spx4, "KNC1 DVB-S Plus X4", BUDGET_KNC1SP); +MAKE_BUDGET_INFO(knc1cp, "KNC1 DVB-C Plus", BUDGET_KNC1CP); +MAKE_BUDGET_INFO(knc1cmk3, "KNC1 DVB-C MK3", BUDGET_KNC1C_MK3); +MAKE_BUDGET_INFO(knc1ctda10024, "KNC1 DVB-C TDA10024", BUDGET_KNC1C_TDA10024); +MAKE_BUDGET_INFO(knc1cpmk3, "KNC1 DVB-C Plus MK3", BUDGET_KNC1CP_MK3); +MAKE_BUDGET_INFO(knc1tp, "KNC1 DVB-T Plus", BUDGET_KNC1TP); +MAKE_BUDGET_INFO(cin1200s, "TerraTec Cinergy 1200 DVB-S", BUDGET_CIN1200S); +MAKE_BUDGET_INFO(cin1200sn, "TerraTec Cinergy 1200 DVB-S", BUDGET_CIN1200S); +MAKE_BUDGET_INFO(cin1200c, "Terratec Cinergy 1200 DVB-C", BUDGET_CIN1200C); +MAKE_BUDGET_INFO(cin1200cmk3, "Terratec Cinergy 1200 DVB-C MK3", BUDGET_CIN1200C_MK3); +MAKE_BUDGET_INFO(cin1200t, "Terratec Cinergy 1200 DVB-T", BUDGET_CIN1200T); + +static const struct pci_device_id pci_tbl[] = { + MAKE_EXTENSION_PCI(knc1s, 0x1131, 0x4f56), + MAKE_EXTENSION_PCI(knc1s, 0x1131, 0x0010), + MAKE_EXTENSION_PCI(knc1s, 0x1894, 0x0010), + MAKE_EXTENSION_PCI(knc1sp, 0x1131, 0x0011), + MAKE_EXTENSION_PCI(knc1sp, 0x1894, 0x0011), + MAKE_EXTENSION_PCI(kncxs, 0x1894, 0x0014), + MAKE_EXTENSION_PCI(knc1spx4, 0x1894, 0x0015), + MAKE_EXTENSION_PCI(kncxs, 0x1894, 0x0016), + MAKE_EXTENSION_PCI(knc1s2, 0x1894, 0x0018), + MAKE_EXTENSION_PCI(knc1s2, 0x1894, 0x0019), + MAKE_EXTENSION_PCI(sates2, 0x1894, 0x001d), + MAKE_EXTENSION_PCI(satewpls, 0x1894, 0x001e), + MAKE_EXTENSION_PCI(satewpls1, 0x1894, 0x001a), + MAKE_EXTENSION_PCI(satewps, 0x1894, 0x001b), + MAKE_EXTENSION_PCI(satewplc, 0x1894, 0x002a), + MAKE_EXTENSION_PCI(satewcmk3, 0x1894, 0x002c), + MAKE_EXTENSION_PCI(satewt, 0x1894, 0x003a), + MAKE_EXTENSION_PCI(knc1c, 0x1894, 0x0020), + MAKE_EXTENSION_PCI(knc1cp, 0x1894, 0x0021), + MAKE_EXTENSION_PCI(knc1cmk3, 0x1894, 0x0022), + MAKE_EXTENSION_PCI(knc1ctda10024, 0x1894, 0x0028), + MAKE_EXTENSION_PCI(knc1cpmk3, 0x1894, 0x0023), + MAKE_EXTENSION_PCI(knc1t, 0x1894, 0x0030), + MAKE_EXTENSION_PCI(knc1tp, 0x1894, 0x0031), + MAKE_EXTENSION_PCI(cin1200s, 0x153b, 0x1154), + MAKE_EXTENSION_PCI(cin1200sn, 0x153b, 0x1155), + MAKE_EXTENSION_PCI(cin1200c, 0x153b, 0x1156), + MAKE_EXTENSION_PCI(cin1200cmk3, 0x153b, 0x1176), + MAKE_EXTENSION_PCI(cin1200t, 0x153b, 0x1157), + { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_extension budget_extension = { + .name = "budget_av", + .flags = SAA7146_USE_I2C_IRQ, + + .pci_tbl = pci_tbl, + + .module = THIS_MODULE, + .attach = budget_av_attach, + .detach = budget_av_detach, + + .irq_mask = MASK_10, + .irq_func = budget_av_irq, +}; + +static int __init budget_av_init(void) +{ + return saa7146_register_extension(&budget_extension); +} + +static void __exit budget_av_exit(void) +{ + saa7146_unregister_extension(&budget_extension); +} + +module_init(budget_av_init); +module_exit(budget_av_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ralph Metzler, Marcus Metzler, Michael Hunold, others"); +MODULE_DESCRIPTION("driver for the SAA7146 based so-called budget PCI DVB w/ analog input and CI-module (e.g. the KNC cards)"); diff --git a/drivers/media/pci/ttpci/budget-ci.c b/drivers/media/pci/ttpci/budget-ci.c new file mode 100644 index 000000000..eb82a670c --- /dev/null +++ b/drivers/media/pci/ttpci/budget-ci.c @@ -0,0 +1,1573 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * budget-ci.ko: driver for the SAA7146 based Budget DVB cards + * with CI (but without analog video input) + * + * Compiled from various sources by Michael Hunold + * + * msp430 IR support contributed by Jack Thomasson + * partially based on the Siemens DVB driver by Ralph+Marcus Metzler + * + * CI interface support (c) 2004 Andrew de Quincey + * + * the project's page is at https://linuxtv.org + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "budget.h" + +#include +#include "stv0299.h" +#include "stv0297.h" +#include "tda1004x.h" +#include "stb0899_drv.h" +#include "stb0899_reg.h" +#include "stb0899_cfg.h" +#include "stb6100.h" +#include "stb6100_cfg.h" +#include "lnbp21.h" +#include "bsbe1.h" +#include "bsru6.h" +#include "tda1002x.h" +#include "tda827x.h" +#include "bsbe1-d01a.h" + +#define MODULE_NAME "budget_ci" + +/* + * Regarding DEBIADDR_IR: + * Some CI modules hang if random addresses are read. + * Using address 0x4000 for the IR read means that we + * use the same address as for CI version, which should + * be a safe default. + */ +#define DEBIADDR_IR 0x4000 +#define DEBIADDR_CICONTROL 0x0000 +#define DEBIADDR_CIVERSION 0x4000 +#define DEBIADDR_IO 0x1000 +#define DEBIADDR_ATTR 0x3000 + +#define CICONTROL_RESET 0x01 +#define CICONTROL_ENABLETS 0x02 +#define CICONTROL_CAMDETECT 0x08 + +#define DEBICICTL 0x00420000 +#define DEBICICAM 0x02420000 + +#define SLOTSTATUS_NONE 1 +#define SLOTSTATUS_PRESENT 2 +#define SLOTSTATUS_RESET 4 +#define SLOTSTATUS_READY 8 +#define SLOTSTATUS_OCCUPIED (SLOTSTATUS_PRESENT|SLOTSTATUS_RESET|SLOTSTATUS_READY) + +/* RC5 device wildcard */ +#define IR_DEVICE_ANY 255 + +static int rc5_device = -1; +module_param(rc5_device, int, 0644); +MODULE_PARM_DESC(rc5_device, "only IR commands to given RC5 device (device = 0 - 31, any device = 255, default: autodetect)"); + +static int ir_debug; +module_param(ir_debug, int, 0644); +MODULE_PARM_DESC(ir_debug, "enable debugging information for IR decoding"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +struct budget_ci_ir { + struct rc_dev *dev; + struct work_struct msp430_irq_bh_work; + char name[72]; /* 40 + 32 for (struct saa7146_dev).name */ + char phys[32]; + int rc5_device; + u32 ir_key; + bool have_command; + bool full_rc5; /* Outputs a full RC5 code */ +}; + +struct budget_ci { + struct budget budget; + struct work_struct ciintf_irq_bh_work; + int slot_status; + int ci_irq; + struct dvb_ca_en50221 ca; + struct budget_ci_ir ir; + u8 tuner_pll_address; /* used for philips_tdm1316l configs */ +}; + +static void msp430_ir_interrupt(struct work_struct *t) +{ + struct budget_ci_ir *ir = from_work(ir, t, msp430_irq_bh_work); + struct budget_ci *budget_ci = container_of(ir, typeof(*budget_ci), ir); + struct rc_dev *dev = budget_ci->ir.dev; + u32 command = ttpci_budget_debiread(&budget_ci->budget, DEBINOSWAP, DEBIADDR_IR, 2, 1, 0) >> 8; + + /* + * The msp430 chip can generate two different bytes, command and device + * + * type1: X1CCCCCC, C = command bits (0 - 63) + * type2: X0TDDDDD, D = device bits (0 - 31), T = RC5 toggle bit + * + * Each signal from the remote control can generate one or more command + * bytes and one or more device bytes. For the repeated bytes, the + * highest bit (X) is set. The first command byte is always generated + * before the first device byte. Other than that, no specific order + * seems to apply. To make life interesting, bytes can also be lost. + * + * Only when we have a command and device byte, a keypress is + * generated. + */ + + if (ir_debug) + pr_info("received byte 0x%02x\n", command); + + /* Remove repeat bit, we use every command */ + command = command & 0x7f; + + /* Is this a RC5 command byte? */ + if (command & 0x40) { + budget_ci->ir.have_command = true; + budget_ci->ir.ir_key = command & 0x3f; + return; + } + + /* It's a RC5 device byte */ + if (!budget_ci->ir.have_command) + return; + budget_ci->ir.have_command = false; + + if (budget_ci->ir.rc5_device != IR_DEVICE_ANY && + budget_ci->ir.rc5_device != (command & 0x1f)) + return; + + if (budget_ci->ir.full_rc5) { + rc_keydown(dev, RC_PROTO_RC5, + RC_SCANCODE_RC5(budget_ci->ir.rc5_device, budget_ci->ir.ir_key), + !!(command & 0x20)); + return; + } + + /* FIXME: We should generate complete scancodes for all devices */ + rc_keydown(dev, RC_PROTO_UNKNOWN, budget_ci->ir.ir_key, + !!(command & 0x20)); +} + +static int msp430_ir_init(struct budget_ci *budget_ci) +{ + struct saa7146_dev *saa = budget_ci->budget.dev; + struct rc_dev *dev; + int error; + + dev = rc_allocate_device(RC_DRIVER_SCANCODE); + if (!dev) { + pr_err("IR interface initialisation failed\n"); + return -ENOMEM; + } + + snprintf(budget_ci->ir.name, sizeof(budget_ci->ir.name), + "Budget-CI dvb ir receiver %s", saa->name); + snprintf(budget_ci->ir.phys, sizeof(budget_ci->ir.phys), + "pci-%s/ir0", pci_name(saa->pci)); + + dev->driver_name = MODULE_NAME; + dev->device_name = budget_ci->ir.name; + dev->input_phys = budget_ci->ir.phys; + dev->input_id.bustype = BUS_PCI; + dev->input_id.version = 1; + if (saa->pci->subsystem_vendor) { + dev->input_id.vendor = saa->pci->subsystem_vendor; + dev->input_id.product = saa->pci->subsystem_device; + } else { + dev->input_id.vendor = saa->pci->vendor; + dev->input_id.product = saa->pci->device; + } + dev->dev.parent = &saa->pci->dev; + + if (rc5_device < 0) + budget_ci->ir.rc5_device = IR_DEVICE_ANY; + else + budget_ci->ir.rc5_device = rc5_device; + + /* Select keymap and address */ + switch (budget_ci->budget.dev->pci->subsystem_device) { + case 0x100c: + case 0x100f: + case 0x1011: + case 0x1012: + /* The hauppauge keymap is a superset of these remotes */ + dev->map_name = RC_MAP_HAUPPAUGE; + budget_ci->ir.full_rc5 = true; + + if (rc5_device < 0) + budget_ci->ir.rc5_device = 0x1f; + break; + case 0x1010: + case 0x1017: + case 0x1019: + case 0x101a: + case 0x101b: + /* for the Technotrend 1500 bundled remote */ + dev->map_name = RC_MAP_TT_1500; + break; + default: + /* unknown remote */ + dev->map_name = RC_MAP_BUDGET_CI_OLD; + break; + } + if (!budget_ci->ir.full_rc5) + dev->scancode_mask = 0xff; + + error = rc_register_device(dev); + if (error) { + pr_err("could not init driver for IR device (code %d)\n", error); + rc_free_device(dev); + return error; + } + + budget_ci->ir.dev = dev; + + INIT_WORK(&budget_ci->ir.msp430_irq_bh_work, msp430_ir_interrupt); + + SAA7146_IER_ENABLE(saa, MASK_06); + saa7146_setgpio(saa, 3, SAA7146_GPIO_IRQHI); + + return 0; +} + +static void msp430_ir_deinit(struct budget_ci *budget_ci) +{ + struct saa7146_dev *saa = budget_ci->budget.dev; + + SAA7146_IER_DISABLE(saa, MASK_06); + saa7146_setgpio(saa, 3, SAA7146_GPIO_INPUT); + cancel_work_sync(&budget_ci->ir.msp430_irq_bh_work); + + rc_unregister_device(budget_ci->ir.dev); + rc_free_device(budget_ci->ir.dev); +} + +static int ciintf_read_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address) +{ + struct budget_ci *budget_ci = ca->data; + + if (slot != 0) + return -EINVAL; + + return ttpci_budget_debiread(&budget_ci->budget, DEBICICAM, + DEBIADDR_ATTR | (address & 0xfff), 1, 1, 0); +} + +static int ciintf_write_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address, u8 value) +{ + struct budget_ci *budget_ci = ca->data; + + if (slot != 0) + return -EINVAL; + + return ttpci_budget_debiwrite(&budget_ci->budget, DEBICICAM, + DEBIADDR_ATTR | (address & 0xfff), 1, value, 1, 0); +} + +static int ciintf_read_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address) +{ + struct budget_ci *budget_ci = ca->data; + + if (slot != 0) + return -EINVAL; + + return ttpci_budget_debiread(&budget_ci->budget, DEBICICAM, + DEBIADDR_IO | (address & 3), 1, 1, 0); +} + +static int ciintf_write_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address, u8 value) +{ + struct budget_ci *budget_ci = ca->data; + + if (slot != 0) + return -EINVAL; + + return ttpci_budget_debiwrite(&budget_ci->budget, DEBICICAM, + DEBIADDR_IO | (address & 3), 1, value, 1, 0); +} + +static int ciintf_slot_reset(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_ci *budget_ci = ca->data; + struct saa7146_dev *saa = budget_ci->budget.dev; + + if (slot != 0) + return -EINVAL; + + if (budget_ci->ci_irq) { + // trigger on RISING edge during reset so we know when READY is re-asserted + saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQHI); + } + budget_ci->slot_status = SLOTSTATUS_RESET; + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 0, 1, 0); + msleep(1); + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, + CICONTROL_RESET, 1, 0); + + saa7146_setgpio(saa, 1, SAA7146_GPIO_OUTHI); + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTB); + return 0; +} + +static int ciintf_slot_shutdown(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_ci *budget_ci = ca->data; + struct saa7146_dev *saa = budget_ci->budget.dev; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(saa, 1, SAA7146_GPIO_OUTHI); + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTB); + return 0; +} + +static int ciintf_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_ci *budget_ci = ca->data; + struct saa7146_dev *saa = budget_ci->budget.dev; + int tmp; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(saa, 1, SAA7146_GPIO_OUTLO); + + tmp = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 1, 0); + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, + tmp | CICONTROL_ENABLETS, 1, 0); + + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTA); + return 0; +} + +static void ciintf_interrupt(struct work_struct *t) +{ + struct budget_ci *budget_ci = from_work(budget_ci, t, + ciintf_irq_bh_work); + struct saa7146_dev *saa = budget_ci->budget.dev; + unsigned int flags; + + // ensure we don't get spurious IRQs during initialisation + if (!budget_ci->budget.ci_present) + return; + + // read the CAM status + flags = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 1, 0); + if (flags & CICONTROL_CAMDETECT) { + + // GPIO should be set to trigger on falling edge if a CAM is present + saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQLO); + + if (budget_ci->slot_status & SLOTSTATUS_NONE) { + // CAM insertion IRQ + budget_ci->slot_status = SLOTSTATUS_PRESENT; + dvb_ca_en50221_camchange_irq(&budget_ci->ca, 0, + DVB_CA_EN50221_CAMCHANGE_INSERTED); + + } else if (budget_ci->slot_status & SLOTSTATUS_RESET) { + // CAM ready (reset completed) + budget_ci->slot_status = SLOTSTATUS_READY; + dvb_ca_en50221_camready_irq(&budget_ci->ca, 0); + + } else if (budget_ci->slot_status & SLOTSTATUS_READY) { + // FR/DA IRQ + dvb_ca_en50221_frda_irq(&budget_ci->ca, 0); + } + } else { + + // trigger on rising edge if a CAM is not present - when a CAM is inserted, we + // only want to get the IRQ when it sets READY. If we trigger on the falling edge, + // the CAM might not actually be ready yet. + saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQHI); + + // generate a CAM removal IRQ if we haven't already + if (budget_ci->slot_status & SLOTSTATUS_OCCUPIED) { + // CAM removal IRQ + budget_ci->slot_status = SLOTSTATUS_NONE; + dvb_ca_en50221_camchange_irq(&budget_ci->ca, 0, + DVB_CA_EN50221_CAMCHANGE_REMOVED); + } + } +} + +static int ciintf_poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open) +{ + struct budget_ci *budget_ci = ca->data; + unsigned int flags; + + // ensure we don't get spurious IRQs during initialisation + if (!budget_ci->budget.ci_present) + return -EINVAL; + + // read the CAM status + flags = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 1, 0); + if (flags & CICONTROL_CAMDETECT) { + // mark it as present if it wasn't before + if (budget_ci->slot_status & SLOTSTATUS_NONE) + budget_ci->slot_status = SLOTSTATUS_PRESENT; + + // during a RESET, we check if we can read from IO memory to see when CAM is ready + if (budget_ci->slot_status & SLOTSTATUS_RESET) { + if (ciintf_read_attribute_mem(ca, slot, 0) == 0x1d) + budget_ci->slot_status = SLOTSTATUS_READY; + } + } else { + budget_ci->slot_status = SLOTSTATUS_NONE; + } + + if (budget_ci->slot_status != SLOTSTATUS_NONE) { + if (budget_ci->slot_status & SLOTSTATUS_READY) + return DVB_CA_EN50221_POLL_CAM_PRESENT | DVB_CA_EN50221_POLL_CAM_READY; + return DVB_CA_EN50221_POLL_CAM_PRESENT; + } + + return 0; +} + +static int ciintf_init(struct budget_ci *budget_ci) +{ + struct saa7146_dev *saa = budget_ci->budget.dev; + int flags; + int result; + int ci_version; + int ca_flags; + + memset(&budget_ci->ca, 0, sizeof(struct dvb_ca_en50221)); + + // enable DEBI pins + saa7146_write(saa, MC1, MASK_27 | MASK_11); + + // test if it is there + ci_version = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CIVERSION, 1, 1, 0); + if ((ci_version & 0xa0) != 0xa0) { + result = -ENODEV; + goto error; + } + + // determine whether a CAM is present or not + flags = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 1, 0); + budget_ci->slot_status = SLOTSTATUS_NONE; + if (flags & CICONTROL_CAMDETECT) + budget_ci->slot_status = SLOTSTATUS_PRESENT; + + // version 0xa2 of the CI firmware doesn't generate interrupts + if (ci_version == 0xa2) { + ca_flags = 0; + budget_ci->ci_irq = 0; + } else { + ca_flags = DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE | + DVB_CA_EN50221_FLAG_IRQ_FR | + DVB_CA_EN50221_FLAG_IRQ_DA; + budget_ci->ci_irq = 1; + } + + // register CI interface + budget_ci->ca.owner = THIS_MODULE; + budget_ci->ca.read_attribute_mem = ciintf_read_attribute_mem; + budget_ci->ca.write_attribute_mem = ciintf_write_attribute_mem; + budget_ci->ca.read_cam_control = ciintf_read_cam_control; + budget_ci->ca.write_cam_control = ciintf_write_cam_control; + budget_ci->ca.slot_reset = ciintf_slot_reset; + budget_ci->ca.slot_shutdown = ciintf_slot_shutdown; + budget_ci->ca.slot_ts_enable = ciintf_slot_ts_enable; + budget_ci->ca.poll_slot_status = ciintf_poll_slot_status; + budget_ci->ca.data = budget_ci; + + result = dvb_ca_en50221_init(&budget_ci->budget.dvb_adapter, + &budget_ci->ca, ca_flags, 1); + if (result != 0) { + pr_err("CI interface detected, but initialisation failed.\n"); + goto error; + } + + // Setup CI slot IRQ + if (budget_ci->ci_irq) { + INIT_WORK(&budget_ci->ciintf_irq_bh_work, ciintf_interrupt); + if (budget_ci->slot_status != SLOTSTATUS_NONE) + saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQLO); + else + saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQHI); + SAA7146_IER_ENABLE(saa, MASK_03); + } + + // enable interface + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, + CICONTROL_RESET, 1, 0); + + // success! + pr_info("CI interface initialised\n"); + budget_ci->budget.ci_present = 1; + + // forge a fake CI IRQ so the CAM state is setup correctly + if (budget_ci->ci_irq) { + flags = DVB_CA_EN50221_CAMCHANGE_REMOVED; + if (budget_ci->slot_status != SLOTSTATUS_NONE) + flags = DVB_CA_EN50221_CAMCHANGE_INSERTED; + dvb_ca_en50221_camchange_irq(&budget_ci->ca, 0, flags); + } + + return 0; + +error: + saa7146_write(saa, MC1, MASK_27); + return result; +} + +static void ciintf_deinit(struct budget_ci *budget_ci) +{ + struct saa7146_dev *saa = budget_ci->budget.dev; + + // disable CI interrupts + if (budget_ci->ci_irq) { + SAA7146_IER_DISABLE(saa, MASK_03); + saa7146_setgpio(saa, 0, SAA7146_GPIO_INPUT); + cancel_work_sync(&budget_ci->ciintf_irq_bh_work); + } + + // reset interface + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 0, 1, 0); + msleep(1); + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, + CICONTROL_RESET, 1, 0); + + // disable TS data stream to CI interface + saa7146_setgpio(saa, 1, SAA7146_GPIO_INPUT); + + // release the CA device + dvb_ca_en50221_release(&budget_ci->ca); + + // disable DEBI pins + saa7146_write(saa, MC1, MASK_27); +} + +static void budget_ci_irq(struct saa7146_dev *dev, u32 *isr) +{ + struct budget_ci *budget_ci = dev->ext_priv; + + dprintk(8, "dev: %p, budget_ci: %p\n", dev, budget_ci); + + if (*isr & MASK_06) + queue_work(system_bh_wq, &budget_ci->ir.msp430_irq_bh_work); + + if (*isr & MASK_10) + ttpci_budget_irq10_handler(dev, isr); + + if ((*isr & MASK_03) && (budget_ci->budget.ci_present) && (budget_ci->ci_irq)) + queue_work(system_bh_wq, &budget_ci->ciintf_irq_bh_work); +} + +static u8 philips_su1278_tt_inittab[] = { + 0x01, 0x0f, + 0x02, 0x30, + 0x03, 0x00, + 0x04, 0x5b, + 0x05, 0x85, + 0x06, 0x02, + 0x07, 0x00, + 0x08, 0x02, + 0x09, 0x00, + 0x0C, 0x01, + 0x0D, 0x81, + 0x0E, 0x44, + 0x0f, 0x14, + 0x10, 0x3c, + 0x11, 0x84, + 0x12, 0xda, + 0x13, 0x97, + 0x14, 0x95, + 0x15, 0xc9, + 0x16, 0x19, + 0x17, 0x8c, + 0x18, 0x59, + 0x19, 0xf8, + 0x1a, 0xfe, + 0x1c, 0x7f, + 0x1d, 0x00, + 0x1e, 0x00, + 0x1f, 0x50, + 0x20, 0x00, + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + 0x28, 0x00, + 0x29, 0x28, + 0x2a, 0x14, + 0x2b, 0x0f, + 0x2c, 0x09, + 0x2d, 0x09, + 0x31, 0x1f, + 0x32, 0x19, + 0x33, 0xfc, + 0x34, 0x93, + 0xff, 0xff +}; + +static int philips_su1278_tt_set_symbol_rate(struct dvb_frontend *fe, u32 srate, u32 ratio) +{ + stv0299_writereg(fe, 0x0e, 0x44); + if (srate >= 10000000) { + stv0299_writereg(fe, 0x13, 0x97); + stv0299_writereg(fe, 0x14, 0x95); + stv0299_writereg(fe, 0x15, 0xc9); + stv0299_writereg(fe, 0x17, 0x8c); + stv0299_writereg(fe, 0x1a, 0xfe); + stv0299_writereg(fe, 0x1c, 0x7f); + stv0299_writereg(fe, 0x2d, 0x09); + } else { + stv0299_writereg(fe, 0x13, 0x99); + stv0299_writereg(fe, 0x14, 0x8d); + stv0299_writereg(fe, 0x15, 0xce); + stv0299_writereg(fe, 0x17, 0x43); + stv0299_writereg(fe, 0x1a, 0x1d); + stv0299_writereg(fe, 0x1c, 0x12); + stv0299_writereg(fe, 0x2d, 0x05); + } + stv0299_writereg(fe, 0x0e, 0x23); + stv0299_writereg(fe, 0x0f, 0x94); + stv0299_writereg(fe, 0x10, 0x39); + stv0299_writereg(fe, 0x15, 0xc9); + + stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg(fe, 0x21, (ratio) & 0xf0); + + return 0; +} + +static int philips_su1278_tt_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct budget_ci *budget_ci = fe->dvb->priv; + u32 div; + u8 buf[4]; + struct i2c_msg msg = {.addr = 0x60, .flags = 0, .buf = buf, .len = sizeof(buf) }; + + if ((p->frequency < 950000) || (p->frequency > 2150000)) + return -EINVAL; + + div = (p->frequency + (500 - 1)) / 500; /* round correctly */ + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0x80 | ((div & 0x18000) >> 10) | 2; + buf[3] = 0x20; + + if (p->symbol_rate < 4000000) + buf[3] |= 1; + + if (p->frequency < 1250000) + buf[3] |= 0; + else if (p->frequency < 1550000) + buf[3] |= 0x40; + else if (p->frequency < 2050000) + buf[3] |= 0x80; + else if (p->frequency < 2150000) + buf[3] |= 0xC0; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget_ci->budget.i2c_adap, &msg, 1) != 1) + return -EIO; + return 0; +} + +static const struct stv0299_config philips_su1278_tt_config = { + + .demod_address = 0x68, + .inittab = philips_su1278_tt_inittab, + .mclk = 64000000UL, + .invert = 0, + .skip_reinit = 1, + .lock_output = STV0299_LOCKOUTPUT_1, + .volt13_op0_op1 = STV0299_VOLT13_OP1, + .min_delay_ms = 50, + .set_symbol_rate = philips_su1278_tt_set_symbol_rate, +}; + + + +static int philips_tdm1316l_tuner_init(struct dvb_frontend *fe) +{ + struct budget_ci *budget_ci = fe->dvb->priv; + static u8 td1316_init[] = { 0x0b, 0xf5, 0x85, 0xab }; + static u8 disable_mc44BC374c[] = { 0x1d, 0x74, 0xa0, 0x68 }; + struct i2c_msg tuner_msg = {.addr = budget_ci->tuner_pll_address, .flags = 0, .buf = td1316_init, .len = + sizeof(td1316_init) }; + + // setup PLL configuration + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + msleep(1); + + // disable the mc44BC374c (do not check for errors) + tuner_msg.addr = 0x65; + tuner_msg.buf = disable_mc44BC374c; + tuner_msg.len = sizeof(disable_mc44BC374c); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1) { + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1); + } + + return 0; +} + +static int philips_tdm1316l_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct budget_ci *budget_ci = fe->dvb->priv; + u8 tuner_buf[4]; + struct i2c_msg tuner_msg = {.addr = budget_ci->tuner_pll_address, .flags = 0, .buf = tuner_buf, .len = sizeof(tuner_buf) }; + int tuner_frequency = 0; + u8 band, cp, filter; + + // determine charge pump + tuner_frequency = p->frequency + 36130000; + if (tuner_frequency < 87000000) + return -EINVAL; + else if (tuner_frequency < 130000000) + cp = 3; + else if (tuner_frequency < 160000000) + cp = 5; + else if (tuner_frequency < 200000000) + cp = 6; + else if (tuner_frequency < 290000000) + cp = 3; + else if (tuner_frequency < 420000000) + cp = 5; + else if (tuner_frequency < 480000000) + cp = 6; + else if (tuner_frequency < 620000000) + cp = 3; + else if (tuner_frequency < 830000000) + cp = 5; + else if (tuner_frequency < 895000000) + cp = 7; + else + return -EINVAL; + + // determine band + if (p->frequency < 49000000) + return -EINVAL; + else if (p->frequency < 159000000) + band = 1; + else if (p->frequency < 444000000) + band = 2; + else if (p->frequency < 861000000) + band = 4; + else + return -EINVAL; + + // setup PLL filter and TDA9889 + switch (p->bandwidth_hz) { + case 6000000: + tda1004x_writereg(fe, 0x0C, 0x14); + filter = 0; + break; + + case 7000000: + tda1004x_writereg(fe, 0x0C, 0x80); + filter = 0; + break; + + case 8000000: + tda1004x_writereg(fe, 0x0C, 0x14); + filter = 1; + break; + + default: + return -EINVAL; + } + + // calculate divisor + // ((36130000+((1000000/6)/2)) + Finput)/(1000000/6) + tuner_frequency = (((p->frequency / 1000) * 6) + 217280) / 1000; + + // setup tuner buffer + tuner_buf[0] = tuner_frequency >> 8; + tuner_buf[1] = tuner_frequency & 0xff; + tuner_buf[2] = 0xca; + tuner_buf[3] = (cp << 5) | (filter << 3) | band; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + + msleep(1); + return 0; +} + +static int philips_tdm1316l_request_firmware(struct dvb_frontend *fe, + const struct firmware **fw, char *name) +{ + struct budget_ci *budget_ci = fe->dvb->priv; + + return request_firmware(fw, name, &budget_ci->budget.dev->pci->dev); +} + +static struct tda1004x_config philips_tdm1316l_config = { + + .demod_address = 0x8, + .invert = 0, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_4M, + .agc_config = TDA10046_AGC_DEFAULT, + .if_freq = TDA10046_FREQ_3617, + .request_firmware = philips_tdm1316l_request_firmware, +}; + +static struct tda1004x_config philips_tdm1316l_config_invert = { + + .demod_address = 0x8, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_4M, + .agc_config = TDA10046_AGC_DEFAULT, + .if_freq = TDA10046_FREQ_3617, + .request_firmware = philips_tdm1316l_request_firmware, +}; + +static int dvbc_philips_tdm1316l_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct budget_ci *budget_ci = fe->dvb->priv; + u8 tuner_buf[5]; + struct i2c_msg tuner_msg = {.addr = budget_ci->tuner_pll_address, + .flags = 0, + .buf = tuner_buf, + .len = sizeof(tuner_buf) }; + int tuner_frequency = 0; + u8 band, cp, filter; + + // determine charge pump + tuner_frequency = p->frequency + 36125000; + if (tuner_frequency < 87000000) { + return -EINVAL; + } else if (tuner_frequency < 130000000) { + cp = 3; + band = 1; + } else if (tuner_frequency < 160000000) { + cp = 5; + band = 1; + } else if (tuner_frequency < 200000000) { + cp = 6; + band = 1; + } else if (tuner_frequency < 290000000) { + cp = 3; + band = 2; + } else if (tuner_frequency < 420000000) { + cp = 5; + band = 2; + } else if (tuner_frequency < 480000000) { + cp = 6; + band = 2; + } else if (tuner_frequency < 620000000) { + cp = 3; + band = 4; + } else if (tuner_frequency < 830000000) { + cp = 5; + band = 4; + } else if (tuner_frequency < 895000000) { + cp = 7; + band = 4; + } else { + return -EINVAL; + } + + // assume PLL filter should always be 8MHz for the moment. + filter = 1; + + // calculate divisor + tuner_frequency = (p->frequency + 36125000 + (62500/2)) / 62500; + + // setup tuner buffer + tuner_buf[0] = tuner_frequency >> 8; + tuner_buf[1] = tuner_frequency & 0xff; + tuner_buf[2] = 0xc8; + tuner_buf[3] = (cp << 5) | (filter << 3) | band; + tuner_buf[4] = 0x80; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + + msleep(50); + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + + msleep(1); + + return 0; +} + +static u8 dvbc_philips_tdm1316l_inittab[] = { + 0x80, 0x01, + 0x80, 0x00, + 0x81, 0x01, + 0x81, 0x00, + 0x00, 0x09, + 0x01, 0x69, + 0x03, 0x00, + 0x04, 0x00, + 0x07, 0x00, + 0x08, 0x00, + 0x20, 0x00, + 0x21, 0x40, + 0x22, 0x00, + 0x23, 0x00, + 0x24, 0x40, + 0x25, 0x88, + 0x30, 0xff, + 0x31, 0x00, + 0x32, 0xff, + 0x33, 0x00, + 0x34, 0x50, + 0x35, 0x7f, + 0x36, 0x00, + 0x37, 0x20, + 0x38, 0x00, + 0x40, 0x1c, + 0x41, 0xff, + 0x42, 0x29, + 0x43, 0x20, + 0x44, 0xff, + 0x45, 0x00, + 0x46, 0x00, + 0x49, 0x04, + 0x4a, 0x00, + 0x4b, 0x7b, + 0x52, 0x30, + 0x55, 0xae, + 0x56, 0x47, + 0x57, 0xe1, + 0x58, 0x3a, + 0x5a, 0x1e, + 0x5b, 0x34, + 0x60, 0x00, + 0x63, 0x00, + 0x64, 0x00, + 0x65, 0x00, + 0x66, 0x00, + 0x67, 0x00, + 0x68, 0x00, + 0x69, 0x00, + 0x6a, 0x02, + 0x6b, 0x00, + 0x70, 0xff, + 0x71, 0x00, + 0x72, 0x00, + 0x73, 0x00, + 0x74, 0x0c, + 0x80, 0x00, + 0x81, 0x00, + 0x82, 0x00, + 0x83, 0x00, + 0x84, 0x04, + 0x85, 0x80, + 0x86, 0x24, + 0x87, 0x78, + 0x88, 0x10, + 0x89, 0x00, + 0x90, 0x01, + 0x91, 0x01, + 0xa0, 0x04, + 0xa1, 0x00, + 0xa2, 0x00, + 0xb0, 0x91, + 0xb1, 0x0b, + 0xc0, 0x53, + 0xc1, 0x70, + 0xc2, 0x12, + 0xd0, 0x00, + 0xd1, 0x00, + 0xd2, 0x00, + 0xd3, 0x00, + 0xd4, 0x00, + 0xd5, 0x00, + 0xde, 0x00, + 0xdf, 0x00, + 0x61, 0x38, + 0x62, 0x0a, + 0x53, 0x13, + 0x59, 0x08, + 0xff, 0xff, +}; + +static struct stv0297_config dvbc_philips_tdm1316l_config = { + .demod_address = 0x1c, + .inittab = dvbc_philips_tdm1316l_inittab, + .invert = 0, + .stop_during_read = 1, +}; + +static struct tda10023_config tda10023_config = { + .demod_address = 0xc, + .invert = 0, + .xtal = 16000000, + .pll_m = 11, + .pll_p = 3, + .pll_n = 1, + .deltaf = 0xa511, +}; + +static struct tda827x_config tda827x_config = { + .config = 0, +}; + +/* TT S2-3200 DVB-S (STB0899) Inittab */ +static const struct stb0899_s1_reg tt3200_stb0899_s1_init_1[] = { + + { STB0899_DEV_ID, 0x81 }, + { STB0899_DISCNTRL1, 0x32 }, + { STB0899_DISCNTRL2, 0x80 }, + { STB0899_DISRX_ST0, 0x04 }, + { STB0899_DISRX_ST1, 0x00 }, + { STB0899_DISPARITY, 0x00 }, + { STB0899_DISSTATUS, 0x20 }, + { STB0899_DISF22, 0x8c }, + { STB0899_DISF22RX, 0x9a }, + { STB0899_SYSREG, 0x0b }, + { STB0899_ACRPRESC, 0x11 }, + { STB0899_ACRDIV1, 0x0a }, + { STB0899_ACRDIV2, 0x05 }, + { STB0899_DACR1, 0x00 }, + { STB0899_DACR2, 0x00 }, + { STB0899_OUTCFG, 0x00 }, + { STB0899_MODECFG, 0x00 }, + { STB0899_IRQSTATUS_3, 0x30 }, + { STB0899_IRQSTATUS_2, 0x00 }, + { STB0899_IRQSTATUS_1, 0x00 }, + { STB0899_IRQSTATUS_0, 0x00 }, + { STB0899_IRQMSK_3, 0xf3 }, + { STB0899_IRQMSK_2, 0xfc }, + { STB0899_IRQMSK_1, 0xff }, + { STB0899_IRQMSK_0, 0xff }, + { STB0899_IRQCFG, 0x00 }, + { STB0899_I2CCFG, 0x88 }, + { STB0899_I2CRPT, 0x48 }, /* 12k Pullup, Repeater=16, Stop=disabled */ + { STB0899_IOPVALUE5, 0x00 }, + { STB0899_IOPVALUE4, 0x20 }, + { STB0899_IOPVALUE3, 0xc9 }, + { STB0899_IOPVALUE2, 0x90 }, + { STB0899_IOPVALUE1, 0x40 }, + { STB0899_IOPVALUE0, 0x00 }, + { STB0899_GPIO00CFG, 0x82 }, + { STB0899_GPIO01CFG, 0x82 }, + { STB0899_GPIO02CFG, 0x82 }, + { STB0899_GPIO03CFG, 0x82 }, + { STB0899_GPIO04CFG, 0x82 }, + { STB0899_GPIO05CFG, 0x82 }, + { STB0899_GPIO06CFG, 0x82 }, + { STB0899_GPIO07CFG, 0x82 }, + { STB0899_GPIO08CFG, 0x82 }, + { STB0899_GPIO09CFG, 0x82 }, + { STB0899_GPIO10CFG, 0x82 }, + { STB0899_GPIO11CFG, 0x82 }, + { STB0899_GPIO12CFG, 0x82 }, + { STB0899_GPIO13CFG, 0x82 }, + { STB0899_GPIO14CFG, 0x82 }, + { STB0899_GPIO15CFG, 0x82 }, + { STB0899_GPIO16CFG, 0x82 }, + { STB0899_GPIO17CFG, 0x82 }, + { STB0899_GPIO18CFG, 0x82 }, + { STB0899_GPIO19CFG, 0x82 }, + { STB0899_GPIO20CFG, 0x82 }, + { STB0899_SDATCFG, 0xb8 }, + { STB0899_SCLTCFG, 0xba }, + { STB0899_AGCRFCFG, 0x1c }, /* 0x11 */ + { STB0899_GPIO22, 0x82 }, /* AGCBB2CFG */ + { STB0899_GPIO21, 0x91 }, /* AGCBB1CFG */ + { STB0899_DIRCLKCFG, 0x82 }, + { STB0899_CLKOUT27CFG, 0x7e }, + { STB0899_STDBYCFG, 0x82 }, + { STB0899_CS0CFG, 0x82 }, + { STB0899_CS1CFG, 0x82 }, + { STB0899_DISEQCOCFG, 0x20 }, + { STB0899_GPIO32CFG, 0x82 }, + { STB0899_GPIO33CFG, 0x82 }, + { STB0899_GPIO34CFG, 0x82 }, + { STB0899_GPIO35CFG, 0x82 }, + { STB0899_GPIO36CFG, 0x82 }, + { STB0899_GPIO37CFG, 0x82 }, + { STB0899_GPIO38CFG, 0x82 }, + { STB0899_GPIO39CFG, 0x82 }, + { STB0899_NCOARSE, 0x15 }, /* 0x15 = 27 Mhz Clock, F/3 = 198MHz, F/6 = 99MHz */ + { STB0899_SYNTCTRL, 0x02 }, /* 0x00 = CLK from CLKI, 0x02 = CLK from XTALI */ + { STB0899_FILTCTRL, 0x00 }, + { STB0899_SYSCTRL, 0x00 }, + { STB0899_STOPCLK1, 0x20 }, + { STB0899_STOPCLK2, 0x00 }, + { STB0899_INTBUFSTATUS, 0x00 }, + { STB0899_INTBUFCTRL, 0x0a }, + { 0xffff, 0xff }, +}; + +static const struct stb0899_s1_reg tt3200_stb0899_s1_init_3[] = { + { STB0899_DEMOD, 0x00 }, + { STB0899_RCOMPC, 0xc9 }, + { STB0899_AGC1CN, 0x41 }, + { STB0899_AGC1REF, 0x10 }, + { STB0899_RTC, 0x7a }, + { STB0899_TMGCFG, 0x4e }, + { STB0899_AGC2REF, 0x34 }, + { STB0899_TLSR, 0x84 }, + { STB0899_CFD, 0xc7 }, + { STB0899_ACLC, 0x87 }, + { STB0899_BCLC, 0x94 }, + { STB0899_EQON, 0x41 }, + { STB0899_LDT, 0xdd }, + { STB0899_LDT2, 0xc9 }, + { STB0899_EQUALREF, 0xb4 }, + { STB0899_TMGRAMP, 0x10 }, + { STB0899_TMGTHD, 0x30 }, + { STB0899_IDCCOMP, 0xfb }, + { STB0899_QDCCOMP, 0x03 }, + { STB0899_POWERI, 0x3b }, + { STB0899_POWERQ, 0x3d }, + { STB0899_RCOMP, 0x81 }, + { STB0899_AGCIQIN, 0x80 }, + { STB0899_AGC2I1, 0x04 }, + { STB0899_AGC2I2, 0xf5 }, + { STB0899_TLIR, 0x25 }, + { STB0899_RTF, 0x80 }, + { STB0899_DSTATUS, 0x00 }, + { STB0899_LDI, 0xca }, + { STB0899_CFRM, 0xf1 }, + { STB0899_CFRL, 0xf3 }, + { STB0899_NIRM, 0x2a }, + { STB0899_NIRL, 0x05 }, + { STB0899_ISYMB, 0x17 }, + { STB0899_QSYMB, 0xfa }, + { STB0899_SFRH, 0x2f }, + { STB0899_SFRM, 0x68 }, + { STB0899_SFRL, 0x40 }, + { STB0899_SFRUPH, 0x2f }, + { STB0899_SFRUPM, 0x68 }, + { STB0899_SFRUPL, 0x40 }, + { STB0899_EQUAI1, 0xfd }, + { STB0899_EQUAQ1, 0x04 }, + { STB0899_EQUAI2, 0x0f }, + { STB0899_EQUAQ2, 0xff }, + { STB0899_EQUAI3, 0xdf }, + { STB0899_EQUAQ3, 0xfa }, + { STB0899_EQUAI4, 0x37 }, + { STB0899_EQUAQ4, 0x0d }, + { STB0899_EQUAI5, 0xbd }, + { STB0899_EQUAQ5, 0xf7 }, + { STB0899_DSTATUS2, 0x00 }, + { STB0899_VSTATUS, 0x00 }, + { STB0899_VERROR, 0xff }, + { STB0899_IQSWAP, 0x2a }, + { STB0899_ECNT1M, 0x00 }, + { STB0899_ECNT1L, 0x00 }, + { STB0899_ECNT2M, 0x00 }, + { STB0899_ECNT2L, 0x00 }, + { STB0899_ECNT3M, 0x00 }, + { STB0899_ECNT3L, 0x00 }, + { STB0899_FECAUTO1, 0x06 }, + { STB0899_FECM, 0x01 }, + { STB0899_VTH12, 0xf0 }, + { STB0899_VTH23, 0xa0 }, + { STB0899_VTH34, 0x78 }, + { STB0899_VTH56, 0x4e }, + { STB0899_VTH67, 0x48 }, + { STB0899_VTH78, 0x38 }, + { STB0899_PRVIT, 0xff }, + { STB0899_VITSYNC, 0x19 }, + { STB0899_RSULC, 0xb1 }, /* DVB = 0xb1, DSS = 0xa1 */ + { STB0899_TSULC, 0x42 }, + { STB0899_RSLLC, 0x40 }, + { STB0899_TSLPL, 0x12 }, + { STB0899_TSCFGH, 0x0c }, + { STB0899_TSCFGM, 0x00 }, + { STB0899_TSCFGL, 0x0c }, + { STB0899_TSOUT, 0x4d }, /* 0x0d for CAM */ + { STB0899_RSSYNCDEL, 0x00 }, + { STB0899_TSINHDELH, 0x02 }, + { STB0899_TSINHDELM, 0x00 }, + { STB0899_TSINHDELL, 0x00 }, + { STB0899_TSLLSTKM, 0x00 }, + { STB0899_TSLLSTKL, 0x00 }, + { STB0899_TSULSTKM, 0x00 }, + { STB0899_TSULSTKL, 0xab }, + { STB0899_PCKLENUL, 0x00 }, + { STB0899_PCKLENLL, 0xcc }, + { STB0899_RSPCKLEN, 0xcc }, + { STB0899_TSSTATUS, 0x80 }, + { STB0899_ERRCTRL1, 0xb6 }, + { STB0899_ERRCTRL2, 0x96 }, + { STB0899_ERRCTRL3, 0x89 }, + { STB0899_DMONMSK1, 0x27 }, + { STB0899_DMONMSK0, 0x03 }, + { STB0899_DEMAPVIT, 0x5c }, + { STB0899_PLPARM, 0x1f }, + { STB0899_PDELCTRL, 0x48 }, + { STB0899_PDELCTRL2, 0x00 }, + { STB0899_BBHCTRL1, 0x00 }, + { STB0899_BBHCTRL2, 0x00 }, + { STB0899_HYSTTHRESH, 0x77 }, + { STB0899_MATCSTM, 0x00 }, + { STB0899_MATCSTL, 0x00 }, + { STB0899_UPLCSTM, 0x00 }, + { STB0899_UPLCSTL, 0x00 }, + { STB0899_DFLCSTM, 0x00 }, + { STB0899_DFLCSTL, 0x00 }, + { STB0899_SYNCCST, 0x00 }, + { STB0899_SYNCDCSTM, 0x00 }, + { STB0899_SYNCDCSTL, 0x00 }, + { STB0899_ISI_ENTRY, 0x00 }, + { STB0899_ISI_BIT_EN, 0x00 }, + { STB0899_MATSTRM, 0x00 }, + { STB0899_MATSTRL, 0x00 }, + { STB0899_UPLSTRM, 0x00 }, + { STB0899_UPLSTRL, 0x00 }, + { STB0899_DFLSTRM, 0x00 }, + { STB0899_DFLSTRL, 0x00 }, + { STB0899_SYNCSTR, 0x00 }, + { STB0899_SYNCDSTRM, 0x00 }, + { STB0899_SYNCDSTRL, 0x00 }, + { STB0899_CFGPDELSTATUS1, 0x10 }, + { STB0899_CFGPDELSTATUS2, 0x00 }, + { STB0899_BBFERRORM, 0x00 }, + { STB0899_BBFERRORL, 0x00 }, + { STB0899_UPKTERRORM, 0x00 }, + { STB0899_UPKTERRORL, 0x00 }, + { 0xffff, 0xff }, +}; + +static struct stb0899_config tt3200_config = { + .init_dev = tt3200_stb0899_s1_init_1, + .init_s2_demod = stb0899_s2_init_2, + .init_s1_demod = tt3200_stb0899_s1_init_3, + .init_s2_fec = stb0899_s2_init_4, + .init_tst = stb0899_s1_init_5, + + .postproc = NULL, + + .demod_address = 0x68, + + .xtal_freq = 27000000, + .inversion = IQ_SWAP_ON, + + .lo_clk = 76500000, + .hi_clk = 99000000, + + .esno_ave = STB0899_DVBS2_ESNO_AVE, + .esno_quant = STB0899_DVBS2_ESNO_QUANT, + .avframes_coarse = STB0899_DVBS2_AVFRAMES_COARSE, + .avframes_fine = STB0899_DVBS2_AVFRAMES_FINE, + .miss_threshold = STB0899_DVBS2_MISS_THRESHOLD, + .uwp_threshold_acq = STB0899_DVBS2_UWP_THRESHOLD_ACQ, + .uwp_threshold_track = STB0899_DVBS2_UWP_THRESHOLD_TRACK, + .uwp_threshold_sof = STB0899_DVBS2_UWP_THRESHOLD_SOF, + .sof_search_timeout = STB0899_DVBS2_SOF_SEARCH_TIMEOUT, + + .btr_nco_bits = STB0899_DVBS2_BTR_NCO_BITS, + .btr_gain_shift_offset = STB0899_DVBS2_BTR_GAIN_SHIFT_OFFSET, + .crl_nco_bits = STB0899_DVBS2_CRL_NCO_BITS, + .ldpc_max_iter = STB0899_DVBS2_LDPC_MAX_ITER, + + .tuner_get_frequency = stb6100_get_frequency, + .tuner_set_frequency = stb6100_set_frequency, + .tuner_set_bandwidth = stb6100_set_bandwidth, + .tuner_get_bandwidth = stb6100_get_bandwidth, + .tuner_set_rfsiggain = NULL +}; + +static struct stb6100_config tt3200_stb6100_config = { + .tuner_address = 0x60, + .refclock = 27000000, +}; + +static void frontend_init(struct budget_ci *budget_ci) +{ + switch (budget_ci->budget.dev->pci->subsystem_device) { + case 0x100c: // Hauppauge/TT Nova-CI budget (stv0299/ALPS BSRU6(tsa5059)) + budget_ci->budget.dvb_frontend = + dvb_attach(stv0299_attach, &alps_bsru6_config, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = alps_bsru6_tuner_set_params; + budget_ci->budget.dvb_frontend->tuner_priv = &budget_ci->budget.i2c_adap; + break; + } + break; + + case 0x100f: // Hauppauge/TT Nova-CI budget (stv0299b/Philips su1278(tsa5059)) + budget_ci->budget.dvb_frontend = + dvb_attach(stv0299_attach, &philips_su1278_tt_config, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = philips_su1278_tt_tuner_set_params; + break; + } + break; + + case 0x1010: // TT DVB-C CI budget (stv0297/Philips tdm1316l(tda6651tt)) + budget_ci->tuner_pll_address = 0x61; + budget_ci->budget.dvb_frontend = + dvb_attach(stv0297_attach, &dvbc_philips_tdm1316l_config, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = dvbc_philips_tdm1316l_tuner_set_params; + break; + } + break; + + case 0x1011: // Hauppauge/TT Nova-T budget (tda10045/Philips tdm1316l(tda6651tt) + TDA9889) + budget_ci->tuner_pll_address = 0x63; + budget_ci->budget.dvb_frontend = + dvb_attach(tda10045_attach, &philips_tdm1316l_config, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + budget_ci->budget.dvb_frontend->ops.tuner_ops.init = philips_tdm1316l_tuner_init; + budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = philips_tdm1316l_tuner_set_params; + break; + } + break; + + case 0x1012: // TT DVB-T CI budget (tda10046/Philips tdm1316l(tda6651tt)) + budget_ci->tuner_pll_address = 0x60; + budget_ci->budget.dvb_frontend = + dvb_attach(tda10046_attach, &philips_tdm1316l_config_invert, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + budget_ci->budget.dvb_frontend->ops.tuner_ops.init = philips_tdm1316l_tuner_init; + budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = philips_tdm1316l_tuner_set_params; + break; + } + break; + + case 0x1017: // TT S-1500 PCI + budget_ci->budget.dvb_frontend = dvb_attach(stv0299_attach, &alps_bsbe1_config, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = alps_bsbe1_tuner_set_params; + budget_ci->budget.dvb_frontend->tuner_priv = &budget_ci->budget.i2c_adap; + + budget_ci->budget.dvb_frontend->ops.dishnetwork_send_legacy_command = NULL; + if (dvb_attach(lnbp21_attach, budget_ci->budget.dvb_frontend, &budget_ci->budget.i2c_adap, LNBP21_LLC, 0) == NULL) { + pr_err("%s(): No LNBP21 found!\n", __func__); + dvb_frontend_detach(budget_ci->budget.dvb_frontend); + budget_ci->budget.dvb_frontend = NULL; + } + } + break; + + case 0x101a: /* TT Budget-C-1501 (philips tda10023/philips tda8274A) */ + budget_ci->budget.dvb_frontend = dvb_attach(tda10023_attach, &tda10023_config, &budget_ci->budget.i2c_adap, 0x48); + if (budget_ci->budget.dvb_frontend) { + if (dvb_attach(tda827x_attach, budget_ci->budget.dvb_frontend, 0x61, &budget_ci->budget.i2c_adap, &tda827x_config) == NULL) { + pr_err("%s(): No tda827x found!\n", __func__); + dvb_frontend_detach(budget_ci->budget.dvb_frontend); + budget_ci->budget.dvb_frontend = NULL; + } + } + break; + + case 0x101b: /* TT S-1500B (BSBE1-D01A - STV0288/STB6000/LNBP21) */ + budget_ci->budget.dvb_frontend = dvb_attach(stv0288_attach, &stv0288_bsbe1_d01a_config, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + if (dvb_attach(stb6000_attach, budget_ci->budget.dvb_frontend, 0x63, &budget_ci->budget.i2c_adap)) { + if (!dvb_attach(lnbp21_attach, budget_ci->budget.dvb_frontend, &budget_ci->budget.i2c_adap, 0, 0)) { + pr_err("%s(): No LNBP21 found!\n", __func__); + dvb_frontend_detach(budget_ci->budget.dvb_frontend); + budget_ci->budget.dvb_frontend = NULL; + } + } else { + pr_err("%s(): No STB6000 found!\n", __func__); + dvb_frontend_detach(budget_ci->budget.dvb_frontend); + budget_ci->budget.dvb_frontend = NULL; + } + } + break; + + case 0x1019: // TT S2-3200 PCI + /* + * NOTE! on some STB0899 versions, the internal PLL takes a longer time + * to settle, aka LOCK. On the older revisions of the chip, we don't see + * this, as a result on the newer chips the entire clock tree, will not + * be stable after a freshly POWER 'ed up situation. + * In this case, we should RESET the STB0899 (Active LOW) and wait for + * PLL stabilization. + * + * On the TT S2 3200 and clones, the STB0899 demodulator's RESETB is + * connected to the SAA7146 GPIO, GPIO2, Pin 142 + */ + /* Reset Demodulator */ + saa7146_setgpio(budget_ci->budget.dev, 2, SAA7146_GPIO_OUTLO); + /* Wait for everything to die */ + msleep(50); + /* Pull it up out of Reset state */ + saa7146_setgpio(budget_ci->budget.dev, 2, SAA7146_GPIO_OUTHI); + /* Wait for PLL to stabilize */ + msleep(250); + /* + * PLL state should be stable now. Ideally, we should check + * for PLL LOCK status. But well, never mind! + */ + budget_ci->budget.dvb_frontend = dvb_attach(stb0899_attach, &tt3200_config, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + if (dvb_attach(stb6100_attach, budget_ci->budget.dvb_frontend, &tt3200_stb6100_config, &budget_ci->budget.i2c_adap)) { + if (!dvb_attach(lnbp21_attach, budget_ci->budget.dvb_frontend, &budget_ci->budget.i2c_adap, 0, 0)) { + pr_err("%s(): No LNBP21 found!\n", __func__); + dvb_frontend_detach(budget_ci->budget.dvb_frontend); + budget_ci->budget.dvb_frontend = NULL; + } + } else { + dvb_frontend_detach(budget_ci->budget.dvb_frontend); + budget_ci->budget.dvb_frontend = NULL; + } + } + break; + + } + + if (budget_ci->budget.dvb_frontend == NULL) { + pr_err("A frontend driver was not found for device [%04x:%04x] subsystem [%04x:%04x]\n", + budget_ci->budget.dev->pci->vendor, + budget_ci->budget.dev->pci->device, + budget_ci->budget.dev->pci->subsystem_vendor, + budget_ci->budget.dev->pci->subsystem_device); + } else { + if (dvb_register_frontend + (&budget_ci->budget.dvb_adapter, budget_ci->budget.dvb_frontend)) { + pr_err("Frontend registration failed!\n"); + dvb_frontend_detach(budget_ci->budget.dvb_frontend); + budget_ci->budget.dvb_frontend = NULL; + } + } +} + +static int budget_ci_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct budget_ci *budget_ci; + int err; + + budget_ci = kzalloc_obj(struct budget_ci); + if (!budget_ci) { + err = -ENOMEM; + goto out1; + } + + dprintk(2, "budget_ci: %p\n", budget_ci); + + dev->ext_priv = budget_ci; + + err = ttpci_budget_init(&budget_ci->budget, dev, info, THIS_MODULE, + adapter_nr); + if (err) + goto out2; + + err = msp430_ir_init(budget_ci); + if (err) + goto out3; + + ciintf_init(budget_ci); + + budget_ci->budget.dvb_adapter.priv = budget_ci; + frontend_init(budget_ci); + + ttpci_budget_init_hooks(&budget_ci->budget); + + return 0; + +out3: + ttpci_budget_deinit(&budget_ci->budget); +out2: + kfree(budget_ci); +out1: + return err; +} + +static int budget_ci_detach(struct saa7146_dev *dev) +{ + struct budget_ci *budget_ci = dev->ext_priv; + struct saa7146_dev *saa = budget_ci->budget.dev; + int err; + + if (budget_ci->budget.ci_present) + ciintf_deinit(budget_ci); + msp430_ir_deinit(budget_ci); + if (budget_ci->budget.dvb_frontend) { + dvb_unregister_frontend(budget_ci->budget.dvb_frontend); + dvb_frontend_detach(budget_ci->budget.dvb_frontend); + } + err = ttpci_budget_deinit(&budget_ci->budget); + + // disable frontend and CI interface + saa7146_setgpio(saa, 2, SAA7146_GPIO_INPUT); + + kfree(budget_ci); + + return err; +} + +static struct saa7146_extension budget_extension; + +MAKE_BUDGET_INFO(ttbs2, "TT-Budget/S-1500 PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttbci, "TT-Budget/WinTV-NOVA-CI PCI", BUDGET_TT_HW_DISEQC); +MAKE_BUDGET_INFO(ttbt2, "TT-Budget/WinTV-NOVA-T PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttbtci, "TT-Budget-T-CI PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttbcci, "TT-Budget-C-CI PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttc1501, "TT-Budget C-1501 PCI", BUDGET_TT); +MAKE_BUDGET_INFO(tt3200, "TT-Budget S2-3200 PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttbs1500b, "TT-Budget S-1500B PCI", BUDGET_TT); + +static const struct pci_device_id pci_tbl[] = { + MAKE_EXTENSION_PCI(ttbci, 0x13c2, 0x100c), + MAKE_EXTENSION_PCI(ttbci, 0x13c2, 0x100f), + MAKE_EXTENSION_PCI(ttbcci, 0x13c2, 0x1010), + MAKE_EXTENSION_PCI(ttbt2, 0x13c2, 0x1011), + MAKE_EXTENSION_PCI(ttbtci, 0x13c2, 0x1012), + MAKE_EXTENSION_PCI(ttbs2, 0x13c2, 0x1017), + MAKE_EXTENSION_PCI(ttc1501, 0x13c2, 0x101a), + MAKE_EXTENSION_PCI(tt3200, 0x13c2, 0x1019), + MAKE_EXTENSION_PCI(ttbs1500b, 0x13c2, 0x101b), + { } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_extension budget_extension = { + .name = "budget_ci dvb", + .flags = SAA7146_USE_I2C_IRQ, + + .module = THIS_MODULE, + .pci_tbl = &pci_tbl[0], + .attach = budget_ci_attach, + .detach = budget_ci_detach, + + .irq_mask = MASK_03 | MASK_06 | MASK_10, + .irq_func = budget_ci_irq, +}; + +static int __init budget_ci_init(void) +{ + return saa7146_register_extension(&budget_extension); +} + +static void __exit budget_ci_exit(void) +{ + saa7146_unregister_extension(&budget_extension); +} + +module_init(budget_ci_init); +module_exit(budget_ci_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michael Hunold, Jack Thomasson, Andrew de Quincey, others"); +MODULE_DESCRIPTION("driver for the SAA7146 based so-called budget PCI DVB cards w/ CI-module produced by Siemens, Technotrend, Hauppauge"); diff --git a/drivers/media/pci/ttpci/budget-core.c b/drivers/media/pci/ttpci/budget-core.c new file mode 100644 index 000000000..29531d9c9 --- /dev/null +++ b/drivers/media/pci/ttpci/budget-core.c @@ -0,0 +1,606 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * budget-core.ko: base-driver for the SAA7146 based Budget DVB cards + * + * Compiled from various sources by Michael Hunold + * + * Copyright (C) 2002 Ralph Metzler + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * 26feb2004 Support for FS Activy Card (Grundig tuner) by + * Michael Dreher , + * Oliver Endriss , + * Andreas 'randy' Weinberger + * + * the project's page is at https://linuxtv.org + */ + + +#include "budget.h" +#include "ttpci-eeprom.h" + +#define TS_WIDTH (2 * TS_SIZE) +#define TS_WIDTH_ACTIVY TS_SIZE +#define TS_WIDTH_DVBC TS_SIZE +#define TS_HEIGHT_MASK 0xf00 +#define TS_HEIGHT_MASK_ACTIVY 0xc00 +#define TS_HEIGHT_MASK_DVBC 0xe00 +#define TS_MIN_BUFSIZE_K 188 +#define TS_MAX_BUFSIZE_K 1410 +#define TS_MAX_BUFSIZE_K_ACTIVY 564 +#define TS_MAX_BUFSIZE_K_DVBC 1316 +#define BUFFER_WARNING_WAIT (30*HZ) + +int budget_debug; +EXPORT_SYMBOL_GPL(budget_debug); +static int dma_buffer_size = TS_MIN_BUFSIZE_K; +module_param_named(debug, budget_debug, int, 0644); +module_param_named(bufsize, dma_buffer_size, int, 0444); +MODULE_PARM_DESC(debug, "Turn on/off budget debugging (default:off)."); +MODULE_PARM_DESC(bufsize, "DMA buffer size in KB, default: 188, min: 188, max: 1410 (Activy: 564)"); + +/**************************************************************************** + * TT budget / WinTV Nova + ****************************************************************************/ + +static int stop_ts_capture(struct budget *budget) +{ + dprintk(2, "budget: %p\n", budget); + + saa7146_write(budget->dev, MC1, MASK_20); // DMA3 off + SAA7146_IER_DISABLE(budget->dev, MASK_10); + return 0; +} + +static int start_ts_capture(struct budget *budget) +{ + struct saa7146_dev *dev = budget->dev; + + dprintk(2, "budget: %p\n", budget); + + if (!budget->feeding || !budget->fe_synced) + return 0; + + saa7146_write(dev, MC1, MASK_20); // DMA3 off + + memset(budget->grabbing, 0x00, budget->buffer_size); + + saa7146_write(dev, PCI_BT_V1, 0x001c0000 | (saa7146_read(dev, PCI_BT_V1) & ~0x001f0000)); + + budget->ttbp = 0; + + /* + * Signal path on the Activy: + * + * tuner -> SAA7146 port A -> SAA7146 BRS -> SAA7146 DMA3 -> memory + * + * Since the tuner feeds 204 bytes packets into the SAA7146, + * DMA3 is configured to strip the trailing 16 FEC bytes: + * Pitch: 188, NumBytes3: 188, NumLines3: 1024 + */ + + switch (budget->card->type) { + case BUDGET_FS_ACTIVY: + saa7146_write(dev, DD1_INIT, 0x04000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25)); + saa7146_write(dev, BRS_CTRL, 0x00000000); + break; + case BUDGET_PATCH: + saa7146_write(dev, DD1_INIT, 0x00000200); + saa7146_write(dev, MC2, (MASK_10 | MASK_26)); + saa7146_write(dev, BRS_CTRL, 0x60000000); + break; + case BUDGET_CIN1200C_MK3: + case BUDGET_KNC1C_MK3: + case BUDGET_KNC1C_TDA10024: + case BUDGET_KNC1CP_MK3: + if (budget->video_port == BUDGET_VIDEO_PORTA) { + saa7146_write(dev, DD1_INIT, 0x06000200); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + saa7146_write(dev, BRS_CTRL, 0x00000000); + } else { + saa7146_write(dev, DD1_INIT, 0x00000600); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + saa7146_write(dev, BRS_CTRL, 0x60000000); + } + break; + default: + if (budget->video_port == BUDGET_VIDEO_PORTA) { + saa7146_write(dev, DD1_INIT, 0x06000200); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + saa7146_write(dev, BRS_CTRL, 0x00000000); + } else { + saa7146_write(dev, DD1_INIT, 0x02000600); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + saa7146_write(dev, BRS_CTRL, 0x60000000); + } + } + + saa7146_write(dev, MC2, (MASK_08 | MASK_24)); + mdelay(10); + + saa7146_write(dev, BASE_ODD3, 0); + if (budget->buffer_size > budget->buffer_height * budget->buffer_width) { + // using odd/even buffers + saa7146_write(dev, BASE_EVEN3, budget->buffer_height * budget->buffer_width); + } else { + // using a single buffer + saa7146_write(dev, BASE_EVEN3, 0); + } + saa7146_write(dev, PROT_ADDR3, budget->buffer_size); + saa7146_write(dev, BASE_PAGE3, budget->pt.dma | ME1 | 0x90); + + saa7146_write(dev, PITCH3, budget->buffer_width); + saa7146_write(dev, NUM_LINE_BYTE3, + (budget->buffer_height << 16) | budget->buffer_width); + + saa7146_write(dev, MC2, (MASK_04 | MASK_20)); + + SAA7146_ISR_CLEAR(budget->dev, MASK_10); /* VPE */ + SAA7146_IER_ENABLE(budget->dev, MASK_10); /* VPE */ + saa7146_write(dev, MC1, (MASK_04 | MASK_20)); /* DMA3 on */ + + return 0; +} + +static int budget_read_fe_status(struct dvb_frontend *fe, + enum fe_status *status) +{ + struct budget *budget = fe->dvb->priv; + int synced; + int ret; + + if (budget->read_fe_status) + ret = budget->read_fe_status(fe, status); + else + ret = -EINVAL; + + if (!ret) { + synced = (*status & FE_HAS_LOCK); + if (synced != budget->fe_synced) { + budget->fe_synced = synced; + spin_lock(&budget->feedlock); + if (synced) + start_ts_capture(budget); + else + stop_ts_capture(budget); + spin_unlock(&budget->feedlock); + } + } + return ret; +} + +static void vpeirq(struct work_struct *t) +{ + struct budget *budget = from_work(budget, t, vpe_bh_work); + u8 *mem = (u8 *) (budget->grabbing); + u32 olddma = budget->ttbp; + u32 newdma = saa7146_read(budget->dev, PCI_VDP3); + u32 count; + + /* Ensure streamed PCI data is synced to CPU */ + dma_sync_sg_for_cpu(&budget->dev->pci->dev, budget->pt.slist, + budget->pt.nents, DMA_FROM_DEVICE); + + /* nearest lower position divisible by 188 */ + newdma -= newdma % 188; + + if (newdma >= budget->buffer_size) + return; + + budget->ttbp = newdma; + + if (budget->feeding == 0 || newdma == olddma) + return; + + if (newdma > olddma) { /* no wraparound, dump olddma..newdma */ + count = newdma - olddma; + dvb_dmx_swfilter_packets(&budget->demux, mem + olddma, count / 188); + } else { /* wraparound, dump olddma..buflen and 0..newdma */ + count = budget->buffer_size - olddma; + dvb_dmx_swfilter_packets(&budget->demux, mem + olddma, count / 188); + count += newdma; + dvb_dmx_swfilter_packets(&budget->demux, mem, newdma / 188); + } + + if (count > budget->buffer_warning_threshold) + budget->buffer_warnings++; + + if (budget->buffer_warnings && time_after(jiffies, budget->buffer_warning_time)) { + pr_warn("%s %s: used %d times >80%% of buffer (%u bytes now)\n", + budget->dev->name, __func__, budget->buffer_warnings, count); + budget->buffer_warning_time = jiffies + BUFFER_WARNING_WAIT; + budget->buffer_warnings = 0; + } +} + + +static int ttpci_budget_debiread_nolock(struct budget *budget, u32 config, + int addr, int count, int nobusyloop) +{ + struct saa7146_dev *saa = budget->dev; + int result; + + result = saa7146_wait_for_debi_done(saa, nobusyloop); + if (result < 0) + return result; + + saa7146_write(saa, DEBI_COMMAND, (count << 17) | 0x10000 | (addr & 0xffff)); + saa7146_write(saa, DEBI_CONFIG, config); + saa7146_write(saa, DEBI_PAGE, 0); + saa7146_write(saa, MC2, (2 << 16) | 2); + + result = saa7146_wait_for_debi_done(saa, nobusyloop); + if (result < 0) + return result; + + result = saa7146_read(saa, DEBI_AD); + result &= (0xffffffffUL >> ((4 - count) * 8)); + return result; +} + +int ttpci_budget_debiread(struct budget *budget, u32 config, int addr, int count, + int uselocks, int nobusyloop) +{ + if (count > 4 || count <= 0) + return 0; + + if (uselocks) { + unsigned long flags; + int result; + + spin_lock_irqsave(&budget->debilock, flags); + result = ttpci_budget_debiread_nolock(budget, config, addr, + count, nobusyloop); + spin_unlock_irqrestore(&budget->debilock, flags); + return result; + } + return ttpci_budget_debiread_nolock(budget, config, addr, + count, nobusyloop); +} +EXPORT_SYMBOL_GPL(ttpci_budget_debiread); + +static int ttpci_budget_debiwrite_nolock(struct budget *budget, u32 config, + int addr, int count, u32 value, int nobusyloop) +{ + struct saa7146_dev *saa = budget->dev; + int result; + + result = saa7146_wait_for_debi_done(saa, nobusyloop); + if (result < 0) + return result; + + saa7146_write(saa, DEBI_COMMAND, (count << 17) | 0x00000 | (addr & 0xffff)); + saa7146_write(saa, DEBI_CONFIG, config); + saa7146_write(saa, DEBI_PAGE, 0); + saa7146_write(saa, DEBI_AD, value); + saa7146_write(saa, MC2, (2 << 16) | 2); + + result = saa7146_wait_for_debi_done(saa, nobusyloop); + return result < 0 ? result : 0; +} + +int ttpci_budget_debiwrite(struct budget *budget, u32 config, int addr, + int count, u32 value, int uselocks, int nobusyloop) +{ + if (count > 4 || count <= 0) + return 0; + + if (uselocks) { + unsigned long flags; + int result; + + spin_lock_irqsave(&budget->debilock, flags); + result = ttpci_budget_debiwrite_nolock(budget, config, addr, + count, value, nobusyloop); + spin_unlock_irqrestore(&budget->debilock, flags); + return result; + } + return ttpci_budget_debiwrite_nolock(budget, config, addr, + count, value, nobusyloop); +} +EXPORT_SYMBOL_GPL(ttpci_budget_debiwrite); + + +/**************************************************************************** + * DVB API SECTION + ****************************************************************************/ + +static int budget_start_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct budget *budget = demux->priv; + int status = 0; + + dprintk(2, "budget: %p\n", budget); + + if (!demux->dmx.frontend) + return -EINVAL; + + spin_lock(&budget->feedlock); + feed->pusi_seen = false; /* have a clean section start */ + if (budget->feeding++ == 0) + status = start_ts_capture(budget); + spin_unlock(&budget->feedlock); + return status; +} + +static int budget_stop_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct budget *budget = demux->priv; + int status = 0; + + dprintk(2, "budget: %p\n", budget); + + spin_lock(&budget->feedlock); + if (--budget->feeding == 0) + status = stop_ts_capture(budget); + spin_unlock(&budget->feedlock); + return status; +} + +static int budget_register(struct budget *budget) +{ + struct dvb_demux *dvbdemux = &budget->demux; + int ret; + + dprintk(2, "budget: %p\n", budget); + + dvbdemux->priv = (void *) budget; + + dvbdemux->filternum = 256; + dvbdemux->feednum = 256; + dvbdemux->start_feed = budget_start_feed; + dvbdemux->stop_feed = budget_stop_feed; + dvbdemux->write_to_decoder = NULL; + + dvbdemux->dmx.capabilities = (DMX_TS_FILTERING | DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING); + + dvb_dmx_init(&budget->demux); + + budget->dmxdev.filternum = 256; + budget->dmxdev.demux = &dvbdemux->dmx; + budget->dmxdev.capabilities = 0; + + dvb_dmxdev_init(&budget->dmxdev, &budget->dvb_adapter); + + budget->hw_frontend.source = DMX_FRONTEND_0; + + ret = dvbdemux->dmx.add_frontend(&dvbdemux->dmx, &budget->hw_frontend); + + if (ret < 0) + goto err_release_dmx; + + budget->mem_frontend.source = DMX_MEMORY_FE; + ret = dvbdemux->dmx.add_frontend(&dvbdemux->dmx, &budget->mem_frontend); + if (ret < 0) + goto err_release_dmx; + + ret = dvbdemux->dmx.connect_frontend(&dvbdemux->dmx, &budget->hw_frontend); + if (ret < 0) + goto err_release_dmx; + + dvb_net_init(&budget->dvb_adapter, &budget->dvb_net, &dvbdemux->dmx); + + return 0; + +err_release_dmx: + dvb_dmxdev_release(&budget->dmxdev); + dvb_dmx_release(&budget->demux); + return ret; +} + +static void budget_unregister(struct budget *budget) +{ + struct dvb_demux *dvbdemux = &budget->demux; + + dprintk(2, "budget: %p\n", budget); + + dvb_net_release(&budget->dvb_net); + + dvbdemux->dmx.close(&dvbdemux->dmx); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &budget->hw_frontend); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &budget->mem_frontend); + + dvb_dmxdev_release(&budget->dmxdev); + dvb_dmx_release(&budget->demux); +} + +int ttpci_budget_init(struct budget *budget, struct saa7146_dev *dev, + struct saa7146_pci_extension_data *info, + struct module *owner, short *adapter_nums) +{ + int ret = 0; + struct budget_info *bi = info->ext_priv; + int max_bufsize; + int height_mask; + + memset(budget, 0, sizeof(struct budget)); + + dprintk(2, "dev: %p, budget: %p\n", dev, budget); + + budget->card = bi; + budget->dev = (struct saa7146_dev *) dev; + + switch (budget->card->type) { + case BUDGET_FS_ACTIVY: + budget->buffer_width = TS_WIDTH_ACTIVY; + max_bufsize = TS_MAX_BUFSIZE_K_ACTIVY; + height_mask = TS_HEIGHT_MASK_ACTIVY; + break; + + case BUDGET_KNC1C: + case BUDGET_KNC1CP: + case BUDGET_CIN1200C: + case BUDGET_KNC1C_MK3: + case BUDGET_KNC1C_TDA10024: + case BUDGET_KNC1CP_MK3: + case BUDGET_CIN1200C_MK3: + budget->buffer_width = TS_WIDTH_DVBC; + max_bufsize = TS_MAX_BUFSIZE_K_DVBC; + height_mask = TS_HEIGHT_MASK_DVBC; + break; + + default: + budget->buffer_width = TS_WIDTH; + max_bufsize = TS_MAX_BUFSIZE_K; + height_mask = TS_HEIGHT_MASK; + } + + if (dma_buffer_size < TS_MIN_BUFSIZE_K) + dma_buffer_size = TS_MIN_BUFSIZE_K; + else if (dma_buffer_size > max_bufsize) + dma_buffer_size = max_bufsize; + + budget->buffer_height = dma_buffer_size * 1024 / budget->buffer_width; + if (budget->buffer_height > 0xfff) { + budget->buffer_height /= 2; + budget->buffer_height &= height_mask; + budget->buffer_size = 2 * budget->buffer_height * budget->buffer_width; + } else { + budget->buffer_height &= height_mask; + budget->buffer_size = budget->buffer_height * budget->buffer_width; + } + budget->buffer_warning_threshold = budget->buffer_size * 80/100; + budget->buffer_warnings = 0; + budget->buffer_warning_time = jiffies; + + dprintk(2, "%s: buffer type = %s, width = %d, height = %d\n", + budget->dev->name, + budget->buffer_size > budget->buffer_width * budget->buffer_height ? "odd/even" : "single", + budget->buffer_width, budget->buffer_height); + pr_info("%s: dma buffer size %u\n", budget->dev->name, budget->buffer_size); + + ret = dvb_register_adapter(&budget->dvb_adapter, budget->card->name, + owner, &budget->dev->pci->dev, adapter_nums); + if (ret < 0) + return ret; + + /* set dd1 stream a & b */ + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25)); + saa7146_write(dev, MC2, (MASK_10 | MASK_26)); + saa7146_write(dev, DD1_INIT, 0x02000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + if (bi->type != BUDGET_FS_ACTIVY) + budget->video_port = BUDGET_VIDEO_PORTB; + else + budget->video_port = BUDGET_VIDEO_PORTA; + spin_lock_init(&budget->feedlock); + spin_lock_init(&budget->debilock); + + /* + * the Siemens DVB needs this if you want to have the i2c chips + * get recognized before the main driver is loaded + */ + if (bi->type != BUDGET_FS_ACTIVY) + saa7146_write(dev, GPIO_CTRL, 0x500000); /* GPIO 3 = 1 */ + + strscpy(budget->i2c_adap.name, budget->card->name, + sizeof(budget->i2c_adap.name)); + + saa7146_i2c_adapter_prepare(dev, &budget->i2c_adap, SAA7146_I2C_BUS_BIT_RATE_120); + strscpy(budget->i2c_adap.name, budget->card->name, + sizeof(budget->i2c_adap.name)); + + if (i2c_add_adapter(&budget->i2c_adap) < 0) { + ret = -ENOMEM; + goto err_dvb_unregister; + } + + ttpci_eeprom_parse_mac(&budget->i2c_adap, budget->dvb_adapter.proposed_mac); + + budget->grabbing = saa7146_vmalloc_build_pgtable(dev->pci, budget->buffer_size, &budget->pt); + if (budget->grabbing == NULL) { + ret = -ENOMEM; + goto err_del_i2c; + } + + saa7146_write(dev, PCI_BT_V1, 0x001c0000); + /* upload all */ + saa7146_write(dev, GPIO_CTRL, 0x000000); + + INIT_WORK(&budget->vpe_bh_work, vpeirq); + + /* frontend power on */ + if (bi->type != BUDGET_FS_ACTIVY) + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); + + ret = budget_register(budget); + if (ret == 0) + return 0; /* Everything OK */ + + /* An error occurred, cleanup resources */ + saa7146_vfree_destroy_pgtable(dev->pci, budget->grabbing, &budget->pt); + +err_del_i2c: + i2c_del_adapter(&budget->i2c_adap); + +err_dvb_unregister: + dvb_unregister_adapter(&budget->dvb_adapter); + + return ret; +} +EXPORT_SYMBOL_GPL(ttpci_budget_init); + +void ttpci_budget_init_hooks(struct budget *budget) +{ + if (budget->dvb_frontend && !budget->read_fe_status) { + budget->read_fe_status = budget->dvb_frontend->ops.read_status; + budget->dvb_frontend->ops.read_status = budget_read_fe_status; + } +} +EXPORT_SYMBOL_GPL(ttpci_budget_init_hooks); + +int ttpci_budget_deinit(struct budget *budget) +{ + struct saa7146_dev *dev = budget->dev; + + dprintk(2, "budget: %p\n", budget); + + budget_unregister(budget); + + cancel_work_sync(&budget->vpe_bh_work); + + saa7146_vfree_destroy_pgtable(dev->pci, budget->grabbing, &budget->pt); + + i2c_del_adapter(&budget->i2c_adap); + + dvb_unregister_adapter(&budget->dvb_adapter); + + return 0; +} +EXPORT_SYMBOL_GPL(ttpci_budget_deinit); + +void ttpci_budget_irq10_handler(struct saa7146_dev *dev, u32 *isr) +{ + struct budget *budget = dev->ext_priv; + + dprintk(8, "dev: %p, budget: %p\n", dev, budget); + + if (*isr & MASK_10) + queue_work(system_bh_wq, &budget->vpe_bh_work); +} +EXPORT_SYMBOL_GPL(ttpci_budget_irq10_handler); + +void ttpci_budget_set_video_port(struct saa7146_dev *dev, int video_port) +{ + struct budget *budget = dev->ext_priv; + + spin_lock(&budget->feedlock); + budget->video_port = video_port; + if (budget->feeding) { + stop_ts_capture(budget); + start_ts_capture(budget); + } + spin_unlock(&budget->feedlock); +} +EXPORT_SYMBOL_GPL(ttpci_budget_set_video_port); + +MODULE_DESCRIPTION("base driver for the SAA7146 based Budget DVB cards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/ttpci/budget.c b/drivers/media/pci/ttpci/budget.c new file mode 100644 index 000000000..c9f419fc0 --- /dev/null +++ b/drivers/media/pci/ttpci/budget.c @@ -0,0 +1,894 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * budget.ko: driver for the SAA7146 based Budget DVB cards + * without analog video input or CI + * + * Compiled from various sources by Michael Hunold + * + * Copyright (C) 2002 Ralph Metzler + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * 26feb2004 Support for FS Activy Card (Grundig tuner) by + * Michael Dreher , + * Oliver Endriss and + * Andreas 'randy' Weinberger + * + * the project's page is at https://linuxtv.org + */ + +#include "budget.h" +#include "stv0299.h" +#include "ves1x93.h" +#include "ves1820.h" +#include "l64781.h" +#include "tda8083.h" +#include "s5h1420.h" +#include "tda10086.h" +#include "tda826x.h" +#include "lnbp21.h" +#include "bsru6.h" +#include "bsbe1.h" +#include "tdhd1.h" +#include "stv6110x.h" +#include "stv090x.h" +#include "isl6423.h" +#include "lnbh24.h" + + +static int diseqc_method; +module_param(diseqc_method, int, 0444); +MODULE_PARM_DESC(diseqc_method, "Select DiSEqC method for subsystem id 13c2:1003, 0: default, 1: more reliable (for newer revisions only)"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static void Set22K(struct budget *budget, int state) +{ + struct saa7146_dev *dev = budget->dev; + + dprintk(2, "budget: %p\n", budget); + saa7146_setgpio(dev, 3, (state ? SAA7146_GPIO_OUTHI : SAA7146_GPIO_OUTLO)); +} + +/* + * Diseqc functions only for TT Budget card + * taken from the Skyvision DVB driver by + * Ralph Metzler + */ + +static void DiseqcSendBit(struct budget *budget, int data) +{ + struct saa7146_dev *dev = budget->dev; + + dprintk(2, "budget: %p\n", budget); + + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); + udelay(data ? 500 : 1000); + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + udelay(data ? 1000 : 500); +} + +static void DiseqcSendByte(struct budget *budget, int data) +{ + int i, par = 1, d; + + dprintk(2, "budget: %p\n", budget); + + for (i = 7; i >= 0; i--) { + d = (data>>i)&1; + par ^= d; + DiseqcSendBit(budget, d); + } + + DiseqcSendBit(budget, par); +} + +static int SendDiSEqCMsg(struct budget *budget, int len, u8 *msg, unsigned long burst) +{ + struct saa7146_dev *dev = budget->dev; + int i; + + dprintk(2, "budget: %p\n", budget); + + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + mdelay(16); + + for (i = 0; i < len; i++) + DiseqcSendByte(budget, msg[i]); + + mdelay(16); + + if (burst != -1) { + if (burst) { + DiseqcSendByte(budget, 0xff); + } else { + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); + mdelay(12); + udelay(500); + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + } + msleep(20); + } + + return 0; +} + +/* + * Routines for the Fujitsu Siemens Activy budget card + * 22 kHz tone and DiSEqC are handled by the frontend. + * Voltage must be set here. + * GPIO 1: LNBP EN, GPIO 2: LNBP VSEL + */ +static int SetVoltage_Activy(struct budget *budget, + enum fe_sec_voltage voltage) +{ + struct saa7146_dev *dev = budget->dev; + + dprintk(2, "budget: %p\n", budget); + + switch (voltage) { + case SEC_VOLTAGE_13: + saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTHI); + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTLO); + break; + case SEC_VOLTAGE_18: + saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTHI); + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); + break; + case SEC_VOLTAGE_OFF: + saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTLO); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int siemens_budget_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage voltage) +{ + struct budget *budget = fe->dvb->priv; + + return SetVoltage_Activy(budget, voltage); +} + +static int budget_set_tone(struct dvb_frontend *fe, + enum fe_sec_tone_mode tone) +{ + struct budget *budget = fe->dvb->priv; + + switch (tone) { + case SEC_TONE_ON: + Set22K(budget, 1); + break; + + case SEC_TONE_OFF: + Set22K(budget, 0); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int budget_diseqc_send_master_cmd(struct dvb_frontend *fe, struct dvb_diseqc_master_cmd *cmd) +{ + struct budget *budget = fe->dvb->priv; + + SendDiSEqCMsg(budget, cmd->msg_len, cmd->msg, 0); + + return 0; +} + +static int budget_diseqc_send_burst(struct dvb_frontend *fe, + enum fe_sec_mini_cmd minicmd) +{ + struct budget *budget = fe->dvb->priv; + + SendDiSEqCMsg(budget, 0, NULL, minicmd); + + return 0; +} + +static int alps_bsrv2_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget *budget = fe->dvb->priv; + u8 pwr = 0; + u8 buf[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = buf, .len = sizeof(buf) }; + u32 div = (c->frequency + 479500) / 125; + + if (c->frequency > 2000000) + pwr = 3; + else if (c->frequency > 1800000) + pwr = 2; + else if (c->frequency > 1600000) + pwr = 1; + else if (c->frequency > 1200000) + pwr = 0; + else if (c->frequency >= 1100000) + pwr = 1; + else + pwr = 2; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = ((div & 0x18000) >> 10) | 0x95; + buf[3] = (pwr << 6) | 0x30; + + // NOTE: since we're using a prescaler of 2, we set the + // divisor frequency to 62.5kHz and divide by 125 above + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget->i2c_adap, &msg, 1) != 1) + return -EIO; + return 0; +} + +static struct ves1x93_config alps_bsrv2_config = { + .demod_address = 0x08, + .xin = 90100000UL, + .invert_pwm = 0, +}; + +static int alps_tdbe2_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget *budget = fe->dvb->priv; + u32 div; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x62, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = (c->frequency + 35937500 + 31250) / 62500; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0x85 | ((div >> 10) & 0x60); + data[3] = (c->frequency < 174000000 ? 0x88 : c->frequency < 470000000 ? 0x84 : 0x81); + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget->i2c_adap, &msg, 1) != 1) + return -EIO; + return 0; +} + +static struct ves1820_config alps_tdbe2_config = { + .demod_address = 0x09, + .xin = 57840000UL, + .invert = 1, + .selagc = VES1820_SELAGC_SIGNAMPERR, +}; + +static int grundig_29504_401_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget *budget = fe->dvb->priv; + u8 *tuner_addr = fe->tuner_priv; + u32 div; + u8 cfg, cpump, band_select; + u8 data[4]; + struct i2c_msg msg = { .flags = 0, .buf = data, .len = sizeof(data) }; + + if (tuner_addr) + msg.addr = *tuner_addr; + else + msg.addr = 0x61; + + div = (36125000 + c->frequency) / 166666; + + cfg = 0x88; + + if (c->frequency < 175000000) + cpump = 2; + else if (c->frequency < 390000000) + cpump = 1; + else if (c->frequency < 470000000) + cpump = 2; + else if (c->frequency < 750000000) + cpump = 1; + else + cpump = 3; + + if (c->frequency < 175000000) + band_select = 0x0e; + else if (c->frequency < 470000000) + band_select = 0x05; + else + band_select = 0x03; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = ((div >> 10) & 0x60) | cfg; + data[3] = (cpump << 6) | band_select; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget->i2c_adap, &msg, 1) != 1) + return -EIO; + return 0; +} + +static struct l64781_config grundig_29504_401_config = { + .demod_address = 0x55, +}; + +static struct l64781_config grundig_29504_401_config_activy = { + .demod_address = 0x54, +}; + +static u8 tuner_address_grundig_29504_401_activy = 0x60; + +static int grundig_29504_451_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget *budget = fe->dvb->priv; + u32 div; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = c->frequency / 125; + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0x8e; + data[3] = 0x00; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget->i2c_adap, &msg, 1) != 1) + return -EIO; + return 0; +} + +static struct tda8083_config grundig_29504_451_config = { + .demod_address = 0x68, +}; + +static int s5h1420_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget *budget = fe->dvb->priv; + u32 div; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = c->frequency / 1000; + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0xc2; + + if (div < 1450) + data[3] = 0x00; + else if (div < 1850) + data[3] = 0x40; + else if (div < 2000) + data[3] = 0x80; + else + data[3] = 0xc0; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&budget->i2c_adap, &msg, 1) != 1) + return -EIO; + + return 0; +} + +static struct s5h1420_config s5h1420_config = { + .demod_address = 0x53, + .invert = 1, + .cdclk_polarity = 1, +}; + +static struct tda10086_config tda10086_config = { + .demod_address = 0x0e, + .invert = 0, + .diseqc_tone = 1, + .xtal_freq = TDA10086_XTAL_16M, +}; + +static const struct stv0299_config alps_bsru6_config_activy = { + .demod_address = 0x68, + .inittab = alps_bsru6_inittab, + .mclk = 88000000UL, + .invert = 1, + .op0_off = 1, + .min_delay_ms = 100, + .set_symbol_rate = alps_bsru6_set_symbol_rate, +}; + +static const struct stv0299_config alps_bsbe1_config_activy = { + .demod_address = 0x68, + .inittab = alps_bsbe1_inittab, + .mclk = 88000000UL, + .invert = 1, + .op0_off = 1, + .min_delay_ms = 100, + .set_symbol_rate = alps_bsbe1_set_symbol_rate, +}; + +static int alps_tdhd1_204_request_firmware(struct dvb_frontend *fe, const struct firmware **fw, char *name) +{ + struct budget *budget = fe->dvb->priv; + + return request_firmware(fw, name, &budget->dev->pci->dev); +} + + +static int i2c_readreg(struct i2c_adapter *i2c, u8 adr, u8 reg) +{ + u8 val; + struct i2c_msg msg[] = { + { .addr = adr, .flags = 0, .buf = ®, .len = 1 }, + { .addr = adr, .flags = I2C_M_RD, .buf = &val, .len = 1 } + }; + + return (i2c_transfer(i2c, msg, 2) != 2) ? -EIO : val; +} + +static u8 read_pwm(struct budget *budget) +{ + u8 b = 0xff; + u8 pwm; + struct i2c_msg msg[] = { { .addr = 0x50, .flags = 0, .buf = &b, .len = 1 }, + { .addr = 0x50, .flags = I2C_M_RD, .buf = &pwm, .len = 1} }; + + if ((i2c_transfer(&budget->i2c_adap, msg, 2) != 2) || (pwm == 0xff)) + pwm = 0x48; + + return pwm; +} + +static struct stv090x_config tt1600_stv090x_config = { + .device = STV0903, + .demod_mode = STV090x_SINGLE, + .clk_mode = STV090x_CLK_EXT, + + .xtal = 13500000, + .address = 0x68, + + .ts1_mode = STV090x_TSMODE_DVBCI, + .ts2_mode = STV090x_TSMODE_SERIAL_CONTINUOUS, + + .repeater_level = STV090x_RPTLEVEL_16, + + .tuner_init = NULL, + .tuner_sleep = NULL, + .tuner_set_mode = NULL, + .tuner_set_frequency = NULL, + .tuner_get_frequency = NULL, + .tuner_set_bandwidth = NULL, + .tuner_get_bandwidth = NULL, + .tuner_set_bbgain = NULL, + .tuner_get_bbgain = NULL, + .tuner_set_refclk = NULL, + .tuner_get_status = NULL, +}; + +static struct stv6110x_config tt1600_stv6110x_config = { + .addr = 0x60, + .refclk = 27000000, + .clk_div = 2, +}; + +static struct isl6423_config tt1600_isl6423_config = { + .current_max = SEC_CURRENT_515m, + .curlim = SEC_CURRENT_LIM_ON, + .mod_extern = 1, + .addr = 0x08, +}; + +static void frontend_init(struct budget *budget) +{ + (void)alps_bsbe1_config; /* avoid warning */ + + switch (budget->dev->pci->subsystem_device) { + case 0x1003: // Hauppauge/TT Nova budget (stv0299/ALPS BSRU6(tsa5059) OR ves1893/ALPS BSRV2(sp5659)) + case 0x1013: + // try the ALPS BSRV2 first of all + budget->dvb_frontend = dvb_attach(ves1x93_attach, &alps_bsrv2_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsrv2_tuner_set_params; + budget->dvb_frontend->ops.diseqc_send_master_cmd = budget_diseqc_send_master_cmd; + budget->dvb_frontend->ops.diseqc_send_burst = budget_diseqc_send_burst; + budget->dvb_frontend->ops.set_tone = budget_set_tone; + break; + } + + // try the ALPS BSRU6 now + budget->dvb_frontend = dvb_attach(stv0299_attach, &alps_bsru6_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsru6_tuner_set_params; + budget->dvb_frontend->tuner_priv = &budget->i2c_adap; + if (budget->dev->pci->subsystem_device == 0x1003 && diseqc_method == 0) { + budget->dvb_frontend->ops.diseqc_send_master_cmd = budget_diseqc_send_master_cmd; + budget->dvb_frontend->ops.diseqc_send_burst = budget_diseqc_send_burst; + budget->dvb_frontend->ops.set_tone = budget_set_tone; + } + break; + } + break; + + case 0x1004: // Hauppauge/TT DVB-C budget (ves1820/ALPS TDBE2(sp5659)) + + budget->dvb_frontend = dvb_attach(ves1820_attach, &alps_tdbe2_config, &budget->i2c_adap, read_pwm(budget)); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = alps_tdbe2_tuner_set_params; + break; + } + break; + + case 0x1005: // Hauppauge/TT Nova-T budget (L64781/Grundig 29504-401(tsa5060)) + + budget->dvb_frontend = dvb_attach(l64781_attach, &grundig_29504_401_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = grundig_29504_401_tuner_set_params; + budget->dvb_frontend->tuner_priv = NULL; + break; + } + break; + + case 0x4f52: /* Cards based on Philips Semi Sylt PCI ref. design */ + budget->dvb_frontend = dvb_attach(stv0299_attach, &alps_bsru6_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + pr_info("tuner ALPS BSRU6 in Philips Semi. Sylt detected\n"); + budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsru6_tuner_set_params; + budget->dvb_frontend->tuner_priv = &budget->i2c_adap; + break; + } + break; + + case 0x4f60: /* Fujitsu Siemens Activy Budget-S PCI rev AL (stv0299/tsa5059) */ + { + int subtype = i2c_readreg(&budget->i2c_adap, 0x50, 0x67); + + if (subtype < 0) + break; + /* fixme: find a better way to identify the card */ + if (subtype < 0x36) { + /* assume ALPS BSRU6 */ + budget->dvb_frontend = dvb_attach(stv0299_attach, &alps_bsru6_config_activy, &budget->i2c_adap); + if (budget->dvb_frontend) { + pr_info("tuner ALPS BSRU6 detected\n"); + budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsru6_tuner_set_params; + budget->dvb_frontend->tuner_priv = &budget->i2c_adap; + budget->dvb_frontend->ops.set_voltage = siemens_budget_set_voltage; + budget->dvb_frontend->ops.dishnetwork_send_legacy_command = NULL; + break; + } + } else { + /* assume ALPS BSBE1 */ + /* reset tuner */ + saa7146_setgpio(budget->dev, 3, SAA7146_GPIO_OUTLO); + msleep(50); + saa7146_setgpio(budget->dev, 3, SAA7146_GPIO_OUTHI); + msleep(250); + budget->dvb_frontend = dvb_attach(stv0299_attach, &alps_bsbe1_config_activy, &budget->i2c_adap); + if (budget->dvb_frontend) { + pr_info("tuner ALPS BSBE1 detected\n"); + budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsbe1_tuner_set_params; + budget->dvb_frontend->tuner_priv = &budget->i2c_adap; + budget->dvb_frontend->ops.set_voltage = siemens_budget_set_voltage; + budget->dvb_frontend->ops.dishnetwork_send_legacy_command = NULL; + break; + } + } + break; + } + + case 0x4f61: // Fujitsu Siemens Activy Budget-S PCI rev GR (tda8083/Grundig 29504-451(tsa5522)) + budget->dvb_frontend = dvb_attach(tda8083_attach, &grundig_29504_451_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = grundig_29504_451_tuner_set_params; + budget->dvb_frontend->ops.set_voltage = siemens_budget_set_voltage; + budget->dvb_frontend->ops.dishnetwork_send_legacy_command = NULL; + } + break; + + case 0x5f60: /* Fujitsu Siemens Activy Budget-T PCI rev AL (tda10046/ALPS TDHD1-204A) */ + budget->dvb_frontend = dvb_attach(tda10046_attach, &alps_tdhd1_204a_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = alps_tdhd1_204a_tuner_set_params; + budget->dvb_frontend->tuner_priv = &budget->i2c_adap; + } + break; + + case 0x5f61: /* Fujitsu Siemens Activy Budget-T PCI rev GR (L64781/Grundig 29504-401(tsa5060)) */ + budget->dvb_frontend = dvb_attach(l64781_attach, &grundig_29504_401_config_activy, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->tuner_priv = &tuner_address_grundig_29504_401_activy; + budget->dvb_frontend->ops.tuner_ops.set_params = grundig_29504_401_tuner_set_params; + } + break; + + case 0x1016: // Hauppauge/TT Nova-S SE (samsung s5h1420/????(tda8260)) + { + struct dvb_frontend *fe; + + fe = dvb_attach(s5h1420_attach, &s5h1420_config, &budget->i2c_adap); + if (fe) { + fe->ops.tuner_ops.set_params = s5h1420_tuner_set_params; + budget->dvb_frontend = fe; + if (dvb_attach(lnbp21_attach, fe, &budget->i2c_adap, + 0, 0) == NULL) { + pr_err("%s(): No LNBP21 found!\n", __func__); + goto error_out; + } + break; + } + } + fallthrough; + case 0x1018: // TT Budget-S-1401 (philips tda10086/philips tda8262) + { + struct dvb_frontend *fe; + + // gpio2 is connected to CLB - reset it + leave it high + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTLO); + msleep(1); + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTHI); + msleep(1); + + fe = dvb_attach(tda10086_attach, &tda10086_config, &budget->i2c_adap); + if (fe) { + budget->dvb_frontend = fe; + if (dvb_attach(tda826x_attach, fe, 0x60, + &budget->i2c_adap, 0) == NULL) + pr_err("%s(): No tda826x found!\n", __func__); + if (dvb_attach(lnbp21_attach, fe, + &budget->i2c_adap, 0, 0) == NULL) { + pr_err("%s(): No LNBP21 found!\n", __func__); + goto error_out; + } + break; + } + } + fallthrough; + + case 0x101c: { /* TT S2-1600 */ + const struct stv6110x_devctl *ctl; + + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTLO); + msleep(50); + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTHI); + msleep(250); + + budget->dvb_frontend = dvb_attach(stv090x_attach, + &tt1600_stv090x_config, + &budget->i2c_adap, + STV090x_DEMODULATOR_0); + + if (budget->dvb_frontend) { + + ctl = dvb_attach(stv6110x_attach, + budget->dvb_frontend, + &tt1600_stv6110x_config, + &budget->i2c_adap); + + if (ctl) { + tt1600_stv090x_config.tuner_init = ctl->tuner_init; + tt1600_stv090x_config.tuner_sleep = ctl->tuner_sleep; + tt1600_stv090x_config.tuner_set_mode = ctl->tuner_set_mode; + tt1600_stv090x_config.tuner_set_frequency = ctl->tuner_set_frequency; + tt1600_stv090x_config.tuner_get_frequency = ctl->tuner_get_frequency; + tt1600_stv090x_config.tuner_set_bandwidth = ctl->tuner_set_bandwidth; + tt1600_stv090x_config.tuner_get_bandwidth = ctl->tuner_get_bandwidth; + tt1600_stv090x_config.tuner_set_bbgain = ctl->tuner_set_bbgain; + tt1600_stv090x_config.tuner_get_bbgain = ctl->tuner_get_bbgain; + tt1600_stv090x_config.tuner_set_refclk = ctl->tuner_set_refclk; + tt1600_stv090x_config.tuner_get_status = ctl->tuner_get_status; + + /* + * call the init function once to initialize + * tuner's clock output divider and demod's + * master clock + */ + if (budget->dvb_frontend->ops.init) + budget->dvb_frontend->ops.init(budget->dvb_frontend); + + if (dvb_attach(isl6423_attach, + budget->dvb_frontend, + &budget->i2c_adap, + &tt1600_isl6423_config) == NULL) { + pr_err("%s(): No Intersil ISL6423 found!\n", __func__); + goto error_out; + } + } else { + pr_err("%s(): No STV6110(A) Silicon Tuner found!\n", __func__); + goto error_out; + } + } + } + break; + + case 0x1020: { /* Omicom S2 */ + const struct stv6110x_devctl *ctl; + + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTLO); + msleep(50); + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTHI); + msleep(250); + + budget->dvb_frontend = dvb_attach(stv090x_attach, + &tt1600_stv090x_config, + &budget->i2c_adap, + STV090x_DEMODULATOR_0); + + if (budget->dvb_frontend) { + pr_info("Omicom S2 detected\n"); + + ctl = dvb_attach(stv6110x_attach, + budget->dvb_frontend, + &tt1600_stv6110x_config, + &budget->i2c_adap); + + if (ctl) { + tt1600_stv090x_config.tuner_init = ctl->tuner_init; + tt1600_stv090x_config.tuner_sleep = ctl->tuner_sleep; + tt1600_stv090x_config.tuner_set_mode = ctl->tuner_set_mode; + tt1600_stv090x_config.tuner_set_frequency = ctl->tuner_set_frequency; + tt1600_stv090x_config.tuner_get_frequency = ctl->tuner_get_frequency; + tt1600_stv090x_config.tuner_set_bandwidth = ctl->tuner_set_bandwidth; + tt1600_stv090x_config.tuner_get_bandwidth = ctl->tuner_get_bandwidth; + tt1600_stv090x_config.tuner_set_bbgain = ctl->tuner_set_bbgain; + tt1600_stv090x_config.tuner_get_bbgain = ctl->tuner_get_bbgain; + tt1600_stv090x_config.tuner_set_refclk = ctl->tuner_set_refclk; + tt1600_stv090x_config.tuner_get_status = ctl->tuner_get_status; + + /* + * call the init function once to initialize + * tuner's clock output divider and demod's + * master clock + */ + if (budget->dvb_frontend->ops.init) + budget->dvb_frontend->ops.init(budget->dvb_frontend); + + if (dvb_attach(lnbh24_attach, + budget->dvb_frontend, + &budget->i2c_adap, + LNBH24_PCL | LNBH24_TTX, + LNBH24_TEN, 0x14>>1) == NULL) { + pr_err("No LNBH24 found!\n"); + goto error_out; + } + } else { + pr_err("%s(): No STV6110(A) Silicon Tuner found!\n", __func__); + goto error_out; + } + } + } + break; + } + + if (budget->dvb_frontend == NULL) { + pr_err("A frontend driver was not found for device [%04x:%04x] subsystem [%04x:%04x]\n", + budget->dev->pci->vendor, + budget->dev->pci->device, + budget->dev->pci->subsystem_vendor, + budget->dev->pci->subsystem_device); + } else { + if (dvb_register_frontend(&budget->dvb_adapter, budget->dvb_frontend)) + goto error_out; + } + return; + +error_out: + pr_err("Frontend registration failed!\n"); + dvb_frontend_detach(budget->dvb_frontend); + budget->dvb_frontend = NULL; +} + +static int budget_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct budget *budget = NULL; + int err; + + budget = kmalloc_obj(struct budget); + if (budget == NULL) + return -ENOMEM; + + dprintk(2, "dev:%p, info:%p, budget:%p\n", dev, info, budget); + + dev->ext_priv = budget; + + err = ttpci_budget_init(budget, dev, info, THIS_MODULE, adapter_nr); + if (err) { + pr_err("==> failed\n"); + kfree(budget); + return err; + } + + budget->dvb_adapter.priv = budget; + frontend_init(budget); + + ttpci_budget_init_hooks(budget); + + return 0; +} + +static int budget_detach(struct saa7146_dev *dev) +{ + struct budget *budget = dev->ext_priv; + int err; + + if (budget->dvb_frontend) { + dvb_unregister_frontend(budget->dvb_frontend); + dvb_frontend_detach(budget->dvb_frontend); + } + + err = ttpci_budget_deinit(budget); + + kfree(budget); + dev->ext_priv = NULL; + + return err; +} + +static struct saa7146_extension budget_extension; + +MAKE_BUDGET_INFO(ttbs, "TT-Budget/WinTV-NOVA-S PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttbc, "TT-Budget/WinTV-NOVA-C PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttbt, "TT-Budget/WinTV-NOVA-T PCI", BUDGET_TT); +MAKE_BUDGET_INFO(satel, "SATELCO Multimedia PCI", BUDGET_TT_HW_DISEQC); +MAKE_BUDGET_INFO(ttbs1401, "TT-Budget-S-1401 PCI", BUDGET_TT); +MAKE_BUDGET_INFO(tt1600, "TT-Budget S2-1600 PCI", BUDGET_TT); +MAKE_BUDGET_INFO(fsacs0, "Fujitsu Siemens Activy Budget-S PCI (rev GR/grundig frontend)", BUDGET_FS_ACTIVY); +MAKE_BUDGET_INFO(fsacs1, "Fujitsu Siemens Activy Budget-S PCI (rev AL/alps frontend)", BUDGET_FS_ACTIVY); +MAKE_BUDGET_INFO(fsact, "Fujitsu Siemens Activy Budget-T PCI (rev GR/Grundig frontend)", BUDGET_FS_ACTIVY); +MAKE_BUDGET_INFO(fsact1, "Fujitsu Siemens Activy Budget-T PCI (rev AL/ALPS TDHD1-204A)", BUDGET_FS_ACTIVY); +MAKE_BUDGET_INFO(omicom, "Omicom S2 PCI", BUDGET_TT); +MAKE_BUDGET_INFO(sylt, "Philips Semi Sylt PCI", BUDGET_TT_HW_DISEQC); + +static const struct pci_device_id pci_tbl[] = { + MAKE_EXTENSION_PCI(ttbs, 0x13c2, 0x1003), + MAKE_EXTENSION_PCI(ttbc, 0x13c2, 0x1004), + MAKE_EXTENSION_PCI(ttbt, 0x13c2, 0x1005), + MAKE_EXTENSION_PCI(satel, 0x13c2, 0x1013), + MAKE_EXTENSION_PCI(ttbs, 0x13c2, 0x1016), + MAKE_EXTENSION_PCI(ttbs1401, 0x13c2, 0x1018), + MAKE_EXTENSION_PCI(tt1600, 0x13c2, 0x101c), + MAKE_EXTENSION_PCI(fsacs1, 0x1131, 0x4f60), + MAKE_EXTENSION_PCI(fsacs0, 0x1131, 0x4f61), + MAKE_EXTENSION_PCI(fsact1, 0x1131, 0x5f60), + MAKE_EXTENSION_PCI(fsact, 0x1131, 0x5f61), + MAKE_EXTENSION_PCI(omicom, 0x14c4, 0x1020), + MAKE_EXTENSION_PCI(sylt, 0x1131, 0x4f52), + { } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_extension budget_extension = { + .name = "budget dvb", + .flags = SAA7146_USE_I2C_IRQ, + + .module = THIS_MODULE, + .pci_tbl = pci_tbl, + .attach = budget_attach, + .detach = budget_detach, + + .irq_mask = MASK_10, + .irq_func = ttpci_budget_irq10_handler, +}; + +static int __init budget_init(void) +{ + return saa7146_register_extension(&budget_extension); +} + +static void __exit budget_exit(void) +{ + saa7146_unregister_extension(&budget_extension); +} + +module_init(budget_init); +module_exit(budget_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ralph Metzler, Marcus Metzler, Michael Hunold, others"); +MODULE_DESCRIPTION("driver for the SAA7146 based so-called budget PCI DVB cards by Siemens, Technotrend, Hauppauge"); diff --git a/drivers/media/pci/ttpci/budget.h b/drivers/media/pci/ttpci/budget.h new file mode 100644 index 000000000..e933764ac --- /dev/null +++ b/drivers/media/pci/ttpci/budget.h @@ -0,0 +1,135 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __BUDGET_DVB__ +#define __BUDGET_DVB__ + +#ifdef pr_fmt +#undef pr_fmt +#endif + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +extern int budget_debug; + +#ifdef dprintk +#undef dprintk +#endif + +#define dprintk(level, fmt, arg...) do { \ + if ((level) & budget_debug) \ + pr_info("%s(): " fmt, __func__, ##arg); \ +} while (0) + +#define TS_SIZE 188 + +struct budget_info { + char *name; + int type; +}; + +/* place to store all the necessary device information */ +struct budget { + + /* devices */ + struct dvb_device dvb_dev; + struct dvb_net dvb_net; + + struct saa7146_dev *dev; + + struct i2c_adapter i2c_adap; + struct budget_info *card; + + unsigned char *grabbing; + struct saa7146_pgtable pt; + + struct work_struct fidb_bh_work; + struct work_struct vpe_bh_work; + + struct dmxdev dmxdev; + struct dvb_demux demux; + + struct dmx_frontend hw_frontend; + struct dmx_frontend mem_frontend; + + int ci_present; + int video_port; + + u32 buffer_width; + u32 buffer_height; + u32 buffer_size; + u32 buffer_warning_threshold; + u32 buffer_warnings; + unsigned long buffer_warning_time; + + u32 ttbp; + int feeding; + + spinlock_t feedlock; + + spinlock_t debilock; + + struct dvb_adapter dvb_adapter; + struct dvb_frontend *dvb_frontend; + int (*read_fe_status)(struct dvb_frontend *fe, enum fe_status *status); + int fe_synced; + + void *priv; +}; + +#define MAKE_BUDGET_INFO(x_var, x_name, x_type) \ +static struct budget_info x_var ## _info = { \ + .name = x_name, \ + .type = x_type }; \ +static struct saa7146_pci_extension_data x_var = { \ + .ext_priv = &x_var ## _info, \ + .ext = &budget_extension } + +#define BUDGET_TT 0 +#define BUDGET_TT_HW_DISEQC 1 +#define BUDGET_PATCH 3 +#define BUDGET_FS_ACTIVY 4 +#define BUDGET_CIN1200S 5 +#define BUDGET_CIN1200C 6 +#define BUDGET_CIN1200T 7 +#define BUDGET_KNC1S 8 +#define BUDGET_KNC1C 9 +#define BUDGET_KNC1T 10 +#define BUDGET_KNC1SP 11 +#define BUDGET_KNC1CP 12 +#define BUDGET_KNC1TP 13 +#define BUDGET_TVSTAR 14 +#define BUDGET_CIN1200C_MK3 15 +#define BUDGET_KNC1C_MK3 16 +#define BUDGET_KNC1CP_MK3 17 +#define BUDGET_KNC1S2 18 +#define BUDGET_KNC1C_TDA10024 19 + +#define BUDGET_VIDEO_PORTA 0 +#define BUDGET_VIDEO_PORTB 1 + +extern int ttpci_budget_init(struct budget *budget, struct saa7146_dev *dev, + struct saa7146_pci_extension_data *info, + struct module *owner, short *adapter_nums); +extern void ttpci_budget_init_hooks(struct budget *budget); +extern int ttpci_budget_deinit(struct budget *budget); +extern void ttpci_budget_irq10_handler(struct saa7146_dev *dev, u32 *isr); +extern void ttpci_budget_set_video_port(struct saa7146_dev *dev, int video_port); +extern int ttpci_budget_debiread(struct budget *budget, u32 config, int addr, int count, + int uselocks, int nobusyloop); +extern int ttpci_budget_debiwrite(struct budget *budget, u32 config, int addr, int count, u32 value, + int uselocks, int nobusyloop); + +#endif diff --git a/drivers/media/pci/tw5864/Kconfig b/drivers/media/pci/tw5864/Kconfig new file mode 100644 index 000000000..111da223e --- /dev/null +++ b/drivers/media/pci/tw5864/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_TW5864 + tristate "Techwell TW5864 video/audio grabber and encoder" + depends on VIDEO_DEV && PCI + select VIDEOBUF2_DMA_CONTIG + help + Support for boards based on Techwell TW5864 chip which provides + multichannel video & audio grabbing and encoding (H.264, MJPEG, + ADPCM G.726). + + To compile this driver as a module, choose M here: the + module will be called tw5864. diff --git a/drivers/media/pci/tw5864/Makefile b/drivers/media/pci/tw5864/Makefile new file mode 100644 index 000000000..69dbceaa3 --- /dev/null +++ b/drivers/media/pci/tw5864/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +tw5864-objs := tw5864-core.o tw5864-video.o tw5864-h264.o tw5864-util.o + +obj-$(CONFIG_VIDEO_TW5864) += tw5864.o diff --git a/drivers/media/pci/tw5864/tw5864-core.c b/drivers/media/pci/tw5864/tw5864-core.c new file mode 100644 index 000000000..6ec8bce4a --- /dev/null +++ b/drivers/media/pci/tw5864/tw5864-core.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * TW5864 driver - core functions + * + * Copyright (C) 2016 Bluecherry, LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tw5864.h" +#include "tw5864-reg.h" + +#define DRIVER_NAME "tw5864" + +MODULE_DESCRIPTION("V4L2 driver module for tw5864-based multimedia capture & encoding devices"); +MODULE_AUTHOR("Bluecherry Maintainers "); +MODULE_AUTHOR("Andrey Utkin "); +MODULE_LICENSE("GPL"); + +/* + * BEWARE OF KNOWN ISSUES WITH VIDEO QUALITY + * + * This driver was developed by Bluecherry LLC by deducing behaviour of + * original manufacturer's driver, from both source code and execution traces. + * It is known that there are some artifacts on output video with this driver: + * - on all known hardware samples: random pixels of wrong color (mostly + * white, red or blue) appearing and disappearing on sequences of P-frames; + * - on some hardware samples (known with H.264 core version e006:2800): + * total madness on P-frames: blocks of wrong luminance; blocks of wrong + * colors "creeping" across the picture. + * There is a workaround for both issues: avoid P-frames by setting GOP size + * to 1. To do that, run this command on device files created by this driver: + * + * v4l2-ctl --device /dev/videoX --set-ctrl=video_gop_size=1 + * + * These issues are not decoding errors; all produced H.264 streams are decoded + * properly. Streams without P-frames don't have these artifacts so it's not + * analog-to-digital conversion issues nor internal memory errors; we conclude + * it's internal H.264 encoder issues. + * We cannot even check the original driver's behaviour because it has never + * worked properly at all in our development environment. So these issues may + * be actually related to firmware or hardware. However it may be that there's + * just some more register settings missing in the driver which would please + * the hardware. + * Manufacturer didn't help much on our inquiries, but feel free to disturb + * again the support of Intersil (owner of former Techwell). + */ + +/* take first free /dev/videoX indexes by default */ +static unsigned int video_nr[] = {[0 ... (TW5864_INPUTS - 1)] = -1 }; + +module_param_array(video_nr, int, NULL, 0444); +MODULE_PARM_DESC(video_nr, "video devices numbers array"); + +/* + * Please add any new PCI IDs to: https://pci-ids.ucw.cz. This keeps + * the PCI ID database up to date. Note that the entries must be + * added under vendor 0x1797 (Techwell Inc.) as subsystem IDs. + */ +static const struct pci_device_id tw5864_pci_tbl[] = { + { PCI_VDEVICE(TECHWELL, PCI_DEVICE_ID_TECHWELL_5864) }, + { } +}; + +void tw5864_irqmask_apply(struct tw5864_dev *dev) +{ + tw_writel(TW5864_INTR_ENABLE_L, dev->irqmask & 0xffff); + tw_writel(TW5864_INTR_ENABLE_H, (dev->irqmask >> 16)); +} + +static void tw5864_interrupts_disable(struct tw5864_dev *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->slock, flags); + dev->irqmask = 0; + tw5864_irqmask_apply(dev); + spin_unlock_irqrestore(&dev->slock, flags); +} + +static void tw5864_timer_isr(struct tw5864_dev *dev); +static void tw5864_h264_isr(struct tw5864_dev *dev); + +static irqreturn_t tw5864_isr(int irq, void *dev_id) +{ + struct tw5864_dev *dev = dev_id; + u32 status; + + status = tw_readl(TW5864_INTR_STATUS_L) | + tw_readl(TW5864_INTR_STATUS_H) << 16; + if (!status) + return IRQ_NONE; + + tw_writel(TW5864_INTR_CLR_L, 0xffff); + tw_writel(TW5864_INTR_CLR_H, 0xffff); + + if (status & TW5864_INTR_VLC_DONE) + tw5864_h264_isr(dev); + + if (status & TW5864_INTR_TIMER) + tw5864_timer_isr(dev); + + if (!(status & (TW5864_INTR_TIMER | TW5864_INTR_VLC_DONE))) { + dev_dbg(&dev->pci->dev, "Unknown interrupt, status 0x%08X\n", + status); + } + + return IRQ_HANDLED; +} + +static void tw5864_h264_isr(struct tw5864_dev *dev) +{ + int channel = tw_readl(TW5864_DSP) & TW5864_DSP_ENC_CHN; + struct tw5864_input *input = &dev->inputs[channel]; + int cur_frame_index, next_frame_index; + struct tw5864_h264_frame *cur_frame, *next_frame; + unsigned long flags; + + spin_lock_irqsave(&dev->slock, flags); + + cur_frame_index = dev->h264_buf_w_index; + next_frame_index = (cur_frame_index + 1) % H264_BUF_CNT; + cur_frame = &dev->h264_buf[cur_frame_index]; + next_frame = &dev->h264_buf[next_frame_index]; + + if (next_frame_index != dev->h264_buf_r_index) { + cur_frame->vlc_len = tw_readl(TW5864_VLC_LENGTH) << 2; + cur_frame->checksum = tw_readl(TW5864_VLC_CRC_REG); + cur_frame->input = input; + cur_frame->timestamp = ktime_get_ns(); + cur_frame->seqno = input->frame_seqno; + cur_frame->gop_seqno = input->frame_gop_seqno; + + dev->h264_buf_w_index = next_frame_index; + queue_work(system_bh_wq, &dev->bh_work); + + cur_frame = next_frame; + + spin_lock(&input->slock); + input->frame_seqno++; + input->frame_gop_seqno++; + if (input->frame_gop_seqno >= input->gop) + input->frame_gop_seqno = 0; + spin_unlock(&input->slock); + } else { + dev_err(&dev->pci->dev, + "Skipped frame on input %d because all buffers busy\n", + channel); + } + + dev->encoder_busy = 0; + + spin_unlock_irqrestore(&dev->slock, flags); + + tw_writel(TW5864_VLC_STREAM_BASE_ADDR, cur_frame->vlc.dma_addr); + tw_writel(TW5864_MV_STREAM_BASE_ADDR, cur_frame->mv.dma_addr); + + /* Additional ack for this interrupt */ + tw_writel(TW5864_VLC_DSP_INTR, 0x00000001); + tw_writel(TW5864_PCI_INTR_STATUS, TW5864_VLC_DONE_INTR); +} + +static void tw5864_input_deadline_update(struct tw5864_input *input) +{ + input->new_frame_deadline = jiffies + msecs_to_jiffies(1000); +} + +static void tw5864_timer_isr(struct tw5864_dev *dev) +{ + unsigned long flags; + int i; + int encoder_busy; + + /* Additional ack for this interrupt */ + tw_writel(TW5864_PCI_INTR_STATUS, TW5864_TIMER_INTR); + + spin_lock_irqsave(&dev->slock, flags); + encoder_busy = dev->encoder_busy; + spin_unlock_irqrestore(&dev->slock, flags); + + if (encoder_busy) + return; + + /* + * Traversing inputs in round-robin fashion, starting from next to the + * last processed one + */ + for (i = 0; i < TW5864_INPUTS; i++) { + int next_input = (i + dev->next_input) % TW5864_INPUTS; + struct tw5864_input *input = &dev->inputs[next_input]; + int raw_buf_id; /* id of internal buf with last raw frame */ + + spin_lock_irqsave(&input->slock, flags); + if (!input->enabled) + goto next; + + /* Check if new raw frame is available */ + raw_buf_id = tw_mask_shift_readl(TW5864_SENIF_ORG_FRM_PTR1, 0x3, + 2 * input->nr); + + if (input->buf_id != raw_buf_id) { + input->buf_id = raw_buf_id; + tw5864_input_deadline_update(input); + spin_unlock_irqrestore(&input->slock, flags); + + spin_lock_irqsave(&dev->slock, flags); + dev->encoder_busy = 1; + dev->next_input = (next_input + 1) % TW5864_INPUTS; + spin_unlock_irqrestore(&dev->slock, flags); + + tw5864_request_encoded_frame(input); + break; + } + + /* No new raw frame; check if channel is stuck */ + if (time_is_after_jiffies(input->new_frame_deadline)) { + /* If stuck, request new raw frames again */ + tw_mask_shift_writel(TW5864_ENC_BUF_PTR_REC1, 0x3, + 2 * input->nr, input->buf_id + 3); + tw5864_input_deadline_update(input); + } +next: + spin_unlock_irqrestore(&input->slock, flags); + } +} + +static int tw5864_initdev(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct tw5864_dev *dev; + int err; + + dev = devm_kzalloc(&pci_dev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + snprintf(dev->name, sizeof(dev->name), "%s:%s", DRIVER_NAME, + pci_name(pci_dev)); + + err = v4l2_device_register(&pci_dev->dev, &dev->v4l2_dev); + if (err) + return err; + + /* pci init */ + dev->pci = pci_dev; + err = pcim_enable_device(pci_dev); + if (err) { + dev_err(&dev->pci->dev, "pcim_enable_device() failed\n"); + goto unreg_v4l2; + } + + pci_set_master(pci_dev); + + err = dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(32)); + if (err) { + dev_err(&dev->pci->dev, "32 bit PCI DMA is not supported\n"); + goto unreg_v4l2; + } + + /* get mmio */ + dev->mmio = pcim_iomap_region(pci_dev, 0, DRIVER_NAME); + err = PTR_ERR_OR_ZERO(dev->mmio); + if (err) { + dev_err(&dev->pci->dev, "Cannot request regions for MMIO\n"); + goto unreg_v4l2; + } + + spin_lock_init(&dev->slock); + + dev_info(&pci_dev->dev, "TW5864 hardware version: %04x\n", + tw_readl(TW5864_HW_VERSION)); + dev_info(&pci_dev->dev, "TW5864 H.264 core version: %04x:%04x\n", + tw_readl(TW5864_H264REV), + tw_readl(TW5864_UNDECLARED_H264REV_PART2)); + + err = tw5864_video_init(dev, video_nr); + if (err) + goto unreg_v4l2; + + /* get irq */ + err = devm_request_irq(&pci_dev->dev, pci_dev->irq, tw5864_isr, + IRQF_SHARED, DRIVER_NAME, dev); + if (err < 0) { + dev_err(&dev->pci->dev, "can't get IRQ %d\n", pci_dev->irq); + goto fini_video; + } + + dev_info(&pci_dev->dev, "Note: there are known video quality issues. For details\n"); + dev_info(&pci_dev->dev, "see the comment in drivers/media/pci/tw5864/tw5864-core.c.\n"); + + return 0; + +fini_video: + tw5864_video_fini(dev); +unreg_v4l2: + v4l2_device_unregister(&dev->v4l2_dev); + return err; +} + +static void tw5864_finidev(struct pci_dev *pci_dev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct tw5864_dev *dev = + container_of(v4l2_dev, struct tw5864_dev, v4l2_dev); + + /* shutdown subsystems */ + tw5864_interrupts_disable(dev); + + /* unregister */ + tw5864_video_fini(dev); + + v4l2_device_unregister(&dev->v4l2_dev); +} + +static struct pci_driver tw5864_pci_driver = { + .name = DRIVER_NAME, + .id_table = tw5864_pci_tbl, + .probe = tw5864_initdev, + .remove = tw5864_finidev, +}; + +module_pci_driver(tw5864_pci_driver); diff --git a/drivers/media/pci/tw5864/tw5864-h264.c b/drivers/media/pci/tw5864/tw5864-h264.c new file mode 100644 index 000000000..608798af6 --- /dev/null +++ b/drivers/media/pci/tw5864/tw5864-h264.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * TW5864 driver - H.264 headers generation functions + * + * Copyright (C) 2016 Bluecherry, LLC + */ + +#include + +#include "tw5864.h" + +static u8 marker[] = { 0x00, 0x00, 0x00, 0x01 }; + +/* + * Exponential-Golomb coding functions + * + * These functions are used for generation of H.264 bitstream headers. + * + * This code is derived from tw5864 reference driver by manufacturers, which + * itself apparently was derived from x264 project. + */ + +/* Bitstream writing context */ +struct bs { + u8 *buf; /* pointer to buffer beginning */ + u8 *buf_end; /* pointer to buffer end */ + u8 *ptr; /* pointer to current byte in buffer */ + unsigned int bits_left; /* number of available bits in current byte */ +}; + +static void bs_init(struct bs *s, void *buf, int size) +{ + s->buf = buf; + s->ptr = buf; + s->buf_end = s->ptr + size; + s->bits_left = 8; +} + +static int bs_len(struct bs *s) +{ + return s->ptr - s->buf; +} + +static void bs_write(struct bs *s, int count, u32 bits) +{ + if (s->ptr >= s->buf_end - 4) + return; + while (count > 0) { + if (count < 32) + bits &= (1 << count) - 1; + if (count < s->bits_left) { + *s->ptr = (*s->ptr << count) | bits; + s->bits_left -= count; + break; + } + *s->ptr = (*s->ptr << s->bits_left) | + (bits >> (count - s->bits_left)); + count -= s->bits_left; + s->ptr++; + s->bits_left = 8; + } +} + +static void bs_write1(struct bs *s, u32 bit) +{ + if (s->ptr < s->buf_end) { + *s->ptr <<= 1; + *s->ptr |= bit; + s->bits_left--; + if (s->bits_left == 0) { + s->ptr++; + s->bits_left = 8; + } + } +} + +static void bs_write_ue(struct bs *s, u32 val) +{ + if (val == 0) { + bs_write1(s, 1); + } else { + val++; + bs_write(s, 2 * fls(val) - 1, val); + } +} + +static void bs_write_se(struct bs *s, int val) +{ + bs_write_ue(s, val <= 0 ? -val * 2 : val * 2 - 1); +} + +static void bs_rbsp_trailing(struct bs *s) +{ + bs_write1(s, 1); + if (s->bits_left != 8) + bs_write(s, s->bits_left, 0x00); +} + +/* H.264 headers generation functions */ + +static int tw5864_h264_gen_sps_rbsp(u8 *buf, size_t size, int width, int height) +{ + struct bs bs, *s; + + s = &bs; + bs_init(s, buf, size); + bs_write(s, 8, 0x42); /* profile_idc, baseline */ + bs_write(s, 1, 1); /* constraint_set0_flag */ + bs_write(s, 1, 1); /* constraint_set1_flag */ + bs_write(s, 1, 0); /* constraint_set2_flag */ + bs_write(s, 5, 0); /* reserved_zero_5bits */ + bs_write(s, 8, 0x1e); /* level_idc */ + bs_write_ue(s, 0); /* seq_parameter_set_id */ + bs_write_ue(s, ilog2(MAX_GOP_SIZE) - 4); /* log2_max_frame_num_minus4 */ + bs_write_ue(s, 0); /* pic_order_cnt_type */ + /* log2_max_pic_order_cnt_lsb_minus4 */ + bs_write_ue(s, ilog2(MAX_GOP_SIZE) - 4); + bs_write_ue(s, 1); /* num_ref_frames */ + bs_write(s, 1, 0); /* gaps_in_frame_num_value_allowed_flag */ + bs_write_ue(s, width / 16 - 1); /* pic_width_in_mbs_minus1 */ + bs_write_ue(s, height / 16 - 1); /* pic_height_in_map_units_minus1 */ + bs_write(s, 1, 1); /* frame_mbs_only_flag */ + bs_write(s, 1, 0); /* direct_8x8_inference_flag */ + bs_write(s, 1, 0); /* frame_cropping_flag */ + bs_write(s, 1, 0); /* vui_parameters_present_flag */ + bs_rbsp_trailing(s); + return bs_len(s); +} + +static int tw5864_h264_gen_pps_rbsp(u8 *buf, size_t size, int qp) +{ + struct bs bs, *s; + + s = &bs; + bs_init(s, buf, size); + bs_write_ue(s, 0); /* pic_parameter_set_id */ + bs_write_ue(s, 0); /* seq_parameter_set_id */ + bs_write(s, 1, 0); /* entropy_coding_mode_flag */ + bs_write(s, 1, 0); /* pic_order_present_flag */ + bs_write_ue(s, 0); /* num_slice_groups_minus1 */ + bs_write_ue(s, 0); /* i_num_ref_idx_l0_active_minus1 */ + bs_write_ue(s, 0); /* i_num_ref_idx_l1_active_minus1 */ + bs_write(s, 1, 0); /* weighted_pred_flag */ + bs_write(s, 2, 0); /* weighted_bipred_idc */ + bs_write_se(s, qp - 26); /* pic_init_qp_minus26 */ + bs_write_se(s, qp - 26); /* pic_init_qs_minus26 */ + bs_write_se(s, 0); /* chroma_qp_index_offset */ + bs_write(s, 1, 0); /* deblocking_filter_control_present_flag */ + bs_write(s, 1, 0); /* constrained_intra_pred_flag */ + bs_write(s, 1, 0); /* redundant_pic_cnt_present_flag */ + bs_rbsp_trailing(s); + return bs_len(s); +} + +static int tw5864_h264_gen_slice_head(u8 *buf, size_t size, + unsigned int idr_pic_id, + unsigned int frame_gop_seqno, + int *tail_nb_bits, u8 *tail) +{ + struct bs bs, *s; + int is_i_frame = frame_gop_seqno == 0; + + s = &bs; + bs_init(s, buf, size); + bs_write_ue(s, 0); /* first_mb_in_slice */ + bs_write_ue(s, is_i_frame ? 2 : 5); /* slice_type - I or P */ + bs_write_ue(s, 0); /* pic_parameter_set_id */ + bs_write(s, ilog2(MAX_GOP_SIZE), frame_gop_seqno); /* frame_num */ + if (is_i_frame) + bs_write_ue(s, idr_pic_id); + + /* pic_order_cnt_lsb */ + bs_write(s, ilog2(MAX_GOP_SIZE), frame_gop_seqno); + + if (is_i_frame) { + bs_write1(s, 0); /* no_output_of_prior_pics_flag */ + bs_write1(s, 0); /* long_term_reference_flag */ + } else { + bs_write1(s, 0); /* num_ref_idx_active_override_flag */ + bs_write1(s, 0); /* ref_pic_list_reordering_flag_l0 */ + bs_write1(s, 0); /* adaptive_ref_pic_marking_mode_flag */ + } + + bs_write_se(s, 0); /* slice_qp_delta */ + + if (s->bits_left != 8) { + *tail = ((s->ptr[0]) << s->bits_left); + *tail_nb_bits = 8 - s->bits_left; + } else { + *tail = 0; + *tail_nb_bits = 0; + } + + return bs_len(s); +} + +void tw5864_h264_put_stream_header(u8 **buf, size_t *space_left, int qp, + int width, int height) +{ + int nal_len; + + /* SPS */ + memcpy(*buf, marker, sizeof(marker)); + *buf += 4; + *space_left -= 4; + + **buf = 0x67; /* SPS NAL header */ + *buf += 1; + *space_left -= 1; + + nal_len = tw5864_h264_gen_sps_rbsp(*buf, *space_left, width, height); + *buf += nal_len; + *space_left -= nal_len; + + /* PPS */ + memcpy(*buf, marker, sizeof(marker)); + *buf += 4; + *space_left -= 4; + + **buf = 0x68; /* PPS NAL header */ + *buf += 1; + *space_left -= 1; + + nal_len = tw5864_h264_gen_pps_rbsp(*buf, *space_left, qp); + *buf += nal_len; + *space_left -= nal_len; +} + +void tw5864_h264_put_slice_header(u8 **buf, size_t *space_left, + unsigned int idr_pic_id, + unsigned int frame_gop_seqno, + int *tail_nb_bits, u8 *tail) +{ + int nal_len; + + memcpy(*buf, marker, sizeof(marker)); + *buf += 4; + *space_left -= 4; + + /* Frame NAL header */ + **buf = (frame_gop_seqno == 0) ? 0x25 : 0x21; + *buf += 1; + *space_left -= 1; + + nal_len = tw5864_h264_gen_slice_head(*buf, *space_left, idr_pic_id, + frame_gop_seqno, tail_nb_bits, + tail); + *buf += nal_len; + *space_left -= nal_len; +} diff --git a/drivers/media/pci/tw5864/tw5864-reg.h b/drivers/media/pci/tw5864/tw5864-reg.h new file mode 100644 index 000000000..25d4a9a6c --- /dev/null +++ b/drivers/media/pci/tw5864/tw5864-reg.h @@ -0,0 +1,2132 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * TW5864 driver - registers description + * + * Copyright (C) 2016 Bluecherry, LLC + */ + +/* According to TW5864_datasheet_0.6d.pdf, tw5864b1-ds.pdf */ + +/* Register Description - Direct Map Space */ +/* 0x0000 ~ 0x1ffc - H264 Register Map */ +/* [15:0] The Version register for H264 core (Read Only) */ +#define TW5864_H264REV 0x0000 + +#define TW5864_EMU 0x0004 +/* Define controls in register TW5864_EMU */ +/* DDR controller enabled */ +#define TW5864_EMU_EN_DDR BIT(0) +/* Enable bit for Inter module */ +#define TW5864_EMU_EN_ME BIT(1) +/* Enable bit for Sensor Interface module */ +#define TW5864_EMU_EN_SEN BIT(2) +/* Enable bit for Host Burst Access */ +#define TW5864_EMU_EN_BHOST BIT(3) +/* Enable bit for Loop Filter module */ +#define TW5864_EMU_EN_LPF BIT(4) +/* Enable bit for PLBK module */ +#define TW5864_EMU_EN_PLBK BIT(5) +/* + * Video Frame mapping in DDR + * 00 CIF + * 01 D1 + * 10 Reserved + * 11 Reserved + * + */ +#define TW5864_DSP_FRAME_TYPE (3 << 6) +#define TW5864_DSP_FRAME_TYPE_D1 BIT(6) + +#define TW5864_UNDECLARED_H264REV_PART2 0x0008 + +#define TW5864_SLICE 0x000c +/* Define controls in register TW5864_SLICE */ +/* VLC Slice end flag */ +#define TW5864_VLC_SLICE_END BIT(0) +/* Master Slice End Flag */ +#define TW5864_MAS_SLICE_END BIT(4) +/* Host to start a new slice Address */ +#define TW5864_START_NSLICE BIT(15) + +/* + * [15:0] Two bit for each channel (channel 0 ~ 7). Each two bits are the buffer + * pointer for the last encoded frame of the corresponding channel. + */ +#define TW5864_ENC_BUF_PTR_REC1 0x0010 + +/* [5:0] DSP_MB_QP and [15:10] DSP_LPF_OFFSET */ +#define TW5864_DSP_QP 0x0018 +/* Define controls in register TW5864_DSP_QP */ +/* [5:0] H264 QP Value for codec */ +#define TW5864_DSP_MB_QP 0x003f +/* + * [15:10] H264 LPF_OFFSET Address + * (Default 0) + */ +#define TW5864_DSP_LPF_OFFSET 0xfc00 + +#define TW5864_DSP_CODEC 0x001c +/* Define controls in register TW5864_DSP_CODEC */ +/* + * 0: Encode (TW5864 Default) + * 1: Decode + */ +#define TW5864_DSP_CODEC_MODE BIT(0) +/* + * 0->3 4 VLC data buffer in DDR (1M each) + * 0->7 8 VLC data buffer in DDR (512k each) + */ +#define TW5864_VLC_BUF_ID (7 << 2) +/* + * 0 4CIF in 1 MB + * 1 1CIF in 1 MB + */ +#define TW5864_CIF_MAP_MD BIT(6) +/* + * 0 2 falf D1 in 1 MB + * 1 1 half D1 in 1 MB + */ +#define TW5864_HD1_MAP_MD BIT(7) +/* VLC Stream valid */ +#define TW5864_VLC_VLD BIT(8) +/* MV Vector Valid */ +#define TW5864_MV_VECT_VLD BIT(9) +/* MV Flag Valid */ +#define TW5864_MV_FLAG_VLD BIT(10) + +#define TW5864_DSP_SEN 0x0020 +/* Define controls in register TW5864_DSP_SEN */ +/* Org Buffer Base for Luma (default 0) */ +#define TW5864_DSP_SEN_PIC_LU 0x000f +/* Org Buffer Base for Chroma (default 4) */ +#define TW5864_DSP_SEN_PIC_CHM 0x00f0 +/* Maximum Number of Buffers (default 4) */ +#define TW5864_DSP_SEN_PIC_MAX 0x0700 +/* + * Original Frame D1 or HD1 switch + * (Default 0) + */ +#define TW5864_DSP_SEN_HFULL 0x1000 + +#define TW5864_DSP_REF_PIC 0x0024 +/* Define controls in register TW5864_DSP_REF_PIC */ +/* Ref Buffer Base for Luma (default 0) */ +#define TW5864_DSP_REF_PIC_LU 0x000f +/* Ref Buffer Base for Chroma (default 4) */ +#define TW5864_DSP_REF_PIC_CHM 0x00f0 +/* Maximum Number of Buffers (default 4) */ +#define TW5864_DSP_REF_PIC_MAX 0x0700 + +/* [15:0] SEN_EN_CH[n] SENIF original frame capture enable for each channel */ +#define TW5864_SEN_EN_CH 0x0028 + +#define TW5864_DSP 0x002c +/* Define controls in register TW5864_DSP */ +/* The ID for channel selected for encoding operation */ +#define TW5864_DSP_ENC_CHN 0x000f +/* See DSP_MB_DELAY below */ +#define TW5864_DSP_MB_WAIT 0x0010 +/* + * DSP Chroma Switch + * 0 DDRB + * 1 DDRA + */ +#define TW5864_DSP_CHROM_SW 0x0020 +/* VLC Flow Control: 1 for enable */ +#define TW5864_DSP_FLW_CNTL 0x0040 +/* + * If DSP_MB_WAIT == 0, MB delay is DSP_MB_DELAY * 16 + * If DSP_MB_DELAY == 1, MB delay is DSP_MB_DELAY * 128 + */ +#define TW5864_DSP_MB_DELAY 0x0f00 + +#define TW5864_DDR 0x0030 +/* Define controls in register TW5864_DDR */ +/* DDR Single Access Page Number */ +#define TW5864_DDR_PAGE_CNTL 0x00ff +/* DDR-DPR Burst Read Enable */ +#define TW5864_DDR_BRST_EN BIT(13) +/* + * DDR A/B Select as HOST access + * 0 Select DDRA + * 1 Select DDRB + */ +#define TW5864_DDR_AB_SEL BIT(14) +/* + * DDR Access Mode Select + * 0 Single R/W Access (Host <-> DDR) + * 1 Burst R/W Access (Host <-> DPR) + */ +#define TW5864_DDR_MODE BIT(15) + +/* The original frame capture pointer. Two bits for each channel */ +/* SENIF_ORG_FRM_PTR [15:0] */ +#define TW5864_SENIF_ORG_FRM_PTR1 0x0038 +/* SENIF_ORG_FRM_PTR [31:16] */ +#define TW5864_SENIF_ORG_FRM_PTR2 0x003c + +#define TW5864_DSP_SEN_MODE 0x0040 +/* Define controls in register TW5864_DSP_SEN_MODE */ +#define TW5864_DSP_SEN_MODE_CH0 0x000f +#define TW5864_DSP_SEN_MODE_CH1 0x00f0 + +/* + * [15:0]: ENC_BUF_PTR_REC[31:16] Two bit for each channel (channel 8 ~ 15). + * Each two bits are the buffer pointer for the last encoded frame of a channel + */ +#define TW5864_ENC_BUF_PTR_REC2 0x004c + +/* Current MV Flag Status Pointer for Channel n. (Read only) */ +/* + * [1:0] CH0_MV_PTR, ..., [15:14] CH7_MV_PTR + */ +#define TW5864_CH_MV_PTR1 0x0060 +/* + * [1:0] CH8_MV_PTR, ..., [15:14] CH15_MV_PTR + */ +#define TW5864_CH_MV_PTR2 0x0064 + +/* + * [15:0] Reset Current MV Flag Status Pointer for Channel n (one bit each) + */ +#define TW5864_RST_MV_PTR 0x0068 +#define TW5864_INTERLACING 0x0200 +/* Define controls in register TW5864_INTERLACING */ +/* + * Inter_Mode Start. 2-nd bit? A guess. Missing in datasheet. Without this bit + * set, the output video is interlaced (stripy). + */ +#define TW5864_DSP_INTER_ST BIT(1) +/* Deinterlacer Enable */ +#define TW5864_DI_EN BIT(2) +/* + * De-interlacer Mode + * 1 Shuffled frame + * 0 Normal Un-Shuffled Frame + */ +#define TW5864_DI_MD BIT(3) +/* + * Down scale original frame in X direction + * 11: Un-used + * 10: down-sample to 1/4 + * 01: down-sample to 1/2 + * 00: down-sample disabled + */ +#define TW5864_DSP_DWN_X (3 << 4) +/* + * Down scale original frame in Y direction + * 11: Un-used + * 10: down-sample to 1/4 + * 01: down-sample to 1/2 + * 00: down-sample disabled + */ +#define TW5864_DSP_DWN_Y (3 << 6) +/* + * 1 Dual Stream + * 0 Single Stream + */ +#define TW5864_DUAL_STR BIT(8) + +#define TW5864_DSP_REF 0x0204 +/* Define controls in register TW5864_DSP_REF */ +/* Number of reference frame (Default 1 for TW5864B) */ +#define TW5864_DSP_REF_FRM 0x000f +/* Window size */ +#define TW5864_DSP_WIN_SIZE 0x02f0 + +#define TW5864_DSP_SKIP 0x0208 +/* Define controls in register TW5864_DSP_SKIP */ +/* + * Skip Offset Enable bit + * 0 DSP_SKIP_OFFSET value is not used (default 8) + * 1 DSP_SKIP_OFFSET value is used in HW + */ +#define TW5864_DSP_SKIP_OFEN 0x0080 +/* Skip mode cost offset (default 8) */ +#define TW5864_DSP_SKIP_OFFSET 0x007f + +#define TW5864_MOTION_SEARCH_ETC 0x020c +/* Define controls in register TW5864_MOTION_SEARCH_ETC */ +/* Enable quarter pel search mode */ +#define TW5864_QPEL_EN BIT(0) +/* Enable half pel search mode */ +#define TW5864_HPEL_EN BIT(1) +/* Enable motion search mode */ +#define TW5864_ME_EN BIT(2) +/* Enable Intra mode */ +#define TW5864_INTRA_EN BIT(3) +/* Enable Skip Mode */ +#define TW5864_SKIP_EN BIT(4) +/* Search Option (Default 2"b01) */ +#define TW5864_SRCH_OPT (3 << 5) + +#define TW5864_DSP_ENC_REC 0x0210 +/* Define controls in register TW5864_DSP_ENC_REC */ +/* Reference Buffer Pointer for encoding */ +#define TW5864_DSP_ENC_REF_PTR 0x0007 +/* Reconstruct Buffer pointer */ +#define TW5864_DSP_REC_BUF_PTR 0x7000 + +/* [15:0] Lambda Value for H264 */ +#define TW5864_DSP_REF_MVP_LAMBDA 0x0214 + +#define TW5864_DSP_PIC_MAX_MB 0x0218 +/* Define controls in register TW5864_DSP_PIC_MAX_MB */ +/* The MB number in Y direction for a frame */ +#define TW5864_DSP_PIC_MAX_MB_Y 0x007f +/* The MB number in X direction for a frame */ +#define TW5864_DSP_PIC_MAX_MB_X 0x7f00 + +/* The original frame pointer for encoding */ +#define TW5864_DSP_ENC_ORG_PTR_REG 0x021c +/* Mask to use with TW5864_DSP_ENC_ORG_PTR */ +#define TW5864_DSP_ENC_ORG_PTR_MASK 0x7000 +/* Number of bits to shift with TW5864_DSP_ENC_ORG_PTR */ +#define TW5864_DSP_ENC_ORG_PTR_SHIFT 12 + +/* DDR base address of OSD rectangle attribute data */ +#define TW5864_DSP_OSD_ATTRI_BASE 0x0220 +/* OSD enable bit for each channel */ +#define TW5864_DSP_OSD_ENABLE 0x0228 + +/* 0x0280 ~ 0x029c - Motion Vector for 1st 4x4 Block, e.g., 80 (X), 84 (Y) */ +#define TW5864_ME_MV_VEC1 0x0280 +/* 0x02a0 ~ 0x02bc - Motion Vector for 2nd 4x4 Block, e.g., A0 (X), A4 (Y) */ +#define TW5864_ME_MV_VEC2 0x02a0 +/* 0x02c0 ~ 0x02dc - Motion Vector for 3rd 4x4 Block, e.g., C0 (X), C4 (Y) */ +#define TW5864_ME_MV_VEC3 0x02c0 +/* 0x02e0 ~ 0x02fc - Motion Vector for 4th 4x4 Block, e.g., E0 (X), E4 (Y) */ +#define TW5864_ME_MV_VEC4 0x02e0 + +/* + * [5:0] + * if (intra16x16_cost < (intra4x4_cost+dsp_i4x4_offset)) + * Intra_mode = intra16x16_mode + * Else + * Intra_mode = intra4x4_mode + */ +#define TW5864_DSP_I4x4_OFFSET 0x040c + +/* + * [6:4] + * 0x5 Only 4x4 + * 0x6 Only 16x16 + * 0x7 16x16 & 4x4 + */ +#define TW5864_DSP_INTRA_MODE 0x0410 +#define TW5864_DSP_INTRA_MODE_SHIFT 4 +#define TW5864_DSP_INTRA_MODE_MASK (7 << 4) +#define TW5864_DSP_INTRA_MODE_4x4 0x5 +#define TW5864_DSP_INTRA_MODE_16x16 0x6 +#define TW5864_DSP_INTRA_MODE_4x4_AND_16x16 0x7 +/* + * [5:0] WEIGHT Factor for I4x4 cost calculation (QP dependent) + */ +#define TW5864_DSP_I4x4_WEIGHT 0x0414 + +/* + * [7:0] Offset used to affect Intra/ME model decision + * If (me_cost < intra_cost + dsp_resid_mode_offset) + * Pred_Mode = me_mode + * Else + * Pred_mode = intra_mode + */ +#define TW5864_DSP_RESID_MODE_OFFSET 0x0604 + +/* 0x0800 ~ 0x09ff - Quantization TABLE Values */ +#define TW5864_QUAN_TAB 0x0800 + +/* Valid channel value [0; f], frame value [0; 3] */ +#define TW5864_RT_CNTR_CH_FRM(channel, frame) \ + (0x0c00 | (channel << 4) | (frame << 2)) + +#define TW5864_FRAME_BUS1 0x0d00 +/* + * 1 Progressive in part A in bus n + * 0 Interlaced in part A in bus n + */ +#define TW5864_PROG_A BIT(0) +/* + * 1 Progressive in part B in bus n + * 0 Interlaced in part B in bus n + */ +#define TW5864_PROG_B BIT(1) +/* + * 1 Frame Mode in bus n + * 0 Field Mode in bus n + */ +#define TW5864_FRAME BIT(2) +/* + * 0 4CIF in bus n + * 1 1D1 + 4 CIF in bus n + * 2 2D1 in bus n + */ +#define TW5864_BUS_D1 (3 << 3) +/* Bus 1 goes in TW5864_FRAME_BUS1 in [4:0] */ +/* Bus 2 goes in TW5864_FRAME_BUS1 in [12:8] */ +#define TW5864_FRAME_BUS2 0x0d04 +/* Bus 3 goes in TW5864_FRAME_BUS2 in [4:0] */ +/* Bus 4 goes in TW5864_FRAME_BUS2 in [12:8] */ + +/* [15:0] Horizontal Mirror for channel n */ +#define TW5864_SENIF_HOR_MIR 0x0d08 +/* [15:0] Vertical Mirror for channel n */ +#define TW5864_SENIF_VER_MIR 0x0d0c + +/* + * FRAME_WIDTH_BUSn_A + * 0x15f: 4 CIF + * 0x2cf: 1 D1 + 3 CIF + * 0x2cf: 2 D1 + * FRAME_WIDTH_BUSn_B + * 0x15f: 4 CIF + * 0x2cf: 1 D1 + 3 CIF + * 0x2cf: 2 D1 + * FRAME_HEIGHT_BUSn_A + * 0x11f: 4CIF (PAL) + * 0x23f: 1D1 + 3CIF (PAL) + * 0x23f: 2 D1 (PAL) + * 0x0ef: 4CIF (NTSC) + * 0x1df: 1D1 + 3CIF (NTSC) + * 0x1df: 2 D1 (NTSC) + * FRAME_HEIGHT_BUSn_B + * 0x11f: 4CIF (PAL) + * 0x23f: 1D1 + 3CIF (PAL) + * 0x23f: 2 D1 (PAL) + * 0x0ef: 4CIF (NTSC) + * 0x1df: 1D1 + 3CIF (NTSC) + * 0x1df: 2 D1 (NTSC) + */ +#define TW5864_FRAME_WIDTH_BUS_A(bus) (0x0d10 + 0x0010 * bus) +#define TW5864_FRAME_WIDTH_BUS_B(bus) (0x0d14 + 0x0010 * bus) +#define TW5864_FRAME_HEIGHT_BUS_A(bus) (0x0d18 + 0x0010 * bus) +#define TW5864_FRAME_HEIGHT_BUS_B(bus) (0x0d1c + 0x0010 * bus) + +/* + * 1: the bus mapped Channel n Full D1 + * 0: the bus mapped Channel n Half D1 + */ +#define TW5864_FULL_HALF_FLAG 0x0d50 + +/* + * 0 The bus mapped Channel select partA Mode + * 1 The bus mapped Channel select partB Mode + */ +#define TW5864_FULL_HALF_MODE_SEL 0x0d54 + +#define TW5864_VLC 0x1000 +/* Define controls in register TW5864_VLC */ +/* QP Value used by H264 CAVLC */ +#define TW5864_VLC_SLICE_QP 0x003f +/* + * Swap byte order of VLC stream in d-word. + * 1 Normal (VLC output= [31:0]) + * 0 Swap (VLC output={[23:16],[31:24],[7:0], [15:8]}) + */ +#define TW5864_VLC_BYTE_SWP BIT(6) +/* Enable Adding 03 circuit for VLC stream */ +#define TW5864_VLC_ADD03_EN BIT(7) +/* Number of bit for VLC bit Align */ +#define TW5864_VLC_BIT_ALIGN_SHIFT 8 +#define TW5864_VLC_BIT_ALIGN_MASK (0x1f << 8) +/* + * Synchronous Interface select for VLC Stream + * 1 CDC_VLCS_MAS read VLC stream + * 0 CPU read VLC stream + */ +#define TW5864_VLC_INF_SEL BIT(13) +/* Enable VLC overflow control */ +#define TW5864_VLC_OVFL_CNTL BIT(14) +/* + * 1 PCI Master Mode + * 0 Non PCI Master Mode + */ +#define TW5864_VLC_PCI_SEL BIT(15) +/* + * 0 Enable Adding 03 to VLC header and stream + * 1 Disable Adding 03 to VLC header of "00000001" + */ +#define TW5864_VLC_A03_DISAB BIT(16) +/* + * Status of VLC stream in DDR (one bit for each buffer) + * 1 VLC is ready in buffer n (HW set) + * 0 VLC is not ready in buffer n (SW clear) + */ +#define TW5864_VLC_BUF_RDY_SHIFT 24 +#define TW5864_VLC_BUF_RDY_MASK (0xff << 24) + +/* Total number of bit in the slice */ +#define TW5864_SLICE_TOTAL_BIT 0x1004 +/* Total number of bit in the residue */ +#define TW5864_RES_TOTAL_BIT 0x1008 + +#define TW5864_VLC_BUF 0x100c +/* Define controls in register TW5864_VLC_BUF */ +/* VLC BK0 full status, write '1' to clear */ +#define TW5864_VLC_BK0_FULL BIT(0) +/* VLC BK1 full status, write '1' to clear */ +#define TW5864_VLC_BK1_FULL BIT(1) +/* VLC end slice status, write '1' to clear */ +#define TW5864_VLC_END_SLICE BIT(2) +/* VLC Buffer overflow status, write '1' to clear */ +#define TW5864_DSP_RD_OF BIT(3) +/* VLC string length in either buffer 0 or 1 at end of frame */ +#define TW5864_VLC_STREAM_LEN_SHIFT 4 +#define TW5864_VLC_STREAM_LEN_MASK (0x1ff << 4) + +/* [15:0] Total coefficient number in a frame */ +#define TW5864_TOTAL_COEF_NO 0x1010 +/* [0] VLC Encoder Interrupt. Write '1' to clear */ +#define TW5864_VLC_DSP_INTR 0x1014 +/* [31:0] VLC stream CRC checksum */ +#define TW5864_VLC_STREAM_CRC 0x1018 + +#define TW5864_VLC_RD 0x101c +/* Define controls in register TW5864_VLC_RD */ +/* + * 1 Read VLC lookup Memory + * 0 Read VLC Stream Memory + */ +#define TW5864_VLC_RD_MEM BIT(0) +/* + * 1 Read VLC Stream Memory in burst mode + * 0 Read VLC Stream Memory in single mode + */ +#define TW5864_VLC_RD_BRST BIT(1) + +/* 0x2000 ~ 0x2ffc - H264 Stream Memory Map */ +/* + * A word is 4 bytes. I.e., + * VLC_STREAM_MEM[0] address: 0x2000 + * VLC_STREAM_MEM[1] address: 0x2004 + * ... + * VLC_STREAM_MEM[3FF] address: 0x2ffc + */ +#define TW5864_VLC_STREAM_MEM_START 0x2000 +#define TW5864_VLC_STREAM_MEM_MAX_OFFSET 0x3ff +#define TW5864_VLC_STREAM_MEM(offset) (TW5864_VLC_STREAM_MEM_START + 4 * offset) + +/* 0x4000 ~ 0x4ffc - Audio Register Map */ +/* [31:0] config 1ms cnt = Realtime clk/1000 */ +#define TW5864_CFG_1MS_CNT 0x4000 + +#define TW5864_ADPCM 0x4004 +/* Define controls in register TW5864_ADPCM */ +/* ADPCM decoder enable */ +#define TW5864_ADPCM_DEC BIT(0) +/* ADPCM input data enable */ +#define TW5864_ADPCM_IN_DATA BIT(1) +/* ADPCM encoder enable */ +#define TW5864_ADPCM_ENC BIT(2) + +#define TW5864_AUD 0x4008 +/* Define controls in register TW5864_AUD */ +/* Record path PCM Audio enable bit for each channel */ +#define TW5864_AUD_ORG_CH_EN 0x00ff +/* Speaker path PCM Audio Enable */ +#define TW5864_SPK_ORG_EN BIT(16) +/* + * 0 16bit + * 1 8bit + */ +#define TW5864_AD_BIT_MODE BIT(17) +#define TW5864_AUD_TYPE_SHIFT 18 +/* + * 0 PCM + * 3 ADPCM + */ +#define TW5864_AUD_TYPE (0xf << 18) +#define TW5864_AUD_SAMPLE_RATE_SHIFT 22 +/* + * 0 8K + * 1 16K + */ +#define TW5864_AUD_SAMPLE_RATE (3 << 22) +/* Channel ID used to select audio channel (0 to 16) for loopback */ +#define TW5864_TESTLOOP_CHID_SHIFT 24 +#define TW5864_TESTLOOP_CHID (0x1f << 24) +/* Enable AD Loopback Test */ +#define TW5864_TEST_ADLOOP_EN BIT(30) +/* + * 0 Asynchronous Mode or PCI target mode + * 1 PCI Initiator Mode + */ +#define TW5864_AUD_MODE BIT(31) + +#define TW5864_AUD_ADPCM 0x400c +/* Define controls in register TW5864_AUD_ADPCM */ +/* Record path ADPCM audio channel enable, one bit for each */ +#define TW5864_AUD_ADPCM_CH_EN 0x00ff +/* Speaker path ADPCM audio channel enable */ +#define TW5864_SPK_ADPCM_EN BIT(16) + +#define TW5864_PC_BLOCK_ADPCM_RD_NO 0x4018 +#define TW5864_PC_BLOCK_ADPCM_RD_NO_MASK 0x1f + +/* + * For ADPCM_ENC_WR_PTR, ADPCM_ENC_RD_PTR (see below): + * Bit[2:0] ch0 + * Bit[5:3] ch1 + * Bit[8:6] ch2 + * Bit[11:9] ch3 + * Bit[14:12] ch4 + * Bit[17:15] ch5 + * Bit[20:18] ch6 + * Bit[23:21] ch7 + * Bit[26:24] ch8 + * Bit[29:27] ch9 + * Bit[32:30] ch10 + * Bit[35:33] ch11 + * Bit[38:36] ch12 + * Bit[41:39] ch13 + * Bit[44:42] ch14 + * Bit[47:45] ch15 + * Bit[50:48] ch16 + */ +#define TW5864_ADPCM_ENC_XX_MASK 0x3fff +#define TW5864_ADPCM_ENC_XX_PTR2_SHIFT 30 +/* ADPCM_ENC_WR_PTR[29:0] */ +#define TW5864_ADPCM_ENC_WR_PTR1 0x401c +/* ADPCM_ENC_WR_PTR[50:30] */ +#define TW5864_ADPCM_ENC_WR_PTR2 0x4020 + +/* ADPCM_ENC_RD_PTR[29:0] */ +#define TW5864_ADPCM_ENC_RD_PTR1 0x4024 +/* ADPCM_ENC_RD_PTR[50:30] */ +#define TW5864_ADPCM_ENC_RD_PTR2 0x4028 + +/* [3:0] rd ch0, [7:4] rd ch1, [11:8] wr ch0, [15:12] wr ch1 */ +#define TW5864_ADPCM_DEC_RD_WR_PTR 0x402c + +/* + * For TW5864_AD_ORIG_WR_PTR, TW5864_AD_ORIG_RD_PTR: + * Bit[3:0] ch0 + * Bit[7:4] ch1 + * Bit[11:8] ch2 + * Bit[15:12] ch3 + * Bit[19:16] ch4 + * Bit[23:20] ch5 + * Bit[27:24] ch6 + * Bit[31:28] ch7 + * Bit[35:32] ch8 + * Bit[39:36] ch9 + * Bit[43:40] ch10 + * Bit[47:44] ch11 + * Bit[51:48] ch12 + * Bit[55:52] ch13 + * Bit[59:56] ch14 + * Bit[63:60] ch15 + * Bit[67:64] ch16 + */ +/* AD_ORIG_WR_PTR[31:0] */ +#define TW5864_AD_ORIG_WR_PTR1 0x4030 +/* AD_ORIG_WR_PTR[63:32] */ +#define TW5864_AD_ORIG_WR_PTR2 0x4034 +/* AD_ORIG_WR_PTR[67:64] */ +#define TW5864_AD_ORIG_WR_PTR3 0x4038 + +/* AD_ORIG_RD_PTR[31:0] */ +#define TW5864_AD_ORIG_RD_PTR1 0x403c +/* AD_ORIG_RD_PTR[63:32] */ +#define TW5864_AD_ORIG_RD_PTR2 0x4040 +/* AD_ORIG_RD_PTR[67:64] */ +#define TW5864_AD_ORIG_RD_PTR3 0x4044 + +#define TW5864_PC_BLOCK_ORIG_RD_NO 0x4048 +#define TW5864_PC_BLOCK_ORIG_RD_NO_MASK 0x1f + +#define TW5864_PCI_AUD 0x404c +/* Define controls in register TW5864_PCI_AUD */ +/* + * The register is applicable to PCI initiator mode only. Used to select PCM(0) + * or ADPCM(1) audio data sent to PC. One bit for each channel + */ +#define TW5864_PCI_DATA_SEL 0xffff +/* + * Audio flow control mode selection bit. + * 0 Flow control disabled. TW5864 continuously sends audio frame to PC + * (initiator mode) + * 1 Flow control enabled + */ +#define TW5864_PCI_FLOW_EN BIT(16) +/* + * When PCI_FLOW_EN is set, PCI need to toggle this bit to send an audio frame + * to PC. One toggle to send one frame. + */ +#define TW5864_PCI_AUD_FRM_EN BIT(17) + +/* [1:0] CS valid to data valid CLK cycles when writing operation */ +#define TW5864_CS2DAT_CNT 0x8000 +/* [2:0] Data valid signal width by system clock cycles */ +#define TW5864_DATA_VLD_WIDTH 0x8004 + +#define TW5864_SYNC 0x8008 +/* Define controls in register TW5864_SYNC */ +/* + * 0 vlc stream to synchronous port + * 1 vlc stream to ddr buffers + */ +#define TW5864_SYNC_CFG BIT(7) +/* + * 0 SYNC Address sampled on Rising edge + * 1 SYNC Address sampled on Falling edge + */ +#define TW5864_SYNC_ADR_EDGE BIT(0) +#define TW5864_VLC_STR_DELAY_SHIFT 1 +/* + * 0 No system delay + * 1 One system clock delay + * 2 Two system clock delay + * 3 Three system clock delay + */ +#define TW5864_VLC_STR_DELAY (3 << 1) +/* + * 0 Rising edge output + * 1 Falling edge output + */ +#define TW5864_VLC_OUT_EDGE BIT(3) + +/* + * [1:0] + * 2'b00 phase set to 180 degree + * 2'b01 phase set to 270 degree + * 2'b10 phase set to 0 degree + * 2'b11 phase set to 90 degree + */ +#define TW5864_I2C_PHASE_CFG 0x800c + +/* + * The system / DDR clock (166 MHz) is generated with an on-chip system clock + * PLL (SYSPLL) using input crystal clock of 27 MHz. The system clock PLL + * frequency is controlled with the following equation. + * CLK_OUT = CLK_IN * (M+1) / ((N+1) * P) + * SYSPLL_M M parameter + * SYSPLL_N N parameter + * SYSPLL_P P parameter + */ +/* SYSPLL_M[7:0] */ +#define TW5864_SYSPLL1 0x8018 +/* Define controls in register TW5864_SYSPLL1 */ +#define TW5864_SYSPLL_M_LOW 0x00ff + +/* [2:0]: SYSPLL_M[10:8], [7:3]: SYSPLL_N[4:0] */ +#define TW5864_SYSPLL2 0x8019 +/* Define controls in register TW5864_SYSPLL2 */ +#define TW5864_SYSPLL_M_HI 0x07 +#define TW5864_SYSPLL_N_LOW_SHIFT 3 +#define TW5864_SYSPLL_N_LOW (0x1f << 3) + +/* + * [1:0]: SYSPLL_N[6:5], [3:2]: SYSPLL_P, [4]: SYSPLL_IREF, [7:5]: SYSPLL_CP_SEL + */ +#define TW5864_SYSPLL3 0x8020 +/* Define controls in register TW5864_SYSPLL3 */ +#define TW5864_SYSPLL_N_HI 0x03 +#define TW5864_SYSPLL_P_SHIFT 2 +#define TW5864_SYSPLL_P (0x03 << 2) +/* + * SYSPLL bias current control + * 0 Lower current (default) + * 1 30% higher current + */ +#define TW5864_SYSPLL_IREF BIT(4) +/* + * SYSPLL charge pump current selection + * 0 1,5 uA + * 1 4 uA + * 2 9 uA + * 3 19 uA + * 4 39 uA + * 5 79 uA + * 6 159 uA + * 7 319 uA + */ +#define TW5864_SYSPLL_CP_SEL_SHIFT 5 +#define TW5864_SYSPLL_CP_SEL (0x07 << 5) + +/* + * [1:0]: SYSPLL_VCO, [3:2]: SYSPLL_LP_X8, [5:4]: SYSPLL_ICP_SEL, + * [6]: SYSPLL_LPF_5PF, [7]: SYSPLL_ED_SEL + */ +#define TW5864_SYSPLL4 0x8021 +/* Define controls in register TW5864_SYSPLL4 */ +/* + * SYSPLL_VCO VCO Range selection + * 00 5 ~ 75 MHz + * 01 50 ~ 140 MHz + * 10 110 ~ 320 MHz + * 11 270 ~ 700 MHz + */ +#define TW5864_SYSPLL_VCO 0x03 +#define TW5864_SYSPLL_LP_X8_SHIFT 2 +/* + * Loop resister + * 0 38.5K ohms + * 1 6.6K ohms (default) + * 2 2.2K ohms + * 3 1.1K ohms + */ +#define TW5864_SYSPLL_LP_X8 (0x03 << 2) +#define TW5864_SYSPLL_ICP_SEL_SHIFT 4 +/* + * PLL charge pump fine tune + * 00 x1 (default) + * 01 x1/2 + * 10 x1/7 + * 11 x1/8 + */ +#define TW5864_SYSPLL_ICP_SEL (0x03 << 4) +/* + * PLL low pass filter phase margin adjustment + * 0 no 5pF (default) + * 1 5pF added + */ +#define TW5864_SYSPLL_LPF_5PF BIT(6) +/* + * PFD select edge for detection + * 0 Falling edge (default) + * 1 Rising edge + */ +#define TW5864_SYSPLL_ED_SEL BIT(7) + +/* [0]: SYSPLL_RST, [4]: SYSPLL_PD */ +#define TW5864_SYSPLL5 0x8024 +/* Define controls in register TW5864_SYSPLL5 */ +/* Reset SYSPLL */ +#define TW5864_SYSPLL_RST BIT(0) +/* Power down SYSPLL */ +#define TW5864_SYSPLL_PD BIT(4) + +#define TW5864_PLL_CFG 0x801c +/* Define controls in register TW5864_PLL_CFG */ +/* + * Issue Soft Reset from Async Host Interface / PCI Interface clock domain. + * Become valid after sync to the xtal clock domain. This bit is set only if + * LOAD register bit is also set to 1. + */ +#define TW5864_SRST BIT(0) +/* + * Issue SYSPLL (166 MHz) configuration latch from Async host interface / PCI + * Interface clock domain. The configuration setting becomes effective only if + * LOAD register bit is also set to 1. + */ +#define TW5864_SYSPLL_CFG BIT(2) +/* + * Issue SPLL (108 MHz) configuration load from Async host interface / PCI + * Interface clock domain. The configuration setting becomes effective only if + * the LOAD register bit is also set to 1. + */ +#define TW5864_SPLL_CFG BIT(4) +/* + * Set this bit to latch the SRST, SYSPLL_CFG, SPLL_CFG setting into the xtal + * clock domain to restart the PLL. This bit is self cleared. + */ +#define TW5864_LOAD BIT(3) + +/* SPLL_IREF, SPLL_LPX4, SPLL_CPX4, SPLL_PD, SPLL_DBG */ +#define TW5864_SPLL 0x8028 + +/* 0x8800 ~ 0x88fc - Interrupt Register Map */ +/* + * Trigger mode of interrupt source 0 ~ 15 + * 1 Edge trigger mode + * 0 Level trigger mode + */ +#define TW5864_TRIGGER_MODE_L 0x8800 +/* Trigger mode of interrupt source 16 ~ 31 */ +#define TW5864_TRIGGER_MODE_H 0x8804 +/* Enable of interrupt source 0 ~ 15 */ +#define TW5864_INTR_ENABLE_L 0x8808 +/* Enable of interrupt source 16 ~ 31 */ +#define TW5864_INTR_ENABLE_H 0x880c +/* Clear interrupt command of interrupt source 0 ~ 15 */ +#define TW5864_INTR_CLR_L 0x8810 +/* Clear interrupt command of interrupt source 16 ~ 31 */ +#define TW5864_INTR_CLR_H 0x8814 +/* + * Assertion of interrupt source 0 ~ 15 + * 1 High level or pos-edge is assertion + * 0 Low level or neg-edge is assertion + */ +#define TW5864_INTR_ASSERT_L 0x8818 +/* Assertion of interrupt source 16 ~ 31 */ +#define TW5864_INTR_ASSERT_H 0x881c +/* + * Output level of interrupt + * 1 Interrupt output is high assertion + * 0 Interrupt output is low assertion + */ +#define TW5864_INTR_OUT_LEVEL 0x8820 +/* + * Status of interrupt source 0 ~ 15 + * Bit[0]: VLC 4k RAM interrupt + * Bit[1]: BURST DDR RAM interrupt + * Bit[2]: MV DSP interrupt + * Bit[3]: video lost interrupt + * Bit[4]: gpio 0 interrupt + * Bit[5]: gpio 1 interrupt + * Bit[6]: gpio 2 interrupt + * Bit[7]: gpio 3 interrupt + * Bit[8]: gpio 4 interrupt + * Bit[9]: gpio 5 interrupt + * Bit[10]: gpio 6 interrupt + * Bit[11]: gpio 7 interrupt + * Bit[12]: JPEG interrupt + * Bit[13:15]: Reserved + */ +#define TW5864_INTR_STATUS_L 0x8838 +/* + * Status of interrupt source 16 ~ 31 + * Bit[0]: Reserved + * Bit[1]: VLC done interrupt + * Bit[2]: Reserved + * Bit[3]: AD Vsync interrupt + * Bit[4]: Preview eof interrupt + * Bit[5]: Preview overflow interrupt + * Bit[6]: Timer interrupt + * Bit[7]: Reserved + * Bit[8]: Audio eof interrupt + * Bit[9]: I2C done interrupt + * Bit[10]: AD interrupt + * Bit[11:15]: Reserved + */ +#define TW5864_INTR_STATUS_H 0x883c + +/* Defines of interrupt bits, united for both low and high word registers */ +#define TW5864_INTR_VLC_RAM BIT(0) +#define TW5864_INTR_BURST BIT(1) +#define TW5864_INTR_MV_DSP BIT(2) +#define TW5864_INTR_VIN_LOST BIT(3) +/* n belongs to [0; 7] */ +#define TW5864_INTR_GPIO(n) (1 << (4 + n)) +#define TW5864_INTR_JPEG BIT(12) +#define TW5864_INTR_VLC_DONE BIT(17) +#define TW5864_INTR_AD_VSYNC BIT(19) +#define TW5864_INTR_PV_EOF BIT(20) +#define TW5864_INTR_PV_OVERFLOW BIT(21) +#define TW5864_INTR_TIMER BIT(22) +#define TW5864_INTR_AUD_EOF BIT(24) +#define TW5864_INTR_I2C_DONE BIT(25) +#define TW5864_INTR_AD BIT(26) + +/* 0x9000 ~ 0x920c - Video Capture (VIF) Register Map */ +/* + * H264EN_CH_STATUS[n] Status of Vsync synchronized H264EN_CH_EN (Read Only) + * 1 Channel Enabled + * 0 Channel Disabled + */ +#define TW5864_H264EN_CH_STATUS 0x9000 +/* + * [15:0] H264EN_CH_EN[n] H264 Encoding Path Enable for channel + * 1 Channel Enabled + * 0 Channel Disabled + */ +#define TW5864_H264EN_CH_EN 0x9004 +/* + * H264EN_CH_DNS[n] H264 Encoding Path Downscale Video Decoder Input for + * channel n + * 1 Downscale Y to 1/2 + * 0 Does not downscale + */ +#define TW5864_H264EN_CH_DNS 0x9008 +/* + * H264EN_CH_PROG[n] H264 Encoding Path channel n is progressive + * 1 Progressive (Not valid for TW5864) + * 0 Interlaced (TW5864 default) + */ +#define TW5864_H264EN_CH_PROG 0x900c +/* + * [3:0] H264EN_BUS_MAX_CH[n] + * H264 Encoding Path maximum number of channel on BUS n + * 0 Max 4 channels + * 1 Max 2 channels + */ +#define TW5864_H264EN_BUS_MAX_CH 0x9010 + +/* + * H264EN_RATE_MAX_LINE_n H264 Encoding path Rate Mapping Maximum Line Number + * on Bus n + */ +#define TW5864_H264EN_RATE_MAX_LINE_EVEN 0x1f +#define TW5864_H264EN_RATE_MAX_LINE_ODD_SHIFT 5 +#define TW5864_H264EN_RATE_MAX_LINE_ODD (0x1f << 5) +/* + * [4:0] H264EN_RATE_MAX_LINE_0 + * [9:5] H264EN_RATE_MAX_LINE_1 + */ +#define TW5864_H264EN_RATE_MAX_LINE_REG1 0x9014 +/* + * [4:0] H264EN_RATE_MAX_LINE_2 + * [9:5] H264EN_RATE_MAX_LINE_3 + */ +#define TW5864_H264EN_RATE_MAX_LINE_REG2 0x9018 + +/* + * H264EN_CHn_FMT H264 Encoding Path Format configuration of Channel n + * 00 D1 (For D1 and hD1 frame) + * 01 (Reserved) + * 10 (Reserved) + * 11 D1 with 1/2 size in X (for CIF frame) + * Note: To be used with 0x9008 register to configure the frame size + */ +/* + * [1:0]: H264EN_CH0_FMT, + * ..., [15:14]: H264EN_CH7_FMT + */ +#define TW5864_H264EN_CH_FMT_REG1 0x9020 +/* + * [1:0]: H264EN_CH8_FMT (?), + * ..., [15:14]: H264EN_CH15_FMT (?) + */ +#define TW5864_H264EN_CH_FMT_REG2 0x9024 + +/* + * H264EN_RATE_CNTL_BUSm_CHn H264 Encoding Path BUS m Rate Control for Channel n + */ +#define TW5864_H264EN_RATE_CNTL_LO_WORD(bus, channel) \ + (0x9100 + bus * 0x20 + channel * 0x08) +#define TW5864_H264EN_RATE_CNTL_HI_WORD(bus, channel) \ + (0x9104 + bus * 0x20 + channel * 0x08) + +/* + * H264EN_BUSm_MAP_CHn The 16-to-1 MUX configuration register for each encoding + * channel (total of 16 channels). Four bits for each channel. + */ +#define TW5864_H264EN_BUS0_MAP 0x9200 +#define TW5864_H264EN_BUS1_MAP 0x9204 +#define TW5864_H264EN_BUS2_MAP 0x9208 +#define TW5864_H264EN_BUS3_MAP 0x920c + +/* This register is not defined in datasheet, but used in reference driver */ +#define TW5864_UNDECLARED_ERROR_FLAGS_0x9218 0x9218 + +#define TW5864_GPIO1 0x9800 +#define TW5864_GPIO2 0x9804 +/* Define controls in registers TW5864_GPIO1, TW5864_GPIO2 */ +/* GPIO DATA of Group n */ +#define TW5864_GPIO_DATA 0x00ff +#define TW5864_GPIO_OEN_SHIFT 8 +/* GPIO Output Enable of Group n */ +#define TW5864_GPIO_OEN (0xff << 8) + +/* 0xa000 ~ 0xa8ff - DDR Controller Register Map */ +/* DDR Controller A */ +/* + * [2:0] Data valid counter after read command to DDR. This is the delay value + * to show how many cycles the data will be back from DDR after we issue a read + * command. + */ +#define TW5864_RD_ACK_VLD_MUX 0xa000 + +#define TW5864_DDR_PERIODS 0xa004 +/* Define controls in register TW5864_DDR_PERIODS */ +/* + * Tras value, the minimum cycle of active to precharge command period, + * default is 7 + */ +#define TW5864_TRAS_CNT_MAX 0x000f +/* + * Trfc value, the minimum cycle of refresh to active or refresh command period, + * default is 4"hf + */ +#define TW5864_RFC_CNT_MAX_SHIFT 8 +#define TW5864_RFC_CNT_MAX (0x0f << 8) +/* + * Trcd value, the minimum cycle of active to internal read/write command + * period, default is 4"h2 + */ +#define TW5864_TCD_CNT_MAX_SHIFT 4 +#define TW5864_TCD_CNT_MAX (0x0f << 4) +/* Twr value, write recovery time, default is 4"h3 */ +#define TW5864_TWR_CNT_MAX_SHIFT 12 +#define TW5864_TWR_CNT_MAX (0x0f << 12) + +/* + * [2:0] CAS latency, the delay cycle between internal read command and the + * availability of the first bit of output data, default is 3 + */ +#define TW5864_CAS_LATENCY 0xa008 +/* + * [15:0] Maximum average periodic refresh, the value is based on the current + * frequency to match 7.8mcs + */ +#define TW5864_DDR_REF_CNTR_MAX 0xa00c +/* + * DDR_ON_CHIP_MAP [1:0] + * 0 256M DDR on board + * 1 512M DDR on board + * 2 1G DDR on board + * DDR_ON_CHIP_MAP [2] + * 0 Only one DDR chip + * 1 Two DDR chips + */ +#define TW5864_DDR_ON_CHIP_MAP 0xa01c +#define TW5864_DDR_SELFTEST_MODE 0xa020 +/* Define controls in register TW5864_DDR_SELFTEST_MODE */ +/* + * 0 Common read/write mode + * 1 DDR self-test mode + */ +#define TW5864_MASTER_MODE BIT(0) +/* + * 0 DDR self-test single read/write + * 1 DDR self-test burst read/write + */ +#define TW5864_SINGLE_PROC BIT(1) +/* + * 0 DDR self-test write command + * 1 DDR self-test read command + */ +#define TW5864_WRITE_FLAG BIT(2) +#define TW5864_DATA_MODE_SHIFT 4 +/* + * 0 write 32'haaaa5555 to DDR + * 1 write 32'hffffffff to DDR + * 2 write 32'hha5a55a5a to DDR + * 3 write increasing data to DDR + */ +#define TW5864_DATA_MODE (0x3 << 4) + +/* [7:0] The maximum data of one burst in DDR self-test mode */ +#define TW5864_BURST_CNTR_MAX 0xa024 +/* [15:0] The maximum burst counter (bit 15~0) in DDR self-test mode */ +#define TW5864_DDR_PROC_CNTR_MAX_L 0xa028 +/* The maximum burst counter (bit 31~16) in DDR self-test mode */ +#define TW5864_DDR_PROC_CNTR_MAX_H 0xa02c +/* [0]: Start one DDR self-test */ +#define TW5864_DDR_SELF_TEST_CMD 0xa030 +/* The maximum error counter (bit 15 ~ 0) in DDR self-test */ +#define TW5864_ERR_CNTR_L 0xa034 + +#define TW5864_ERR_CNTR_H_AND_FLAG 0xa038 +/* Define controls in register TW5864_ERR_CNTR_H_AND_FLAG */ +/* The maximum error counter (bit 30 ~ 16) in DDR self-test */ +#define TW5864_ERR_CNTR_H_MASK 0x3fff +/* DDR self-test end flag */ +#define TW5864_END_FLAG 0x8000 + +/* + * DDR Controller B: same as 0xa000 ~ 0xa038, but add TW5864_DDR_B_OFFSET to all + * addresses + */ +#define TW5864_DDR_B_OFFSET 0x0800 + +/* 0xb004 ~ 0xb018 - HW version/ARB12 Register Map */ +/* [15:0] Default is C013 */ +#define TW5864_HW_VERSION 0xb004 + +#define TW5864_REQS_ENABLE 0xb010 +/* Define controls in register TW5864_REQS_ENABLE */ +/* Audio data in to DDR enable (default 1) */ +#define TW5864_AUD_DATA_IN_ENB BIT(0) +/* Audio encode request to DDR enable (default 1) */ +#define TW5864_AUD_ENC_REQ_ENB BIT(1) +/* Audio decode request0 to DDR enable (default 1) */ +#define TW5864_AUD_DEC_REQ0_ENB BIT(2) +/* Audio decode request1 to DDR enable (default 1) */ +#define TW5864_AUD_DEC_REQ1_ENB BIT(3) +/* VLC stream request to DDR enable (default 1) */ +#define TW5864_VLC_STRM_REQ_ENB BIT(4) +/* H264 MV request to DDR enable (default 1) */ +#define TW5864_DVM_MV_REQ_ENB BIT(5) +/* mux_core MVD request to DDR enable (default 1) */ +#define TW5864_MVD_REQ_ENB BIT(6) +/* mux_core MVD temp data request to DDR enable (default 1) */ +#define TW5864_MVD_TMP_REQ_ENB BIT(7) +/* JPEG request to DDR enable (default 1) */ +#define TW5864_JPEG_REQ_ENB BIT(8) +/* mv_flag request to DDR enable (default 1) */ +#define TW5864_MV_FLAG_REQ_ENB BIT(9) + +#define TW5864_ARB12 0xb018 +/* Define controls in register TW5864_ARB12 */ +/* ARB12 Enable (default 1) */ +#define TW5864_ARB12_ENB BIT(15) +/* ARB12 maximum value of time out counter (default 15"h1FF) */ +#define TW5864_ARB12_TIME_OUT_CNT 0x7fff + +/* 0xb800 ~ 0xb80c - Indirect Access Register Map */ +/* + * Spec says: + * In order to access the indirect register space, the following procedure is + * followed. + * But reference driver implementation, and current driver, too, does it + * differently. + * + * Write Registers: + * (1) Write IND_DATA at 0xb804 ~ 0xb807 + * (2) Read BUSY flag from 0xb803. Wait until BUSY signal is 0. + * (3) Write IND_ADDR at 0xb800 ~ 0xb801. Set R/W to "1", ENABLE to "1" + * Read Registers: + * (1) Read BUSY flag from 0xb803. Wait until BUSY signal is 0. + * (2) Write IND_ADDR at 0xb800 ~ 0xb801. Set R/W to "0", ENABLE to "1" + * (3) Read BUSY flag from 0xb803. Wait until BUSY signal is 0. + * (4) Read IND_DATA from 0xb804 ~ 0xb807 + */ +#define TW5864_IND_CTL 0xb800 +/* Define controls in register TW5864_IND_CTL */ +/* Address used to access indirect register space */ +#define TW5864_IND_ADDR 0x0000ffff +/* Wait until this bit is "0" before using indirect access */ +#define TW5864_BUSY BIT(31) +/* Activate the indirect access. This bit is self cleared */ +#define TW5864_ENABLE BIT(25) +/* Read/Write command */ +#define TW5864_RW BIT(24) + +/* [31:0] Data used to read/write indirect register space */ +#define TW5864_IND_DATA 0xb804 + +/* 0xc000 ~ 0xc7fc - Preview Register Map */ +/* Mostly skipped this section. */ +/* + * [15:0] Status of Vsync Synchronized PCI_PV_CH_EN (Read Only) + * 1 Channel Enabled + * 0 Channel Disabled + */ +#define TW5864_PCI_PV_CH_STATUS 0xc000 +/* + * [15:0] PCI Preview Path Enable for channel n + * 1 Channel Enable + * 0 Channel Disable + */ +#define TW5864_PCI_PV_CH_EN 0xc004 + +/* 0xc800 ~ 0xc804 - JPEG Capture Register Map */ +/* Skipped. */ +/* 0xd000 ~ 0xd0fc - JPEG Control Register Map */ +/* Skipped. */ + +/* 0xe000 ~ 0xfc04 - Motion Vector Register Map */ + +/* ME Motion Vector data (Four Byte Each) 0xe000 ~ 0xe7fc */ +#define TW5864_ME_MV_VEC_START 0xe000 +#define TW5864_ME_MV_VEC_MAX_OFFSET 0x1ff +#define TW5864_ME_MV_VEC(offset) (TW5864_ME_MV_VEC_START + 4 * offset) + +#define TW5864_MV 0xfc00 +/* Define controls in register TW5864_MV */ +/* mv bank0 full status , write "1" to clear */ +#define TW5864_MV_BK0_FULL BIT(0) +/* mv bank1 full status , write "1" to clear */ +#define TW5864_MV_BK1_FULL BIT(1) +/* slice end status; write "1" to clear */ +#define TW5864_MV_EOF BIT(2) +/* mv encode interrupt status; write "1" to clear */ +#define TW5864_MV_DSP_INTR BIT(3) +/* mv write memory overflow, write "1" to clear */ +#define TW5864_DSP_WR_OF BIT(4) +#define TW5864_MV_LEN_SHIFT 5 +/* mv stream length */ +#define TW5864_MV_LEN (0xff << 5) +/* The configured status bit written into bit 15 of 0xfc04 */ +#define TW5864_MPI_DDR_SEL BIT(13) + +#define TW5864_MPI_DDR_SEL_REG 0xfc04 +/* Define controls in register TW5864_MPI_DDR_SEL_REG */ +/* + * SW configure register + * 0 MV is saved in internal DPR + * 1 MV is saved in DDR + */ +#define TW5864_MPI_DDR_SEL2 BIT(15) + +/* 0x18000 ~ 0x181fc - PCI Master/Slave Control Map */ +#define TW5864_PCI_INTR_STATUS 0x18000 +/* Define controls in register TW5864_PCI_INTR_STATUS */ +/* vlc done */ +#define TW5864_VLC_DONE_INTR BIT(1) +/* ad vsync */ +#define TW5864_AD_VSYNC_INTR BIT(3) +/* preview eof */ +#define TW5864_PREV_EOF_INTR BIT(4) +/* preview overflow interrupt */ +#define TW5864_PREV_OVERFLOW_INTR BIT(5) +/* timer interrupt */ +#define TW5864_TIMER_INTR BIT(6) +/* audio eof */ +#define TW5864_AUDIO_EOF_INTR BIT(8) +/* IIC done */ +#define TW5864_IIC_DONE_INTR BIT(24) +/* ad interrupt (e.g.: video lost, video format changed) */ +#define TW5864_AD_INTR_REG BIT(25) + +#define TW5864_PCI_INTR_CTL 0x18004 +/* Define controls in register TW5864_PCI_INTR_CTL */ +/* master enable */ +#define TW5864_PCI_MAST_ENB BIT(0) +/* mvd&vlc master enable */ +#define TW5864_MVD_VLC_MAST_ENB 0x06 +/* (Need to set 0 in TW5864A) */ +#define TW5864_AD_MAST_ENB BIT(3) +/* preview master enable */ +#define TW5864_PREV_MAST_ENB BIT(4) +/* preview overflow enable */ +#define TW5864_PREV_OVERFLOW_ENB BIT(5) +/* timer interrupt enable */ +#define TW5864_TIMER_INTR_ENB BIT(6) +/* JPEG master (push mode) enable */ +#define TW5864_JPEG_MAST_ENB BIT(7) +#define TW5864_AU_MAST_ENB_CHN_SHIFT 8 +/* audio master channel enable */ +#define TW5864_AU_MAST_ENB_CHN (0xffff << 8) +/* IIC interrupt enable */ +#define TW5864_IIC_INTR_ENB BIT(24) +/* ad interrupt enable */ +#define TW5864_AD_INTR_ENB BIT(25) +/* target burst enable */ +#define TW5864_PCI_TAR_BURST_ENB BIT(26) +/* vlc stream burst enable */ +#define TW5864_PCI_VLC_BURST_ENB BIT(27) +/* ddr burst enable (1 enable, and must set DDR_BRST_EN) */ +#define TW5864_PCI_DDR_BURST_ENB BIT(28) + +/* + * Because preview and audio have 16 channels separately, so using this + * registers to indicate interrupt status for every channels. This is secondary + * interrupt status register. OR operating of the PREV_INTR_REG is + * PREV_EOF_INTR, OR operating of the AU_INTR_REG bits is AUDIO_EOF_INTR + */ +#define TW5864_PREV_AND_AU_INTR 0x18008 +/* Define controls in register TW5864_PREV_AND_AU_INTR */ +/* preview eof interrupt flag */ +#define TW5864_PREV_INTR_REG 0x0000ffff +#define TW5864_AU_INTR_REG_SHIFT 16 +/* audio eof interrupt flag */ +#define TW5864_AU_INTR_REG (0xffff << 16) + +#define TW5864_MASTER_ENB_REG 0x1800c +/* Define controls in register TW5864_MASTER_ENB_REG */ +/* master enable */ +#define TW5864_PCI_VLC_INTR_ENB BIT(1) +/* mvd and vlc master enable */ +#define TW5864_PCI_PREV_INTR_ENB BIT(4) +/* ad vsync master enable */ +#define TW5864_PCI_PREV_OF_INTR_ENB BIT(5) +/* jpeg master enable */ +#define TW5864_PCI_JPEG_INTR_ENB BIT(7) +/* preview master enable */ +#define TW5864_PCI_AUD_INTR_ENB BIT(8) + +/* + * Every channel of preview and audio have ping-pong buffers in system memory, + * this register is the buffer flag to notify software which buffer is been + * operated. + */ +#define TW5864_PREV_AND_AU_BUF_FLAG 0x18010 +/* Define controls in register TW5864_PREV_AND_AU_BUF_FLAG */ +/* preview buffer A/B flag */ +#define TW5864_PREV_BUF_FLAG 0xffff +#define TW5864_AUDIO_BUF_FLAG_SHIFT 16 +/* audio buffer A/B flag */ +#define TW5864_AUDIO_BUF_FLAG (0xffff << 16) + +#define TW5864_IIC 0x18014 +/* Define controls in register TW5864_IIC */ +/* register data */ +#define TW5864_IIC_DATA 0x00ff +#define TW5864_IIC_REG_ADDR_SHIFT 8 +/* register addr */ +#define TW5864_IIC_REG_ADDR (0xff << 8) +/* rd/wr flag rd=1,wr=0 */ +#define TW5864_IIC_RW BIT(16) +#define TW5864_IIC_DEV_ADDR_SHIFT 17 +/* device addr */ +#define TW5864_IIC_DEV_ADDR (0x7f << 17) +/* + * iic done, software kick off one time iic transaction through setting this + * bit to 1. Then poll this bit, value 1 indicate iic transaction have + * completed, if read, valid data have been stored in iic_data + */ +#define TW5864_IIC_DONE BIT(24) + +#define TW5864_RST_AND_IF_INFO 0x18018 +/* Define controls in register TW5864_RST_AND_IF_INFO */ +/* application software soft reset */ +#define TW5864_APP_SOFT_RST BIT(0) +#define TW5864_PCI_INF_VERSION_SHIFT 16 +/* PCI interface version, read only */ +#define TW5864_PCI_INF_VERSION (0xffff << 16) + +/* vlc stream crc value, it is calculated in pci module */ +#define TW5864_VLC_CRC_REG 0x1801c +/* + * vlc max length, it is defined by software based on software assign memory + * space for vlc + */ +#define TW5864_VLC_MAX_LENGTH 0x18020 +/* vlc length of one frame */ +#define TW5864_VLC_LENGTH 0x18024 +/* vlc original crc value */ +#define TW5864_VLC_INTRA_CRC_I_REG 0x18028 +/* vlc original crc value */ +#define TW5864_VLC_INTRA_CRC_O_REG 0x1802c +/* mv stream crc value, it is calculated in pci module */ +#define TW5864_VLC_PAR_CRC_REG 0x18030 +/* mv length */ +#define TW5864_VLC_PAR_LENGTH_REG 0x18034 +/* mv original crc value */ +#define TW5864_VLC_PAR_I_REG 0x18038 +/* mv original crc value */ +#define TW5864_VLC_PAR_O_REG 0x1803c + +/* + * Configuration register for 9[or 10] CIFs or 1D1+15QCIF Preview mode. + * PREV_PCI_ENB_CHN[0] Enable 9th preview channel (9CIF prev) or 1D1 channel in + * (1D1+15QCIF prev) + * PREV_PCI_ENB_CHN[1] Enable 10th preview channel + */ +#define TW5864_PREV_PCI_ENB_CHN 0x18040 +/* Description skipped. */ +#define TW5864_PREV_FRAME_FORMAT_IN 0x18044 +/* IIC enable */ +#define TW5864_IIC_ENB 0x18048 +/* + * Timer interrupt interval + * 0 1ms + * 1 2ms + * 2 4ms + * 3 8ms + */ +#define TW5864_PCI_INTTM_SCALE 0x1804c + +/* + * The above register is pci base address registers. Application software will + * initialize them to tell chip where the corresponding stream will be dumped + * to. Application software will select appropriate base address interval based + * on the stream length. + */ +/* VLC stream base address */ +#define TW5864_VLC_STREAM_BASE_ADDR 0x18080 +/* MV stream base address */ +#define TW5864_MV_STREAM_BASE_ADDR 0x18084 +/* 0x180a0 ~ 0x180bc: audio burst base address. Skipped. */ +/* 0x180c0 ~ 0x180dc: JPEG Push Mode Buffer Base Address. Skipped. */ +/* 0x18100 ~ 0x1817c: preview burst base address. Skipped. */ + +/* 0x80000 ~ 0x87fff - DDR Burst RW Register Map */ +#define TW5864_DDR_CTL 0x80000 +/* Define controls in register TW5864_DDR_CTL */ +#define TW5864_BRST_LENGTH_SHIFT 2 +/* Length of 32-bit data burst */ +#define TW5864_BRST_LENGTH (0x3fff << 2) +/* + * Burst Read/Write + * 0 Read Burst from DDR + * 1 Write Burst to DDR + */ +#define TW5864_BRST_RW BIT(16) +/* Begin a new DDR Burst. This bit is self cleared */ +#define TW5864_NEW_BRST_CMD BIT(17) +/* DDR Burst End Flag */ +#define TW5864_BRST_END BIT(24) +/* Enable Error Interrupt for Single DDR Access */ +#define TW5864_SING_ERR_INTR BIT(25) +/* Enable Error Interrupt for Burst DDR Access */ +#define TW5864_BRST_ERR_INTR BIT(26) +/* Enable Interrupt for End of DDR Burst Access */ +#define TW5864_BRST_END_INTR BIT(27) +/* DDR Single Access Error Flag */ +#define TW5864_SINGLE_ERR BIT(28) +/* DDR Single Access Busy Flag */ +#define TW5864_SINGLE_BUSY BIT(29) +/* DDR Burst Access Error Flag */ +#define TW5864_BRST_ERR BIT(30) +/* DDR Burst Access Busy Flag */ +#define TW5864_BRST_BUSY BIT(31) + +/* [27:0] DDR Access Address. Bit [1:0] has to be 0 */ +#define TW5864_DDR_ADDR 0x80004 +/* DDR Access Internal Buffer Address. Bit [1:0] has to be 0 */ +#define TW5864_DPR_BUF_ADDR 0x80008 +/* SRAM Buffer MPI Access Space. Totally 16 KB */ +#define TW5864_DPR_BUF_START 0x84000 +/* 0x84000 - 0x87ffc */ +#define TW5864_DPR_BUF_SIZE 0x4000 + +/* Indirect Map Space */ +/* + * The indirect space is accessed through 0xb800 ~ 0xb807 registers in direct + * access space + */ +/* Analog Video / Audio Decoder / Encoder */ +/* Allowed channel values: [0; 3] */ +/* Read-only register */ +#define TW5864_INDIR_VIN_0(channel) (0x000 + channel * 0x010) +/* Define controls in register TW5864_INDIR_VIN_0 */ +/* + * 1 Video not present. (sync is not detected in number of consecutive line + * periods specified by MISSCNT register) + * 0 Video detected. + */ +#define TW5864_INDIR_VIN_0_VDLOSS BIT(7) +/* + * 1 Horizontal sync PLL is locked to the incoming video source. + * 0 Horizontal sync PLL is not locked. + */ +#define TW5864_INDIR_VIN_0_HLOCK BIT(6) +/* + * 1 Sub-carrier PLL is locked to the incoming video source. + * 0 Sub-carrier PLL is not locked. + */ +#define TW5864_INDIR_VIN_0_SLOCK BIT(5) +/* + * 1 Even field is being decoded. + * 0 Odd field is being decoded. + */ +#define TW5864_INDIR_VIN_0_FLD BIT(4) +/* + * 1 Vertical logic is locked to the incoming video source. + * 0 Vertical logic is not locked. + */ +#define TW5864_INDIR_VIN_0_VLOCK BIT(3) +/* + * 1 No color burst signal detected. + * 0 Color burst signal detected. + */ +#define TW5864_INDIR_VIN_0_MONO BIT(1) +/* + * 0 60Hz source detected + * 1 50Hz source detected + * The actual vertical scanning frequency depends on the current standard + * invoked. + */ +#define TW5864_INDIR_VIN_0_DET50 BIT(0) + +#define TW5864_INDIR_VIN_1(channel) (0x001 + channel * 0x010) +/* VCR signal indicator. Read-only. */ +#define TW5864_INDIR_VIN_1_VCR BIT(7) +/* Weak signal indicator 2. Read-only. */ +#define TW5864_INDIR_VIN_1_WKAIR BIT(6) +/* Weak signal indicator controlled by WKTH. Read-only. */ +#define TW5864_INDIR_VIN_1_WKAIR1 BIT(5) +/* + * 1 = Standard signal + * 0 = Non-standard signal + * Read-only + */ +#define TW5864_INDIR_VIN_1_VSTD BIT(4) +/* + * 1 = Non-interlaced signal + * 0 = interlaced signal + * Read-only + */ +#define TW5864_INDIR_VIN_1_NINTL BIT(3) +/* + * Vertical Sharpness Control. Writable. + * 0 = None (default) + * 7 = Highest + * **Note: VSHP must be set to '0' if COMB = 0 + */ +#define TW5864_INDIR_VIN_1_VSHP 0x07 + +/* HDELAY_XY[7:0] */ +#define TW5864_INDIR_VIN_2_HDELAY_XY_LO(channel) (0x002 + channel * 0x010) +/* HACTIVE_XY[7:0] */ +#define TW5864_INDIR_VIN_3_HACTIVE_XY_LO(channel) (0x003 + channel * 0x010) +/* VDELAY_XY[7:0] */ +#define TW5864_INDIR_VIN_4_VDELAY_XY_LO(channel) (0x004 + channel * 0x010) +/* VACTIVE_XY[7:0] */ +#define TW5864_INDIR_VIN_5_VACTIVE_XY_LO(channel) (0x005 + channel * 0x010) + +#define TW5864_INDIR_VIN_6(channel) (0x006 + channel * 0x010) +/* Define controls in register TW5864_INDIR_VIN_6 */ +#define TW5864_INDIR_VIN_6_HDELAY_XY_HI 0x03 +#define TW5864_INDIR_VIN_6_HACTIVE_XY_HI_SHIFT 2 +#define TW5864_INDIR_VIN_6_HACTIVE_XY_HI (0x03 << 2) +#define TW5864_INDIR_VIN_6_VDELAY_XY_HI BIT(4) +#define TW5864_INDIR_VIN_6_VACTIVE_XY_HI BIT(5) + +/* + * HDELAY_XY This 10bit register defines the starting location of horizontal + * active pixel for display / record path. A unit is 1 pixel. The default value + * is 0x00f for NTSC and 0x00a for PAL. + * + * HACTIVE_XY This 10bit register defines the number of horizontal active pixel + * for display / record path. A unit is 1 pixel. The default value is decimal + * 720. + * + * VDELAY_XY This 9bit register defines the starting location of vertical + * active for display / record path. A unit is 1 line. The default value is + * decimal 6. + * + * VACTIVE_XY This 9bit register defines the number of vertical active lines + * for display / record path. A unit is 1 line. The default value is decimal + * 240. + */ + +/* HUE These bits control the color hue as 2's complement number. They have + * value from +36o (7Fh) to -36o (80h) with an increment of 2.8o. The 2 LSB has + * no effect. The positive value gives greenish tone and negative value gives + * purplish tone. The default value is 0o (00h). This is effective only on NTSC + * system. The default is 00h. + */ +#define TW5864_INDIR_VIN_7_HUE(channel) (0x007 + channel * 0x010) + +#define TW5864_INDIR_VIN_8(channel) (0x008 + channel * 0x010) +/* Define controls in register TW5864_INDIR_VIN_8 */ +/* + * This bit controls the center frequency of the peaking filter. + * The corresponding gain adjustment is HFLT. + * 0 Low + * 1 center + */ +#define TW5864_INDIR_VIN_8_SCURVE BIT(7) +/* CTI level selection. The default is 1. + * 0 None + * 3 Highest + */ +#define TW5864_INDIR_VIN_8_CTI_SHIFT 4 +#define TW5864_INDIR_VIN_8_CTI (0x03 << 4) + +/* + * These bits control the amount of sharpness enhancement on the luminance + * signals. There are 16 levels of control with "0" having no effect on the + * output image. 1 through 15 provides sharpness enhancement with "F" being the + * strongest. The default is 1. + */ +#define TW5864_INDIR_VIN_8_SHARPNESS 0x0f + +/* + * These bits control the luminance contrast gain. A value of 100 (64h) has a + * gain of 1. The range adjustment is from 0% to 255% at 1% per step. The + * default is 64h. + */ +#define TW5864_INDIR_VIN_9_CNTRST(channel) (0x009 + channel * 0x010) + +/* + * These bits control the brightness. They have value of -128 to 127 in 2's + * complement form. Positive value increases brightness. A value 0 has no + * effect on the data. The default is 00h. + */ +#define TW5864_INDIR_VIN_A_BRIGHT(channel) (0x00a + channel * 0x010) + +/* + * These bits control the digital gain adjustment to the U (or Cb) component of + * the digital video signal. The color saturation can be adjusted by adjusting + * the U and V color gain components by the same amount in the normal + * situation. The U and V can also be adjusted independently to provide greater + * flexibility. The range of adjustment is 0 to 200%. A value of 128 (80h) has + * gain of 100%. The default is 80h. + */ +#define TW5864_INDIR_VIN_B_SAT_U(channel) (0x00b + channel * 0x010) + +/* + * These bits control the digital gain adjustment to the V (or Cr) component of + * the digital video signal. The color saturation can be adjusted by adjusting + * the U and V color gain components by the same amount in the normal + * situation. The U and V can also be adjusted independently to provide greater + * flexibility. The range of adjustment is 0 to 200%. A value of 128 (80h) has + * gain of 100%. The default is 80h. + */ +#define TW5864_INDIR_VIN_C_SAT_V(channel) (0x00c + channel * 0x010) + +/* Read-only */ +#define TW5864_INDIR_VIN_D(channel) (0x00d + channel * 0x010) +/* Define controls in register TW5864_INDIR_VIN_D */ +/* Macrovision color stripe detection may be un-reliable */ +#define TW5864_INDIR_VIN_D_CSBAD BIT(3) +/* Macrovision AGC pulse detected */ +#define TW5864_INDIR_VIN_D_MCVSN BIT(2) +/* Macrovision color stripe protection burst detected */ +#define TW5864_INDIR_VIN_D_CSTRIPE BIT(1) +/* + * This bit is valid only when color stripe protection is detected, i.e. if + * CSTRIPE=1, + * 1 Type 2 color stripe protection + * 0 Type 3 color stripe protection + */ +#define TW5864_INDIR_VIN_D_CTYPE2 BIT(0) + +/* Read-only */ +#define TW5864_INDIR_VIN_E(channel) (0x00e + channel * 0x010) +/* Define controls in register TW5864_INDIR_VIN_E */ +/* + * Read-only. + * 0 Idle + * 1 Detection in progress + */ +#define TW5864_INDIR_VIN_E_DETSTUS BIT(7) +/* + * STDNOW Current standard invoked + * 0 NTSC (M) + * 1 PAL (B, D, G, H, I) + * 2 SECAM + * 3 NTSC4.43 + * 4 PAL (M) + * 5 PAL (CN) + * 6 PAL 60 + * 7 Not valid + */ +#define TW5864_INDIR_VIN_E_STDNOW_SHIFT 4 +#define TW5864_INDIR_VIN_E_STDNOW (0x07 << 4) + +/* + * 1 Disable the shadow registers + * 0 Enable VACTIVE and HDELAY shadow registers value depending on STANDARD. + * (Default) + */ +#define TW5864_INDIR_VIN_E_ATREG BIT(3) +/* + * STANDARD Standard selection + * 0 NTSC (M) + * 1 PAL (B, D, G, H, I) + * 2 SECAM + * 3 NTSC4.43 + * 4 PAL (M) + * 5 PAL (CN) + * 6 PAL 60 + * 7 Auto detection (Default) + */ +#define TW5864_INDIR_VIN_E_STANDARD 0x07 + +#define TW5864_INDIR_VIN_F(channel) (0x00f + channel * 0x010) +/* Define controls in register TW5864_INDIR_VIN_F */ +/* + * 1 Writing 1 to this bit will manually initiate the auto format detection + * process. This bit is a self-clearing bit + * 0 Manual initiation of auto format detection is done. (Default) + */ +#define TW5864_INDIR_VIN_F_ATSTART BIT(7) +/* Enable recognition of PAL60 (Default) */ +#define TW5864_INDIR_VIN_F_PAL60EN BIT(6) +/* Enable recognition of PAL (CN). (Default) */ +#define TW5864_INDIR_VIN_F_PALCNEN BIT(5) +/* Enable recognition of PAL (M). (Default) */ +#define TW5864_INDIR_VIN_F_PALMEN BIT(4) +/* Enable recognition of NTSC 4.43. (Default) */ +#define TW5864_INDIR_VIN_F_NTSC44EN BIT(3) +/* Enable recognition of SECAM. (Default) */ +#define TW5864_INDIR_VIN_F_SECAMEN BIT(2) +/* Enable recognition of PAL (B, D, G, H, I). (Default) */ +#define TW5864_INDIR_VIN_F_PALBEN BIT(1) +/* Enable recognition of NTSC (M). (Default) */ +#define TW5864_INDIR_VIN_F_NTSCEN BIT(0) + +/* Some registers skipped. */ + +/* Use falling edge to sample VD1-VD4 from 54 MHz to 108 MHz */ +#define TW5864_INDIR_VD_108_POL 0x041 +#define TW5864_INDIR_VD_108_POL_VD12 BIT(0) +#define TW5864_INDIR_VD_108_POL_VD34 BIT(1) +#define TW5864_INDIR_VD_108_POL_BOTH \ + (TW5864_INDIR_VD_108_POL_VD12 | TW5864_INDIR_VD_108_POL_VD34) + +/* Some registers skipped. */ + +/* + * Audio Input ADC gain control + * 0 0.25 + * 1 0.31 + * 2 0.38 + * 3 0.44 + * 4 0.50 + * 5 0.63 + * 6 0.75 + * 7 0.88 + * 8 1.00 (default) + * 9 1.25 + * 10 1.50 + * 11 1.75 + * 12 2.00 + * 13 2.25 + * 14 2.50 + * 15 2.75 + */ +/* [3:0] channel 0, [7:4] channel 1 */ +#define TW5864_INDIR_AIGAIN1 0x060 +/* [3:0] channel 2, [7:4] channel 3 */ +#define TW5864_INDIR_AIGAIN2 0x061 + +/* Some registers skipped */ + +#define TW5864_INDIR_AIN_0x06D 0x06d +/* Define controls in register TW5864_INDIR_AIN_0x06D */ +/* + * LAWMD Select u-Law/A-Law/PCM/SB data output format on ADATR and ADATM pin. + * 0 PCM output (default) + * 1 SB (Signed MSB bit in PCM data is inverted) output + * 2 u-Law output + * 3 A-Law output + */ +#define TW5864_INDIR_AIN_LAWMD_SHIFT 6 +#define TW5864_INDIR_AIN_LAWMD (0x03 << 6) +/* + * Disable the mixing ratio value for all audio. + * 0 Apply individual mixing ratio value for each audio (default) + * 1 Apply nominal value for all audio commonly + */ +#define TW5864_INDIR_AIN_MIX_DERATIO BIT(5) +/* + * Enable the mute function for audio channel AINn when n is 0 to 3. It effects + * only for mixing. When n = 4, it enable the mute function of the playback + * audio input. It effects only for single chip or the last stage chip + * 0 Normal + * 1 Muted (default) + */ +#define TW5864_INDIR_AIN_MIX_MUTE 0x1f + +/* Some registers skipped */ + +#define TW5864_INDIR_AIN_0x0E3 0x0e3 +/* Define controls in register TW5864_INDIR_AIN_0x0E3 */ +/* + * ADATP signal is coming from external ADPCM decoder, instead of on-chip ADPCM + * decoder + */ +#define TW5864_INDIR_AIN_0x0E3_EXT_ADATP BIT(7) +/* ACLKP output signal polarity inverse */ +#define TW5864_INDIR_AIN_0x0E3_ACLKPPOLO BIT(6) +/* + * ACLKR input signal polarity inverse. + * 0 Not inversed (Default) + * 1 Inversed + */ +#define TW5864_INDIR_AIN_0x0E3_ACLKRPOL BIT(5) +/* + * ACLKP input signal polarity inverse. + * 0 Not inversed (Default) + * 1 Inversed + */ +#define TW5864_INDIR_AIN_0x0E3_ACLKPPOLI BIT(4) +/* + * ACKI [21:0] control automatic set up with AFMD registers + * This mode is only effective when ACLKRMASTER=1 + * 0 ACKI [21:0] registers set up ACKI control + * 1 ACKI control is automatically set up by AFMD register values + */ +#define TW5864_INDIR_AIN_0x0E3_AFAUTO BIT(3) +/* + * AFAUTO control mode + * 0 8kHz setting (Default) + * 1 16kHz setting + * 2 32kHz setting + * 3 44.1kHz setting + * 4 48kHz setting + */ +#define TW5864_INDIR_AIN_0x0E3_AFMD 0x07 + +#define TW5864_INDIR_AIN_0x0E4 0x0e4 +/* Define controls in register TW5864_INDIR_AIN_0x0ED */ +/* + * 8bit I2S Record output mode. + * 0 L/R half length separated output (Default). + * 1 One continuous packed output equal to DSP output format. + */ +#define TW5864_INDIR_AIN_0x0E4_I2S8MODE BIT(7) +/* + * Audio Clock Master ACLKR output wave format. + * 0 High periods is one 27MHz clock period (default). + * 1 Almost duty 50-50% clock output on ACLKR pin. If this mode is selected, two + * times bigger number value need to be set up on the ACKI register. If + * AFAUTO=1, ACKI control is automatically set up even if MASCKMD=1. + */ +#define TW5864_INDIR_AIN_0x0E4_MASCKMD BIT(6) +/* Playback ACLKP/ASYNP/ADATP input data MSB-LSB swapping */ +#define TW5864_INDIR_AIN_0x0E4_PBINSWAP BIT(5) +/* + * ASYNR input signal delay. + * 0 No delay + * 1 Add one 27MHz period delay in ASYNR signal input + */ +#define TW5864_INDIR_AIN_0x0E4_ASYNRDLY BIT(4) +/* + * ASYNP input signal delay. + * 0 no delay + * 1 add one 27MHz period delay in ASYNP signal input + */ +#define TW5864_INDIR_AIN_0x0E4_ASYNPDLY BIT(3) +/* + * ADATP input data delay by one ACLKP clock. + * 0 No delay (Default). This is for I2S type 1T delay input interface. + * 1 Add 1 ACLKP clock delay in ADATP input data. This is for left-justified + * type 0T delay input interface. + */ +#define TW5864_INDIR_AIN_0x0E4_ADATPDLY BIT(2) +/* + * Select u-Law/A-Law/PCM/SB data input format on ADATP pin. + * 0 PCM input (Default) + * 1 SB (Signed MSB bit in PCM data is inverted) input + * 2 u-Law input + * 3 A-Law input + */ +#define TW5864_INDIR_AIN_0x0E4_INLAWMD 0x03 + +/* + * Enable state register updating and interrupt request of audio AIN5 detection + * for each input + */ +#define TW5864_INDIR_AIN_A5DETENA 0x0e5 + +/* Some registers skipped */ + +/* + * [7:3]: DEV_ID The TW5864 product ID code is 01000 + * [2:0]: REV_ID The revision number is 0h + */ +#define TW5864_INDIR_ID 0x0fe + +#define TW5864_INDIR_IN_PIC_WIDTH(channel) (0x200 + 4 * channel) +#define TW5864_INDIR_IN_PIC_HEIGHT(channel) (0x201 + 4 * channel) +#define TW5864_INDIR_OUT_PIC_WIDTH(channel) (0x202 + 4 * channel) +#define TW5864_INDIR_OUT_PIC_HEIGHT(channel) (0x203 + 4 * channel) + +/* Some registers skipped */ + +#define TW5864_INDIR_CROP_ETC 0x260 +/* Define controls in register TW5864_INDIR_CROP_ETC */ +/* Enable cropping from 720 to 704 */ +#define TW5864_INDIR_CROP_ETC_CROP_EN 0x4 + +/* + * Interrupt status register from the front-end. Write "1" to each bit to clear + * the interrupt + * 15:0 Motion detection interrupt for channel 0 ~ 15 + * 31:16 Night detection interrupt for channel 0 ~ 15 + * 47:32 Blind detection interrupt for channel 0 ~ 15 + * 63:48 No video interrupt for channel 0 ~ 15 + * 79:64 Line mode underflow interrupt for channel 0 ~ 15 + * 95:80 Line mode overflow interrupt for channel 0 ~ 15 + */ +/* 0x2d0~0x2d7: [63:0] bits */ +#define TW5864_INDIR_INTERRUPT1 0x2d0 +/* 0x2e0~0x2e3: [95:64] bits */ +#define TW5864_INDIR_INTERRUPT2 0x2e0 + +/* + * Interrupt mask register for interrupts in 0x2d0 ~ 0x2d7 + * 15:0 Motion detection interrupt for channel 0 ~ 15 + * 31:16 Night detection interrupt for channel 0 ~ 15 + * 47:32 Blind detection interrupt for channel 0 ~ 15 + * 63:48 No video interrupt for channel 0 ~ 15 + * 79:64 Line mode underflow interrupt for channel 0 ~ 15 + * 95:80 Line mode overflow interrupt for channel 0 ~ 15 + */ +/* 0x2d8~0x2df: [63:0] bits */ +#define TW5864_INDIR_INTERRUPT_MASK1 0x2d8 +/* 0x2e8~0x2eb: [95:64] bits */ +#define TW5864_INDIR_INTERRUPT_MASK2 0x2e8 + +/* [11:0]: Interrupt summary register for interrupts & interrupt mask from in + * 0x2d0 ~ 0x2d7 and 0x2d8 ~ 0x2df + * bit 0: interrupt occurs in 0x2d0 & 0x2d8 + * bit 1: interrupt occurs in 0x2d1 & 0x2d9 + * bit 2: interrupt occurs in 0x2d2 & 0x2da + * bit 3: interrupt occurs in 0x2d3 & 0x2db + * bit 4: interrupt occurs in 0x2d4 & 0x2dc + * bit 5: interrupt occurs in 0x2d5 & 0x2dd + * bit 6: interrupt occurs in 0x2d6 & 0x2de + * bit 7: interrupt occurs in 0x2d7 & 0x2df + * bit 8: interrupt occurs in 0x2e0 & 0x2e8 + * bit 9: interrupt occurs in 0x2e1 & 0x2e9 + * bit 10: interrupt occurs in 0x2e2 & 0x2ea + * bit 11: interrupt occurs in 0x2e3 & 0x2eb + */ +#define TW5864_INDIR_INTERRUPT_SUMMARY 0x2f0 + +/* Motion / Blind / Night Detection */ +/* valid value for channel is [0:15] */ +#define TW5864_INDIR_DETECTION_CTL0(channel) (0x300 + channel * 0x08) +/* Define controls in register TW5864_INDIR_DETECTION_CTL0 */ +/* + * Disable the motion and blind detection. + * 0 Enable motion and blind detection (default) + * 1 Disable motion and blind detection + */ +#define TW5864_INDIR_DETECTION_CTL0_MD_DIS BIT(5) +/* + * Request to start motion detection on manual trigger mode + * 0 None Operation (default) + * 1 Request to start motion detection + */ +#define TW5864_INDIR_DETECTION_CTL0_MD_STRB BIT(3) +/* + * Select the trigger mode of motion detection + * 0 Automatic trigger mode of motion detection (default) + * 1 Manual trigger mode for motion detection + */ +#define TW5864_INDIR_DETECTION_CTL0_MD_STRB_EN BIT(2) +/* + * Define the threshold of cell for blind detection. + * 0 Low threshold (More sensitive) (default) + * : : + * 3 High threshold (Less sensitive) + */ +#define TW5864_INDIR_DETECTION_CTL0_BD_CELSENS 0x03 + +#define TW5864_INDIR_DETECTION_CTL1(channel) (0x301 + channel * 0x08) +/* Define controls in register TW5864_INDIR_DETECTION_CTL1 */ +/* + * Control the temporal sensitivity of motion detector. + * 0 More Sensitive (default) + * : : + * 15 Less Sensitive + */ +#define TW5864_INDIR_DETECTION_CTL1_MD_TMPSENS_SHIFT 4 +#define TW5864_INDIR_DETECTION_CTL1_MD_TMPSENS (0x0f << 4) +/* + * Adjust the horizontal starting position for motion detection + * 0 0 pixel (default) + * : : + * 15 15 pixels + */ +#define TW5864_INDIR_DETECTION_CTL1_MD_PIXEL_OS 0x0f + +#define TW5864_INDIR_DETECTION_CTL2(channel) (0x302 + channel * 0x08) +/* Define controls in register TW5864_INDIR_DETECTION_CTL2 */ +/* + * Control the updating time of reference field for motion detection. + * 0 Update reference field every field (default) + * 1 Update reference field according to MD_SPEED + */ +#define TW5864_INDIR_DETECTION_CTL2_MD_REFFLD BIT(7) +/* + * Select the field for motion detection. + * 0 Detecting motion for only odd field (default) + * 1 Detecting motion for only even field + * 2 Detecting motion for any field + * 3 Detecting motion for both odd and even field + */ +#define TW5864_INDIR_DETECTION_CTL2_MD_FIELD_SHIFT 5 +#define TW5864_INDIR_DETECTION_CTL2_MD_FIELD (0x03 << 5) +/* + * Control the level sensitivity of motion detector. + * 0 More sensitive (default) + * : : + * 15 Less sensitive + */ +#define TW5864_INDIR_DETECTION_CTL2_MD_LVSENS 0x1f + +#define TW5864_INDIR_DETECTION_CTL3(channel) (0x303 + channel * 0x08) +/* Define controls in register TW5864_INDIR_DETECTION_CTL3 */ +/* + * Define the threshold of sub-cell number for motion detection. + * 0 Motion is detected if 1 sub-cell has motion (More sensitive) (default) + * 1 Motion is detected if 2 sub-cells have motion + * 2 Motion is detected if 3 sub-cells have motion + * 3 Motion is detected if 4 sub-cells have motion (Less sensitive) + */ +#define TW5864_INDIR_DETECTION_CTL3_MD_CELSENS_SHIFT 6 +#define TW5864_INDIR_DETECTION_CTL3_MD_CELSENS (0x03 << 6) +/* + * Control the velocity of motion detector. + * Large value is suitable for slow motion detection. + * In MD_DUAL_EN = 1, MD_SPEED should be limited to 0 ~ 31. + * 0 1 field intervals (default) + * 1 2 field intervals + * : : + * 61 62 field intervals + * 62 63 field intervals + * 63 Not supported + */ +#define TW5864_INDIR_DETECTION_CTL3_MD_SPEED 0x3f + +#define TW5864_INDIR_DETECTION_CTL4(channel) (0x304 + channel * 0x08) +/* Define controls in register TW5864_INDIR_DETECTION_CTL4 */ +/* + * Control the spatial sensitivity of motion detector. + * 0 More Sensitive (default) + * : : + * 15 Less Sensitive + */ +#define TW5864_INDIR_DETECTION_CTL4_MD_SPSENS_SHIFT 4 +#define TW5864_INDIR_DETECTION_CTL4_MD_SPSENS (0x0f << 4) +/* + * Define the threshold of level for blind detection. + * 0 Low threshold (More sensitive) (default) + * : : + * 15 High threshold (Less sensitive) + */ +#define TW5864_INDIR_DETECTION_CTL4_BD_LVSENS 0x0f + +#define TW5864_INDIR_DETECTION_CTL5(channel) (0x305 + channel * 0x08) +/* + * Define the threshold of temporal sensitivity for night detection. + * 0 Low threshold (More sensitive) (default) + * : : + * 15 High threshold (Less sensitive) + */ +#define TW5864_INDIR_DETECTION_CTL5_ND_TMPSENS_SHIFT 4 +#define TW5864_INDIR_DETECTION_CTL5_ND_TMPSENS (0x0f << 4) +/* + * Define the threshold of level for night detection. + * 0 Low threshold (More sensitive) (default) + * : : + * 3 High threshold (Less sensitive) + */ +#define TW5864_INDIR_DETECTION_CTL5_ND_LVSENS 0x0f + +/* + * [11:0] The base address of the motion detection buffer. This address is in + * unit of 64K bytes. The generated DDR address will be {MD_BASE_ADDR, + * 16"h0000}. The default value should be 12"h000 + */ +#define TW5864_INDIR_MD_BASE_ADDR 0x380 + +/* + * This controls the channel of the motion detection result shown in register + * 0x3a0 ~ 0x3b7. Before reading back motion result, always set this first. + */ +#define TW5864_INDIR_RGR_MOTION_SEL 0x382 + +/* [15:0] MD strobe has been performed at channel n (read only) */ +#define TW5864_INDIR_MD_STRB 0x386 +/* NO_VIDEO Detected from channel n (read only) */ +#define TW5864_INDIR_NOVID_DET 0x388 +/* Motion Detected from channel n (read only) */ +#define TW5864_INDIR_MD_DET 0x38a +/* Blind Detected from channel n (read only) */ +#define TW5864_INDIR_BD_DET 0x38c +/* Night Detected from channel n (read only) */ +#define TW5864_INDIR_ND_DET 0x38e + +/* 192 bit motion flag of the channel specified by RGR_MOTION_SEL in 0x382 */ +#define TW5864_INDIR_MOTION_FLAG 0x3a0 +#define TW5864_INDIR_MOTION_FLAG_BYTE_COUNT 24 + +/* + * [9:0] The motion cell count of a specific channel selected by 0x382. This is + * for DI purpose + */ +#define TW5864_INDIR_MD_DI_CNT 0x3b8 +/* The motion detection cell sensitivity for DI purpose */ +#define TW5864_INDIR_MD_DI_CELLSENS 0x3ba +/* The motion detection threshold level for DI purpose */ +#define TW5864_INDIR_MD_DI_LVSENS 0x3bb + +/* 192 bit motion mask of the channel specified by MASK_CH_SEL in 0x3fe */ +#define TW5864_INDIR_MOTION_MASK 0x3e0 +#define TW5864_INDIR_MOTION_MASK_BYTE_COUNT 24 + +/* [4:0] The channel selection to access masks in 0x3e0 ~ 0x3f7 */ +#define TW5864_INDIR_MASK_CH_SEL 0x3fe + +/* Clock PLL / Analog IP Control */ +/* Some registers skipped */ + +#define TW5864_INDIR_DDRA_DLL_DQS_SEL0 0xee6 +#define TW5864_INDIR_DDRA_DLL_DQS_SEL1 0xee7 +#define TW5864_INDIR_DDRA_DLL_CLK90_SEL 0xee8 +#define TW5864_INDIR_DDRA_DLL_TEST_SEL_AND_TAP_S 0xee9 + +#define TW5864_INDIR_DDRB_DLL_DQS_SEL0 0xeeb +#define TW5864_INDIR_DDRB_DLL_DQS_SEL1 0xeec +#define TW5864_INDIR_DDRB_DLL_CLK90_SEL 0xeed +#define TW5864_INDIR_DDRB_DLL_TEST_SEL_AND_TAP_S 0xeee + +#define TW5864_INDIR_RESET 0xef0 +#define TW5864_INDIR_RESET_VD BIT(7) +#define TW5864_INDIR_RESET_DLL BIT(6) +#define TW5864_INDIR_RESET_MUX_CORE BIT(5) + +#define TW5864_INDIR_PV_VD_CK_POL 0xefd +#define TW5864_INDIR_PV_VD_CK_POL_PV(channel) BIT(channel) +#define TW5864_INDIR_PV_VD_CK_POL_VD(channel) BIT(channel + 4) + +#define TW5864_INDIR_CLK0_SEL 0xefe +#define TW5864_INDIR_CLK0_SEL_VD_SHIFT 0 +#define TW5864_INDIR_CLK0_SEL_VD_MASK 0x3 +#define TW5864_INDIR_CLK0_SEL_PV_SHIFT 2 +#define TW5864_INDIR_CLK0_SEL_PV_MASK (0x3 << 2) +#define TW5864_INDIR_CLK0_SEL_PV2_SHIFT 4 +#define TW5864_INDIR_CLK0_SEL_PV2_MASK (0x3 << 4) diff --git a/drivers/media/pci/tw5864/tw5864-util.c b/drivers/media/pci/tw5864/tw5864-util.c new file mode 100644 index 000000000..b9cebe9d1 --- /dev/null +++ b/drivers/media/pci/tw5864/tw5864-util.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "tw5864.h" + +void tw5864_indir_writeb(struct tw5864_dev *dev, u16 addr, u8 data) +{ + int retries = 30000; + + while (tw_readl(TW5864_IND_CTL) & BIT(31) && --retries) + ; + if (!retries) + dev_err(&dev->pci->dev, + "tw_indir_writel() retries exhausted before writing\n"); + + tw_writel(TW5864_IND_DATA, data); + tw_writel(TW5864_IND_CTL, addr << 2 | TW5864_RW | TW5864_ENABLE); +} + +u8 tw5864_indir_readb(struct tw5864_dev *dev, u16 addr) +{ + int retries = 30000; + + while (tw_readl(TW5864_IND_CTL) & BIT(31) && --retries) + ; + if (!retries) + dev_err(&dev->pci->dev, + "tw_indir_readl() retries exhausted before reading\n"); + + tw_writel(TW5864_IND_CTL, addr << 2 | TW5864_ENABLE); + + retries = 30000; + while (tw_readl(TW5864_IND_CTL) & BIT(31) && --retries) + ; + if (!retries) + dev_err(&dev->pci->dev, + "tw_indir_readl() retries exhausted at reading\n"); + + return tw_readl(TW5864_IND_DATA); +} diff --git a/drivers/media/pci/tw5864/tw5864-video.c b/drivers/media/pci/tw5864/tw5864-video.c new file mode 100644 index 000000000..0a08708e5 --- /dev/null +++ b/drivers/media/pci/tw5864/tw5864-video.c @@ -0,0 +1,1513 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * TW5864 driver - video encoding functions + * + * Copyright (C) 2016 Bluecherry, LLC + */ + +#include +#include +#include +#include +#include + +#include "tw5864.h" +#include "tw5864-reg.h" + +#define QUANTIZATION_TABLE_LEN 96 +#define VLC_LOOKUP_TABLE_LEN 1024 + +static const u16 forward_quantization_table[QUANTIZATION_TABLE_LEN] = { + 0x3333, 0x1f82, 0x3333, 0x1f82, 0x1f82, 0x147b, 0x1f82, 0x147b, + 0x3333, 0x1f82, 0x3333, 0x1f82, 0x1f82, 0x147b, 0x1f82, 0x147b, + 0x2e8c, 0x1d42, 0x2e8c, 0x1d42, 0x1d42, 0x1234, 0x1d42, 0x1234, + 0x2e8c, 0x1d42, 0x2e8c, 0x1d42, 0x1d42, 0x1234, 0x1d42, 0x1234, + 0x2762, 0x199a, 0x2762, 0x199a, 0x199a, 0x1062, 0x199a, 0x1062, + 0x2762, 0x199a, 0x2762, 0x199a, 0x199a, 0x1062, 0x199a, 0x1062, + 0x2492, 0x16c1, 0x2492, 0x16c1, 0x16c1, 0x0e3f, 0x16c1, 0x0e3f, + 0x2492, 0x16c1, 0x2492, 0x16c1, 0x16c1, 0x0e3f, 0x16c1, 0x0e3f, + 0x2000, 0x147b, 0x2000, 0x147b, 0x147b, 0x0d1b, 0x147b, 0x0d1b, + 0x2000, 0x147b, 0x2000, 0x147b, 0x147b, 0x0d1b, 0x147b, 0x0d1b, + 0x1c72, 0x11cf, 0x1c72, 0x11cf, 0x11cf, 0x0b4d, 0x11cf, 0x0b4d, + 0x1c72, 0x11cf, 0x1c72, 0x11cf, 0x11cf, 0x0b4d, 0x11cf, 0x0b4d +}; + +static const u16 inverse_quantization_table[QUANTIZATION_TABLE_LEN] = { + 0x800a, 0x800d, 0x800a, 0x800d, 0x800d, 0x8010, 0x800d, 0x8010, + 0x800a, 0x800d, 0x800a, 0x800d, 0x800d, 0x8010, 0x800d, 0x8010, + 0x800b, 0x800e, 0x800b, 0x800e, 0x800e, 0x8012, 0x800e, 0x8012, + 0x800b, 0x800e, 0x800b, 0x800e, 0x800e, 0x8012, 0x800e, 0x8012, + 0x800d, 0x8010, 0x800d, 0x8010, 0x8010, 0x8014, 0x8010, 0x8014, + 0x800d, 0x8010, 0x800d, 0x8010, 0x8010, 0x8014, 0x8010, 0x8014, + 0x800e, 0x8012, 0x800e, 0x8012, 0x8012, 0x8017, 0x8012, 0x8017, + 0x800e, 0x8012, 0x800e, 0x8012, 0x8012, 0x8017, 0x8012, 0x8017, + 0x8010, 0x8014, 0x8010, 0x8014, 0x8014, 0x8019, 0x8014, 0x8019, + 0x8010, 0x8014, 0x8010, 0x8014, 0x8014, 0x8019, 0x8014, 0x8019, + 0x8012, 0x8017, 0x8012, 0x8017, 0x8017, 0x801d, 0x8017, 0x801d, + 0x8012, 0x8017, 0x8012, 0x8017, 0x8017, 0x801d, 0x8017, 0x801d +}; + +static const u16 encoder_vlc_lookup_table[VLC_LOOKUP_TABLE_LEN] = { + 0x011, 0x000, 0x000, 0x000, 0x065, 0x021, 0x000, 0x000, 0x087, 0x064, + 0x031, 0x000, 0x097, 0x086, 0x075, 0x053, 0x0a7, 0x096, 0x085, 0x063, + 0x0b7, 0x0a6, 0x095, 0x074, 0x0df, 0x0b6, 0x0a5, 0x084, 0x0db, 0x0de, + 0x0b5, 0x094, 0x0d8, 0x0da, 0x0dd, 0x0a4, 0x0ef, 0x0ee, 0x0d9, 0x0b4, + 0x0eb, 0x0ea, 0x0ed, 0x0dc, 0x0ff, 0x0fe, 0x0e9, 0x0ec, 0x0fb, 0x0fa, + 0x0fd, 0x0e8, 0x10f, 0x0f1, 0x0f9, 0x0fc, 0x10b, 0x10e, 0x10d, 0x0f8, + 0x107, 0x10a, 0x109, 0x10c, 0x104, 0x106, 0x105, 0x108, 0x023, 0x000, + 0x000, 0x000, 0x06b, 0x022, 0x000, 0x000, 0x067, 0x057, 0x033, 0x000, + 0x077, 0x06a, 0x069, 0x045, 0x087, 0x066, 0x065, 0x044, 0x084, 0x076, + 0x075, 0x056, 0x097, 0x086, 0x085, 0x068, 0x0bf, 0x096, 0x095, 0x064, + 0x0bb, 0x0be, 0x0bd, 0x074, 0x0cf, 0x0ba, 0x0b9, 0x094, 0x0cb, 0x0ce, + 0x0cd, 0x0bc, 0x0c8, 0x0ca, 0x0c9, 0x0b8, 0x0df, 0x0de, 0x0dd, 0x0cc, + 0x0db, 0x0da, 0x0d9, 0x0dc, 0x0d7, 0x0eb, 0x0d6, 0x0d8, 0x0e9, 0x0e8, + 0x0ea, 0x0d1, 0x0e7, 0x0e6, 0x0e5, 0x0e4, 0x04f, 0x000, 0x000, 0x000, + 0x06f, 0x04e, 0x000, 0x000, 0x06b, 0x05f, 0x04d, 0x000, 0x068, 0x05c, + 0x05e, 0x04c, 0x07f, 0x05a, 0x05b, 0x04b, 0x07b, 0x058, 0x059, 0x04a, + 0x079, 0x06e, 0x06d, 0x049, 0x078, 0x06a, 0x069, 0x048, 0x08f, 0x07e, + 0x07d, 0x05d, 0x08b, 0x08e, 0x07a, 0x06c, 0x09f, 0x08a, 0x08d, 0x07c, + 0x09b, 0x09e, 0x089, 0x08c, 0x098, 0x09a, 0x09d, 0x088, 0x0ad, 0x097, + 0x099, 0x09c, 0x0a9, 0x0ac, 0x0ab, 0x0aa, 0x0a5, 0x0a8, 0x0a7, 0x0a6, + 0x0a1, 0x0a4, 0x0a3, 0x0a2, 0x021, 0x000, 0x000, 0x000, 0x067, 0x011, + 0x000, 0x000, 0x064, 0x066, 0x031, 0x000, 0x063, 0x073, 0x072, 0x065, + 0x062, 0x083, 0x082, 0x070, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x011, 0x010, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x011, 0x021, 0x020, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x023, 0x022, 0x021, 0x020, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x023, 0x022, 0x021, 0x031, + 0x030, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x023, 0x022, 0x033, 0x032, 0x031, 0x030, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x023, 0x030, + 0x031, 0x033, 0x032, 0x035, 0x034, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x037, 0x036, 0x035, 0x034, 0x033, 0x032, + 0x031, 0x041, 0x051, 0x061, 0x071, 0x081, 0x091, 0x0a1, 0x0b1, 0x000, + 0x002, 0x000, 0x0e4, 0x011, 0x0f4, 0x002, 0x024, 0x003, 0x005, 0x012, + 0x034, 0x013, 0x065, 0x024, 0x013, 0x063, 0x015, 0x022, 0x075, 0x034, + 0x044, 0x023, 0x023, 0x073, 0x054, 0x033, 0x033, 0x004, 0x043, 0x014, + 0x011, 0x043, 0x014, 0x001, 0x025, 0x015, 0x035, 0x025, 0x064, 0x055, + 0x045, 0x035, 0x074, 0x065, 0x085, 0x0d5, 0x012, 0x095, 0x055, 0x045, + 0x095, 0x0e5, 0x084, 0x075, 0x022, 0x0a5, 0x094, 0x085, 0x032, 0x0b5, + 0x003, 0x0c5, 0x001, 0x044, 0x0a5, 0x032, 0x0b5, 0x094, 0x0c5, 0x0a4, + 0x0a4, 0x054, 0x0d5, 0x0b4, 0x0b4, 0x064, 0x0f5, 0x0f5, 0x053, 0x0d4, + 0x0e5, 0x0c4, 0x105, 0x105, 0x0c4, 0x074, 0x063, 0x0e4, 0x0d4, 0x084, + 0x073, 0x0f4, 0x004, 0x005, 0x000, 0x053, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x011, 0x021, 0x031, 0x030, 0x011, 0x021, 0x020, 0x000, + 0x011, 0x010, 0x000, 0x000, 0x011, 0x033, 0x032, 0x043, 0x042, 0x053, + 0x052, 0x063, 0x062, 0x073, 0x072, 0x083, 0x082, 0x093, 0x092, 0x091, + 0x037, 0x036, 0x035, 0x034, 0x033, 0x045, 0x044, 0x043, 0x042, 0x053, + 0x052, 0x063, 0x062, 0x061, 0x060, 0x000, 0x045, 0x037, 0x036, 0x035, + 0x044, 0x043, 0x034, 0x033, 0x042, 0x053, 0x052, 0x061, 0x051, 0x060, + 0x000, 0x000, 0x053, 0x037, 0x045, 0x044, 0x036, 0x035, 0x034, 0x043, + 0x033, 0x042, 0x052, 0x051, 0x050, 0x000, 0x000, 0x000, 0x045, 0x044, + 0x043, 0x037, 0x036, 0x035, 0x034, 0x033, 0x042, 0x051, 0x041, 0x050, + 0x000, 0x000, 0x000, 0x000, 0x061, 0x051, 0x037, 0x036, 0x035, 0x034, + 0x033, 0x032, 0x041, 0x031, 0x060, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x061, 0x051, 0x035, 0x034, 0x033, 0x023, 0x032, 0x041, 0x031, 0x060, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x061, 0x041, 0x051, 0x033, + 0x023, 0x022, 0x032, 0x031, 0x060, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x061, 0x060, 0x041, 0x023, 0x022, 0x031, 0x021, 0x051, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x051, 0x050, + 0x031, 0x023, 0x022, 0x021, 0x041, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x040, 0x041, 0x031, 0x032, 0x011, 0x033, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x040, 0x041, 0x021, 0x011, 0x031, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x031, 0x011, 0x021, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x020, 0x021, 0x011, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, 0x011, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000 +}; + +static const unsigned int lambda_lookup_table[] = { + 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, + 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, + 0x0040, 0x0040, 0x0040, 0x0040, 0x0060, 0x0060, 0x0060, 0x0080, + 0x0080, 0x0080, 0x00a0, 0x00c0, 0x00c0, 0x00e0, 0x0100, 0x0120, + 0x0140, 0x0160, 0x01a0, 0x01c0, 0x0200, 0x0240, 0x0280, 0x02e0, + 0x0320, 0x03a0, 0x0400, 0x0480, 0x0500, 0x05a0, 0x0660, 0x0720, + 0x0800, 0x0900, 0x0a20, 0x0b60 +}; + +static const unsigned int intra4x4_lambda3[] = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 3, 3, 3, 4, + 4, 4, 5, 6, 6, 7, 8, 9, + 10, 11, 13, 14, 16, 18, 20, 23, + 25, 29, 32, 36, 40, 45, 51, 57, + 64, 72, 81, 91 +}; + +static v4l2_std_id tw5864_get_v4l2_std(enum tw5864_vid_std std); +static enum tw5864_vid_std tw5864_from_v4l2_std(v4l2_std_id v4l2_std); + +static void tw5864_handle_frame_work(struct work_struct *t); +static void tw5864_handle_frame(struct tw5864_h264_frame *frame); +static void tw5864_frame_interval_set(struct tw5864_input *input); + +static int tw5864_queue_setup(struct vb2_queue *q, unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_ctxs[]) +{ + if (*num_planes) + return sizes[0] < H264_VLC_BUF_SIZE ? -EINVAL : 0; + + sizes[0] = H264_VLC_BUF_SIZE; + *num_planes = 1; + + return 0; +} + +static void tw5864_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vb2_queue *vq = vb->vb2_queue; + struct tw5864_input *dev = vb2_get_drv_priv(vq); + struct tw5864_buf *buf = container_of(vbuf, struct tw5864_buf, vb); + unsigned long flags; + + spin_lock_irqsave(&dev->slock, flags); + list_add_tail(&buf->list, &dev->active); + spin_unlock_irqrestore(&dev->slock, flags); +} + +static int tw5864_input_std_get(struct tw5864_input *input, + enum tw5864_vid_std *std) +{ + struct tw5864_dev *dev = input->root; + u8 std_reg = tw_indir_readb(TW5864_INDIR_VIN_E(input->nr)); + + *std = (std_reg & 0x70) >> 4; + + if (std_reg & 0x80) { + dev_dbg(&dev->pci->dev, + "Video format detection is in progress, please wait\n"); + return -EAGAIN; + } + + return 0; +} + +static int tw5864_enable_input(struct tw5864_input *input) +{ + struct tw5864_dev *dev = input->root; + int nr = input->nr; + unsigned long flags; + int d1_width = 720; + int d1_height; + int frame_width_bus_value = 0; + int frame_height_bus_value = 0; + int reg_frame_bus = 0x1c; + int fmt_reg_value = 0; + int downscale_enabled = 0; + + dev_dbg(&dev->pci->dev, "Enabling channel %d\n", nr); + + input->frame_seqno = 0; + input->frame_gop_seqno = 0; + input->h264_idr_pic_id = 0; + + input->reg_dsp_qp = input->qp; + input->reg_dsp_ref_mvp_lambda = lambda_lookup_table[input->qp]; + input->reg_dsp_i4x4_weight = intra4x4_lambda3[input->qp]; + input->reg_emu = TW5864_EMU_EN_LPF | TW5864_EMU_EN_BHOST + | TW5864_EMU_EN_SEN | TW5864_EMU_EN_ME | TW5864_EMU_EN_DDR; + input->reg_dsp = nr /* channel id */ + | TW5864_DSP_CHROM_SW + | ((0xa << 8) & TW5864_DSP_MB_DELAY) + ; + + input->resolution = D1; + + d1_height = (input->std == STD_NTSC) ? 480 : 576; + + input->width = d1_width; + input->height = d1_height; + + input->reg_interlacing = 0x4; + + switch (input->resolution) { + case D1: + frame_width_bus_value = 0x2cf; + frame_height_bus_value = input->height - 1; + reg_frame_bus = 0x1c; + fmt_reg_value = 0; + downscale_enabled = 0; + input->reg_dsp_codec |= TW5864_CIF_MAP_MD | TW5864_HD1_MAP_MD; + input->reg_emu |= TW5864_DSP_FRAME_TYPE_D1; + input->reg_interlacing = TW5864_DI_EN | TW5864_DSP_INTER_ST; + + tw_setl(TW5864_FULL_HALF_FLAG, 1 << nr); + break; + case HD1: + input->height /= 2; + input->width /= 2; + frame_width_bus_value = 0x2cf; + frame_height_bus_value = input->height * 2 - 1; + reg_frame_bus = 0x1c; + fmt_reg_value = 0; + downscale_enabled = 0; + input->reg_dsp_codec |= TW5864_HD1_MAP_MD; + input->reg_emu |= TW5864_DSP_FRAME_TYPE_D1; + + tw_clearl(TW5864_FULL_HALF_FLAG, 1 << nr); + + break; + case CIF: + input->height /= 4; + input->width /= 2; + frame_width_bus_value = 0x15f; + frame_height_bus_value = input->height * 2 - 1; + reg_frame_bus = 0x07; + fmt_reg_value = 1; + downscale_enabled = 1; + input->reg_dsp_codec |= TW5864_CIF_MAP_MD; + + tw_clearl(TW5864_FULL_HALF_FLAG, 1 << nr); + break; + case QCIF: + input->height /= 4; + input->width /= 4; + frame_width_bus_value = 0x15f; + frame_height_bus_value = input->height * 2 - 1; + reg_frame_bus = 0x07; + fmt_reg_value = 1; + downscale_enabled = 1; + input->reg_dsp_codec |= TW5864_CIF_MAP_MD; + + tw_clearl(TW5864_FULL_HALF_FLAG, 1 << nr); + break; + } + + /* analog input width / 4 */ + tw_indir_writeb(TW5864_INDIR_IN_PIC_WIDTH(nr), d1_width / 4); + tw_indir_writeb(TW5864_INDIR_IN_PIC_HEIGHT(nr), d1_height / 4); + + /* output width / 4 */ + tw_indir_writeb(TW5864_INDIR_OUT_PIC_WIDTH(nr), input->width / 4); + tw_indir_writeb(TW5864_INDIR_OUT_PIC_HEIGHT(nr), input->height / 4); + + /* + * Crop width from 720 to 704. + * Above register settings need value 720 involved. + */ + input->width = 704; + tw_indir_writeb(TW5864_INDIR_CROP_ETC, + tw_indir_readb(TW5864_INDIR_CROP_ETC) | + TW5864_INDIR_CROP_ETC_CROP_EN); + + tw_writel(TW5864_DSP_PIC_MAX_MB, + ((input->width / 16) << 8) | (input->height / 16)); + + tw_writel(TW5864_FRAME_WIDTH_BUS_A(nr), + frame_width_bus_value); + tw_writel(TW5864_FRAME_WIDTH_BUS_B(nr), + frame_width_bus_value); + tw_writel(TW5864_FRAME_HEIGHT_BUS_A(nr), + frame_height_bus_value); + tw_writel(TW5864_FRAME_HEIGHT_BUS_B(nr), + (frame_height_bus_value + 1) / 2 - 1); + + tw5864_frame_interval_set(input); + + if (downscale_enabled) + tw_setl(TW5864_H264EN_CH_DNS, 1 << nr); + + tw_mask_shift_writel(TW5864_H264EN_CH_FMT_REG1, 0x3, 2 * nr, + fmt_reg_value); + + tw_mask_shift_writel((nr < 2 + ? TW5864_H264EN_RATE_MAX_LINE_REG1 + : TW5864_H264EN_RATE_MAX_LINE_REG2), + 0x1f, 5 * (nr % 2), + input->std == STD_NTSC ? 29 : 24); + + tw_mask_shift_writel((nr < 2) ? TW5864_FRAME_BUS1 : + TW5864_FRAME_BUS2, 0xff, (nr % 2) * 8, + reg_frame_bus); + + spin_lock_irqsave(&dev->slock, flags); + input->enabled = 1; + spin_unlock_irqrestore(&dev->slock, flags); + + return 0; +} + +void tw5864_request_encoded_frame(struct tw5864_input *input) +{ + struct tw5864_dev *dev = input->root; + u32 enc_buf_id_new; + + tw_setl(TW5864_DSP_CODEC, TW5864_CIF_MAP_MD | TW5864_HD1_MAP_MD); + tw_writel(TW5864_EMU, input->reg_emu); + tw_writel(TW5864_INTERLACING, input->reg_interlacing); + tw_writel(TW5864_DSP, input->reg_dsp); + + tw_writel(TW5864_DSP_QP, input->reg_dsp_qp); + tw_writel(TW5864_DSP_REF_MVP_LAMBDA, input->reg_dsp_ref_mvp_lambda); + tw_writel(TW5864_DSP_I4x4_WEIGHT, input->reg_dsp_i4x4_weight); + tw_mask_shift_writel(TW5864_DSP_INTRA_MODE, TW5864_DSP_INTRA_MODE_MASK, + TW5864_DSP_INTRA_MODE_SHIFT, + TW5864_DSP_INTRA_MODE_16x16); + + if (input->frame_gop_seqno == 0) { + /* Produce I-frame */ + tw_writel(TW5864_MOTION_SEARCH_ETC, TW5864_INTRA_EN); + input->h264_idr_pic_id++; + input->h264_idr_pic_id &= TW5864_DSP_REF_FRM; + } else { + /* Produce P-frame */ + tw_writel(TW5864_MOTION_SEARCH_ETC, TW5864_INTRA_EN | + TW5864_ME_EN | BIT(5) /* SRCH_OPT default */); + } + tw5864_prepare_frame_headers(input); + tw_writel(TW5864_VLC, + TW5864_VLC_PCI_SEL | + ((input->tail_nb_bits + 24) << TW5864_VLC_BIT_ALIGN_SHIFT) | + input->reg_dsp_qp); + + enc_buf_id_new = tw_mask_shift_readl(TW5864_ENC_BUF_PTR_REC1, 0x3, + 2 * input->nr); + tw_writel(TW5864_DSP_ENC_ORG_PTR_REG, + enc_buf_id_new << TW5864_DSP_ENC_ORG_PTR_SHIFT); + tw_writel(TW5864_DSP_ENC_REC, + enc_buf_id_new << 12 | ((enc_buf_id_new + 3) & 3)); + + tw_writel(TW5864_SLICE, TW5864_START_NSLICE); + tw_writel(TW5864_SLICE, 0); +} + +static int tw5864_disable_input(struct tw5864_input *input) +{ + struct tw5864_dev *dev = input->root; + unsigned long flags; + + dev_dbg(&dev->pci->dev, "Disabling channel %d\n", input->nr); + + spin_lock_irqsave(&dev->slock, flags); + input->enabled = 0; + spin_unlock_irqrestore(&dev->slock, flags); + return 0; +} + +static int tw5864_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct tw5864_input *input = vb2_get_drv_priv(q); + int ret; + + ret = tw5864_enable_input(input); + if (!ret) + return 0; + + while (!list_empty(&input->active)) { + struct tw5864_buf *buf = list_entry(input->active.next, + struct tw5864_buf, list); + + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); + } + return ret; +} + +static void tw5864_stop_streaming(struct vb2_queue *q) +{ + unsigned long flags; + struct tw5864_input *input = vb2_get_drv_priv(q); + + tw5864_disable_input(input); + + spin_lock_irqsave(&input->slock, flags); + if (input->vb) { + vb2_buffer_done(&input->vb->vb.vb2_buf, VB2_BUF_STATE_ERROR); + input->vb = NULL; + } + while (!list_empty(&input->active)) { + struct tw5864_buf *buf = list_entry(input->active.next, + struct tw5864_buf, list); + + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&input->slock, flags); +} + +static const struct vb2_ops tw5864_video_qops = { + .queue_setup = tw5864_queue_setup, + .buf_queue = tw5864_buf_queue, + .start_streaming = tw5864_start_streaming, + .stop_streaming = tw5864_stop_streaming, +}; + +static int tw5864_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct tw5864_input *input = + container_of(ctrl->handler, struct tw5864_input, hdl); + struct tw5864_dev *dev = input->root; + unsigned long flags; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + tw_indir_writeb(TW5864_INDIR_VIN_A_BRIGHT(input->nr), + (u8)ctrl->val); + break; + case V4L2_CID_HUE: + tw_indir_writeb(TW5864_INDIR_VIN_7_HUE(input->nr), + (u8)ctrl->val); + break; + case V4L2_CID_CONTRAST: + tw_indir_writeb(TW5864_INDIR_VIN_9_CNTRST(input->nr), + (u8)ctrl->val); + break; + case V4L2_CID_SATURATION: + tw_indir_writeb(TW5864_INDIR_VIN_B_SAT_U(input->nr), + (u8)ctrl->val); + tw_indir_writeb(TW5864_INDIR_VIN_C_SAT_V(input->nr), + (u8)ctrl->val); + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + input->gop = ctrl->val; + return 0; + case V4L2_CID_MPEG_VIDEO_H264_MIN_QP: + spin_lock_irqsave(&input->slock, flags); + input->qp = ctrl->val; + input->reg_dsp_qp = input->qp; + input->reg_dsp_ref_mvp_lambda = lambda_lookup_table[input->qp]; + input->reg_dsp_i4x4_weight = intra4x4_lambda3[input->qp]; + spin_unlock_irqrestore(&input->slock, flags); + return 0; + case V4L2_CID_DETECT_MD_GLOBAL_THRESHOLD: + memset(input->md_threshold_grid_values, ctrl->val, + sizeof(input->md_threshold_grid_values)); + return 0; + case V4L2_CID_DETECT_MD_MODE: + return 0; + case V4L2_CID_DETECT_MD_THRESHOLD_GRID: + /* input->md_threshold_grid_ctrl->p_new.p_u16 contains data */ + memcpy(input->md_threshold_grid_values, + input->md_threshold_grid_ctrl->p_new.p_u16, + sizeof(input->md_threshold_grid_values)); + return 0; + } + return 0; +} + +static int tw5864_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw5864_input *input = video_drvdata(file); + + f->fmt.pix.width = 704; + switch (input->std) { + default: + WARN_ON_ONCE(1); + return -EINVAL; + case STD_NTSC: + f->fmt.pix.height = 480; + break; + case STD_PAL: + case STD_SECAM: + f->fmt.pix.height = 576; + break; + } + f->fmt.pix.field = V4L2_FIELD_INTERLACED; + f->fmt.pix.pixelformat = V4L2_PIX_FMT_H264; + f->fmt.pix.sizeimage = H264_VLC_BUF_SIZE; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + return 0; +} + +static int tw5864_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct tw5864_input *input = video_drvdata(file); + struct tw5864_dev *dev = input->root; + + u8 indir_0x000 = tw_indir_readb(TW5864_INDIR_VIN_0(input->nr)); + u8 indir_0x00d = tw_indir_readb(TW5864_INDIR_VIN_D(input->nr)); + u8 v1 = indir_0x000; + u8 v2 = indir_0x00d; + + if (i->index) + return -EINVAL; + + i->type = V4L2_INPUT_TYPE_CAMERA; + snprintf(i->name, sizeof(i->name), "Encoder %d", input->nr); + i->std = TW5864_NORMS; + if (v1 & (1 << 7)) + i->status |= V4L2_IN_ST_NO_SYNC; + if (!(v1 & (1 << 6))) + i->status |= V4L2_IN_ST_NO_H_LOCK; + if (v1 & (1 << 2)) + i->status |= V4L2_IN_ST_NO_SIGNAL; + if (v1 & (1 << 1)) + i->status |= V4L2_IN_ST_NO_COLOR; + if (v2 & (1 << 2)) + i->status |= V4L2_IN_ST_MACROVISION; + + return 0; +} + +static int tw5864_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int tw5864_s_input(struct file *file, void *priv, unsigned int i) +{ + if (i) + return -EINVAL; + return 0; +} + +static int tw5864_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct tw5864_input *input = video_drvdata(file); + + strscpy(cap->driver, "tw5864", sizeof(cap->driver)); + snprintf(cap->card, sizeof(cap->card), "TW5864 Encoder %d", + input->nr); + return 0; +} + +static int tw5864_querystd(struct file *file, void *priv, v4l2_std_id *std) +{ + struct tw5864_input *input = video_drvdata(file); + enum tw5864_vid_std tw_std; + int ret; + + ret = tw5864_input_std_get(input, &tw_std); + if (ret) + return ret; + *std = tw5864_get_v4l2_std(tw_std); + + return 0; +} + +static int tw5864_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct tw5864_input *input = video_drvdata(file); + + *std = input->v4l2_std; + return 0; +} + +static int tw5864_s_std(struct file *file, void *priv, v4l2_std_id std) +{ + struct tw5864_input *input = video_drvdata(file); + struct tw5864_dev *dev = input->root; + + input->v4l2_std = std; + input->std = tw5864_from_v4l2_std(std); + tw_indir_writeb(TW5864_INDIR_VIN_E(input->nr), input->std); + return 0; +} + +static int tw5864_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index) + return -EINVAL; + + f->pixelformat = V4L2_PIX_FMT_H264; + + return 0; +} + +static int tw5864_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_MOTION_DET: + /* + * Allow for up to 30 events (1 second for NTSC) to be stored. + */ + return v4l2_event_subscribe(fh, sub, 30, NULL); + default: + return v4l2_ctrl_subscribe_event(fh, sub); + } +} + +static void tw5864_frame_interval_set(struct tw5864_input *input) +{ + /* + * This register value seems to follow such approach: In each second + * interval, when processing Nth frame, it checks Nth bit of register + * value and, if the bit is 1, it processes the frame, otherwise the + * frame is discarded. + * So unary representation would work, but more or less equal gaps + * between the frames should be preserved. + * + * For 1 FPS - 0x00000001 + * 00000000 00000000 00000000 00000001 + * + * For max FPS - set all 25/30 lower bits: + * 00111111 11111111 11111111 11111111 (NTSC) + * 00000001 11111111 11111111 11111111 (PAL) + * + * For half of max FPS - use such pattern: + * 00010101 01010101 01010101 01010101 (NTSC) + * 00000001 01010101 01010101 01010101 (PAL) + * + * Et cetera. + * + * The value supplied to hardware is capped by mask of 25/30 lower bits. + */ + struct tw5864_dev *dev = input->root; + u32 unary_framerate = 0; + int shift = 0; + int std_max_fps = input->std == STD_NTSC ? 30 : 25; + + for (shift = 0; shift < std_max_fps; shift += input->frame_interval) + unary_framerate |= 0x00000001 << shift; + + tw_writel(TW5864_H264EN_RATE_CNTL_LO_WORD(input->nr, 0), + unary_framerate >> 16); + tw_writel(TW5864_H264EN_RATE_CNTL_HI_WORD(input->nr, 0), + unary_framerate & 0xffff); +} + +static int tw5864_frameinterval_get(struct tw5864_input *input, + struct v4l2_fract *frameinterval) +{ + struct tw5864_dev *dev = input->root; + + switch (input->std) { + case STD_NTSC: + frameinterval->numerator = 1001; + frameinterval->denominator = 30000; + break; + case STD_PAL: + case STD_SECAM: + frameinterval->numerator = 1; + frameinterval->denominator = 25; + break; + default: + dev_warn(&dev->pci->dev, "tw5864_frameinterval_get requested for unknown std %d\n", + input->std); + return -EINVAL; + } + + return 0; +} + +static int tw5864_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct tw5864_input *input = video_drvdata(file); + + if (fsize->index > 0) + return -EINVAL; + if (fsize->pixel_format != V4L2_PIX_FMT_H264) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = 704; + fsize->discrete.height = input->std == STD_NTSC ? 480 : 576; + + return 0; +} + +static int tw5864_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *fintv) +{ + struct tw5864_input *input = video_drvdata(file); + struct v4l2_fract frameinterval; + int std_max_fps = input->std == STD_NTSC ? 30 : 25; + struct v4l2_frmsizeenum fsize = { .index = fintv->index, + .pixel_format = fintv->pixel_format }; + int ret; + + ret = tw5864_enum_framesizes(file, priv, &fsize); + if (ret) + return ret; + + if (fintv->width != fsize.discrete.width || + fintv->height != fsize.discrete.height) + return -EINVAL; + + fintv->type = V4L2_FRMIVAL_TYPE_STEPWISE; + + ret = tw5864_frameinterval_get(input, &frameinterval); + if (ret) + return ret; + + fintv->stepwise.step = frameinterval; + fintv->stepwise.min = frameinterval; + fintv->stepwise.max = frameinterval; + fintv->stepwise.max.numerator *= std_max_fps; + + return ret; +} + +static int tw5864_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct tw5864_input *input = video_drvdata(file); + struct v4l2_captureparm *cp = &sp->parm.capture; + int ret; + + cp->capability = V4L2_CAP_TIMEPERFRAME; + + ret = tw5864_frameinterval_get(input, &cp->timeperframe); + if (ret) + return ret; + + cp->timeperframe.numerator *= input->frame_interval; + cp->capturemode = 0; + cp->readbuffers = 2; + + return ret; +} + +static int tw5864_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct tw5864_input *input = video_drvdata(file); + struct v4l2_fract *t = &sp->parm.capture.timeperframe; + struct v4l2_fract time_base; + int ret; + + ret = tw5864_frameinterval_get(input, &time_base); + if (ret) + return ret; + + if (!t->numerator || !t->denominator) { + t->numerator = time_base.numerator * input->frame_interval; + t->denominator = time_base.denominator; + } else if (t->denominator != time_base.denominator) { + t->numerator = t->numerator * time_base.denominator / + t->denominator; + t->denominator = time_base.denominator; + } + + input->frame_interval = t->numerator / time_base.numerator; + if (input->frame_interval < 1) + input->frame_interval = 1; + tw5864_frame_interval_set(input); + return tw5864_g_parm(file, priv, sp); +} + +static const struct v4l2_ctrl_ops tw5864_ctrl_ops = { + .s_ctrl = tw5864_s_ctrl, +}; + +static const struct v4l2_file_operations video_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +#ifdef CONFIG_VIDEO_ADV_DEBUG + +#define INDIR_SPACE_MAP_SHIFT 0x100000 + +static int tw5864_g_reg(struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + struct tw5864_input *input = video_drvdata(file); + struct tw5864_dev *dev = input->root; + + if (reg->reg < INDIR_SPACE_MAP_SHIFT) { + if (reg->reg > 0x87fff) + return -EINVAL; + reg->size = 4; + reg->val = tw_readl(reg->reg); + } else { + __u64 indir_addr = reg->reg - INDIR_SPACE_MAP_SHIFT; + + if (indir_addr > 0xefe) + return -EINVAL; + reg->size = 1; + reg->val = tw_indir_readb(reg->reg); + } + return 0; +} + +static int tw5864_s_reg(struct file *file, void *fh, + const struct v4l2_dbg_register *reg) +{ + struct tw5864_input *input = video_drvdata(file); + struct tw5864_dev *dev = input->root; + + if (reg->reg < INDIR_SPACE_MAP_SHIFT) { + if (reg->reg > 0x87fff) + return -EINVAL; + tw_writel(reg->reg, reg->val); + } else { + __u64 indir_addr = reg->reg - INDIR_SPACE_MAP_SHIFT; + + if (indir_addr > 0xefe) + return -EINVAL; + tw_indir_writeb(reg->reg, reg->val); + } + return 0; +} +#endif + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = tw5864_querycap, + .vidioc_enum_fmt_vid_cap = tw5864_enum_fmt_vid_cap, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_querystd = tw5864_querystd, + .vidioc_s_std = tw5864_s_std, + .vidioc_g_std = tw5864_g_std, + .vidioc_enum_input = tw5864_enum_input, + .vidioc_g_input = tw5864_g_input, + .vidioc_s_input = tw5864_s_input, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_try_fmt_vid_cap = tw5864_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = tw5864_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = tw5864_fmt_vid_cap, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = tw5864_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_enum_framesizes = tw5864_enum_framesizes, + .vidioc_enum_frameintervals = tw5864_enum_frameintervals, + .vidioc_s_parm = tw5864_s_parm, + .vidioc_g_parm = tw5864_g_parm, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = tw5864_g_reg, + .vidioc_s_register = tw5864_s_reg, +#endif +}; + +static const struct video_device tw5864_video_template = { + .name = "tw5864_video", + .fops = &video_fops, + .ioctl_ops = &video_ioctl_ops, + .release = video_device_release_empty, + .tvnorms = TW5864_NORMS, + .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING, +}; + +/* Motion Detection Threshold matrix */ +static const struct v4l2_ctrl_config tw5864_md_thresholds = { + .ops = &tw5864_ctrl_ops, + .id = V4L2_CID_DETECT_MD_THRESHOLD_GRID, + .dims = {MD_CELLS_HOR, MD_CELLS_VERT}, + .def = 14, + /* See tw5864_md_metric_from_mvd() */ + .max = 2 * 0x0f, + .step = 1, +}; + +static int tw5864_video_input_init(struct tw5864_input *dev, int video_nr); +static void tw5864_video_input_fini(struct tw5864_input *dev); +static void tw5864_encoder_tables_upload(struct tw5864_dev *dev); + +int tw5864_video_init(struct tw5864_dev *dev, int *video_nr) +{ + int i; + int ret; + unsigned long flags; + int last_dma_allocated = -1; + int last_input_nr_registered = -1; + + for (i = 0; i < H264_BUF_CNT; i++) { + struct tw5864_h264_frame *frame = &dev->h264_buf[i]; + + frame->vlc.addr = dma_alloc_coherent(&dev->pci->dev, + H264_VLC_BUF_SIZE, + &frame->vlc.dma_addr, + GFP_KERNEL | GFP_DMA32); + if (!frame->vlc.addr) { + dev_err(&dev->pci->dev, "dma alloc fail\n"); + ret = -ENOMEM; + goto free_dma; + } + frame->mv.addr = dma_alloc_coherent(&dev->pci->dev, + H264_MV_BUF_SIZE, + &frame->mv.dma_addr, + GFP_KERNEL | GFP_DMA32); + if (!frame->mv.addr) { + dev_err(&dev->pci->dev, "dma alloc fail\n"); + ret = -ENOMEM; + dma_free_coherent(&dev->pci->dev, H264_VLC_BUF_SIZE, + frame->vlc.addr, frame->vlc.dma_addr); + goto free_dma; + } + last_dma_allocated = i; + } + + tw5864_encoder_tables_upload(dev); + + /* Picture is distorted without this block */ + /* use falling edge to sample 54M to 108M */ + tw_indir_writeb(TW5864_INDIR_VD_108_POL, TW5864_INDIR_VD_108_POL_BOTH); + tw_indir_writeb(TW5864_INDIR_CLK0_SEL, 0x00); + + tw_indir_writeb(TW5864_INDIR_DDRA_DLL_DQS_SEL0, 0x02); + tw_indir_writeb(TW5864_INDIR_DDRA_DLL_DQS_SEL1, 0x02); + tw_indir_writeb(TW5864_INDIR_DDRA_DLL_CLK90_SEL, 0x02); + tw_indir_writeb(TW5864_INDIR_DDRB_DLL_DQS_SEL0, 0x02); + tw_indir_writeb(TW5864_INDIR_DDRB_DLL_DQS_SEL1, 0x02); + tw_indir_writeb(TW5864_INDIR_DDRB_DLL_CLK90_SEL, 0x02); + + /* video input reset */ + tw_indir_writeb(TW5864_INDIR_RESET, 0); + tw_indir_writeb(TW5864_INDIR_RESET, TW5864_INDIR_RESET_VD | + TW5864_INDIR_RESET_DLL | TW5864_INDIR_RESET_MUX_CORE); + msleep(20); + + /* + * Select Part A mode for all channels. + * tw_setl instead of tw_clearl for Part B mode. + * + * I guess "Part B" is primarily for downscaled version of same channel + * which goes in Part A of same bus + */ + tw_writel(TW5864_FULL_HALF_MODE_SEL, 0); + + tw_indir_writeb(TW5864_INDIR_PV_VD_CK_POL, + TW5864_INDIR_PV_VD_CK_POL_VD(0) | + TW5864_INDIR_PV_VD_CK_POL_VD(1) | + TW5864_INDIR_PV_VD_CK_POL_VD(2) | + TW5864_INDIR_PV_VD_CK_POL_VD(3)); + + spin_lock_irqsave(&dev->slock, flags); + dev->encoder_busy = 0; + dev->h264_buf_r_index = 0; + dev->h264_buf_w_index = 0; + tw_writel(TW5864_VLC_STREAM_BASE_ADDR, + dev->h264_buf[dev->h264_buf_w_index].vlc.dma_addr); + tw_writel(TW5864_MV_STREAM_BASE_ADDR, + dev->h264_buf[dev->h264_buf_w_index].mv.dma_addr); + spin_unlock_irqrestore(&dev->slock, flags); + + tw_writel(TW5864_SEN_EN_CH, 0x000f); + tw_writel(TW5864_H264EN_CH_EN, 0x000f); + + tw_writel(TW5864_H264EN_BUS0_MAP, 0x00000000); + tw_writel(TW5864_H264EN_BUS1_MAP, 0x00001111); + tw_writel(TW5864_H264EN_BUS2_MAP, 0x00002222); + tw_writel(TW5864_H264EN_BUS3_MAP, 0x00003333); + + /* + * Quote from Intersil (manufacturer): + * 0x0038 is managed by HW, and by default it won't pass the pointer set + * at 0x0010. So if you don't do encoding, 0x0038 should stay at '3' + * (with 4 frames in buffer). If you encode one frame and then move + * 0x0010 to '1' for example, HW will take one more frame and set it to + * buffer #0, and then you should see 0x0038 is set to '0'. There is + * only one HW encoder engine, so 4 channels cannot get encoded + * simultaneously. But each channel does have its own buffer (for + * original frames and reconstructed frames). So there is no problem to + * manage encoding for 4 channels at same time and no need to force + * I-frames in switching channels. + * End of quote. + * + * If we set 0x0010 (TW5864_ENC_BUF_PTR_REC1) to 0 (for any channel), we + * have no "rolling" (until we change this value). + * If we set 0x0010 (TW5864_ENC_BUF_PTR_REC1) to 0x3, it starts to roll + * continuously together with 0x0038. + */ + tw_writel(TW5864_ENC_BUF_PTR_REC1, 0x00ff); + tw_writel(TW5864_PCI_INTTM_SCALE, 0); + + tw_writel(TW5864_INTERLACING, TW5864_DI_EN); + tw_writel(TW5864_MASTER_ENB_REG, TW5864_PCI_VLC_INTR_ENB); + tw_writel(TW5864_PCI_INTR_CTL, + TW5864_TIMER_INTR_ENB | TW5864_PCI_MAST_ENB | + TW5864_MVD_VLC_MAST_ENB); + + dev->irqmask |= TW5864_INTR_VLC_DONE | TW5864_INTR_TIMER; + tw5864_irqmask_apply(dev); + + INIT_WORK(&dev->bh_work, tw5864_handle_frame_work); + + for (i = 0; i < TW5864_INPUTS; i++) { + dev->inputs[i].root = dev; + dev->inputs[i].nr = i; + ret = tw5864_video_input_init(&dev->inputs[i], video_nr[i]); + if (ret) + goto fini_video_inputs; + last_input_nr_registered = i; + } + + return 0; + +fini_video_inputs: + for (i = last_input_nr_registered; i >= 0; i--) + tw5864_video_input_fini(&dev->inputs[i]); + + cancel_work_sync(&dev->bh_work); + +free_dma: + for (i = last_dma_allocated; i >= 0; i--) { + dma_free_coherent(&dev->pci->dev, H264_VLC_BUF_SIZE, + dev->h264_buf[i].vlc.addr, + dev->h264_buf[i].vlc.dma_addr); + dma_free_coherent(&dev->pci->dev, H264_MV_BUF_SIZE, + dev->h264_buf[i].mv.addr, + dev->h264_buf[i].mv.dma_addr); + } + + return ret; +} + +static int tw5864_video_input_init(struct tw5864_input *input, int video_nr) +{ + struct tw5864_dev *dev = input->root; + int ret; + struct v4l2_ctrl_handler *hdl = &input->hdl; + + mutex_init(&input->lock); + spin_lock_init(&input->slock); + + /* setup video buffers queue */ + INIT_LIST_HEAD(&input->active); + input->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + input->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + input->vidq.io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; + input->vidq.ops = &tw5864_video_qops; + input->vidq.mem_ops = &vb2_dma_contig_memops; + input->vidq.drv_priv = input; + input->vidq.gfp_flags = 0; + input->vidq.buf_struct_size = sizeof(struct tw5864_buf); + input->vidq.lock = &input->lock; + input->vidq.min_queued_buffers = 2; + input->vidq.dev = &input->root->pci->dev; + ret = vb2_queue_init(&input->vidq); + if (ret) + goto free_mutex; + + input->vdev = tw5864_video_template; + input->vdev.v4l2_dev = &input->root->v4l2_dev; + input->vdev.lock = &input->lock; + input->vdev.queue = &input->vidq; + video_set_drvdata(&input->vdev, input); + + /* Initialize the device control structures */ + v4l2_ctrl_handler_init(hdl, 6); + v4l2_ctrl_new_std(hdl, &tw5864_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(hdl, &tw5864_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 100); + v4l2_ctrl_new_std(hdl, &tw5864_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &tw5864_ctrl_ops, V4L2_CID_HUE, -128, 127, 1, 0); + v4l2_ctrl_new_std(hdl, &tw5864_ctrl_ops, V4L2_CID_MPEG_VIDEO_GOP_SIZE, + 1, MAX_GOP_SIZE, 1, GOP_SIZE); + v4l2_ctrl_new_std(hdl, &tw5864_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_MIN_QP, 28, 51, 1, QP_VALUE); + v4l2_ctrl_new_std_menu(hdl, &tw5864_ctrl_ops, + V4L2_CID_DETECT_MD_MODE, + V4L2_DETECT_MD_MODE_THRESHOLD_GRID, 0, + V4L2_DETECT_MD_MODE_DISABLED); + v4l2_ctrl_new_std(hdl, &tw5864_ctrl_ops, + V4L2_CID_DETECT_MD_GLOBAL_THRESHOLD, + tw5864_md_thresholds.min, tw5864_md_thresholds.max, + tw5864_md_thresholds.step, tw5864_md_thresholds.def); + input->md_threshold_grid_ctrl = + v4l2_ctrl_new_custom(hdl, &tw5864_md_thresholds, NULL); + if (hdl->error) { + ret = hdl->error; + goto free_v4l2_hdl; + } + input->vdev.ctrl_handler = hdl; + v4l2_ctrl_handler_setup(hdl); + + input->qp = QP_VALUE; + input->gop = GOP_SIZE; + input->frame_interval = 1; + + ret = video_register_device(&input->vdev, VFL_TYPE_VIDEO, video_nr); + if (ret) + goto free_v4l2_hdl; + + dev_info(&input->root->pci->dev, "Registered video device %s\n", + video_device_node_name(&input->vdev)); + + /* + * Set default video standard. Doesn't matter which, the detected value + * will be found out by VIDIOC_QUERYSTD handler. + */ + input->v4l2_std = V4L2_STD_NTSC_M; + input->std = STD_NTSC; + + tw_indir_writeb(TW5864_INDIR_VIN_E(video_nr), 0x07); + /* to initiate auto format recognition */ + tw_indir_writeb(TW5864_INDIR_VIN_F(video_nr), 0xff); + + return 0; + +free_v4l2_hdl: + v4l2_ctrl_handler_free(hdl); +free_mutex: + mutex_destroy(&input->lock); + + return ret; +} + +static void tw5864_video_input_fini(struct tw5864_input *dev) +{ + vb2_video_unregister_device(&dev->vdev); + v4l2_ctrl_handler_free(&dev->hdl); +} + +void tw5864_video_fini(struct tw5864_dev *dev) +{ + int i; + + cancel_work_sync(&dev->bh_work); + + for (i = 0; i < TW5864_INPUTS; i++) + tw5864_video_input_fini(&dev->inputs[i]); + + for (i = 0; i < H264_BUF_CNT; i++) { + dma_free_coherent(&dev->pci->dev, H264_VLC_BUF_SIZE, + dev->h264_buf[i].vlc.addr, + dev->h264_buf[i].vlc.dma_addr); + dma_free_coherent(&dev->pci->dev, H264_MV_BUF_SIZE, + dev->h264_buf[i].mv.addr, + dev->h264_buf[i].mv.dma_addr); + } +} + +void tw5864_prepare_frame_headers(struct tw5864_input *input) +{ + struct tw5864_buf *vb = input->vb; + u8 *dst; + size_t dst_space; + unsigned long flags; + + if (!vb) { + spin_lock_irqsave(&input->slock, flags); + if (list_empty(&input->active)) { + spin_unlock_irqrestore(&input->slock, flags); + input->vb = NULL; + return; + } + vb = list_first_entry(&input->active, struct tw5864_buf, list); + list_del(&vb->list); + spin_unlock_irqrestore(&input->slock, flags); + } + + dst = vb2_plane_vaddr(&vb->vb.vb2_buf, 0); + dst_space = vb2_plane_size(&vb->vb.vb2_buf, 0); + + /* + * Low-level bitstream writing functions don't have a fine way to say + * correctly that supplied buffer is too small. So we just check there + * and warn, and don't care at lower level. + * Currently all headers take below 32 bytes. + * The buffer is supposed to have plenty of free space at this point, + * anyway. + */ + if (WARN_ON_ONCE(dst_space < 128)) + return; + + /* + * Generate H264 headers: + * If this is first frame, put SPS and PPS + */ + if (input->frame_gop_seqno == 0) + tw5864_h264_put_stream_header(&dst, &dst_space, input->qp, + input->width, input->height); + + /* Put slice header */ + tw5864_h264_put_slice_header(&dst, &dst_space, input->h264_idr_pic_id, + input->frame_gop_seqno, + &input->tail_nb_bits, &input->tail); + input->vb = vb; + input->buf_cur_ptr = dst; + input->buf_cur_space_left = dst_space; +} + +/* + * Returns heuristic motion detection metric value from known components of + * hardware-provided Motion Vector Data. + */ +static unsigned int tw5864_md_metric_from_mvd(u32 mvd) +{ + /* + * Format of motion vector data exposed by tw5864, according to + * manufacturer: + * mv_x 10 bits + * mv_y 10 bits + * non_zero_members 8 bits + * mb_type 3 bits + * reserved 1 bit + * + * non_zero_members: number of non-zero residuals in each macro block + * after quantization + * + * unsigned int reserved = mvd >> 31; + * unsigned int mb_type = (mvd >> 28) & 0x7; + * unsigned int non_zero_members = (mvd >> 20) & 0xff; + */ + unsigned int mv_y = (mvd >> 10) & 0x3ff; + unsigned int mv_x = mvd & 0x3ff; + + /* heuristic: */ + mv_x &= 0x0f; + mv_y &= 0x0f; + + return mv_y + mv_x; +} + +static int tw5864_is_motion_triggered(struct tw5864_h264_frame *frame) +{ + struct tw5864_input *input = frame->input; + u32 *mv = (u32 *)frame->mv.addr; + int i; + int detected = 0; + + for (i = 0; i < MD_CELLS; i++) { + const u16 thresh = input->md_threshold_grid_values[i]; + const unsigned int metric = tw5864_md_metric_from_mvd(mv[i]); + + if (metric > thresh) + detected = 1; + + if (detected) + break; + } + return detected; +} + +static void tw5864_handle_frame_work(struct work_struct *t) +{ + struct tw5864_dev *dev = from_work(dev, t, bh_work); + unsigned long flags; + int batch_size = H264_BUF_CNT; + + spin_lock_irqsave(&dev->slock, flags); + while (dev->h264_buf_r_index != dev->h264_buf_w_index && batch_size--) { + struct tw5864_h264_frame *frame = + &dev->h264_buf[dev->h264_buf_r_index]; + + spin_unlock_irqrestore(&dev->slock, flags); + dma_sync_single_for_cpu(&dev->pci->dev, frame->vlc.dma_addr, + H264_VLC_BUF_SIZE, DMA_FROM_DEVICE); + dma_sync_single_for_cpu(&dev->pci->dev, frame->mv.dma_addr, + H264_MV_BUF_SIZE, DMA_FROM_DEVICE); + tw5864_handle_frame(frame); + dma_sync_single_for_device(&dev->pci->dev, frame->vlc.dma_addr, + H264_VLC_BUF_SIZE, DMA_FROM_DEVICE); + dma_sync_single_for_device(&dev->pci->dev, frame->mv.dma_addr, + H264_MV_BUF_SIZE, DMA_FROM_DEVICE); + spin_lock_irqsave(&dev->slock, flags); + + dev->h264_buf_r_index++; + dev->h264_buf_r_index %= H264_BUF_CNT; + } + spin_unlock_irqrestore(&dev->slock, flags); +} + +#ifdef DEBUG +static u32 tw5864_vlc_checksum(u32 *data, int len) +{ + u32 val, count_len = len; + + val = *data++; + while (((count_len >> 2) - 1) > 0) { + val ^= *data++; + count_len -= 4; + } + val ^= htonl((len >> 2)); + return val; +} +#endif + +static void tw5864_handle_frame(struct tw5864_h264_frame *frame) +{ +#define SKIP_VLCBUF_BYTES 3 + struct tw5864_input *input = frame->input; + struct tw5864_dev *dev = input->root; + struct tw5864_buf *vb; + struct vb2_v4l2_buffer *v4l2_buf; + int frame_len = frame->vlc_len - SKIP_VLCBUF_BYTES; + u8 *dst = input->buf_cur_ptr; + u8 tail_mask, vlc_mask = 0; + int i; + u8 vlc_first_byte = ((u8 *)(frame->vlc.addr + SKIP_VLCBUF_BYTES))[0]; + unsigned long flags; + int zero_run; + u8 *src; + u8 *src_end; + +#ifdef DEBUG + if (frame->checksum != + tw5864_vlc_checksum((u32 *)frame->vlc.addr, frame_len)) + dev_err(&dev->pci->dev, + "Checksum of encoded frame doesn't match!\n"); +#endif + + spin_lock_irqsave(&input->slock, flags); + vb = input->vb; + input->vb = NULL; + spin_unlock_irqrestore(&input->slock, flags); + + if (!vb) { /* Gone because of disabling */ + dev_dbg(&dev->pci->dev, "vb is empty, dropping frame\n"); + return; + } + + v4l2_buf = to_vb2_v4l2_buffer(&vb->vb.vb2_buf); + + /* + * Check for space. + * Mind the overhead of startcode emulation prevention. + */ + if (input->buf_cur_space_left < frame_len * 5 / 4) { + dev_err_once(&dev->pci->dev, + "Left space in vb2 buffer, %d bytes, is less than considered safely enough to put frame of length %d. Dropping this frame.\n", + input->buf_cur_space_left, frame_len); + return; + } + + for (i = 0; i < 8 - input->tail_nb_bits; i++) + vlc_mask |= 1 << i; + tail_mask = (~vlc_mask) & 0xff; + + dst[0] = (input->tail & tail_mask) | (vlc_first_byte & vlc_mask); + frame_len--; + dst++; + + /* H.264 startcode emulation prevention */ + src = frame->vlc.addr + SKIP_VLCBUF_BYTES + 1; + src_end = src + frame_len; + zero_run = 0; + for (; src < src_end; src++) { + if (zero_run < 2) { + if (*src == 0) + ++zero_run; + else + zero_run = 0; + } else { + if ((*src & ~0x03) == 0) + *dst++ = 0x03; + zero_run = *src == 0; + } + *dst++ = *src; + } + + vb2_set_plane_payload(&vb->vb.vb2_buf, 0, + dst - (u8 *)vb2_plane_vaddr(&vb->vb.vb2_buf, 0)); + + vb->vb.vb2_buf.timestamp = frame->timestamp; + v4l2_buf->field = V4L2_FIELD_INTERLACED; + v4l2_buf->sequence = frame->seqno; + + /* Check for motion flags */ + if (frame->gop_seqno /* P-frame */ && + tw5864_is_motion_triggered(frame)) { + struct v4l2_event ev = { + .type = V4L2_EVENT_MOTION_DET, + .u.motion_det = { + .flags = V4L2_EVENT_MD_FL_HAVE_FRAME_SEQ, + .frame_sequence = v4l2_buf->sequence, + }, + }; + + v4l2_event_queue(&input->vdev, &ev); + } + + vb2_buffer_done(&vb->vb.vb2_buf, VB2_BUF_STATE_DONE); +} + +static v4l2_std_id tw5864_get_v4l2_std(enum tw5864_vid_std std) +{ + switch (std) { + case STD_NTSC: return V4L2_STD_NTSC_M; + case STD_PAL: return V4L2_STD_PAL_B; + case STD_SECAM: return V4L2_STD_SECAM_B; + case STD_NTSC443: return V4L2_STD_NTSC_443; + case STD_PAL_M: return V4L2_STD_PAL_M; + case STD_PAL_CN: return V4L2_STD_PAL_Nc; + case STD_PAL_60: return V4L2_STD_PAL_60; + case STD_INVALID: return V4L2_STD_UNKNOWN; + } + return 0; +} + +static enum tw5864_vid_std tw5864_from_v4l2_std(v4l2_std_id v4l2_std) +{ + if (v4l2_std & V4L2_STD_NTSC_M) + return STD_NTSC; + if (v4l2_std & V4L2_STD_PAL_B) + return STD_PAL; + if (v4l2_std & V4L2_STD_SECAM_B) + return STD_SECAM; + if (v4l2_std & V4L2_STD_NTSC_443) + return STD_NTSC443; + if (v4l2_std & V4L2_STD_PAL_M) + return STD_PAL_M; + if (v4l2_std & V4L2_STD_PAL_Nc) + return STD_PAL_CN; + if (v4l2_std & V4L2_STD_PAL_60) + return STD_PAL_60; + + return STD_INVALID; +} + +static void tw5864_encoder_tables_upload(struct tw5864_dev *dev) +{ + int i; + + tw_writel(TW5864_VLC_RD, 0x1); + for (i = 0; i < VLC_LOOKUP_TABLE_LEN; i++) { + tw_writel((TW5864_VLC_STREAM_MEM_START + i * 4), + encoder_vlc_lookup_table[i]); + } + tw_writel(TW5864_VLC_RD, 0x0); + + for (i = 0; i < QUANTIZATION_TABLE_LEN; i++) { + tw_writel((TW5864_QUAN_TAB + i * 4), + forward_quantization_table[i]); + } + + for (i = 0; i < QUANTIZATION_TABLE_LEN; i++) { + tw_writel((TW5864_QUAN_TAB + i * 4), + inverse_quantization_table[i]); + } +} diff --git a/drivers/media/pci/tw5864/tw5864.h b/drivers/media/pci/tw5864/tw5864.h new file mode 100644 index 000000000..2da5f4215 --- /dev/null +++ b/drivers/media/pci/tw5864/tw5864.h @@ -0,0 +1,197 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * TW5864 driver - common header file + * + * Copyright (C) 2016 Bluecherry, LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tw5864-reg.h" + +#define PCI_DEVICE_ID_TECHWELL_5864 0x5864 + +#define TW5864_NORMS V4L2_STD_ALL + +/* ----------------------------------------------------------- */ +/* card configuration */ + +#define TW5864_INPUTS 4 + +/* The TW5864 uses 192 (16x12) detection cells in full screen for motion + * detection. Each detection cell is composed of 44 pixels and 20 lines for + * NTSC and 24 lines for PAL. + */ +#define MD_CELLS_HOR 16 +#define MD_CELLS_VERT 12 +#define MD_CELLS (MD_CELLS_HOR * MD_CELLS_VERT) + +#define H264_VLC_BUF_SIZE 0x80000 +#define H264_MV_BUF_SIZE 0x2000 /* device writes 5396 bytes */ +#define QP_VALUE 28 +#define MAX_GOP_SIZE 255 +#define GOP_SIZE MAX_GOP_SIZE + +enum resolution { + D1 = 1, + HD1 = 2, /* half d1 - 360x(240|288) */ + CIF = 3, + QCIF = 4, +}; + +/* ----------------------------------------------------------- */ +/* device / file handle status */ + +struct tw5864_dev; /* forward delclaration */ + +/* buffer for one video/vbi/ts frame */ +struct tw5864_buf { + struct vb2_v4l2_buffer vb; + struct list_head list; + + unsigned int size; +}; + +struct tw5864_dma_buf { + void *addr; + dma_addr_t dma_addr; +}; + +enum tw5864_vid_std { + STD_NTSC = 0, /* NTSC (M) */ + STD_PAL = 1, /* PAL (B, D, G, H, I) */ + STD_SECAM = 2, /* SECAM */ + STD_NTSC443 = 3, /* NTSC4.43 */ + STD_PAL_M = 4, /* PAL (M) */ + STD_PAL_CN = 5, /* PAL (CN) */ + STD_PAL_60 = 6, /* PAL 60 */ + STD_INVALID = 7, + STD_AUTO = 7, +}; + +struct tw5864_input { + int nr; /* input number */ + struct tw5864_dev *root; + struct mutex lock; /* used for vidq and vdev */ + spinlock_t slock; /* used for sync between ISR, bh_work & V4L2 API */ + struct video_device vdev; + struct v4l2_ctrl_handler hdl; + struct vb2_queue vidq; + struct list_head active; + enum resolution resolution; + unsigned int width, height; + unsigned int frame_seqno; + unsigned int frame_gop_seqno; + unsigned int h264_idr_pic_id; + int enabled; + enum tw5864_vid_std std; + v4l2_std_id v4l2_std; + int tail_nb_bits; + u8 tail; + u8 *buf_cur_ptr; + int buf_cur_space_left; + + u32 reg_interlacing; + u32 reg_vlc; + u32 reg_dsp_codec; + u32 reg_dsp; + u32 reg_emu; + u32 reg_dsp_qp; + u32 reg_dsp_ref_mvp_lambda; + u32 reg_dsp_i4x4_weight; + u32 buf_id; + + struct tw5864_buf *vb; + + struct v4l2_ctrl *md_threshold_grid_ctrl; + u16 md_threshold_grid_values[12 * 16]; + int qp; + int gop; + + /* + * In (1/MAX_FPS) units. + * For max FPS (default), set to 1. + * For 1 FPS, set to e.g. 32. + */ + int frame_interval; + unsigned long new_frame_deadline; +}; + +struct tw5864_h264_frame { + struct tw5864_dma_buf vlc; + struct tw5864_dma_buf mv; + int vlc_len; + u32 checksum; + struct tw5864_input *input; + u64 timestamp; + unsigned int seqno; + unsigned int gop_seqno; +}; + +/* global device status */ +struct tw5864_dev { + spinlock_t slock; /* used for sync between ISR, bh_work & V4L2 API */ + struct v4l2_device v4l2_dev; + struct tw5864_input inputs[TW5864_INPUTS]; +#define H264_BUF_CNT 4 + struct tw5864_h264_frame h264_buf[H264_BUF_CNT]; + int h264_buf_r_index; + int h264_buf_w_index; + + struct work_struct bh_work; + + int encoder_busy; + /* Input number to check next for ready raw picture (in RR fashion) */ + int next_input; + + /* pci i/o */ + char name[64]; + struct pci_dev *pci; + void __iomem *mmio; + u32 irqmask; +}; + +#define tw_readl(reg) readl(dev->mmio + reg) +#define tw_mask_readl(reg, mask) \ + (tw_readl(reg) & (mask)) +#define tw_mask_shift_readl(reg, mask, shift) \ + (tw_mask_readl((reg), ((mask) << (shift))) >> (shift)) + +#define tw_writel(reg, value) writel((value), dev->mmio + reg) +#define tw_mask_writel(reg, mask, value) \ + tw_writel(reg, (tw_readl(reg) & ~(mask)) | ((value) & (mask))) +#define tw_mask_shift_writel(reg, mask, shift, value) \ + tw_mask_writel((reg), ((mask) << (shift)), ((value) << (shift))) + +#define tw_setl(reg, bit) tw_writel((reg), tw_readl(reg) | (bit)) +#define tw_clearl(reg, bit) tw_writel((reg), tw_readl(reg) & ~(bit)) + +u8 tw5864_indir_readb(struct tw5864_dev *dev, u16 addr); +#define tw_indir_readb(addr) tw5864_indir_readb(dev, addr) +void tw5864_indir_writeb(struct tw5864_dev *dev, u16 addr, u8 data); +#define tw_indir_writeb(addr, data) tw5864_indir_writeb(dev, addr, data) + +void tw5864_irqmask_apply(struct tw5864_dev *dev); +int tw5864_video_init(struct tw5864_dev *dev, int *video_nr); +void tw5864_video_fini(struct tw5864_dev *dev); +void tw5864_prepare_frame_headers(struct tw5864_input *input); +void tw5864_h264_put_stream_header(u8 **buf, size_t *space_left, int qp, + int width, int height); +void tw5864_h264_put_slice_header(u8 **buf, size_t *space_left, + unsigned int idr_pic_id, + unsigned int frame_gop_seqno, + int *tail_nb_bits, u8 *tail); +void tw5864_request_encoded_frame(struct tw5864_input *input); diff --git a/drivers/media/pci/tw68/Kconfig b/drivers/media/pci/tw68/Kconfig new file mode 100644 index 000000000..ef9c0e886 --- /dev/null +++ b/drivers/media/pci/tw68/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_TW68 + tristate "Techwell tw68x Video For Linux" + depends on VIDEO_DEV && PCI + select VIDEOBUF2_DMA_SG + help + Support for Techwell tw68xx based frame grabber boards. + + To compile this driver as a module, choose M here: the + module will be called tw68. diff --git a/drivers/media/pci/tw68/Makefile b/drivers/media/pci/tw68/Makefile new file mode 100644 index 000000000..d1aec257e --- /dev/null +++ b/drivers/media/pci/tw68/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +tw68-objs := tw68-core.o tw68-video.o tw68-risc.o + +obj-$(CONFIG_VIDEO_TW68) += tw68.o diff --git a/drivers/media/pci/tw68/tw68-core.c b/drivers/media/pci/tw68/tw68-core.c new file mode 100644 index 000000000..509d7ddec --- /dev/null +++ b/drivers/media/pci/tw68/tw68-core.c @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * tw68-core.c + * Core functions for the Techwell 68xx driver + * + * Much of this code is derived from the cx88 and sa7134 drivers, which + * were in turn derived from the bt87x driver. The original work was by + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab, + * Hans Verkuil, Andy Walls and many others. Their work is gratefully + * acknowledged. Full credit goes to them - any problems within this code + * are mine. + * + * Copyright (C) 2009 William M. Brack + * + * Refactored and updated to the latest v4l core frameworks: + * + * Copyright (C) 2014 Hans Verkuil + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "tw68.h" +#include "tw68-reg.h" + +MODULE_DESCRIPTION("v4l2 driver module for tw6800 based video capture cards"); +MODULE_AUTHOR("William M. Brack"); +MODULE_AUTHOR("Hans Verkuil "); +MODULE_LICENSE("GPL"); + +static unsigned int latency = UNSET; +module_param(latency, int, 0444); +MODULE_PARM_DESC(latency, "pci latency timer"); + +static unsigned int video_nr[] = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET }; +module_param_array(video_nr, int, NULL, 0444); +MODULE_PARM_DESC(video_nr, "video device number"); + +static unsigned int card[] = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET }; +module_param_array(card, int, NULL, 0444); +MODULE_PARM_DESC(card, "card type"); + +static atomic_t tw68_instance = ATOMIC_INIT(0); + +/* ------------------------------------------------------------------ */ + +/* + * Please add any new PCI IDs to: https://pci-ids.ucw.cz. This keeps + * the PCI ID database up to date. Note that the entries must be + * added under vendor 0x1797 (Techwell Inc.) as subsystem IDs. + */ +static const struct pci_device_id tw68_pci_tbl[] = { + { PCI_VDEVICE(TECHWELL, PCI_DEVICE_ID_TECHWELL_6800) }, + { PCI_VDEVICE(TECHWELL, PCI_DEVICE_ID_TECHWELL_6801) }, + { PCI_VDEVICE(TECHWELL, PCI_DEVICE_ID_TECHWELL_6804) }, + { PCI_VDEVICE(TECHWELL, PCI_DEVICE_ID_TECHWELL_6816_1) }, + { PCI_VDEVICE(TECHWELL, PCI_DEVICE_ID_TECHWELL_6816_2) }, + { PCI_VDEVICE(TECHWELL, PCI_DEVICE_ID_TECHWELL_6816_3) }, + { PCI_VDEVICE(TECHWELL, PCI_DEVICE_ID_TECHWELL_6816_4) }, + { } +}; + +/* ------------------------------------------------------------------ */ + + +/* + * The device is given a "soft reset". According to the specifications, + * after this "all register content remain unchanged", so we also write + * to all specified registers manually as well (mostly to manufacturer's + * specified reset values) + */ +static int tw68_hw_init1(struct tw68_dev *dev) +{ + /* Assure all interrupts are disabled */ + tw_writel(TW68_INTMASK, 0); /* 020 */ + /* Clear any pending interrupts */ + tw_writel(TW68_INTSTAT, 0xffffffff); /* 01C */ + /* Stop risc processor, set default buffer level */ + tw_writel(TW68_DMAC, 0x1600); + + tw_writeb(TW68_ACNTL, 0x80); /* 218 soft reset */ + msleep(100); + + tw_writeb(TW68_INFORM, 0x40); /* 208 mux0, 27mhz xtal */ + tw_writeb(TW68_OPFORM, 0x04); /* 20C analog line-lock */ + tw_writeb(TW68_HSYNC, 0); /* 210 color-killer high sens */ + tw_writeb(TW68_ACNTL, 0x42); /* 218 int vref #2, chroma adc off */ + + tw_writeb(TW68_CROP_HI, 0x02); /* 21C Hactive m.s. bits */ + tw_writeb(TW68_VDELAY_LO, 0x12);/* 220 Mfg specified reset value */ + tw_writeb(TW68_VACTIVE_LO, 0xf0); + tw_writeb(TW68_HDELAY_LO, 0x0f); + tw_writeb(TW68_HACTIVE_LO, 0xd0); + + tw_writeb(TW68_CNTRL1, 0xcd); /* 230 Wide Chroma BPF B/W + * Secam reduction, Adap comb for + * NTSC, Op Mode 1 */ + + tw_writeb(TW68_VSCALE_LO, 0); /* 234 */ + tw_writeb(TW68_SCALE_HI, 0x11); /* 238 */ + tw_writeb(TW68_HSCALE_LO, 0); /* 23c */ + tw_writeb(TW68_BRIGHT, 0); /* 240 */ + tw_writeb(TW68_CONTRAST, 0x5c); /* 244 */ + tw_writeb(TW68_SHARPNESS, 0x51);/* 248 */ + tw_writeb(TW68_SAT_U, 0x80); /* 24C */ + tw_writeb(TW68_SAT_V, 0x80); /* 250 */ + tw_writeb(TW68_HUE, 0x00); /* 254 */ + + /* TODO - Check that none of these are set by control defaults */ + tw_writeb(TW68_SHARP2, 0x53); /* 258 Mfg specified reset val */ + tw_writeb(TW68_VSHARP, 0x80); /* 25C Sharpness Coring val 8 */ + tw_writeb(TW68_CORING, 0x44); /* 260 CTI and Vert Peak coring */ + tw_writeb(TW68_CNTRL2, 0x00); /* 268 No power saving enabled */ + tw_writeb(TW68_SDT, 0x07); /* 270 Enable shadow reg, auto-det */ + tw_writeb(TW68_SDTR, 0x7f); /* 274 All stds recog, don't start */ + tw_writeb(TW68_CLMPG, 0x50); /* 280 Clamp end at 40 sys clocks */ + tw_writeb(TW68_IAGC, 0x22); /* 284 Mfg specified reset val */ + tw_writeb(TW68_AGCGAIN, 0xf0); /* 288 AGC gain when loop disabled */ + tw_writeb(TW68_PEAKWT, 0xd8); /* 28C White peak threshold */ + tw_writeb(TW68_CLMPL, 0x3c); /* 290 Y channel clamp level */ +/* tw_writeb(TW68_SYNCT, 0x38);*/ /* 294 Sync amplitude */ + tw_writeb(TW68_SYNCT, 0x30); /* 294 Sync amplitude */ + tw_writeb(TW68_MISSCNT, 0x44); /* 298 Horiz sync, VCR detect sens */ + tw_writeb(TW68_PCLAMP, 0x28); /* 29C Clamp pos from PLL sync */ + /* Bit DETV of VCNTL1 helps sync multi cams/chip board */ + tw_writeb(TW68_VCNTL1, 0x04); /* 2A0 */ + tw_writeb(TW68_VCNTL2, 0); /* 2A4 */ + tw_writeb(TW68_CKILL, 0x68); /* 2A8 Mfg specified reset val */ + tw_writeb(TW68_COMB, 0x44); /* 2AC Mfg specified reset val */ + tw_writeb(TW68_LDLY, 0x30); /* 2B0 Max positive luma delay */ + tw_writeb(TW68_MISC1, 0x14); /* 2B4 Mfg specified reset val */ + tw_writeb(TW68_LOOP, 0xa5); /* 2B8 Mfg specified reset val */ + tw_writeb(TW68_MISC2, 0xe0); /* 2BC Enable colour killer */ + tw_writeb(TW68_MVSN, 0); /* 2C0 */ + tw_writeb(TW68_CLMD, 0x05); /* 2CC slice level auto, clamp med. */ + tw_writeb(TW68_IDCNTL, 0); /* 2D0 Writing zero to this register + * selects NTSC ID detection, + * but doesn't change the + * sensitivity (which has a reset + * value of 1E). Since we are + * not doing auto-detection, it + * has no real effect */ + tw_writeb(TW68_CLCNTL1, 0); /* 2D4 */ + tw_writel(TW68_VBIC, 0x03); /* 010 */ + tw_writel(TW68_CAP_CTL, 0x03); /* 040 Enable both even & odd flds */ + tw_writel(TW68_DMAC, 0x2000); /* patch set had 0x2080 */ + tw_writel(TW68_TESTREG, 0); /* 02C */ + + /* + * Some common boards, especially inexpensive single-chip models, + * use the GPIO bits 0-3 to control an on-board video-output mux. + * For these boards, we need to set up the GPIO register into + * "normal" mode, set bits 0-3 as output, and then set those bits + * zero. + * + * Eventually, it would be nice if we could identify these boards + * uniquely, and only do this initialisation if the board has been + * identify. For the moment, however, it shouldn't hurt anything + * to do these steps. + */ + tw_writel(TW68_GPIOC, 0); /* Set the GPIO to "normal", no ints */ + tw_writel(TW68_GPOE, 0x0f); /* Set bits 0-3 to "output" */ + tw_writel(TW68_GPDATA, 0); /* Set all bits to low state */ + + /* Initialize the device control structures */ + mutex_init(&dev->lock); + spin_lock_init(&dev->slock); + + /* Initialize any subsystems */ + tw68_video_init1(dev); + return 0; +} + +static irqreturn_t tw68_irq(int irq, void *dev_id) +{ + struct tw68_dev *dev = dev_id; + u32 status, orig; + int loop; + + status = orig = tw_readl(TW68_INTSTAT) & dev->pci_irqmask; + /* Check if anything to do */ + if (0 == status) + return IRQ_NONE; /* Nope - return */ + for (loop = 0; loop < 10; loop++) { + if (status & dev->board_virqmask) /* video interrupt */ + tw68_irq_video_done(dev, status); + status = tw_readl(TW68_INTSTAT) & dev->pci_irqmask; + if (0 == status) + return IRQ_HANDLED; + } + dev_dbg(&dev->pci->dev, "%s: **** INTERRUPT NOT HANDLED - clearing mask (orig 0x%08x, cur 0x%08x)", + dev->name, orig, tw_readl(TW68_INTSTAT)); + dev_dbg(&dev->pci->dev, "%s: pci_irqmask 0x%08x; board_virqmask 0x%08x ****\n", + dev->name, dev->pci_irqmask, dev->board_virqmask); + tw_clearl(TW68_INTMASK, dev->pci_irqmask); + return IRQ_HANDLED; +} + +static int tw68_initdev(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct tw68_dev *dev; + int vidnr = -1; + int err; + + dev = devm_kzalloc(&pci_dev->dev, sizeof(*dev), GFP_KERNEL); + if (NULL == dev) + return -ENOMEM; + + dev->instance = v4l2_device_set_name(&dev->v4l2_dev, "tw68", + &tw68_instance); + + err = v4l2_device_register(&pci_dev->dev, &dev->v4l2_dev); + if (err) + return err; + + /* pci init */ + dev->pci = pci_dev; + if (pci_enable_device(pci_dev)) { + err = -EIO; + goto fail1; + } + + dev->name = dev->v4l2_dev.name; + + if (UNSET != latency) { + pr_info("%s: setting pci latency timer to %d\n", + dev->name, latency); + pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, latency); + } + + /* print pci info */ + pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &dev->pci_rev); + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat); + pr_info("%s: found at %s, rev: %d, irq: %d, latency: %d, mmio: 0x%llx\n", + dev->name, pci_name(pci_dev), dev->pci_rev, pci_dev->irq, + dev->pci_lat, (u64)pci_resource_start(pci_dev, 0)); + pci_set_master(pci_dev); + err = dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(32)); + if (err) { + pr_info("%s: Oops: no 32bit PCI DMA ???\n", dev->name); + goto fail1; + } + + switch (pci_id->device) { + case PCI_DEVICE_ID_TECHWELL_6800: /* TW6800 */ + dev->vdecoder = TW6800; + dev->board_virqmask = TW68_VID_INTS; + break; + case PCI_DEVICE_ID_TECHWELL_6801: /* Video decoder for TW6802 */ + dev->vdecoder = TW6801; + dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX; + break; + case PCI_DEVICE_ID_TECHWELL_6804: /* Video decoder for TW6804 */ + dev->vdecoder = TW6804; + dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX; + break; + default: + dev->vdecoder = TWXXXX; /* To be announced */ + dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX; + break; + } + + /* get mmio */ + if (!request_mem_region(pci_resource_start(pci_dev, 0), + pci_resource_len(pci_dev, 0), + dev->name)) { + err = -EBUSY; + pr_err("%s: can't get MMIO memory @ 0x%llx\n", + dev->name, + (unsigned long long)pci_resource_start(pci_dev, 0)); + goto fail1; + } + dev->lmmio = ioremap(pci_resource_start(pci_dev, 0), + pci_resource_len(pci_dev, 0)); + dev->bmmio = (__u8 __iomem *)dev->lmmio; + if (NULL == dev->lmmio) { + err = -EIO; + pr_err("%s: can't ioremap() MMIO memory\n", + dev->name); + goto fail2; + } + /* initialize hardware #1 */ + /* Then do any initialisation wanted before interrupts are on */ + tw68_hw_init1(dev); + + /* get irq */ + err = devm_request_irq(&pci_dev->dev, pci_dev->irq, tw68_irq, + IRQF_SHARED, dev->name, dev); + if (err < 0) { + pr_err("%s: can't get IRQ %d\n", + dev->name, pci_dev->irq); + goto fail3; + } + + /* + * Now do remainder of initialisation, first for + * things unique for this card, then for general board + */ + if (dev->instance < TW68_MAXBOARDS) + vidnr = video_nr[dev->instance]; + /* initialise video function first */ + err = tw68_video_init2(dev, vidnr); + if (err < 0) { + pr_err("%s: can't register video device\n", + dev->name); + goto fail4; + } + tw_setl(TW68_INTMASK, dev->pci_irqmask); + + pr_info("%s: registered device %s\n", + dev->name, video_device_node_name(&dev->vdev)); + + return 0; + +fail4: + video_unregister_device(&dev->vdev); +fail3: + iounmap(dev->lmmio); +fail2: + release_mem_region(pci_resource_start(pci_dev, 0), + pci_resource_len(pci_dev, 0)); +fail1: + v4l2_device_unregister(&dev->v4l2_dev); + return err; +} + +static void tw68_finidev(struct pci_dev *pci_dev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct tw68_dev *dev = + container_of(v4l2_dev, struct tw68_dev, v4l2_dev); + + /* shutdown subsystems */ + tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN); + tw_writel(TW68_INTMASK, 0); + + /* unregister */ + video_unregister_device(&dev->vdev); + v4l2_ctrl_handler_free(&dev->hdl); + + /* release resources */ + iounmap(dev->lmmio); + release_mem_region(pci_resource_start(pci_dev, 0), + pci_resource_len(pci_dev, 0)); + + v4l2_device_unregister(&dev->v4l2_dev); +} + +static int __maybe_unused tw68_suspend(struct device *dev_d) +{ + struct pci_dev *pci_dev = to_pci_dev(dev_d); + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct tw68_dev *dev = container_of(v4l2_dev, + struct tw68_dev, v4l2_dev); + + tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN); + dev->pci_irqmask &= ~TW68_VID_INTS; + tw_writel(TW68_INTMASK, 0); + + synchronize_irq(pci_dev->irq); + + vb2_discard_done(&dev->vidq); + + return 0; +} + +static int __maybe_unused tw68_resume(struct device *dev_d) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev_d); + struct tw68_dev *dev = container_of(v4l2_dev, + struct tw68_dev, v4l2_dev); + struct tw68_buf *buf; + unsigned long flags; + + /* Do things that are done in tw68_initdev , + except of initializing memory structures.*/ + + msleep(100); + + tw68_set_tvnorm_hw(dev); + + /*resume unfinished buffer(s)*/ + spin_lock_irqsave(&dev->slock, flags); + buf = container_of(dev->active.next, struct tw68_buf, list); + + tw68_video_start_dma(dev, buf); + + spin_unlock_irqrestore(&dev->slock, flags); + + return 0; +} + +/* ----------------------------------------------------------- */ + +static SIMPLE_DEV_PM_OPS(tw68_pm_ops, tw68_suspend, tw68_resume); + +static struct pci_driver tw68_pci_driver = { + .name = "tw68", + .id_table = tw68_pci_tbl, + .probe = tw68_initdev, + .remove = tw68_finidev, + .driver.pm = &tw68_pm_ops, +}; + +module_pci_driver(tw68_pci_driver); diff --git a/drivers/media/pci/tw68/tw68-reg.h b/drivers/media/pci/tw68/tw68-reg.h new file mode 100644 index 000000000..8aeef452e --- /dev/null +++ b/drivers/media/pci/tw68/tw68-reg.h @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * tw68-reg.h - TW68xx register offsets + * + * Much of this code is derived from the cx88 and sa7134 drivers, which + * were in turn derived from the bt87x driver. The original work was by + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab, + * Hans Verkuil, Andy Walls and many others. Their work is gratefully + * acknowledged. Full credit goes to them - any problems within this code + * are mine. + * + * Copyright (C) William M. Brack + * + * Refactored and updated to the latest v4l core frameworks: + * + * Copyright (C) 2014 Hans Verkuil +*/ + +#ifndef _TW68_REG_H_ +#define _TW68_REG_H_ + +/* ---------------------------------------------------------------------- */ +#define TW68_DMAC 0x000 +#define TW68_DMAP_SA 0x004 +#define TW68_DMAP_EXE 0x008 +#define TW68_DMAP_PP 0x00c +#define TW68_VBIC 0x010 +#define TW68_SBUSC 0x014 +#define TW68_SBUSSD 0x018 +#define TW68_INTSTAT 0x01C +#define TW68_INTMASK 0x020 +#define TW68_GPIOC 0x024 +#define TW68_GPOE 0x028 +#define TW68_TESTREG 0x02C +#define TW68_SBUSRD 0x030 +#define TW68_SBUS_TRIG 0x034 +#define TW68_CAP_CTL 0x040 +#define TW68_SUBSYS 0x054 +#define TW68_I2C_RST 0x064 +#define TW68_VBIINST 0x06C +/* define bits in FIFO and DMAP Control reg */ +#define TW68_DMAP_EN (1 << 0) +#define TW68_FIFO_EN (1 << 1) +/* define the Interrupt Status Register bits */ +#define TW68_SBDONE (1 << 0) +#define TW68_DMAPI (1 << 1) +#define TW68_GPINT (1 << 2) +#define TW68_FFOF (1 << 3) +#define TW68_FDMIS (1 << 4) +#define TW68_DMAPERR (1 << 5) +#define TW68_PABORT (1 << 6) +#define TW68_SBDONE2 (1 << 12) +#define TW68_SBERR2 (1 << 13) +#define TW68_PPERR (1 << 14) +#define TW68_FFERR (1 << 15) +#define TW68_DET50 (1 << 16) +#define TW68_FLOCK (1 << 17) +#define TW68_CCVALID (1 << 18) +#define TW68_VLOCK (1 << 19) +#define TW68_FIELD (1 << 20) +#define TW68_SLOCK (1 << 21) +#define TW68_HLOCK (1 << 22) +#define TW68_VDLOSS (1 << 23) +#define TW68_SBERR (1 << 24) +/* define the i2c control register bits */ +#define TW68_SBMODE (0) +#define TW68_WREN (1) +#define TW68_SSCLK (6) +#define TW68_SSDAT (7) +#define TW68_SBCLK (8) +#define TW68_WDLEN (16) +#define TW68_RDLEN (20) +#define TW68_SBRW (24) +#define TW68_SBDEV (25) + +#define TW68_SBMODE_B (1 << TW68_SBMODE) +#define TW68_WREN_B (1 << TW68_WREN) +#define TW68_SSCLK_B (1 << TW68_SSCLK) +#define TW68_SSDAT_B (1 << TW68_SSDAT) +#define TW68_SBRW_B (1 << TW68_SBRW) + +#define TW68_GPDATA 0x100 +#define TW68_STATUS1 0x204 +#define TW68_INFORM 0x208 +#define TW68_OPFORM 0x20C +#define TW68_HSYNC 0x210 +#define TW68_ACNTL 0x218 +#define TW68_CROP_HI 0x21C +#define TW68_VDELAY_LO 0x220 +#define TW68_VACTIVE_LO 0x224 +#define TW68_HDELAY_LO 0x228 +#define TW68_HACTIVE_LO 0x22C +#define TW68_CNTRL1 0x230 +#define TW68_VSCALE_LO 0x234 +#define TW68_SCALE_HI 0x238 +#define TW68_HSCALE_LO 0x23C +#define TW68_BRIGHT 0x240 +#define TW68_CONTRAST 0x244 +#define TW68_SHARPNESS 0x248 +#define TW68_SAT_U 0x24C +#define TW68_SAT_V 0x250 +#define TW68_HUE 0x254 +#define TW68_SHARP2 0x258 +#define TW68_VSHARP 0x25C +#define TW68_CORING 0x260 +#define TW68_VBICNTL 0x264 +#define TW68_CNTRL2 0x268 +#define TW68_CC_DATA 0x26C +#define TW68_SDT 0x270 +#define TW68_SDTR 0x274 +#define TW68_RESERV2 0x278 +#define TW68_RESERV3 0x27C +#define TW68_CLMPG 0x280 +#define TW68_IAGC 0x284 +#define TW68_AGCGAIN 0x288 +#define TW68_PEAKWT 0x28C +#define TW68_CLMPL 0x290 +#define TW68_SYNCT 0x294 +#define TW68_MISSCNT 0x298 +#define TW68_PCLAMP 0x29C +#define TW68_VCNTL1 0x2A0 +#define TW68_VCNTL2 0x2A4 +#define TW68_CKILL 0x2A8 +#define TW68_COMB 0x2AC +#define TW68_LDLY 0x2B0 +#define TW68_MISC1 0x2B4 +#define TW68_LOOP 0x2B8 +#define TW68_MISC2 0x2BC +#define TW68_MVSN 0x2C0 +#define TW68_STATUS2 0x2C4 +#define TW68_HFREF 0x2C8 +#define TW68_CLMD 0x2CC +#define TW68_IDCNTL 0x2D0 +#define TW68_CLCNTL1 0x2D4 + +/* Audio */ +#define TW68_ACKI1 0x300 +#define TW68_ACKI2 0x304 +#define TW68_ACKI3 0x308 +#define TW68_ACKN1 0x30C +#define TW68_ACKN2 0x310 +#define TW68_ACKN3 0x314 +#define TW68_SDIV 0x318 +#define TW68_LRDIV 0x31C +#define TW68_ACCNTL 0x320 + +#define TW68_VSCTL 0x3B8 +#define TW68_CHROMAGVAL 0x3BC + +#define TW68_F2CROP_HI 0x3DC +#define TW68_F2VDELAY_LO 0x3E0 +#define TW68_F2VACTIVE_LO 0x3E4 +#define TW68_F2HDELAY_LO 0x3E8 +#define TW68_F2HACTIVE_LO 0x3EC +#define TW68_F2CNT 0x3F0 +#define TW68_F2VSCALE_LO 0x3F4 +#define TW68_F2SCALE_HI 0x3F8 +#define TW68_F2HSCALE_LO 0x3FC + +#define RISC_INT_BIT 0x08000000 +#define RISC_SYNCO 0xC0000000 +#define RISC_SYNCE 0xD0000000 +#define RISC_JUMP 0xB0000000 +#define RISC_LINESTART 0x90000000 +#define RISC_INLINE 0xA0000000 + +#define VideoFormatNTSC 0 +#define VideoFormatNTSCJapan 0 +#define VideoFormatPALBDGHI 1 +#define VideoFormatSECAM 2 +#define VideoFormatNTSC443 3 +#define VideoFormatPALM 4 +#define VideoFormatPALN 5 +#define VideoFormatPALNC 5 +#define VideoFormatPAL60 6 +#define VideoFormatAuto 7 + +#define ColorFormatRGB32 0x00 +#define ColorFormatRGB24 0x10 +#define ColorFormatRGB16 0x20 +#define ColorFormatRGB15 0x30 +#define ColorFormatYUY2 0x40 +#define ColorFormatBSWAP 0x04 +#define ColorFormatWSWAP 0x08 +#define ColorFormatGamma 0x80 +#endif diff --git a/drivers/media/pci/tw68/tw68-risc.c b/drivers/media/pci/tw68/tw68-risc.c new file mode 100644 index 000000000..e793db613 --- /dev/null +++ b/drivers/media/pci/tw68/tw68-risc.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * tw68_risc.c + * Part of the device driver for Techwell 68xx based cards + * + * Much of this code is derived from the cx88 and sa7134 drivers, which + * were in turn derived from the bt87x driver. The original work was by + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab, + * Hans Verkuil, Andy Walls and many others. Their work is gratefully + * acknowledged. Full credit goes to them - any problems within this code + * are mine. + * + * Copyright (C) 2009 William M. Brack + * + * Refactored and updated to the latest v4l core frameworks: + * + * Copyright (C) 2014 Hans Verkuil + */ + +#include "tw68.h" + +/** + * tw68_risc_field + * @rp: pointer to current risc program position + * @sglist: pointer to "scatter-gather list" of buffer pointers + * @offset: offset to target memory buffer + * @sync_line: 0 -> no sync, 1 -> odd sync, 2 -> even sync + * @bpl: number of bytes per scan line + * @padding: number of bytes of padding to add + * @lines: number of lines in field + * @jump: insert a jump at the start + */ +static __le32 *tw68_risc_field(__le32 *rp, struct scatterlist *sglist, + unsigned int offset, u32 sync_line, + unsigned int bpl, unsigned int padding, + unsigned int lines, bool jump) +{ + struct scatterlist *sg; + unsigned int line, todo, done; + + if (jump) { + *(rp++) = cpu_to_le32(RISC_JUMP); + *(rp++) = 0; + } + + /* sync instruction */ + if (sync_line == 1) + *(rp++) = cpu_to_le32(RISC_SYNCO); + else + *(rp++) = cpu_to_le32(RISC_SYNCE); + *(rp++) = 0; + + /* scan lines */ + sg = sglist; + for (line = 0; line < lines; line++) { + /* calculate next starting position */ + while (offset && offset >= sg_dma_len(sg)) { + offset -= sg_dma_len(sg); + sg = sg_next(sg); + } + if (bpl <= sg_dma_len(sg) - offset) { + /* fits into current chunk */ + *(rp++) = cpu_to_le32(RISC_LINESTART | + /* (offset<<12) |*/ bpl); + *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset); + offset += bpl; + } else { + /* + * scanline needs to be split. Put the start in + * whatever memory remains using RISC_LINESTART, + * then the remainder into following addresses + * given by the scatter-gather list. + */ + todo = bpl; /* one full line to be done */ + /* first fragment */ + done = (sg_dma_len(sg) - offset); + *(rp++) = cpu_to_le32(RISC_LINESTART | + (7 << 24) | + done); + *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset); + todo -= done; + sg = sg_next(sg); + /* succeeding fragments have no offset */ + while (todo > sg_dma_len(sg)) { + *(rp++) = cpu_to_le32(RISC_INLINE | + (done << 12) | + sg_dma_len(sg)); + *(rp++) = cpu_to_le32(sg_dma_address(sg)); + todo -= sg_dma_len(sg); + sg = sg_next(sg); + done += sg_dma_len(sg); + } + if (todo) { + /* final chunk - offset 0, count 'todo' */ + *(rp++) = cpu_to_le32(RISC_INLINE | + (done << 12) | + todo); + *(rp++) = cpu_to_le32(sg_dma_address(sg)); + } + offset = todo; + } + offset += padding; + } + + return rp; +} + +/** + * tw68_risc_buffer + * + * This routine is called by tw68-video. It allocates + * memory for the dma controller "program" and then fills in that + * memory with the appropriate "instructions". + * + * @pci: structure with info about the pci + * slot which our device is in. + * @buf: structure with info about the memory + * used for our controller program. + * @sglist: scatter-gather list entry + * @top_offset: offset within the risc program area for the + * first odd frame line + * @bottom_offset: offset within the risc program area for the + * first even frame line + * @bpl: number of data bytes per scan line + * @padding: number of extra bytes to add at end of line + * @lines: number of scan lines + */ +int tw68_risc_buffer(struct pci_dev *pci, + struct tw68_buf *buf, + struct scatterlist *sglist, + unsigned int top_offset, + unsigned int bottom_offset, + unsigned int bpl, + unsigned int padding, + unsigned int lines) +{ + u32 instructions, fields; + __le32 *rp; + + fields = 0; + if (UNSET != top_offset) + fields++; + if (UNSET != bottom_offset) + fields++; + /* + * estimate risc mem: worst case is one write per page border + + * one write per scan line + syncs + 2 jumps (all 2 dwords). + * Padding can cause next bpl to start close to a page border. + * First DMA region may be smaller than PAGE_SIZE + */ + instructions = fields * (1 + (((bpl + padding) * lines) / + PAGE_SIZE) + lines) + 4; + buf->size = instructions * 8; + buf->cpu = dma_alloc_coherent(&pci->dev, buf->size, &buf->dma, + GFP_KERNEL); + if (buf->cpu == NULL) + return -ENOMEM; + + /* write risc instructions */ + rp = buf->cpu; + if (UNSET != top_offset) /* generates SYNCO */ + rp = tw68_risc_field(rp, sglist, top_offset, 1, + bpl, padding, lines, true); + if (UNSET != bottom_offset) /* generates SYNCE */ + rp = tw68_risc_field(rp, sglist, bottom_offset, 2, + bpl, padding, lines, top_offset == UNSET); + + /* save pointer to jmp instruction address */ + buf->jmp = rp; + buf->cpu[1] = cpu_to_le32(buf->dma + 8); + /* assure risc buffer hasn't overflowed */ + BUG_ON((buf->jmp - buf->cpu + 2) * sizeof(buf->cpu[0]) > buf->size); + return 0; +} + +#if 0 +/* ------------------------------------------------------------------ */ +/* debug helper code */ + +static void tw68_risc_decode(u32 risc, u32 addr) +{ +#define RISC_OP(reg) (((reg) >> 28) & 7) + static struct instr_details { + char *name; + u8 has_data_type; + u8 has_byte_info; + u8 has_addr; + } instr[8] = { + [RISC_OP(RISC_SYNCO)] = {"syncOdd", 0, 0, 0}, + [RISC_OP(RISC_SYNCE)] = {"syncEven", 0, 0, 0}, + [RISC_OP(RISC_JUMP)] = {"jump", 0, 0, 1}, + [RISC_OP(RISC_LINESTART)] = {"lineStart", 1, 1, 1}, + [RISC_OP(RISC_INLINE)] = {"inline", 1, 1, 1}, + }; + u32 p; + + p = RISC_OP(risc); + if (!(risc & 0x80000000) || !instr[p].name) { + pr_debug("0x%08x [ INVALID ]\n", risc); + return; + } + pr_debug("0x%08x %-9s IRQ=%d", + risc, instr[p].name, (risc >> 27) & 1); + if (instr[p].has_data_type) + pr_debug(" Type=%d", (risc >> 24) & 7); + if (instr[p].has_byte_info) + pr_debug(" Start=0x%03x Count=%03u", + (risc >> 12) & 0xfff, risc & 0xfff); + if (instr[p].has_addr) + pr_debug(" StartAddr=0x%08x", addr); + pr_debug("\n"); +} + +void tw68_risc_program_dump(struct tw68_core *core, struct tw68_buf *buf) +{ + const __le32 *addr; + + pr_debug("%s: risc_program_dump: risc=%p, buf->cpu=0x%p, buf->jmp=0x%p\n", + core->name, buf, buf->cpu, buf->jmp); + for (addr = buf->cpu; addr <= buf->jmp; addr += 2) + tw68_risc_decode(*addr, *(addr+1)); +} +#endif diff --git a/drivers/media/pci/tw68/tw68-video.c b/drivers/media/pci/tw68/tw68-video.c new file mode 100644 index 000000000..6232bac17 --- /dev/null +++ b/drivers/media/pci/tw68/tw68-video.c @@ -0,0 +1,1017 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * tw68 functions to handle video data + * + * Much of this code is derived from the cx88 and sa7134 drivers, which + * were in turn derived from the bt87x driver. The original work was by + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab, + * Hans Verkuil, Andy Walls and many others. Their work is gratefully + * acknowledged. Full credit goes to them - any problems within this code + * are mine. + * + * Copyright (C) 2009 William M. Brack + * + * Refactored and updated to the latest v4l core frameworks: + * + * Copyright (C) 2014 Hans Verkuil + */ + +#include +#include +#include +#include + +#include "tw68.h" +#include "tw68-reg.h" + +/* ------------------------------------------------------------------ */ +/* data structs for video */ +/* + * FIXME - + * Note that the saa7134 has formats, e.g. YUV420, which are classified + * as "planar". These affect overlay mode, and are flagged with a field + * ".planar" in the format. Do we need to implement this in this driver? + */ +static const struct tw68_format formats[] = { + { + .fourcc = V4L2_PIX_FMT_RGB555, + .depth = 16, + .twformat = ColorFormatRGB15, + }, { + .fourcc = V4L2_PIX_FMT_RGB555X, + .depth = 16, + .twformat = ColorFormatRGB15 | ColorFormatBSWAP, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .twformat = ColorFormatRGB16, + }, { + .fourcc = V4L2_PIX_FMT_RGB565X, + .depth = 16, + .twformat = ColorFormatRGB16 | ColorFormatBSWAP, + }, { + .fourcc = V4L2_PIX_FMT_BGR24, + .depth = 24, + .twformat = ColorFormatRGB24, + }, { + .fourcc = V4L2_PIX_FMT_RGB24, + .depth = 24, + .twformat = ColorFormatRGB24 | ColorFormatBSWAP, + }, { + .fourcc = V4L2_PIX_FMT_BGR32, + .depth = 32, + .twformat = ColorFormatRGB32, + }, { + .fourcc = V4L2_PIX_FMT_RGB32, + .depth = 32, + .twformat = ColorFormatRGB32 | ColorFormatBSWAP | + ColorFormatWSWAP, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .twformat = ColorFormatYUY2, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .twformat = ColorFormatYUY2 | ColorFormatBSWAP, + } +}; +#define FORMATS ARRAY_SIZE(formats) + +#define NORM_625_50 \ + .h_delay = 3, \ + .h_delay0 = 133, \ + .h_start = 0, \ + .h_stop = 719, \ + .v_delay = 24, \ + .vbi_v_start_0 = 7, \ + .vbi_v_stop_0 = 22, \ + .video_v_start = 24, \ + .video_v_stop = 311, \ + .vbi_v_start_1 = 319 + +#define NORM_525_60 \ + .h_delay = 8, \ + .h_delay0 = 138, \ + .h_start = 0, \ + .h_stop = 719, \ + .v_delay = 22, \ + .vbi_v_start_0 = 10, \ + .vbi_v_stop_0 = 21, \ + .video_v_start = 22, \ + .video_v_stop = 262, \ + .vbi_v_start_1 = 273 + +/* + * The following table is searched by tw68_s_std, first for a specific + * match, then for an entry which contains the desired id. The table + * entries should therefore be ordered in ascending order of specificity. + */ +static const struct tw68_tvnorm tvnorms[] = { + { + .name = "PAL", /* autodetect */ + .id = V4L2_STD_PAL, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + .format = VideoFormatPALBDGHI, + }, { + .name = "NTSC", + .id = V4L2_STD_NTSC, + NORM_525_60, + + .sync_control = 0x59, + .luma_control = 0x40, + .chroma_ctrl1 = 0x89, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x0e, + .vgate_misc = 0x18, + .format = VideoFormatNTSC, + }, { + .name = "SECAM", + .id = V4L2_STD_SECAM, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x1b, + .chroma_ctrl1 = 0xd1, + .chroma_gain = 0x80, + .chroma_ctrl2 = 0x00, + .vgate_misc = 0x1c, + .format = VideoFormatSECAM, + }, { + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + NORM_525_60, + + .sync_control = 0x59, + .luma_control = 0x40, + .chroma_ctrl1 = 0xb9, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x0e, + .vgate_misc = 0x18, + .format = VideoFormatPALM, + }, { + .name = "PAL-Nc", + .id = V4L2_STD_PAL_Nc, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0xa1, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + .format = VideoFormatPALNC, + }, { + .name = "PAL-60", + .id = V4L2_STD_PAL_60, + .h_delay = 186, + .h_start = 0, + .h_stop = 719, + .v_delay = 26, + .video_v_start = 23, + .video_v_stop = 262, + .vbi_v_start_0 = 10, + .vbi_v_stop_0 = 21, + .vbi_v_start_1 = 273, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + .format = VideoFormatPAL60, + } +}; +#define TVNORMS ARRAY_SIZE(tvnorms) + +static const struct tw68_format *format_by_fourcc(unsigned int fourcc) +{ + unsigned int i; + + for (i = 0; i < FORMATS; i++) + if (formats[i].fourcc == fourcc) + return formats+i; + return NULL; +} + + +/* ------------------------------------------------------------------ */ +/* + * Note that the cropping rectangles are described in terms of a single + * frame, i.e. line positions are only 1/2 the interlaced equivalent + */ +static void set_tvnorm(struct tw68_dev *dev, const struct tw68_tvnorm *norm) +{ + if (norm != dev->tvnorm) { + dev->width = 720; + dev->height = (norm->id & V4L2_STD_525_60) ? 480 : 576; + dev->tvnorm = norm; + tw68_set_tvnorm_hw(dev); + } +} + +/* + * tw68_set_scale + * + * Scaling and Cropping for video decoding + * + * We are working with 3 values for horizontal and vertical - scale, + * delay and active. + * + * HACTIVE represent the actual number of pixels in the "usable" image, + * before scaling. HDELAY represents the number of pixels skipped + * between the start of the horizontal sync and the start of the image. + * HSCALE is calculated using the formula + * HSCALE = (HACTIVE / (#pixels desired)) * 256 + * + * The vertical registers are similar, except based upon the total number + * of lines in the image, and the first line of the image (i.e. ignoring + * vertical sync and VBI). + * + * Note that the number of bytes reaching the FIFO (and hence needing + * to be processed by the DMAP program) is completely dependent upon + * these values, especially HSCALE. + * + * Parameters: + * @dev pointer to the device structure, needed for + * getting current norm (as well as debug print) + * @width actual image width (from user buffer) + * @height actual image height + * @field indicates Top, Bottom or Interlaced + */ +static int tw68_set_scale(struct tw68_dev *dev, unsigned int width, + unsigned int height, enum v4l2_field field) +{ + const struct tw68_tvnorm *norm = dev->tvnorm; + /* set individually for debugging clarity */ + int hactive, hdelay, hscale; + int vactive, vdelay, vscale; + int comb; + + if (V4L2_FIELD_HAS_BOTH(field)) /* if field is interlaced */ + height /= 2; /* we must set for 1-frame */ + + pr_debug("%s: width=%d, height=%d, both=%d\n" + " tvnorm h_delay=%d, h_start=%d, h_stop=%d, v_delay=%d, v_start=%d, v_stop=%d\n", + __func__, width, height, V4L2_FIELD_HAS_BOTH(field), + norm->h_delay, norm->h_start, norm->h_stop, + norm->v_delay, norm->video_v_start, + norm->video_v_stop); + + switch (dev->vdecoder) { + case TW6800: + hdelay = norm->h_delay0; + break; + default: + hdelay = norm->h_delay; + break; + } + + hdelay += norm->h_start; + hactive = norm->h_stop - norm->h_start + 1; + + hscale = (hactive * 256) / (width); + + vdelay = norm->v_delay; + vactive = ((norm->id & V4L2_STD_525_60) ? 524 : 624) / 2 - norm->video_v_start; + vscale = (vactive * 256) / height; + + pr_debug("%s: %dx%d [%s%s,%s]\n", __func__, + width, height, + V4L2_FIELD_HAS_TOP(field) ? "T" : "", + V4L2_FIELD_HAS_BOTTOM(field) ? "B" : "", + v4l2_norm_to_name(dev->tvnorm->id)); + pr_debug("%s: hactive=%d, hdelay=%d, hscale=%d; vactive=%d, vdelay=%d, vscale=%d\n", + __func__, + hactive, hdelay, hscale, vactive, vdelay, vscale); + + comb = ((vdelay & 0x300) >> 2) | + ((vactive & 0x300) >> 4) | + ((hdelay & 0x300) >> 6) | + ((hactive & 0x300) >> 8); + pr_debug("%s: setting CROP_HI=%02x, VDELAY_LO=%02x, VACTIVE_LO=%02x, HDELAY_LO=%02x, HACTIVE_LO=%02x\n", + __func__, comb, vdelay, vactive, hdelay, hactive); + tw_writeb(TW68_CROP_HI, comb); + tw_writeb(TW68_VDELAY_LO, vdelay & 0xff); + tw_writeb(TW68_VACTIVE_LO, vactive & 0xff); + tw_writeb(TW68_HDELAY_LO, hdelay & 0xff); + tw_writeb(TW68_HACTIVE_LO, hactive & 0xff); + + comb = ((vscale & 0xf00) >> 4) | ((hscale & 0xf00) >> 8); + pr_debug("%s: setting SCALE_HI=%02x, VSCALE_LO=%02x, HSCALE_LO=%02x\n", + __func__, comb, vscale, hscale); + tw_writeb(TW68_SCALE_HI, comb); + tw_writeb(TW68_VSCALE_LO, vscale); + tw_writeb(TW68_HSCALE_LO, hscale); + + return 0; +} + +/* ------------------------------------------------------------------ */ + +int tw68_video_start_dma(struct tw68_dev *dev, struct tw68_buf *buf) +{ + /* Set cropping and scaling */ + tw68_set_scale(dev, dev->width, dev->height, dev->field); + /* + * Set start address for RISC program. Note that if the DMAP + * processor is currently running, it must be stopped before + * a new address can be set. + */ + tw_clearl(TW68_DMAC, TW68_DMAP_EN); + tw_writel(TW68_DMAP_SA, buf->dma); + /* Clear any pending interrupts */ + tw_writel(TW68_INTSTAT, dev->board_virqmask); + /* Enable the risc engine and the fifo */ + tw_andorl(TW68_DMAC, 0xff, dev->fmt->twformat | + ColorFormatGamma | TW68_DMAP_EN | TW68_FIFO_EN); + dev->pci_irqmask |= dev->board_virqmask; + tw_setl(TW68_INTMASK, dev->pci_irqmask); + return 0; +} + +/* ------------------------------------------------------------------ */ + +/* calc max # of buffers from size (must not exceed the 4MB virtual + * address space per DMA channel) */ +static int tw68_buffer_count(unsigned int size, unsigned int count) +{ + unsigned int maxcount; + + maxcount = (4 * 1024 * 1024) / roundup(size, PAGE_SIZE); + if (count > maxcount) + count = maxcount; + return count; +} + +/* ------------------------------------------------------------- */ +/* vb2 queue operations */ + +static int tw68_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct tw68_dev *dev = vb2_get_drv_priv(q); + unsigned int q_num_bufs = vb2_get_num_buffers(q); + unsigned int tot_bufs = q_num_bufs + *num_buffers; + unsigned size = (dev->fmt->depth * dev->width * dev->height) >> 3; + + if (tot_bufs < 2) + tot_bufs = 2; + tot_bufs = tw68_buffer_count(size, tot_bufs); + *num_buffers = tot_bufs - q_num_bufs; + /* + * We allow create_bufs, but only if the sizeimage is >= as the + * current sizeimage. The tw68_buffer_count calculation becomes quite + * difficult otherwise. + */ + if (*num_planes) + return sizes[0] < size ? -EINVAL : 0; + *num_planes = 1; + sizes[0] = size; + + return 0; +} + +/* + * The risc program for each buffers works as follows: it starts with a simple + * 'JUMP to addr + 8', which is effectively a NOP. Then the program to DMA the + * buffer follows and at the end we have a JUMP back to the start + 8 (skipping + * the initial JUMP). + * + * This is the program of the first buffer to be queued if the active list is + * empty and it just keeps DMAing this buffer without generating any interrupts. + * + * If a new buffer is added then the initial JUMP in the program generates an + * interrupt as well which signals that the previous buffer has been DMAed + * successfully and that it can be returned to userspace. + * + * It also sets the final jump of the previous buffer to the start of the new + * buffer, thus chaining the new buffer into the DMA chain. This is a single + * atomic u32 write, so there is no race condition. + * + * The end-result of all this that you only get an interrupt when a buffer + * is ready, so the control flow is very easy. + */ +static void tw68_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vb2_queue *vq = vb->vb2_queue; + struct tw68_dev *dev = vb2_get_drv_priv(vq); + struct tw68_buf *buf = container_of(vbuf, struct tw68_buf, vb); + struct tw68_buf *prev; + unsigned long flags; + + spin_lock_irqsave(&dev->slock, flags); + + /* append a 'JUMP to start of buffer' to the buffer risc program */ + buf->jmp[0] = cpu_to_le32(RISC_JUMP); + buf->jmp[1] = cpu_to_le32(buf->dma + 8); + + if (!list_empty(&dev->active)) { + prev = list_entry(dev->active.prev, struct tw68_buf, list); + buf->cpu[0] |= cpu_to_le32(RISC_INT_BIT); + prev->jmp[1] = cpu_to_le32(buf->dma); + } + list_add_tail(&buf->list, &dev->active); + spin_unlock_irqrestore(&dev->slock, flags); +} + +/* + * buffer_prepare + * + * Set the ancillary information into the buffer structure. This + * includes generating the necessary risc program if it hasn't already + * been done for the current buffer format. + * The structure fh contains the details of the format requested by the + * user - type, width, height and #fields. This is compared with the + * last format set for the current buffer. If they differ, the risc + * code (which controls the filling of the buffer) is (re-)generated. + */ +static int tw68_buf_prepare(struct vb2_buffer *vb) +{ + int ret; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vb2_queue *vq = vb->vb2_queue; + struct tw68_dev *dev = vb2_get_drv_priv(vq); + struct tw68_buf *buf = container_of(vbuf, struct tw68_buf, vb); + struct sg_table *dma = vb2_dma_sg_plane_desc(vb, 0); + unsigned size, bpl; + + size = (dev->width * dev->height * dev->fmt->depth) >> 3; + if (vb2_plane_size(vb, 0) < size) + return -EINVAL; + vb2_set_plane_payload(vb, 0, size); + + bpl = (dev->width * dev->fmt->depth) >> 3; + switch (dev->field) { + case V4L2_FIELD_TOP: + ret = tw68_risc_buffer(dev->pci, buf, dma->sgl, + 0, UNSET, bpl, 0, dev->height); + break; + case V4L2_FIELD_BOTTOM: + ret = tw68_risc_buffer(dev->pci, buf, dma->sgl, + UNSET, 0, bpl, 0, dev->height); + break; + case V4L2_FIELD_SEQ_TB: + ret = tw68_risc_buffer(dev->pci, buf, dma->sgl, + 0, bpl * (dev->height >> 1), + bpl, 0, dev->height >> 1); + break; + case V4L2_FIELD_SEQ_BT: + ret = tw68_risc_buffer(dev->pci, buf, dma->sgl, + bpl * (dev->height >> 1), 0, + bpl, 0, dev->height >> 1); + break; + case V4L2_FIELD_INTERLACED: + default: + ret = tw68_risc_buffer(dev->pci, buf, dma->sgl, + 0, bpl, bpl, bpl, dev->height >> 1); + break; + } + return ret; +} + +static void tw68_buf_finish(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vb2_queue *vq = vb->vb2_queue; + struct tw68_dev *dev = vb2_get_drv_priv(vq); + struct tw68_buf *buf = container_of(vbuf, struct tw68_buf, vb); + + if (buf->cpu) + dma_free_coherent(&dev->pci->dev, buf->size, buf->cpu, buf->dma); +} + +static int tw68_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct tw68_dev *dev = vb2_get_drv_priv(q); + struct tw68_buf *buf = + container_of(dev->active.next, struct tw68_buf, list); + + dev->seqnr = 0; + tw68_video_start_dma(dev, buf); + return 0; +} + +static void tw68_stop_streaming(struct vb2_queue *q) +{ + struct tw68_dev *dev = vb2_get_drv_priv(q); + + /* Stop risc & fifo */ + tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN); + while (!list_empty(&dev->active)) { + struct tw68_buf *buf = + container_of(dev->active.next, struct tw68_buf, list); + + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } +} + +static const struct vb2_ops tw68_video_qops = { + .queue_setup = tw68_queue_setup, + .buf_queue = tw68_buf_queue, + .buf_prepare = tw68_buf_prepare, + .buf_finish = tw68_buf_finish, + .start_streaming = tw68_start_streaming, + .stop_streaming = tw68_stop_streaming, +}; + +/* ------------------------------------------------------------------ */ + +static int tw68_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct tw68_dev *dev = + container_of(ctrl->handler, struct tw68_dev, hdl); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + tw_writeb(TW68_BRIGHT, ctrl->val); + break; + case V4L2_CID_HUE: + tw_writeb(TW68_HUE, ctrl->val); + break; + case V4L2_CID_CONTRAST: + tw_writeb(TW68_CONTRAST, ctrl->val); + break; + case V4L2_CID_SATURATION: + tw_writeb(TW68_SAT_U, ctrl->val); + tw_writeb(TW68_SAT_V, ctrl->val); + break; + case V4L2_CID_COLOR_KILLER: + if (ctrl->val) + tw_andorb(TW68_MISC2, 0xe0, 0xe0); + else + tw_andorb(TW68_MISC2, 0xe0, 0x00); + break; + case V4L2_CID_CHROMA_AGC: + if (ctrl->val) + tw_andorb(TW68_LOOP, 0x30, 0x20); + else + tw_andorb(TW68_LOOP, 0x30, 0x00); + break; + } + return 0; +} + +/* ------------------------------------------------------------------ */ + +/* + * Note that this routine returns what is stored in the fh structure, and + * does not interrogate any of the device registers. + */ +static int tw68_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw68_dev *dev = video_drvdata(file); + + f->fmt.pix.width = dev->width; + f->fmt.pix.height = dev->height; + f->fmt.pix.field = dev->field; + f->fmt.pix.pixelformat = dev->fmt->fourcc; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * (dev->fmt->depth)) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + return 0; +} + +static int tw68_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw68_dev *dev = video_drvdata(file); + const struct tw68_format *fmt; + enum v4l2_field field; + unsigned int maxh; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + maxh = (dev->tvnorm->id & V4L2_STD_525_60) ? 480 : 576; + + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + break; + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_SEQ_BT: + case V4L2_FIELD_SEQ_TB: + maxh = maxh * 2; + break; + default: + field = (f->fmt.pix.height > maxh / 2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + break; + } + + f->fmt.pix.field = field; + if (f->fmt.pix.width < 48) + f->fmt.pix.width = 48; + if (f->fmt.pix.height < 32) + f->fmt.pix.height = 32; + if (f->fmt.pix.width > 720) + f->fmt.pix.width = 720; + if (f->fmt.pix.height > maxh) + f->fmt.pix.height = maxh; + f->fmt.pix.width &= ~0x03; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * (fmt->depth)) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + return 0; +} + +/* + * Note that tw68_s_fmt_vid_cap sets the information into the fh structure, + * and it will be used for all future new buffers. However, there could be + * some number of buffers on the "active" chain which will be filled before + * the change takes place. + */ +static int tw68_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw68_dev *dev = video_drvdata(file); + int err; + + err = tw68_try_fmt_vid_cap(file, priv, f); + if (0 != err) + return err; + + dev->fmt = format_by_fourcc(f->fmt.pix.pixelformat); + dev->width = f->fmt.pix.width; + dev->height = f->fmt.pix.height; + dev->field = f->fmt.pix.field; + return 0; +} + +static int tw68_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct tw68_dev *dev = video_drvdata(file); + unsigned int n; + + n = i->index; + if (n >= TW68_INPUT_MAX) + return -EINVAL; + i->index = n; + i->type = V4L2_INPUT_TYPE_CAMERA; + snprintf(i->name, sizeof(i->name), "Composite %d", n); + + /* If the query is for the current input, get live data */ + if (n == dev->input) { + int v1 = tw_readb(TW68_STATUS1); + int v2 = tw_readb(TW68_MVSN); + + if (0 != (v1 & (1 << 7))) + i->status |= V4L2_IN_ST_NO_SYNC; + if (0 != (v1 & (1 << 6))) + i->status |= V4L2_IN_ST_NO_H_LOCK; + if (0 != (v1 & (1 << 2))) + i->status |= V4L2_IN_ST_NO_SIGNAL; + if (0 != (v1 & 1 << 1)) + i->status |= V4L2_IN_ST_NO_COLOR; + if (0 != (v2 & (1 << 2))) + i->status |= V4L2_IN_ST_MACROVISION; + } + i->std = video_devdata(file)->tvnorms; + return 0; +} + +static int tw68_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct tw68_dev *dev = video_drvdata(file); + + *i = dev->input; + return 0; +} + +static int tw68_s_input(struct file *file, void *priv, unsigned int i) +{ + struct tw68_dev *dev = video_drvdata(file); + + if (i >= TW68_INPUT_MAX) + return -EINVAL; + dev->input = i; + tw_andorb(TW68_INFORM, 0x03 << 2, dev->input << 2); + return 0; +} + +static int tw68_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, "tw68", sizeof(cap->driver)); + strscpy(cap->card, "Techwell Capture Card", + sizeof(cap->card)); + return 0; +} + +static int tw68_s_std(struct file *file, void *priv, v4l2_std_id id) +{ + struct tw68_dev *dev = video_drvdata(file); + unsigned int i; + + if (vb2_is_busy(&dev->vidq)) + return -EBUSY; + + /* Look for match on complete norm id (may have mult bits) */ + for (i = 0; i < TVNORMS; i++) { + if (id == tvnorms[i].id) + break; + } + + /* If no exact match, look for norm which contains this one */ + if (i == TVNORMS) { + for (i = 0; i < TVNORMS; i++) + if (id & tvnorms[i].id) + break; + } + /* If still not matched, give up */ + if (i == TVNORMS) + return -EINVAL; + + set_tvnorm(dev, &tvnorms[i]); /* do the actual setting */ + return 0; +} + +static int tw68_g_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct tw68_dev *dev = video_drvdata(file); + + *id = dev->tvnorm->id; + return 0; +} + +static int tw68_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index >= FORMATS) + return -EINVAL; + + f->pixelformat = formats[f->index].fourcc; + + return 0; +} + +/* + * Used strictly for internal development and debugging, this routine + * prints out the current register contents for the tw68xx device. + */ +static void tw68_dump_regs(struct tw68_dev *dev) +{ + unsigned char line[80]; + int i, j, k; + unsigned char *cptr; + + pr_info("Full dump of TW68 registers:\n"); + /* First we do the PCI regs, 8 4-byte regs per line */ + for (i = 0; i < 0x100; i += 32) { + cptr = line; + cptr += sprintf(cptr, "%03x ", i); + /* j steps through the next 4 words */ + for (j = i; j < i + 16; j += 4) + cptr += sprintf(cptr, "%08x ", tw_readl(j)); + *cptr++ = ' '; + for (; j < i + 32; j += 4) + cptr += sprintf(cptr, "%08x ", tw_readl(j)); + *cptr++ = '\n'; + *cptr = 0; + pr_info("%s", line); + } + /* Next the control regs, which are single-byte, address mod 4 */ + while (i < 0x400) { + cptr = line; + cptr += sprintf(cptr, "%03x ", i); + /* Print out 4 groups of 4 bytes */ + for (j = 0; j < 4; j++) { + for (k = 0; k < 4; k++) { + cptr += sprintf(cptr, "%02x ", + tw_readb(i)); + i += 4; + } + *cptr++ = ' '; + } + *cptr++ = '\n'; + *cptr = 0; + pr_info("%s", line); + } +} + +static int vidioc_log_status(struct file *file, void *priv) +{ + struct tw68_dev *dev = video_drvdata(file); + + tw68_dump_regs(dev); + return v4l2_ctrl_log_status(file, priv); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int vidioc_g_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct tw68_dev *dev = video_drvdata(file); + + if (reg->size == 1) + reg->val = tw_readb(reg->reg); + else + reg->val = tw_readl(reg->reg); + return 0; +} + +static int vidioc_s_register(struct file *file, void *priv, + const struct v4l2_dbg_register *reg) +{ + struct tw68_dev *dev = video_drvdata(file); + + if (reg->size == 1) + tw_writeb(reg->reg, reg->val); + else + tw_writel(reg->reg & 0xffff, reg->val); + return 0; +} +#endif + +static const struct v4l2_ctrl_ops tw68_ctrl_ops = { + .s_ctrl = tw68_s_ctrl, +}; + +static const struct v4l2_file_operations video_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = tw68_querycap, + .vidioc_enum_fmt_vid_cap = tw68_enum_fmt_vid_cap, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_s_std = tw68_s_std, + .vidioc_g_std = tw68_g_std, + .vidioc_enum_input = tw68_enum_input, + .vidioc_g_input = tw68_g_input, + .vidioc_s_input = tw68_s_input, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_g_fmt_vid_cap = tw68_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = tw68_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = tw68_s_fmt_vid_cap, + .vidioc_log_status = vidioc_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = vidioc_g_register, + .vidioc_s_register = vidioc_s_register, +#endif +}; + +static const struct video_device tw68_video_template = { + .name = "tw68_video", + .fops = &video_fops, + .ioctl_ops = &video_ioctl_ops, + .release = video_device_release_empty, + .tvnorms = TW68_NORMS, + .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING, +}; + +/* ------------------------------------------------------------------ */ +/* exported stuff */ +void tw68_set_tvnorm_hw(struct tw68_dev *dev) +{ + tw_andorb(TW68_SDT, 0x07, dev->tvnorm->format); +} + +int tw68_video_init1(struct tw68_dev *dev) +{ + struct v4l2_ctrl_handler *hdl = &dev->hdl; + + v4l2_ctrl_handler_init(hdl, 6); + v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 20); + v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 100); + v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + /* NTSC only */ + v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops, + V4L2_CID_COLOR_KILLER, 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops, + V4L2_CID_CHROMA_AGC, 0, 1, 1, 1); + if (hdl->error) { + v4l2_ctrl_handler_free(hdl); + return hdl->error; + } + dev->v4l2_dev.ctrl_handler = hdl; + v4l2_ctrl_handler_setup(hdl); + return 0; +} + +int tw68_video_init2(struct tw68_dev *dev, int video_nr) +{ + int ret; + + set_tvnorm(dev, &tvnorms[0]); + + dev->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24); + dev->width = 720; + dev->height = 576; + dev->field = V4L2_FIELD_INTERLACED; + + INIT_LIST_HEAD(&dev->active); + dev->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dev->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + dev->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ | VB2_DMABUF; + dev->vidq.ops = &tw68_video_qops; + dev->vidq.mem_ops = &vb2_dma_sg_memops; + dev->vidq.drv_priv = dev; + dev->vidq.gfp_flags = __GFP_DMA32 | __GFP_KSWAPD_RECLAIM; + dev->vidq.buf_struct_size = sizeof(struct tw68_buf); + dev->vidq.lock = &dev->lock; + dev->vidq.min_queued_buffers = 2; + dev->vidq.dev = &dev->pci->dev; + ret = vb2_queue_init(&dev->vidq); + if (ret) + return ret; + dev->vdev = tw68_video_template; + dev->vdev.v4l2_dev = &dev->v4l2_dev; + dev->vdev.lock = &dev->lock; + dev->vdev.queue = &dev->vidq; + video_set_drvdata(&dev->vdev, dev); + return video_register_device(&dev->vdev, VFL_TYPE_VIDEO, video_nr); +} + +/* + * tw68_irq_video_done + */ +void tw68_irq_video_done(struct tw68_dev *dev, unsigned long status) +{ + __u32 reg; + + /* reset interrupts handled by this routine */ + tw_writel(TW68_INTSTAT, status); + /* + * Check most likely first + * + * DMAPI shows we have reached the end of the risc code + * for the current buffer. + */ + if (status & TW68_DMAPI) { + struct tw68_buf *buf; + + spin_lock(&dev->slock); + buf = list_entry(dev->active.next, struct tw68_buf, list); + list_del(&buf->list); + spin_unlock(&dev->slock); + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + buf->vb.field = dev->field; + buf->vb.sequence = dev->seqnr++; + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + status &= ~(TW68_DMAPI); + if (0 == status) + return; + } + if (status & (TW68_VLOCK | TW68_HLOCK)) + dev_dbg(&dev->pci->dev, "Lost sync\n"); + if (status & TW68_PABORT) + dev_err(&dev->pci->dev, "PABORT interrupt\n"); + if (status & TW68_DMAPERR) + dev_err(&dev->pci->dev, "DMAPERR interrupt\n"); + /* + * On TW6800, FDMIS is apparently generated if video input is switched + * during operation. Therefore, it is not enabled for that chip. + */ + if (status & TW68_FDMIS) + dev_dbg(&dev->pci->dev, "FDMIS interrupt\n"); + if (status & TW68_FFOF) { + /* probably a logic error */ + reg = tw_readl(TW68_DMAC) & TW68_FIFO_EN; + tw_clearl(TW68_DMAC, TW68_FIFO_EN); + dev_dbg(&dev->pci->dev, "FFOF interrupt\n"); + tw_setl(TW68_DMAC, reg); + } + if (status & TW68_FFERR) + dev_dbg(&dev->pci->dev, "FFERR interrupt\n"); +} diff --git a/drivers/media/pci/tw68/tw68.h b/drivers/media/pci/tw68/tw68.h new file mode 100644 index 000000000..66be6b3bb --- /dev/null +++ b/drivers/media/pci/tw68/tw68.h @@ -0,0 +1,205 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * tw68 driver common header file + * + * Much of this code is derived from the cx88 and sa7134 drivers, which + * were in turn derived from the bt87x driver. The original work was by + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab, + * Hans Verkuil, Andy Walls and many others. Their work is gratefully + * acknowledged. Full credit goes to them - any problems within this code + * are mine. + * + * Copyright (C) 2009 William M. Brack + * + * Refactored and updated to the latest v4l core frameworks: + * + * Copyright (C) 2014 Hans Verkuil + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "tw68-reg.h" + +#define UNSET (-1U) + +#define TW68_NORMS ( \ + V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM | \ + V4L2_STD_PAL_M | V4L2_STD_PAL_Nc | V4L2_STD_PAL_60) + +#define TW68_VID_INTS (TW68_FFERR | TW68_PABORT | TW68_DMAPERR | \ + TW68_FFOF | TW68_DMAPI) +/* TW6800 chips have trouble with these, so we don't set them for that chip */ +#define TW68_VID_INTSX (TW68_FDMIS | TW68_HLOCK | TW68_VLOCK) + +#define TW68_I2C_INTS (TW68_SBERR | TW68_SBDONE | TW68_SBERR2 | \ + TW68_SBDONE2) + +enum tw68_decoder_type { + TW6800, + TW6801, + TW6804, + TWXXXX, +}; + +/* ----------------------------------------------------------- */ +/* static data */ + +struct tw68_tvnorm { + char *name; + v4l2_std_id id; + + /* video decoder */ + u32 sync_control; + u32 luma_control; + u32 chroma_ctrl1; + u32 chroma_gain; + u32 chroma_ctrl2; + u32 vgate_misc; + + /* video scaler */ + u32 h_delay; + u32 h_delay0; /* for TW6800 */ + u32 h_start; + u32 h_stop; + u32 v_delay; + u32 video_v_start; + u32 video_v_stop; + u32 vbi_v_start_0; + u32 vbi_v_stop_0; + u32 vbi_v_start_1; + + /* Techwell specific */ + u32 format; +}; + +struct tw68_format { + u32 fourcc; + u32 depth; + u32 twformat; +}; + +/* ----------------------------------------------------------- */ +/* card configuration */ + +#define TW68_BOARD_NOAUTO UNSET +#define TW68_BOARD_UNKNOWN 0 +#define TW68_BOARD_GENERIC_6802 1 + +#define TW68_MAXBOARDS 16 +#define TW68_INPUT_MAX 4 + +/* ----------------------------------------------------------- */ +/* device / file handle status */ + +#define BUFFER_TIMEOUT msecs_to_jiffies(500) /* 0.5 seconds */ + +struct tw68_dev; /* forward delclaration */ + +/* buffer for one video/vbi/ts frame */ +struct tw68_buf { + struct vb2_v4l2_buffer vb; + struct list_head list; + + unsigned int size; + __le32 *cpu; + __le32 *jmp; + dma_addr_t dma; +}; + +struct tw68_fmt { + char *name; + u32 fourcc; /* v4l2 format id */ + int depth; + int flags; + u32 twformat; +}; + +/* global device status */ +struct tw68_dev { + struct mutex lock; + spinlock_t slock; + u16 instance; + struct v4l2_device v4l2_dev; + + /* various device info */ + enum tw68_decoder_type vdecoder; + struct video_device vdev; + struct v4l2_ctrl_handler hdl; + + /* pci i/o */ + char *name; + struct pci_dev *pci; + unsigned char pci_rev, pci_lat; + u32 __iomem *lmmio; + u8 __iomem *bmmio; + u32 pci_irqmask; + /* The irq mask to be used will depend upon the chip type */ + u32 board_virqmask; + + /* video capture */ + const struct tw68_format *fmt; + unsigned width, height; + unsigned seqnr; + unsigned field; + struct vb2_queue vidq; + struct list_head active; + + /* various v4l controls */ + const struct tw68_tvnorm *tvnorm; /* video */ + + int input; +}; + +/* ----------------------------------------------------------- */ + +#define tw_readl(reg) readl(dev->lmmio + ((reg) >> 2)) +#define tw_readb(reg) readb(dev->bmmio + (reg)) +#define tw_writel(reg, value) writel((value), dev->lmmio + ((reg) >> 2)) +#define tw_writeb(reg, value) writeb((value), dev->bmmio + (reg)) + +#define tw_andorl(reg, mask, value) \ + writel((readl(dev->lmmio+((reg)>>2)) & ~(mask)) |\ + ((value) & (mask)), dev->lmmio+((reg)>>2)) +#define tw_andorb(reg, mask, value) \ + writeb((readb(dev->bmmio + (reg)) & ~(mask)) |\ + ((value) & (mask)), dev->bmmio+(reg)) +#define tw_setl(reg, bit) tw_andorl((reg), (bit), (bit)) +#define tw_setb(reg, bit) tw_andorb((reg), (bit), (bit)) +#define tw_clearl(reg, bit) \ + writel((readl(dev->lmmio + ((reg) >> 2)) & ~(bit)), \ + dev->lmmio + ((reg) >> 2)) +#define tw_clearb(reg, bit) \ + writeb((readb(dev->bmmio+(reg)) & ~(bit)), \ + dev->bmmio + (reg)) + +#define tw_wait(us) { udelay(us); } + +/* ----------------------------------------------------------- */ +/* tw68-video.c */ + +void tw68_set_tvnorm_hw(struct tw68_dev *dev); + +int tw68_video_init1(struct tw68_dev *dev); +int tw68_video_init2(struct tw68_dev *dev, int video_nr); +void tw68_irq_video_done(struct tw68_dev *dev, unsigned long status); +int tw68_video_start_dma(struct tw68_dev *dev, struct tw68_buf *buf); + +/* ----------------------------------------------------------- */ +/* tw68-risc.c */ + +int tw68_risc_buffer(struct pci_dev *pci, struct tw68_buf *buf, + struct scatterlist *sglist, unsigned int top_offset, + unsigned int bottom_offset, unsigned int bpl, + unsigned int padding, unsigned int lines); diff --git a/drivers/media/pci/tw686x/Kconfig b/drivers/media/pci/tw686x/Kconfig new file mode 100644 index 000000000..a4edad6aa --- /dev/null +++ b/drivers/media/pci/tw686x/Kconfig @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_TW686X + tristate "Intersil/Techwell TW686x video capture cards" + depends on PCI && VIDEO_DEV && SND + select VIDEOBUF2_VMALLOC + select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_DMA_SG + select SND_PCM + help + Support for Intersil/Techwell TW686x-based frame grabber cards. + + Currently supported chips: + - TW6864 (4 video channels), + - TW6865 (4 video channels, not tested, second generation chip), + - TW6868 (8 video channels but only 4 first channels using + built-in video decoder are supported, not tested), + - TW6869 (8 video channels, second generation chip). + + To compile this driver as a module, choose M here: the module + will be named tw686x. diff --git a/drivers/media/pci/tw686x/Makefile b/drivers/media/pci/tw686x/Makefile new file mode 100644 index 000000000..1795dffb5 --- /dev/null +++ b/drivers/media/pci/tw686x/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +tw686x-objs := tw686x-core.o tw686x-video.o tw686x-audio.o + +obj-$(CONFIG_VIDEO_TW686X) += tw686x.o diff --git a/drivers/media/pci/tw686x/tw686x-audio.c b/drivers/media/pci/tw686x/tw686x-audio.c new file mode 100644 index 000000000..1ae3845b6 --- /dev/null +++ b/drivers/media/pci/tw686x/tw686x-audio.c @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 VanguardiaSur - www.vanguardiasur.com.ar + * + * Based on the audio support from the tw6869 driver: + * Copyright 2015 www.starterkit.ru + * + * Based on: + * Driver for Intersil|Techwell TW6869 based DVR cards + * (c) 2011-12 liran [Intersil|Techwell China] + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "tw686x.h" +#include "tw686x-regs.h" + +#define AUDIO_CHANNEL_OFFSET 8 + +void tw686x_audio_irq(struct tw686x_dev *dev, unsigned long requests, + unsigned int pb_status) +{ + unsigned long flags; + unsigned int ch, pb; + + for_each_set_bit(ch, &requests, max_channels(dev)) { + struct tw686x_audio_channel *ac = &dev->audio_channels[ch]; + struct tw686x_audio_buf *done = NULL; + struct tw686x_audio_buf *next = NULL; + struct tw686x_dma_desc *desc; + + pb = !!(pb_status & BIT(AUDIO_CHANNEL_OFFSET + ch)); + + spin_lock_irqsave(&ac->lock, flags); + + /* Sanity check */ + if (!ac->ss || !ac->curr_bufs[0] || !ac->curr_bufs[1]) { + spin_unlock_irqrestore(&ac->lock, flags); + continue; + } + + if (!list_empty(&ac->buf_list)) { + next = list_first_entry(&ac->buf_list, + struct tw686x_audio_buf, list); + list_move_tail(&next->list, &ac->buf_list); + done = ac->curr_bufs[!pb]; + ac->curr_bufs[pb] = next; + } + spin_unlock_irqrestore(&ac->lock, flags); + + if (!done) + continue; + /* + * Checking for a non-nil dma_desc[pb]->virt buffer is + * the same as checking for memcpy DMA mode. + */ + desc = &ac->dma_descs[pb]; + if (desc->virt) { + memcpy(done->virt, desc->virt, + dev->period_size); + } else { + u32 reg = pb ? ADMA_B_ADDR[ch] : ADMA_P_ADDR[ch]; + reg_write(dev, reg, next->dma); + } + ac->ptr = done->dma - ac->buf[0].dma; + snd_pcm_period_elapsed(ac->ss); + } +} + +/* + * Audio parameters are global and shared among all + * capture channels. The driver prevents changes to + * the parameters if any audio channel is capturing. + */ +static const struct snd_pcm_hardware tw686x_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = TW686X_AUDIO_PAGE_MAX * AUDIO_DMA_SIZE_MAX, + .period_bytes_min = AUDIO_DMA_SIZE_MIN, + .period_bytes_max = AUDIO_DMA_SIZE_MAX, + .periods_min = TW686X_AUDIO_PERIODS_MIN, + .periods_max = TW686X_AUDIO_PERIODS_MAX, +}; + +static int tw686x_pcm_open(struct snd_pcm_substream *ss) +{ + struct tw686x_dev *dev = snd_pcm_substream_chip(ss); + struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number]; + struct snd_pcm_runtime *rt = ss->runtime; + int err; + + ac->ss = ss; + rt->hw = tw686x_capture_hw; + + err = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + + return 0; +} + +static int tw686x_pcm_close(struct snd_pcm_substream *ss) +{ + struct tw686x_dev *dev = snd_pcm_substream_chip(ss); + struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number]; + + ac->ss = NULL; + return 0; +} + +static int tw686x_pcm_prepare(struct snd_pcm_substream *ss) +{ + struct tw686x_dev *dev = snd_pcm_substream_chip(ss); + struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number]; + struct snd_pcm_runtime *rt = ss->runtime; + unsigned int period_size = snd_pcm_lib_period_bytes(ss); + struct tw686x_audio_buf *p_buf, *b_buf; + unsigned long flags; + int i; + + spin_lock_irqsave(&dev->lock, flags); + /* + * Given the audio parameters are global (i.e. shared across + * DMA channels), we need to check new params are allowed. + */ + if (((dev->audio_rate != rt->rate) || + (dev->period_size != period_size)) && dev->audio_enabled) + goto err_audio_busy; + + tw686x_disable_channel(dev, AUDIO_CHANNEL_OFFSET + ac->ch); + spin_unlock_irqrestore(&dev->lock, flags); + + if (dev->audio_rate != rt->rate) { + u32 reg; + + dev->audio_rate = rt->rate; + reg = ((125000000 / rt->rate) << 16) + + ((125000000 % rt->rate) << 16) / rt->rate; + + reg_write(dev, AUDIO_CONTROL2, reg); + } + + if (dev->period_size != period_size) { + u32 reg; + + dev->period_size = period_size; + reg = reg_read(dev, AUDIO_CONTROL1); + reg &= ~(AUDIO_DMA_SIZE_MASK << AUDIO_DMA_SIZE_SHIFT); + reg |= period_size << AUDIO_DMA_SIZE_SHIFT; + + reg_write(dev, AUDIO_CONTROL1, reg); + } + + if (rt->periods < TW686X_AUDIO_PERIODS_MIN || + rt->periods > TW686X_AUDIO_PERIODS_MAX) + return -EINVAL; + + spin_lock_irqsave(&ac->lock, flags); + INIT_LIST_HEAD(&ac->buf_list); + + for (i = 0; i < rt->periods; i++) { + ac->buf[i].dma = rt->dma_addr + period_size * i; + ac->buf[i].virt = rt->dma_area + period_size * i; + INIT_LIST_HEAD(&ac->buf[i].list); + list_add_tail(&ac->buf[i].list, &ac->buf_list); + } + + p_buf = list_first_entry(&ac->buf_list, struct tw686x_audio_buf, list); + list_move_tail(&p_buf->list, &ac->buf_list); + + b_buf = list_first_entry(&ac->buf_list, struct tw686x_audio_buf, list); + list_move_tail(&b_buf->list, &ac->buf_list); + + ac->curr_bufs[0] = p_buf; + ac->curr_bufs[1] = b_buf; + ac->ptr = 0; + + if (dev->dma_mode != TW686X_DMA_MODE_MEMCPY) { + reg_write(dev, ADMA_P_ADDR[ac->ch], p_buf->dma); + reg_write(dev, ADMA_B_ADDR[ac->ch], b_buf->dma); + } + + spin_unlock_irqrestore(&ac->lock, flags); + + return 0; + +err_audio_busy: + spin_unlock_irqrestore(&dev->lock, flags); + return -EBUSY; +} + +static int tw686x_pcm_trigger(struct snd_pcm_substream *ss, int cmd) +{ + struct tw686x_dev *dev = snd_pcm_substream_chip(ss); + struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number]; + unsigned long flags; + int err = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (ac->curr_bufs[0] && ac->curr_bufs[1]) { + spin_lock_irqsave(&dev->lock, flags); + dev->audio_enabled = 1; + tw686x_enable_channel(dev, + AUDIO_CHANNEL_OFFSET + ac->ch); + spin_unlock_irqrestore(&dev->lock, flags); + + mod_timer(&dev->dma_delay_timer, + jiffies + msecs_to_jiffies(100)); + } else { + err = -EIO; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + spin_lock_irqsave(&dev->lock, flags); + dev->audio_enabled = 0; + tw686x_disable_channel(dev, AUDIO_CHANNEL_OFFSET + ac->ch); + spin_unlock_irqrestore(&dev->lock, flags); + + spin_lock_irqsave(&ac->lock, flags); + ac->curr_bufs[0] = NULL; + ac->curr_bufs[1] = NULL; + spin_unlock_irqrestore(&ac->lock, flags); + break; + default: + err = -EINVAL; + } + return err; +} + +static snd_pcm_uframes_t tw686x_pcm_pointer(struct snd_pcm_substream *ss) +{ + struct tw686x_dev *dev = snd_pcm_substream_chip(ss); + struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number]; + + return bytes_to_frames(ss->runtime, ac->ptr); +} + +static const struct snd_pcm_ops tw686x_pcm_ops = { + .open = tw686x_pcm_open, + .close = tw686x_pcm_close, + .prepare = tw686x_pcm_prepare, + .trigger = tw686x_pcm_trigger, + .pointer = tw686x_pcm_pointer, +}; + +static int tw686x_snd_pcm_init(struct tw686x_dev *dev) +{ + struct snd_card *card = dev->snd_card; + struct snd_pcm *pcm; + struct snd_pcm_substream *ss; + unsigned int i; + int err; + + err = snd_pcm_new(card, card->driver, 0, 0, max_channels(dev), &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &tw686x_pcm_ops); + snd_pcm_chip(pcm) = dev; + pcm->info_flags = 0; + strscpy(pcm->name, "tw686x PCM", sizeof(pcm->name)); + + for (i = 0, ss = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + ss; ss = ss->next, i++) + snprintf(ss->name, sizeof(ss->name), "vch%u audio", i); + + snd_pcm_set_managed_buffer_all(pcm, + SNDRV_DMA_TYPE_DEV, + &dev->pci_dev->dev, + TW686X_AUDIO_PAGE_MAX * AUDIO_DMA_SIZE_MAX, + TW686X_AUDIO_PAGE_MAX * AUDIO_DMA_SIZE_MAX); + return 0; +} + +static void tw686x_audio_dma_free(struct tw686x_dev *dev, + struct tw686x_audio_channel *ac) +{ + int pb; + + for (pb = 0; pb < 2; pb++) { + if (!ac->dma_descs[pb].virt) + continue; + dma_free_coherent(&dev->pci_dev->dev, ac->dma_descs[pb].size, + ac->dma_descs[pb].virt, + ac->dma_descs[pb].phys); + ac->dma_descs[pb].virt = NULL; + } +} + +static int tw686x_audio_dma_alloc(struct tw686x_dev *dev, + struct tw686x_audio_channel *ac) +{ + int pb; + + /* + * In the memcpy DMA mode we allocate a coherent buffer + * and use it for the DMA capture. Otherwise, DMA + * acts on the ALSA buffers as received in pcm_prepare. + */ + if (dev->dma_mode != TW686X_DMA_MODE_MEMCPY) + return 0; + + for (pb = 0; pb < 2; pb++) { + u32 reg = pb ? ADMA_B_ADDR[ac->ch] : ADMA_P_ADDR[ac->ch]; + void *virt; + + virt = dma_alloc_coherent(&dev->pci_dev->dev, + AUDIO_DMA_SIZE_MAX, + &ac->dma_descs[pb].phys, GFP_KERNEL); + if (!virt) { + dev_err(&dev->pci_dev->dev, + "dma%d: unable to allocate audio DMA %s-buffer\n", + ac->ch, pb ? "B" : "P"); + return -ENOMEM; + } + ac->dma_descs[pb].virt = virt; + ac->dma_descs[pb].size = AUDIO_DMA_SIZE_MAX; + reg_write(dev, reg, ac->dma_descs[pb].phys); + } + return 0; +} + +void tw686x_audio_free(struct tw686x_dev *dev) +{ + unsigned long flags; + u32 dma_ch_mask; + u32 dma_cmd; + + spin_lock_irqsave(&dev->lock, flags); + dma_cmd = reg_read(dev, DMA_CMD); + dma_ch_mask = reg_read(dev, DMA_CHANNEL_ENABLE); + reg_write(dev, DMA_CMD, dma_cmd & ~0xff00); + reg_write(dev, DMA_CHANNEL_ENABLE, dma_ch_mask & ~0xff00); + spin_unlock_irqrestore(&dev->lock, flags); + + if (!dev->snd_card) + return; + snd_card_free(dev->snd_card); + dev->snd_card = NULL; +} + +int tw686x_audio_init(struct tw686x_dev *dev) +{ + struct pci_dev *pci_dev = dev->pci_dev; + struct snd_card *card; + int err, ch; + + /* Enable external audio */ + reg_write(dev, AUDIO_CONTROL1, BIT(0)); + + err = snd_card_new(&pci_dev->dev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1, + THIS_MODULE, 0, &card); + if (err < 0) + return err; + + dev->snd_card = card; + strscpy(card->driver, "tw686x", sizeof(card->driver)); + strscpy(card->shortname, "tw686x", sizeof(card->shortname)); + strscpy(card->longname, pci_name(pci_dev), sizeof(card->longname)); + snd_card_set_dev(card, &pci_dev->dev); + + for (ch = 0; ch < max_channels(dev); ch++) { + struct tw686x_audio_channel *ac; + + ac = &dev->audio_channels[ch]; + spin_lock_init(&ac->lock); + ac->dev = dev; + ac->ch = ch; + + err = tw686x_audio_dma_alloc(dev, ac); + if (err < 0) + goto err_cleanup; + } + + err = tw686x_snd_pcm_init(dev); + if (err < 0) + goto err_cleanup; + + err = snd_card_register(card); + if (!err) + return 0; + +err_cleanup: + for (ch = 0; ch < max_channels(dev); ch++) { + if (!dev->audio_channels[ch].dev) + continue; + tw686x_audio_dma_free(dev, &dev->audio_channels[ch]); + } + snd_card_free(card); + dev->snd_card = NULL; + return err; +} diff --git a/drivers/media/pci/tw686x/tw686x-core.c b/drivers/media/pci/tw686x/tw686x-core.c new file mode 100644 index 000000000..5a4ab329c --- /dev/null +++ b/drivers/media/pci/tw686x/tw686x-core.c @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 VanguardiaSur - www.vanguardiasur.com.ar + * + * Based on original driver by Krzysztof Ha?asa: + * Copyright (C) 2015 Industrial Research Institute for Automation + * and Measurements PIAP + * + * Notes + * ----- + * + * 1. Under stress-testing, it has been observed that the PCIe link + * goes down, without reason. Therefore, the driver takes special care + * to allow device hot-unplugging. + * + * 2. TW686X devices are capable of setting a few different DMA modes, + * including: scatter-gather, field and frame modes. However, + * under stress testings it has been found that the machine can + * freeze completely if DMA registers are programmed while streaming + * is active. + * + * Therefore, driver implements a dma_mode called 'memcpy' which + * avoids cycling the DMA buffers, and insteads allocates extra DMA buffers + * and then copies into vmalloc'ed user buffers. + * + * In addition to this, when streaming is on, the driver tries to access + * hardware registers as infrequently as possible. This is done by using + * a timer to limit the rate at which DMA is reset on DMA channels error. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tw686x.h" +#include "tw686x-regs.h" + +/* + * This module parameter allows to control the DMA_TIMER_INTERVAL value. + * The DMA_TIMER_INTERVAL register controls the minimum DMA interrupt + * time span (iow, the maximum DMA interrupt rate) thus allowing for + * IRQ coalescing. + * + * The chip datasheet does not mention a time unit for this value, so + * users wanting fine-grain control over the interrupt rate should + * determine the desired value through testing. + */ +static u32 dma_interval = 0x00098968; +module_param(dma_interval, int, 0444); +MODULE_PARM_DESC(dma_interval, "Minimum time span for DMA interrupting host"); + +static unsigned int dma_mode = TW686X_DMA_MODE_MEMCPY; +static const char *dma_mode_name(unsigned int mode) +{ + switch (mode) { + case TW686X_DMA_MODE_MEMCPY: + return "memcpy"; + case TW686X_DMA_MODE_CONTIG: + return "contig"; + case TW686X_DMA_MODE_SG: + return "sg"; + default: + return "unknown"; + } +} + +static int tw686x_dma_mode_get(char *buffer, const struct kernel_param *kp) +{ + return sprintf(buffer, "%s", dma_mode_name(dma_mode)); +} + +static int tw686x_dma_mode_set(const char *val, const struct kernel_param *kp) +{ + if (!strcasecmp(val, dma_mode_name(TW686X_DMA_MODE_MEMCPY))) + dma_mode = TW686X_DMA_MODE_MEMCPY; + else if (!strcasecmp(val, dma_mode_name(TW686X_DMA_MODE_CONTIG))) + dma_mode = TW686X_DMA_MODE_CONTIG; + else if (!strcasecmp(val, dma_mode_name(TW686X_DMA_MODE_SG))) + dma_mode = TW686X_DMA_MODE_SG; + else + return -EINVAL; + return 0; +} +module_param_call(dma_mode, tw686x_dma_mode_set, tw686x_dma_mode_get, + &dma_mode, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(dma_mode, "DMA operation mode (memcpy/contig/sg, default=memcpy)"); + +void tw686x_disable_channel(struct tw686x_dev *dev, unsigned int channel) +{ + u32 dma_en = reg_read(dev, DMA_CHANNEL_ENABLE); + u32 dma_cmd = reg_read(dev, DMA_CMD); + + dma_en &= ~BIT(channel); + dma_cmd &= ~BIT(channel); + + /* Must remove it from pending too */ + dev->pending_dma_en &= ~BIT(channel); + dev->pending_dma_cmd &= ~BIT(channel); + + /* Stop DMA if no channels are enabled */ + if (!dma_en) + dma_cmd = 0; + reg_write(dev, DMA_CHANNEL_ENABLE, dma_en); + reg_write(dev, DMA_CMD, dma_cmd); +} + +void tw686x_enable_channel(struct tw686x_dev *dev, unsigned int channel) +{ + u32 dma_en = reg_read(dev, DMA_CHANNEL_ENABLE); + u32 dma_cmd = reg_read(dev, DMA_CMD); + + dev->pending_dma_en |= dma_en | BIT(channel); + dev->pending_dma_cmd |= dma_cmd | DMA_CMD_ENABLE | BIT(channel); +} + +/* + * The purpose of this awful hack is to avoid enabling the DMA + * channels "too fast" which makes some TW686x devices very + * angry and freeze the CPU (see note 1). + */ +static void tw686x_dma_delay(struct timer_list *t) +{ + struct tw686x_dev *dev = timer_container_of(dev, t, dma_delay_timer); + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + + reg_write(dev, DMA_CHANNEL_ENABLE, dev->pending_dma_en); + reg_write(dev, DMA_CMD, dev->pending_dma_cmd); + dev->pending_dma_en = 0; + dev->pending_dma_cmd = 0; + + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void tw686x_reset_channels(struct tw686x_dev *dev, unsigned int ch_mask) +{ + u32 dma_en, dma_cmd; + + dma_en = reg_read(dev, DMA_CHANNEL_ENABLE); + dma_cmd = reg_read(dev, DMA_CMD); + + /* + * Save pending register status, the timer will + * restore them. + */ + dev->pending_dma_en |= dma_en; + dev->pending_dma_cmd |= dma_cmd; + + /* Disable the reset channels */ + reg_write(dev, DMA_CHANNEL_ENABLE, dma_en & ~ch_mask); + + if ((dma_en & ~ch_mask) == 0) { + dev_dbg(&dev->pci_dev->dev, "reset: stopping DMA\n"); + dma_cmd &= ~DMA_CMD_ENABLE; + } + reg_write(dev, DMA_CMD, dma_cmd & ~ch_mask); +} + +static irqreturn_t tw686x_irq(int irq, void *dev_id) +{ + struct tw686x_dev *dev = (struct tw686x_dev *)dev_id; + unsigned int video_requests, audio_requests, reset_ch; + u32 fifo_status, fifo_signal, fifo_ov, fifo_bad, fifo_errors; + u32 int_status, dma_en, video_en, pb_status; + unsigned long flags; + + int_status = reg_read(dev, INT_STATUS); /* cleared on read */ + fifo_status = reg_read(dev, VIDEO_FIFO_STATUS); + + /* INT_STATUS does not include FIFO_STATUS errors! */ + if (!int_status && !TW686X_FIFO_ERROR(fifo_status)) + return IRQ_NONE; + + if (int_status & INT_STATUS_DMA_TOUT) { + dev_dbg(&dev->pci_dev->dev, + "DMA timeout. Resetting DMA for all channels\n"); + reset_ch = ~0; + goto reset_channels; + } + + spin_lock_irqsave(&dev->lock, flags); + dma_en = reg_read(dev, DMA_CHANNEL_ENABLE); + spin_unlock_irqrestore(&dev->lock, flags); + + video_en = dma_en & 0xff; + fifo_signal = ~(fifo_status & 0xff) & video_en; + fifo_ov = fifo_status >> 24; + fifo_bad = fifo_status >> 16; + + /* Mask of channels with signal and FIFO errors */ + fifo_errors = fifo_signal & (fifo_ov | fifo_bad); + + reset_ch = 0; + pb_status = reg_read(dev, PB_STATUS); + + /* Coalesce video frame/error events */ + video_requests = (int_status & video_en) | fifo_errors; + audio_requests = (int_status & dma_en) >> 8; + + if (video_requests) + tw686x_video_irq(dev, video_requests, pb_status, + fifo_status, &reset_ch); + if (audio_requests) + tw686x_audio_irq(dev, audio_requests, pb_status); + +reset_channels: + if (reset_ch) { + spin_lock_irqsave(&dev->lock, flags); + tw686x_reset_channels(dev, reset_ch); + spin_unlock_irqrestore(&dev->lock, flags); + mod_timer(&dev->dma_delay_timer, + jiffies + msecs_to_jiffies(100)); + } + + return IRQ_HANDLED; +} + +static void tw686x_dev_release(struct v4l2_device *v4l2_dev) +{ + struct tw686x_dev *dev = container_of(v4l2_dev, struct tw686x_dev, + v4l2_dev); + unsigned int ch; + + for (ch = 0; ch < max_channels(dev); ch++) + v4l2_ctrl_handler_free(&dev->video_channels[ch].ctrl_handler); + + v4l2_device_unregister(&dev->v4l2_dev); + + kfree(dev->audio_channels); + kfree(dev->video_channels); + kfree(dev); +} + +static int tw686x_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct tw686x_dev *dev; + int err; + + dev = kzalloc_obj(*dev); + if (!dev) + return -ENOMEM; + dev->type = pci_id->driver_data; + dev->dma_mode = dma_mode; + sprintf(dev->name, "tw%04X", pci_dev->device); + + dev->video_channels = kzalloc_objs(*dev->video_channels, + max_channels(dev)); + if (!dev->video_channels) { + err = -ENOMEM; + goto free_dev; + } + + dev->audio_channels = kzalloc_objs(*dev->audio_channels, + max_channels(dev)); + if (!dev->audio_channels) { + err = -ENOMEM; + goto free_video; + } + + pr_info("%s: PCI %s, IRQ %d, MMIO 0x%lx (%s mode)\n", dev->name, + pci_name(pci_dev), pci_dev->irq, + (unsigned long)pci_resource_start(pci_dev, 0), + dma_mode_name(dma_mode)); + + dev->pci_dev = pci_dev; + if (pci_enable_device(pci_dev)) { + err = -EIO; + goto free_audio; + } + + pci_set_master(pci_dev); + err = dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(32)); + if (err) { + dev_err(&pci_dev->dev, "32-bit PCI DMA not supported\n"); + err = -EIO; + goto disable_pci; + } + + err = pci_request_regions(pci_dev, dev->name); + if (err) { + dev_err(&pci_dev->dev, "unable to request PCI region\n"); + goto disable_pci; + } + + dev->mmio = pci_ioremap_bar(pci_dev, 0); + if (!dev->mmio) { + dev_err(&pci_dev->dev, "unable to remap PCI region\n"); + err = -ENOMEM; + goto free_region; + } + + /* Reset all subsystems */ + reg_write(dev, SYS_SOFT_RST, 0x0f); + mdelay(1); + + reg_write(dev, SRST[0], 0x3f); + if (max_channels(dev) > 4) + reg_write(dev, SRST[1], 0x3f); + + /* Disable the DMA engine */ + reg_write(dev, DMA_CMD, 0); + reg_write(dev, DMA_CHANNEL_ENABLE, 0); + + /* Enable DMA FIFO overflow and pointer check */ + reg_write(dev, DMA_CONFIG, 0xffffff04); + reg_write(dev, DMA_CHANNEL_TIMEOUT, 0x140c8584); + reg_write(dev, DMA_TIMER_INTERVAL, dma_interval); + + spin_lock_init(&dev->lock); + + timer_setup(&dev->dma_delay_timer, tw686x_dma_delay, 0); + + /* + * This must be set right before initializing v4l2_dev. + * It's used to release resources after the last handle + * held is released. + */ + dev->v4l2_dev.release = tw686x_dev_release; + err = tw686x_video_init(dev); + if (err) { + dev_err(&pci_dev->dev, "can't register video\n"); + goto iounmap; + } + + err = tw686x_audio_init(dev); + if (err) + dev_warn(&pci_dev->dev, "can't register audio\n"); + + err = request_irq(pci_dev->irq, tw686x_irq, IRQF_SHARED, + dev->name, dev); + if (err < 0) { + dev_err(&pci_dev->dev, "unable to request interrupt\n"); + goto tw686x_free; + } + + pci_set_drvdata(pci_dev, dev); + return 0; + +tw686x_free: + tw686x_video_free(dev); + tw686x_audio_free(dev); +iounmap: + pci_iounmap(pci_dev, dev->mmio); +free_region: + pci_release_regions(pci_dev); +disable_pci: + pci_disable_device(pci_dev); +free_audio: + kfree(dev->audio_channels); +free_video: + kfree(dev->video_channels); +free_dev: + kfree(dev); + return err; +} + +static void tw686x_remove(struct pci_dev *pci_dev) +{ + struct tw686x_dev *dev = pci_get_drvdata(pci_dev); + unsigned long flags; + + /* This guarantees the IRQ handler is no longer running, + * which means we can kiss good-bye some resources. + */ + free_irq(pci_dev->irq, dev); + + tw686x_video_free(dev); + tw686x_audio_free(dev); + timer_delete_sync(&dev->dma_delay_timer); + + pci_iounmap(pci_dev, dev->mmio); + pci_release_regions(pci_dev); + pci_disable_device(pci_dev); + + /* + * Setting pci_dev to NULL allows to detect hardware is no longer + * available and will be used by vb2_ops. This is required because + * the device sometimes hot-unplugs itself as the result of a PCIe + * link down. + * The lock is really important here. + */ + spin_lock_irqsave(&dev->lock, flags); + dev->pci_dev = NULL; + spin_unlock_irqrestore(&dev->lock, flags); + + /* + * This calls tw686x_dev_release if it's the last reference. + * Otherwise, release is postponed until there are no users left. + */ + v4l2_device_put(&dev->v4l2_dev); +} + +/* + * On TW6864 and TW6868, all channels share the pair of video DMA SG tables, + * with 10-bit start_idx and end_idx determining start and end of frame buffer + * for particular channel. + * TW6868 with all its 8 channels would be problematic (only 127 SG entries per + * channel) but we support only 4 channels on this chip anyway (the first + * 4 channels are driven with internal video decoder, the other 4 would require + * an external TW286x part). + * + * On TW6865 and TW6869, each channel has its own DMA SG table, with indexes + * starting with 0. Both chips have complete sets of internal video decoders + * (respectively 4 or 8-channel). + * + * All chips have separate SG tables for two video frames. + */ + +/* driver_data is number of A/V channels */ +static const struct pci_device_id tw686x_pci_tbl[] = { + { + PCI_VDEVICE(TECHWELL, 0x6864), + .driver_data = 4, + }, { + PCI_VDEVICE(TECHWELL, 0x6865), /* not tested */ + .driver_data = 4 | TYPE_SECOND_GEN, + }, + /* + * TW6868 supports 8 A/V channels with an external TW2865 chip; + * not supported by the driver. + */ + { + PCI_VDEVICE(TECHWELL, 0x6868), /* not tested */ + .driver_data = 4, + }, { + PCI_VDEVICE(TECHWELL, 0x6869), + .driver_data = 8 | TYPE_SECOND_GEN + }, + { } +}; +MODULE_DEVICE_TABLE(pci, tw686x_pci_tbl); + +static struct pci_driver tw686x_pci_driver = { + .name = "tw686x", + .id_table = tw686x_pci_tbl, + .probe = tw686x_probe, + .remove = tw686x_remove, +}; +module_pci_driver(tw686x_pci_driver); + +MODULE_DESCRIPTION("Driver for video frame grabber cards based on Intersil/Techwell TW686[4589]"); +MODULE_AUTHOR("Ezequiel Garcia "); +MODULE_AUTHOR("Krzysztof Ha?asa "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/pci/tw686x/tw686x-regs.h b/drivers/media/pci/tw686x/tw686x-regs.h new file mode 100644 index 000000000..8adacc928 --- /dev/null +++ b/drivers/media/pci/tw686x/tw686x-regs.h @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* DMA controller registers */ +#define REG8_1(a0) ((const u16[8]) { a0, a0 + 1, a0 + 2, a0 + 3, \ + a0 + 4, a0 + 5, a0 + 6, a0 + 7}) +#define REG8_2(a0) ((const u16[8]) { a0, a0 + 2, a0 + 4, a0 + 6, \ + a0 + 8, a0 + 0xa, a0 + 0xc, a0 + 0xe}) +#define REG8_8(a0) ((const u16[8]) { a0, a0 + 8, a0 + 0x10, a0 + 0x18, \ + a0 + 0x20, a0 + 0x28, a0 + 0x30, \ + a0 + 0x38}) +#define INT_STATUS 0x00 +#define PB_STATUS 0x01 +#define DMA_CMD 0x02 +#define VIDEO_FIFO_STATUS 0x03 +#define VIDEO_CHANNEL_ID 0x04 +#define VIDEO_PARSER_STATUS 0x05 +#define SYS_SOFT_RST 0x06 +#define DMA_PAGE_TABLE0_ADDR ((const u16[8]) { 0x08, 0xd0, 0xd2, 0xd4, \ + 0xd6, 0xd8, 0xda, 0xdc }) +#define DMA_PAGE_TABLE1_ADDR ((const u16[8]) { 0x09, 0xd1, 0xd3, 0xd5, \ + 0xd7, 0xd9, 0xdb, 0xdd }) +#define DMA_CHANNEL_ENABLE 0x0a +#define DMA_CONFIG 0x0b +#define DMA_TIMER_INTERVAL 0x0c +#define DMA_CHANNEL_TIMEOUT 0x0d +#define VDMA_CHANNEL_CONFIG REG8_1(0x10) +#define ADMA_P_ADDR REG8_2(0x18) +#define ADMA_B_ADDR REG8_2(0x19) +#define DMA10_P_ADDR 0x28 +#define DMA10_B_ADDR 0x29 +#define VIDEO_CONTROL1 0x2a +#define VIDEO_CONTROL2 0x2b +#define AUDIO_CONTROL1 0x2c +#define AUDIO_CONTROL2 0x2d +#define PHASE_REF 0x2e +#define GPIO_REG 0x2f +#define INTL_HBAR_CTRL REG8_1(0x30) +#define AUDIO_CONTROL3 0x38 +#define VIDEO_FIELD_CTRL REG8_1(0x39) +#define HSCALER_CTRL REG8_1(0x42) +#define VIDEO_SIZE REG8_1(0x4A) +#define VIDEO_SIZE_F2 REG8_1(0x52) +#define MD_CONF REG8_1(0x60) +#define MD_INIT REG8_1(0x68) +#define MD_MAP0 REG8_1(0x70) +#define VDMA_P_ADDR REG8_8(0x80) /* not used in DMA SG mode */ +#define VDMA_WHP REG8_8(0x81) +#define VDMA_B_ADDR REG8_8(0x82) +#define VDMA_F2_P_ADDR REG8_8(0x84) +#define VDMA_F2_WHP REG8_8(0x85) +#define VDMA_F2_B_ADDR REG8_8(0x86) +#define EP_REG_ADDR 0xfe +#define EP_REG_DATA 0xff + +/* Video decoder registers */ +#define VDREG8(a0) ((const u16[8]) { \ + a0 + 0x000, a0 + 0x010, a0 + 0x020, a0 + 0x030, \ + a0 + 0x100, a0 + 0x110, a0 + 0x120, a0 + 0x130}) +#define VIDSTAT VDREG8(0x100) +#define BRIGHT VDREG8(0x101) +#define CONTRAST VDREG8(0x102) +#define SHARPNESS VDREG8(0x103) +#define SAT_U VDREG8(0x104) +#define SAT_V VDREG8(0x105) +#define HUE VDREG8(0x106) +#define CROP_HI VDREG8(0x107) +#define VDELAY_LO VDREG8(0x108) +#define VACTIVE_LO VDREG8(0x109) +#define HDELAY_LO VDREG8(0x10a) +#define HACTIVE_LO VDREG8(0x10b) +#define MVSN VDREG8(0x10c) +#define STATUS2 VDREG8(0x10d) +#define SDT VDREG8(0x10e) +#define SDT_EN VDREG8(0x10f) + +#define VSCALE_LO VDREG8(0x144) +#define SCALE_HI VDREG8(0x145) +#define HSCALE_LO VDREG8(0x146) +#define F2CROP_HI VDREG8(0x147) +#define F2VDELAY_LO VDREG8(0x148) +#define F2VACTIVE_LO VDREG8(0x149) +#define F2HDELAY_LO VDREG8(0x14a) +#define F2HACTIVE_LO VDREG8(0x14b) +#define F2VSCALE_LO VDREG8(0x14c) +#define F2SCALE_HI VDREG8(0x14d) +#define F2HSCALE_LO VDREG8(0x14e) +#define F2CNT VDREG8(0x14f) + +#define VDREG2(a0) ((const u16[2]) { a0, a0 + 0x100 }) +#define SRST VDREG2(0x180) +#define ACNTL VDREG2(0x181) +#define ACNTL2 VDREG2(0x182) +#define CNTRL1 VDREG2(0x183) +#define CKHY VDREG2(0x184) +#define SHCOR VDREG2(0x185) +#define CORING VDREG2(0x186) +#define CLMPG VDREG2(0x187) +#define IAGC VDREG2(0x188) +#define VCTRL1 VDREG2(0x18f) +#define MISC1 VDREG2(0x194) +#define LOOP VDREG2(0x195) +#define MISC2 VDREG2(0x196) + +#define CLMD VDREG2(0x197) +#define ANPWRDOWN VDREG2(0x1ce) +#define AIGAIN ((const u16[8]) { 0x1d0, 0x1d1, 0x1d2, 0x1d3, \ + 0x2d0, 0x2d1, 0x2d2, 0x2d3 }) + +#define SYS_MODE_DMA_SHIFT 13 +#define AUDIO_DMA_SIZE_SHIFT 19 +#define AUDIO_DMA_SIZE_MIN SZ_512 +#define AUDIO_DMA_SIZE_MAX SZ_4K +#define AUDIO_DMA_SIZE_MASK (SZ_8K - 1) + +#define DMA_CMD_ENABLE BIT(31) +#define INT_STATUS_DMA_TOUT BIT(17) +#define TW686X_VIDSTAT_HLOCK BIT(6) +#define TW686X_VIDSTAT_VDLOSS BIT(7) + +#define TW686X_STD_NTSC_M 0 +#define TW686X_STD_PAL 1 +#define TW686X_STD_SECAM 2 +#define TW686X_STD_NTSC_443 3 +#define TW686X_STD_PAL_M 4 +#define TW686X_STD_PAL_CN 5 +#define TW686X_STD_PAL_60 6 + +#define TW686X_FIELD_MODE 0x3 +#define TW686X_FRAME_MODE 0x2 +/* 0x1 is reserved */ +#define TW686X_SG_MODE 0x0 + +#define TW686X_FIFO_ERROR(x) (x & ~(0xff)) diff --git a/drivers/media/pci/tw686x/tw686x-video.c b/drivers/media/pci/tw686x/tw686x-video.c new file mode 100644 index 000000000..785dd797d --- /dev/null +++ b/drivers/media/pci/tw686x/tw686x-video.c @@ -0,0 +1,1307 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 VanguardiaSur - www.vanguardiasur.com.ar + * + * Based on original driver by Krzysztof Ha?asa: + * Copyright (C) 2015 Industrial Research Institute for Automation + * and Measurements PIAP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tw686x.h" +#include "tw686x-regs.h" + +#define TW686X_INPUTS_PER_CH 4 +#define TW686X_VIDEO_WIDTH 720 +#define TW686X_VIDEO_HEIGHT(id) ((id & V4L2_STD_525_60) ? 480 : 576) +#define TW686X_MAX_FPS(id) ((id & V4L2_STD_525_60) ? 30 : 25) + +#define TW686X_MAX_SG_ENTRY_SIZE 4096 +#define TW686X_MAX_SG_DESC_COUNT 256 /* PAL 720x576 needs 203 4-KB pages */ +#define TW686X_SG_TABLE_SIZE (TW686X_MAX_SG_DESC_COUNT * sizeof(struct tw686x_sg_desc)) + +static const struct tw686x_format formats[] = { + { + .fourcc = V4L2_PIX_FMT_UYVY, + .mode = 0, + .depth = 16, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .mode = 5, + .depth = 16, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .mode = 6, + .depth = 16, + } +}; + +static void tw686x_buf_done(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; + struct tw686x_dev *dev = vc->dev; + struct vb2_v4l2_buffer *vb; + struct vb2_buffer *vb2_buf; + + if (vc->curr_bufs[pb]) { + vb = &vc->curr_bufs[pb]->vb; + + vb->field = dev->dma_ops->field; + vb->sequence = vc->sequence++; + vb2_buf = &vb->vb2_buf; + + if (dev->dma_mode == TW686X_DMA_MODE_MEMCPY) + memcpy(vb2_plane_vaddr(vb2_buf, 0), desc->virt, + desc->size); + vb2_buf->timestamp = ktime_get_ns(); + vb2_buffer_done(vb2_buf, VB2_BUF_STATE_DONE); + } + + vc->pb = !pb; +} + +/* + * We can call this even when alloc_dma failed for the given channel + */ +static void tw686x_memcpy_dma_free(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; + struct tw686x_dev *dev = vc->dev; + struct pci_dev *pci_dev; + unsigned long flags; + + /* Check device presence. Shouldn't really happen! */ + spin_lock_irqsave(&dev->lock, flags); + pci_dev = dev->pci_dev; + spin_unlock_irqrestore(&dev->lock, flags); + if (!pci_dev) { + WARN(1, "trying to deallocate on missing device\n"); + return; + } + + if (desc->virt) { + dma_free_coherent(&dev->pci_dev->dev, desc->size, desc->virt, + desc->phys); + desc->virt = NULL; + } +} + +static int tw686x_memcpy_dma_alloc(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dev *dev = vc->dev; + u32 reg = pb ? VDMA_B_ADDR[vc->ch] : VDMA_P_ADDR[vc->ch]; + unsigned int len; + void *virt; + + WARN(vc->dma_descs[pb].virt, + "Allocating buffer but previous still here\n"); + + len = (vc->width * vc->height * vc->format->depth) >> 3; + virt = dma_alloc_coherent(&dev->pci_dev->dev, len, + &vc->dma_descs[pb].phys, GFP_KERNEL); + if (!virt) { + v4l2_err(&dev->v4l2_dev, + "dma%d: unable to allocate %s-buffer\n", + vc->ch, pb ? "B" : "P"); + return -ENOMEM; + } + vc->dma_descs[pb].size = len; + vc->dma_descs[pb].virt = virt; + reg_write(dev, reg, vc->dma_descs[pb].phys); + + return 0; +} + +static void tw686x_memcpy_buf_refill(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_v4l2_buf *buf; + + while (!list_empty(&vc->vidq_queued)) { + + buf = list_first_entry(&vc->vidq_queued, + struct tw686x_v4l2_buf, list); + list_del(&buf->list); + + vc->curr_bufs[pb] = buf; + return; + } + vc->curr_bufs[pb] = NULL; +} + +static const struct tw686x_dma_ops memcpy_dma_ops = { + .alloc = tw686x_memcpy_dma_alloc, + .free = tw686x_memcpy_dma_free, + .buf_refill = tw686x_memcpy_buf_refill, + .mem_ops = &vb2_vmalloc_memops, + .hw_dma_mode = TW686X_FRAME_MODE, + .field = V4L2_FIELD_INTERLACED, +}; + +static void tw686x_contig_buf_refill(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_v4l2_buf *buf; + + while (!list_empty(&vc->vidq_queued)) { + u32 reg = pb ? VDMA_B_ADDR[vc->ch] : VDMA_P_ADDR[vc->ch]; + dma_addr_t phys; + + buf = list_first_entry(&vc->vidq_queued, + struct tw686x_v4l2_buf, list); + list_del(&buf->list); + + phys = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + reg_write(vc->dev, reg, phys); + + buf->vb.vb2_buf.state = VB2_BUF_STATE_ACTIVE; + vc->curr_bufs[pb] = buf; + return; + } + vc->curr_bufs[pb] = NULL; +} + +static const struct tw686x_dma_ops contig_dma_ops = { + .buf_refill = tw686x_contig_buf_refill, + .mem_ops = &vb2_dma_contig_memops, + .hw_dma_mode = TW686X_FRAME_MODE, + .field = V4L2_FIELD_INTERLACED, +}; + +static int tw686x_sg_desc_fill(struct tw686x_sg_desc *descs, + struct tw686x_v4l2_buf *buf, + unsigned int buf_len) +{ + struct sg_table *vbuf = vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0); + unsigned int len, entry_len; + struct scatterlist *sg; + int i, count; + + /* Clear the scatter-gather table */ + memset(descs, 0, TW686X_SG_TABLE_SIZE); + + count = 0; + for_each_sg(vbuf->sgl, sg, vbuf->nents, i) { + dma_addr_t phys = sg_dma_address(sg); + len = sg_dma_len(sg); + + while (len && buf_len) { + + if (count == TW686X_MAX_SG_DESC_COUNT) + return -ENOMEM; + + entry_len = min_t(unsigned int, len, + TW686X_MAX_SG_ENTRY_SIZE); + entry_len = min_t(unsigned int, entry_len, buf_len); + descs[count].phys = cpu_to_le32(phys); + descs[count++].flags_length = + cpu_to_le32(BIT(30) | entry_len); + phys += entry_len; + len -= entry_len; + buf_len -= entry_len; + } + + if (!buf_len) + return 0; + } + + return -ENOMEM; +} + +static void tw686x_sg_buf_refill(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dev *dev = vc->dev; + struct tw686x_v4l2_buf *buf; + + while (!list_empty(&vc->vidq_queued)) { + unsigned int buf_len; + + buf = list_first_entry(&vc->vidq_queued, + struct tw686x_v4l2_buf, list); + list_del(&buf->list); + + buf_len = (vc->width * vc->height * vc->format->depth) >> 3; + if (tw686x_sg_desc_fill(vc->sg_descs[pb], buf, buf_len)) { + v4l2_err(&dev->v4l2_dev, + "dma%d: unable to fill %s-buffer\n", + vc->ch, pb ? "B" : "P"); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + continue; + } + + buf->vb.vb2_buf.state = VB2_BUF_STATE_ACTIVE; + vc->curr_bufs[pb] = buf; + return; + } + + vc->curr_bufs[pb] = NULL; +} + +static void tw686x_sg_dma_free(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; + struct tw686x_dev *dev = vc->dev; + + if (desc->size) { + dma_free_coherent(&dev->pci_dev->dev, desc->size, desc->virt, + desc->phys); + desc->virt = NULL; + } + + vc->sg_descs[pb] = NULL; +} + +static int tw686x_sg_dma_alloc(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; + struct tw686x_dev *dev = vc->dev; + u32 reg = pb ? DMA_PAGE_TABLE1_ADDR[vc->ch] : + DMA_PAGE_TABLE0_ADDR[vc->ch]; + void *virt; + + if (desc->size) { + virt = dma_alloc_coherent(&dev->pci_dev->dev, desc->size, + &desc->phys, GFP_KERNEL); + if (!virt) { + v4l2_err(&dev->v4l2_dev, + "dma%d: unable to allocate %s-buffer\n", + vc->ch, pb ? "B" : "P"); + return -ENOMEM; + } + desc->virt = virt; + reg_write(dev, reg, desc->phys); + } else { + virt = dev->video_channels[0].dma_descs[pb].virt + + vc->ch * TW686X_SG_TABLE_SIZE; + } + + vc->sg_descs[pb] = virt; + return 0; +} + +static int tw686x_sg_setup(struct tw686x_dev *dev) +{ + unsigned int sg_table_size, pb, ch, channels; + + if (is_second_gen(dev)) { + /* + * TW6865/TW6869: each channel needs a pair of + * P-B descriptor tables. + */ + channels = max_channels(dev); + sg_table_size = TW686X_SG_TABLE_SIZE; + } else { + /* + * TW6864/TW6868: we need to allocate a pair of + * P-B descriptor tables, common for all channels. + * Each table will be bigger than 4 KB. + */ + channels = 1; + sg_table_size = max_channels(dev) * TW686X_SG_TABLE_SIZE; + } + + for (ch = 0; ch < channels; ch++) { + struct tw686x_video_channel *vc = &dev->video_channels[ch]; + + for (pb = 0; pb < 2; pb++) + vc->dma_descs[pb].size = sg_table_size; + } + + return 0; +} + +static const struct tw686x_dma_ops sg_dma_ops = { + .setup = tw686x_sg_setup, + .alloc = tw686x_sg_dma_alloc, + .free = tw686x_sg_dma_free, + .buf_refill = tw686x_sg_buf_refill, + .mem_ops = &vb2_dma_sg_memops, + .hw_dma_mode = TW686X_SG_MODE, + .field = V4L2_FIELD_SEQ_TB, +}; + +static const unsigned int fps_map[15] = { + /* + * bit 31 enables selecting the field control register + * bits 0-29 are a bitmask with fields that will be output. + * For NTSC (and PAL-M, PAL-60), all 30 bits are used. + * For other PAL standards, only the first 25 bits are used. + */ + 0x00000000, /* output all fields */ + 0x80000006, /* 2 fps (60Hz), 2 fps (50Hz) */ + 0x80018006, /* 4 fps (60Hz), 4 fps (50Hz) */ + 0x80618006, /* 6 fps (60Hz), 6 fps (50Hz) */ + 0x81818186, /* 8 fps (60Hz), 8 fps (50Hz) */ + 0x86186186, /* 10 fps (60Hz), 8 fps (50Hz) */ + 0x86619866, /* 12 fps (60Hz), 10 fps (50Hz) */ + 0x86666666, /* 14 fps (60Hz), 12 fps (50Hz) */ + 0x9999999e, /* 16 fps (60Hz), 14 fps (50Hz) */ + 0x99e6799e, /* 18 fps (60Hz), 16 fps (50Hz) */ + 0x9e79e79e, /* 20 fps (60Hz), 16 fps (50Hz) */ + 0x9e7e7e7e, /* 22 fps (60Hz), 18 fps (50Hz) */ + 0x9fe7f9fe, /* 24 fps (60Hz), 20 fps (50Hz) */ + 0x9ffe7ffe, /* 26 fps (60Hz), 22 fps (50Hz) */ + 0x9ffffffe, /* 28 fps (60Hz), 24 fps (50Hz) */ +}; + +static unsigned int tw686x_real_fps(unsigned int index, unsigned int max_fps) +{ + unsigned long mask; + + if (!index || index >= ARRAY_SIZE(fps_map)) + return max_fps; + + mask = GENMASK(max_fps - 1, 0); + return hweight_long(fps_map[index] & mask); +} + +static unsigned int tw686x_fps_idx(unsigned int fps, unsigned int max_fps) +{ + unsigned int idx, real_fps; + int delta; + + /* First guess */ + idx = (12 + 15 * fps) / max_fps; + + /* Minimal possible framerate is 2 frames per second */ + if (!idx) + return 1; + + /* Check if the difference is bigger than abs(1) and adjust */ + real_fps = tw686x_real_fps(idx, max_fps); + delta = real_fps - fps; + if (delta < -1) + idx++; + else if (delta > 1) + idx--; + + /* Max framerate */ + if (idx >= 15) + return 0; + + return idx; +} + +static void tw686x_set_framerate(struct tw686x_video_channel *vc, + unsigned int fps) +{ + unsigned int i; + + i = tw686x_fps_idx(fps, TW686X_MAX_FPS(vc->video_standard)); + reg_write(vc->dev, VIDEO_FIELD_CTRL[vc->ch], fps_map[i]); + vc->fps = tw686x_real_fps(i, TW686X_MAX_FPS(vc->video_standard)); +} + +static const struct tw686x_format *format_by_fourcc(unsigned int fourcc) +{ + unsigned int cnt; + + for (cnt = 0; cnt < ARRAY_SIZE(formats); cnt++) + if (formats[cnt].fourcc == fourcc) + return &formats[cnt]; + return NULL; +} + +static int tw686x_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct tw686x_video_channel *vc = vb2_get_drv_priv(vq); + unsigned int q_num_bufs = vb2_get_num_buffers(vq); + unsigned int szimage = + (vc->width * vc->height * vc->format->depth) >> 3; + + /* + * Let's request at least three buffers: two for the + * DMA engine and one for userspace. + */ + if (q_num_bufs + *nbuffers < 3) + *nbuffers = 3 - q_num_bufs; + + if (*nplanes) { + if (*nplanes != 1 || sizes[0] < szimage) + return -EINVAL; + return 0; + } + + sizes[0] = szimage; + *nplanes = 1; + return 0; +} + +static void tw686x_buf_queue(struct vb2_buffer *vb) +{ + struct tw686x_video_channel *vc = vb2_get_drv_priv(vb->vb2_queue); + struct tw686x_dev *dev = vc->dev; + struct pci_dev *pci_dev; + unsigned long flags; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct tw686x_v4l2_buf *buf = + container_of(vbuf, struct tw686x_v4l2_buf, vb); + + /* Check device presence */ + spin_lock_irqsave(&dev->lock, flags); + pci_dev = dev->pci_dev; + spin_unlock_irqrestore(&dev->lock, flags); + if (!pci_dev) { + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + return; + } + + spin_lock_irqsave(&vc->qlock, flags); + list_add_tail(&buf->list, &vc->vidq_queued); + spin_unlock_irqrestore(&vc->qlock, flags); +} + +static void tw686x_clear_queue(struct tw686x_video_channel *vc, + enum vb2_buffer_state state) +{ + unsigned int pb; + + while (!list_empty(&vc->vidq_queued)) { + struct tw686x_v4l2_buf *buf; + + buf = list_first_entry(&vc->vidq_queued, + struct tw686x_v4l2_buf, list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, state); + } + + for (pb = 0; pb < 2; pb++) { + if (vc->curr_bufs[pb]) + vb2_buffer_done(&vc->curr_bufs[pb]->vb.vb2_buf, state); + vc->curr_bufs[pb] = NULL; + } +} + +static int tw686x_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct tw686x_video_channel *vc = vb2_get_drv_priv(vq); + struct tw686x_dev *dev = vc->dev; + struct pci_dev *pci_dev; + unsigned long flags; + int pb, err; + + /* Check device presence */ + spin_lock_irqsave(&dev->lock, flags); + pci_dev = dev->pci_dev; + spin_unlock_irqrestore(&dev->lock, flags); + if (!pci_dev) { + err = -ENODEV; + goto err_clear_queue; + } + + spin_lock_irqsave(&vc->qlock, flags); + + /* Sanity check */ + if (dev->dma_mode == TW686X_DMA_MODE_MEMCPY && + (!vc->dma_descs[0].virt || !vc->dma_descs[1].virt)) { + spin_unlock_irqrestore(&vc->qlock, flags); + v4l2_err(&dev->v4l2_dev, + "video%d: refusing to start without DMA buffers\n", + vc->num); + err = -ENOMEM; + goto err_clear_queue; + } + + for (pb = 0; pb < 2; pb++) + dev->dma_ops->buf_refill(vc, pb); + spin_unlock_irqrestore(&vc->qlock, flags); + + vc->sequence = 0; + vc->pb = 0; + + spin_lock_irqsave(&dev->lock, flags); + tw686x_enable_channel(dev, vc->ch); + spin_unlock_irqrestore(&dev->lock, flags); + + mod_timer(&dev->dma_delay_timer, jiffies + msecs_to_jiffies(100)); + + return 0; + +err_clear_queue: + spin_lock_irqsave(&vc->qlock, flags); + tw686x_clear_queue(vc, VB2_BUF_STATE_QUEUED); + spin_unlock_irqrestore(&vc->qlock, flags); + return err; +} + +static void tw686x_stop_streaming(struct vb2_queue *vq) +{ + struct tw686x_video_channel *vc = vb2_get_drv_priv(vq); + struct tw686x_dev *dev = vc->dev; + struct pci_dev *pci_dev; + unsigned long flags; + + /* Check device presence */ + spin_lock_irqsave(&dev->lock, flags); + pci_dev = dev->pci_dev; + spin_unlock_irqrestore(&dev->lock, flags); + if (pci_dev) + tw686x_disable_channel(dev, vc->ch); + + spin_lock_irqsave(&vc->qlock, flags); + tw686x_clear_queue(vc, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&vc->qlock, flags); +} + +static int tw686x_buf_prepare(struct vb2_buffer *vb) +{ + struct tw686x_video_channel *vc = vb2_get_drv_priv(vb->vb2_queue); + unsigned int size = + (vc->width * vc->height * vc->format->depth) >> 3; + + if (vb2_plane_size(vb, 0) < size) + return -EINVAL; + vb2_set_plane_payload(vb, 0, size); + return 0; +} + +static const struct vb2_ops tw686x_video_qops = { + .queue_setup = tw686x_queue_setup, + .buf_queue = tw686x_buf_queue, + .buf_prepare = tw686x_buf_prepare, + .start_streaming = tw686x_start_streaming, + .stop_streaming = tw686x_stop_streaming, +}; + +static int tw686x_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct tw686x_video_channel *vc; + struct tw686x_dev *dev; + unsigned int ch; + + vc = container_of(ctrl->handler, struct tw686x_video_channel, + ctrl_handler); + dev = vc->dev; + ch = vc->ch; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + reg_write(dev, BRIGHT[ch], ctrl->val & 0xff); + return 0; + + case V4L2_CID_CONTRAST: + reg_write(dev, CONTRAST[ch], ctrl->val); + return 0; + + case V4L2_CID_SATURATION: + reg_write(dev, SAT_U[ch], ctrl->val); + reg_write(dev, SAT_V[ch], ctrl->val); + return 0; + + case V4L2_CID_HUE: + reg_write(dev, HUE[ch], ctrl->val & 0xff); + return 0; + } + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops ctrl_ops = { + .s_ctrl = tw686x_s_ctrl, +}; + +static int tw686x_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct tw686x_dev *dev = vc->dev; + + f->fmt.pix.width = vc->width; + f->fmt.pix.height = vc->height; + f->fmt.pix.field = dev->dma_ops->field; + f->fmt.pix.pixelformat = vc->format->fourcc; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + f->fmt.pix.bytesperline = (f->fmt.pix.width * vc->format->depth) / 8; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + return 0; +} + +static int tw686x_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct tw686x_dev *dev = vc->dev; + unsigned int video_height = TW686X_VIDEO_HEIGHT(vc->video_standard); + const struct tw686x_format *format; + + format = format_by_fourcc(f->fmt.pix.pixelformat); + if (!format) { + format = &formats[0]; + f->fmt.pix.pixelformat = format->fourcc; + } + + if (f->fmt.pix.width <= TW686X_VIDEO_WIDTH / 2) + f->fmt.pix.width = TW686X_VIDEO_WIDTH / 2; + else + f->fmt.pix.width = TW686X_VIDEO_WIDTH; + + if (f->fmt.pix.height <= video_height / 2) + f->fmt.pix.height = video_height / 2; + else + f->fmt.pix.height = video_height; + + f->fmt.pix.bytesperline = (f->fmt.pix.width * format->depth) / 8; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + f->fmt.pix.field = dev->dma_ops->field; + + return 0; +} + +static int tw686x_set_format(struct tw686x_video_channel *vc, + unsigned int pixelformat, unsigned int width, + unsigned int height, bool realloc) +{ + struct tw686x_dev *dev = vc->dev; + u32 val, dma_width, dma_height, dma_line_width; + int err, pb; + + vc->format = format_by_fourcc(pixelformat); + vc->width = width; + vc->height = height; + + /* We need new DMA buffers if the framesize has changed */ + if (dev->dma_ops->alloc && realloc) { + for (pb = 0; pb < 2; pb++) + dev->dma_ops->free(vc, pb); + + for (pb = 0; pb < 2; pb++) { + err = dev->dma_ops->alloc(vc, pb); + if (err) { + if (pb > 0) + dev->dma_ops->free(vc, 0); + return err; + } + } + } + + val = reg_read(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch]); + + if (vc->width <= TW686X_VIDEO_WIDTH / 2) + val |= BIT(23); + else + val &= ~BIT(23); + + if (vc->height <= TW686X_VIDEO_HEIGHT(vc->video_standard) / 2) + val |= BIT(24); + else + val &= ~BIT(24); + + val &= ~0x7ffff; + + /* Program the DMA scatter-gather */ + if (dev->dma_mode == TW686X_DMA_MODE_SG) { + u32 start_idx, end_idx; + + start_idx = is_second_gen(dev) ? + 0 : vc->ch * TW686X_MAX_SG_DESC_COUNT; + end_idx = start_idx + TW686X_MAX_SG_DESC_COUNT - 1; + + val |= (end_idx << 10) | start_idx; + } + + val &= ~(0x7 << 20); + val |= vc->format->mode << 20; + reg_write(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch], val); + + /* Program the DMA frame size */ + dma_width = (vc->width * 2) & 0x7ff; + dma_height = vc->height / 2; + dma_line_width = (vc->width * 2) & 0x7ff; + val = (dma_height << 22) | (dma_line_width << 11) | dma_width; + reg_write(vc->dev, VDMA_WHP[vc->ch], val); + return 0; +} + +static int tw686x_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + unsigned long area; + bool realloc; + int err; + + if (vb2_is_busy(&vc->vidq)) + return -EBUSY; + + area = vc->width * vc->height; + err = tw686x_try_fmt_vid_cap(file, priv, f); + if (err) + return err; + + realloc = area != (f->fmt.pix.width * f->fmt.pix.height); + return tw686x_set_format(vc, f->fmt.pix.pixelformat, + f->fmt.pix.width, f->fmt.pix.height, + realloc); +} + +static int tw686x_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct tw686x_dev *dev = vc->dev; + + strscpy(cap->driver, "tw686x", sizeof(cap->driver)); + strscpy(cap->card, dev->name, sizeof(cap->card)); + return 0; +} + +static int tw686x_set_standard(struct tw686x_video_channel *vc, v4l2_std_id id) +{ + u32 val; + + if (id & V4L2_STD_NTSC) + val = 0; + else if (id & V4L2_STD_PAL) + val = 1; + else if (id & V4L2_STD_SECAM) + val = 2; + else if (id & V4L2_STD_NTSC_443) + val = 3; + else if (id & V4L2_STD_PAL_M) + val = 4; + else if (id & V4L2_STD_PAL_Nc) + val = 5; + else if (id & V4L2_STD_PAL_60) + val = 6; + else + return -EINVAL; + + vc->video_standard = id; + reg_write(vc->dev, SDT[vc->ch], val); + + val = reg_read(vc->dev, VIDEO_CONTROL1); + if (id & V4L2_STD_525_60) + val &= ~(1 << (SYS_MODE_DMA_SHIFT + vc->ch)); + else + val |= (1 << (SYS_MODE_DMA_SHIFT + vc->ch)); + reg_write(vc->dev, VIDEO_CONTROL1, val); + + return 0; +} + +static int tw686x_s_std(struct file *file, void *priv, v4l2_std_id id) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct v4l2_format f; + int ret; + + if (vc->video_standard == id) + return 0; + + if (vb2_is_busy(&vc->vidq)) + return -EBUSY; + + ret = tw686x_set_standard(vc, id); + if (ret) + return ret; + /* + * Adjust format after V4L2_STD_525_60/V4L2_STD_625_50 change, + * calling g_fmt and s_fmt will sanitize the height + * according to the standard. + */ + tw686x_g_fmt_vid_cap(file, priv, &f); + tw686x_s_fmt_vid_cap(file, priv, &f); + + /* + * Frame decimation depends on the chosen standard, + * so reset it to the current value. + */ + tw686x_set_framerate(vc, vc->fps); + return 0; +} + +static int tw686x_querystd(struct file *file, void *priv, v4l2_std_id *std) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct tw686x_dev *dev = vc->dev; + unsigned int old_std, detected_std = 0; + unsigned long end; + + if (vb2_is_streaming(&vc->vidq)) + return -EBUSY; + + /* Enable and start standard detection */ + old_std = reg_read(dev, SDT[vc->ch]); + reg_write(dev, SDT[vc->ch], 0x7); + reg_write(dev, SDT_EN[vc->ch], 0xff); + + end = jiffies + msecs_to_jiffies(500); + while (time_is_after_jiffies(end)) { + + detected_std = reg_read(dev, SDT[vc->ch]); + if (!(detected_std & BIT(7))) + break; + msleep(100); + } + reg_write(dev, SDT[vc->ch], old_std); + + /* Exit if still busy */ + if (detected_std & BIT(7)) + return 0; + + detected_std = (detected_std >> 4) & 0x7; + switch (detected_std) { + case TW686X_STD_NTSC_M: + *std &= V4L2_STD_NTSC; + break; + case TW686X_STD_NTSC_443: + *std &= V4L2_STD_NTSC_443; + break; + case TW686X_STD_PAL_M: + *std &= V4L2_STD_PAL_M; + break; + case TW686X_STD_PAL_60: + *std &= V4L2_STD_PAL_60; + break; + case TW686X_STD_PAL: + *std &= V4L2_STD_PAL; + break; + case TW686X_STD_PAL_CN: + *std &= V4L2_STD_PAL_Nc; + break; + case TW686X_STD_SECAM: + *std &= V4L2_STD_SECAM; + break; + default: + *std = 0; + } + return 0; +} + +static int tw686x_g_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + + *id = vc->video_standard; + return 0; +} + +static int tw686x_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + + if (fsize->index) + return -EINVAL; + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.max_width = TW686X_VIDEO_WIDTH; + fsize->stepwise.min_width = fsize->stepwise.max_width / 2; + fsize->stepwise.step_width = fsize->stepwise.min_width; + fsize->stepwise.max_height = TW686X_VIDEO_HEIGHT(vc->video_standard); + fsize->stepwise.min_height = fsize->stepwise.max_height / 2; + fsize->stepwise.step_height = fsize->stepwise.min_height; + return 0; +} + +static int tw686x_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *ival) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + int max_fps = TW686X_MAX_FPS(vc->video_standard); + int max_rates = DIV_ROUND_UP(max_fps, 2); + + if (ival->index >= max_rates) + return -EINVAL; + + ival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + ival->discrete.numerator = 1; + if (ival->index < (max_rates - 1)) + ival->discrete.denominator = (ival->index + 1) * 2; + else + ival->discrete.denominator = max_fps; + return 0; +} + +static int tw686x_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct v4l2_captureparm *cp = &sp->parm.capture; + + if (sp->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + sp->parm.capture.readbuffers = 3; + + cp->capability = V4L2_CAP_TIMEPERFRAME; + cp->timeperframe.numerator = 1; + cp->timeperframe.denominator = vc->fps; + return 0; +} + +static int tw686x_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct v4l2_captureparm *cp = &sp->parm.capture; + unsigned int denominator = cp->timeperframe.denominator; + unsigned int numerator = cp->timeperframe.numerator; + unsigned int fps; + + if (vb2_is_busy(&vc->vidq)) + return -EBUSY; + + fps = (!numerator || !denominator) ? 0 : denominator / numerator; + if (vc->fps != fps) + tw686x_set_framerate(vc, fps); + return tw686x_g_parm(file, priv, sp); +} + +static int tw686x_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index >= ARRAY_SIZE(formats)) + return -EINVAL; + f->pixelformat = formats[f->index].fourcc; + return 0; +} + +static void tw686x_set_input(struct tw686x_video_channel *vc, unsigned int i) +{ + u32 val; + + vc->input = i; + + val = reg_read(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch]); + val &= ~(0x3 << 30); + val |= i << 30; + reg_write(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch], val); +} + +static int tw686x_s_input(struct file *file, void *priv, unsigned int i) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + + if (i >= TW686X_INPUTS_PER_CH) + return -EINVAL; + if (i == vc->input) + return 0; + /* + * Not sure we are able to support on the fly input change + */ + if (vb2_is_busy(&vc->vidq)) + return -EBUSY; + + tw686x_set_input(vc, i); + return 0; +} + +static int tw686x_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + + *i = vc->input; + return 0; +} + +static int tw686x_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + unsigned int vidstat; + + if (i->index >= TW686X_INPUTS_PER_CH) + return -EINVAL; + + snprintf(i->name, sizeof(i->name), "Composite%d", i->index); + i->type = V4L2_INPUT_TYPE_CAMERA; + i->std = vc->device->tvnorms; + i->capabilities = V4L2_IN_CAP_STD; + + vidstat = reg_read(vc->dev, VIDSTAT[vc->ch]); + i->status = 0; + if (vidstat & TW686X_VIDSTAT_VDLOSS) + i->status |= V4L2_IN_ST_NO_SIGNAL; + if (!(vidstat & TW686X_VIDSTAT_HLOCK)) + i->status |= V4L2_IN_ST_NO_H_LOCK; + + return 0; +} + +static const struct v4l2_file_operations tw686x_video_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .unlocked_ioctl = video_ioctl2, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .read = vb2_fop_read, + .mmap = vb2_fop_mmap, +}; + +static const struct v4l2_ioctl_ops tw686x_video_ioctl_ops = { + .vidioc_querycap = tw686x_querycap, + .vidioc_g_fmt_vid_cap = tw686x_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = tw686x_s_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = tw686x_enum_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = tw686x_try_fmt_vid_cap, + + .vidioc_querystd = tw686x_querystd, + .vidioc_g_std = tw686x_g_std, + .vidioc_s_std = tw686x_s_std, + + .vidioc_g_parm = tw686x_g_parm, + .vidioc_s_parm = tw686x_s_parm, + .vidioc_enum_framesizes = tw686x_enum_framesizes, + .vidioc_enum_frameintervals = tw686x_enum_frameintervals, + + .vidioc_enum_input = tw686x_enum_input, + .vidioc_g_input = tw686x_g_input, + .vidioc_s_input = tw686x_s_input, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +void tw686x_video_irq(struct tw686x_dev *dev, unsigned long requests, + unsigned int pb_status, unsigned int fifo_status, + unsigned int *reset_ch) +{ + struct tw686x_video_channel *vc; + unsigned long flags; + unsigned int ch, pb; + + for_each_set_bit(ch, &requests, max_channels(dev)) { + vc = &dev->video_channels[ch]; + + /* + * This can either be a blue frame (with signal-lost bit set) + * or a good frame (with signal-lost bit clear). If we have just + * got signal, then this channel needs resetting. + */ + if (vc->no_signal && !(fifo_status & BIT(ch))) { + v4l2_printk(KERN_DEBUG, &dev->v4l2_dev, + "video%d: signal recovered\n", vc->num); + vc->no_signal = false; + *reset_ch |= BIT(ch); + vc->pb = 0; + continue; + } + vc->no_signal = !!(fifo_status & BIT(ch)); + + /* Check FIFO errors only if there's signal */ + if (!vc->no_signal) { + u32 fifo_ov, fifo_bad; + + fifo_ov = (fifo_status >> 24) & BIT(ch); + fifo_bad = (fifo_status >> 16) & BIT(ch); + if (fifo_ov || fifo_bad) { + /* Mark this channel for reset */ + v4l2_printk(KERN_DEBUG, &dev->v4l2_dev, + "video%d: FIFO error\n", vc->num); + *reset_ch |= BIT(ch); + vc->pb = 0; + continue; + } + } + + pb = !!(pb_status & BIT(ch)); + if (vc->pb != pb) { + /* Mark this channel for reset */ + v4l2_printk(KERN_DEBUG, &dev->v4l2_dev, + "video%d: unexpected p-b buffer!\n", + vc->num); + *reset_ch |= BIT(ch); + vc->pb = 0; + continue; + } + + spin_lock_irqsave(&vc->qlock, flags); + tw686x_buf_done(vc, pb); + dev->dma_ops->buf_refill(vc, pb); + spin_unlock_irqrestore(&vc->qlock, flags); + } +} + +void tw686x_video_free(struct tw686x_dev *dev) +{ + unsigned int ch, pb; + + for (ch = 0; ch < max_channels(dev); ch++) { + struct tw686x_video_channel *vc = &dev->video_channels[ch]; + + video_unregister_device(vc->device); + + if (dev->dma_ops->free) + for (pb = 0; pb < 2; pb++) + dev->dma_ops->free(vc, pb); + } +} + +int tw686x_video_init(struct tw686x_dev *dev) +{ + unsigned int ch, val; + int err; + + if (dev->dma_mode == TW686X_DMA_MODE_MEMCPY) + dev->dma_ops = &memcpy_dma_ops; + else if (dev->dma_mode == TW686X_DMA_MODE_CONTIG) + dev->dma_ops = &contig_dma_ops; + else if (dev->dma_mode == TW686X_DMA_MODE_SG) + dev->dma_ops = &sg_dma_ops; + else + return -EINVAL; + + err = v4l2_device_register(&dev->pci_dev->dev, &dev->v4l2_dev); + if (err) + return err; + + if (dev->dma_ops->setup) { + err = dev->dma_ops->setup(dev); + if (err) + return err; + } + + /* Initialize vc->dev and vc->ch for the error path */ + for (ch = 0; ch < max_channels(dev); ch++) { + struct tw686x_video_channel *vc = &dev->video_channels[ch]; + + vc->dev = dev; + vc->ch = ch; + } + + for (ch = 0; ch < max_channels(dev); ch++) { + struct tw686x_video_channel *vc = &dev->video_channels[ch]; + struct video_device *vdev; + + mutex_init(&vc->vb_mutex); + spin_lock_init(&vc->qlock); + INIT_LIST_HEAD(&vc->vidq_queued); + + /* default settings */ + err = tw686x_set_standard(vc, V4L2_STD_NTSC); + if (err) + goto error; + + err = tw686x_set_format(vc, formats[0].fourcc, + TW686X_VIDEO_WIDTH, + TW686X_VIDEO_HEIGHT(vc->video_standard), + true); + if (err) + goto error; + + tw686x_set_input(vc, 0); + tw686x_set_framerate(vc, 30); + reg_write(dev, VDELAY_LO[ch], 0x14); + reg_write(dev, HACTIVE_LO[ch], 0xd0); + reg_write(dev, VIDEO_SIZE[ch], 0); + + vc->vidq.io_modes = VB2_READ | VB2_MMAP | VB2_DMABUF; + vc->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vc->vidq.drv_priv = vc; + vc->vidq.buf_struct_size = sizeof(struct tw686x_v4l2_buf); + vc->vidq.ops = &tw686x_video_qops; + vc->vidq.mem_ops = dev->dma_ops->mem_ops; + vc->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vc->vidq.min_queued_buffers = 2; + vc->vidq.lock = &vc->vb_mutex; + vc->vidq.gfp_flags = dev->dma_mode != TW686X_DMA_MODE_MEMCPY ? + GFP_DMA32 : 0; + vc->vidq.dev = &dev->pci_dev->dev; + + err = vb2_queue_init(&vc->vidq); + if (err) { + v4l2_err(&dev->v4l2_dev, + "dma%d: cannot init vb2 queue\n", ch); + goto error; + } + + err = v4l2_ctrl_handler_init(&vc->ctrl_handler, 4); + if (err) { + v4l2_err(&dev->v4l2_dev, + "dma%d: cannot init ctrl handler\n", ch); + goto error; + } + v4l2_ctrl_new_std(&vc->ctrl_handler, &ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(&vc->ctrl_handler, &ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 100); + v4l2_ctrl_new_std(&vc->ctrl_handler, &ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(&vc->ctrl_handler, &ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + err = vc->ctrl_handler.error; + if (err) + goto error; + + err = v4l2_ctrl_handler_setup(&vc->ctrl_handler); + if (err) + goto error; + + vdev = video_device_alloc(); + if (!vdev) { + v4l2_err(&dev->v4l2_dev, + "dma%d: unable to allocate device\n", ch); + err = -ENOMEM; + goto error; + } + + snprintf(vdev->name, sizeof(vdev->name), "%s video", dev->name); + vdev->fops = &tw686x_video_fops; + vdev->ioctl_ops = &tw686x_video_ioctl_ops; + vdev->release = video_device_release; + vdev->v4l2_dev = &dev->v4l2_dev; + vdev->queue = &vc->vidq; + vdev->tvnorms = V4L2_STD_525_60 | V4L2_STD_625_50; + vdev->minor = -1; + vdev->lock = &vc->vb_mutex; + vdev->ctrl_handler = &vc->ctrl_handler; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; + vc->device = vdev; + video_set_drvdata(vdev, vc); + + err = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (err < 0) { + video_device_release(vdev); + goto error; + } + vc->num = vdev->num; + } + + val = TW686X_DEF_PHASE_REF; + for (ch = 0; ch < max_channels(dev); ch++) + val |= dev->dma_ops->hw_dma_mode << (16 + ch * 2); + reg_write(dev, PHASE_REF, val); + + reg_write(dev, MISC2[0], 0xe7); + reg_write(dev, VCTRL1[0], 0xcc); + reg_write(dev, LOOP[0], 0xa5); + if (max_channels(dev) > 4) { + reg_write(dev, VCTRL1[1], 0xcc); + reg_write(dev, LOOP[1], 0xa5); + reg_write(dev, MISC2[1], 0xe7); + } + return 0; + +error: + tw686x_video_free(dev); + return err; +} diff --git a/drivers/media/pci/tw686x/tw686x.h b/drivers/media/pci/tw686x/tw686x.h new file mode 100644 index 000000000..21a989950 --- /dev/null +++ b/drivers/media/pci/tw686x/tw686x.h @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2015 VanguardiaSur - www.vanguardiasur.com.ar + * + * Copyright (C) 2015 Industrial Research Institute for Automation + * and Measurements PIAP + * Written by Krzysztof Ha?asa + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tw686x-regs.h" + +#define TYPE_MAX_CHANNELS 0x0f +#define TYPE_SECOND_GEN 0x10 +#define TW686X_DEF_PHASE_REF 0x1518 + +#define TW686X_AUDIO_PAGE_MAX 16 +#define TW686X_AUDIO_PERIODS_MIN 2 +#define TW686X_AUDIO_PERIODS_MAX TW686X_AUDIO_PAGE_MAX + +#define TW686X_DMA_MODE_MEMCPY 0 +#define TW686X_DMA_MODE_CONTIG 1 +#define TW686X_DMA_MODE_SG 2 + +struct tw686x_format { + char *name; + unsigned int fourcc; + unsigned int depth; + unsigned int mode; +}; + +struct tw686x_dma_desc { + dma_addr_t phys; + void *virt; + unsigned int size; +}; + +struct tw686x_sg_desc { + /* 3 MSBits for flags, 13 LSBits for length */ + __le32 flags_length; + __le32 phys; +}; + +struct tw686x_audio_buf { + dma_addr_t dma; + void *virt; + struct list_head list; +}; + +struct tw686x_v4l2_buf { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +struct tw686x_audio_channel { + struct tw686x_dev *dev; + struct snd_pcm_substream *ss; + unsigned int ch; + struct tw686x_audio_buf *curr_bufs[2]; + struct tw686x_dma_desc dma_descs[2]; + dma_addr_t ptr; + + struct tw686x_audio_buf buf[TW686X_AUDIO_PAGE_MAX]; + struct list_head buf_list; + spinlock_t lock; +}; + +struct tw686x_video_channel { + struct tw686x_dev *dev; + + struct vb2_queue vidq; + struct list_head vidq_queued; + struct video_device *device; + struct tw686x_v4l2_buf *curr_bufs[2]; + struct tw686x_dma_desc dma_descs[2]; + struct tw686x_sg_desc *sg_descs[2]; + + struct v4l2_ctrl_handler ctrl_handler; + const struct tw686x_format *format; + struct mutex vb_mutex; + spinlock_t qlock; + v4l2_std_id video_standard; + unsigned int width, height; + unsigned int h_halve, v_halve; + unsigned int ch; + unsigned int num; + unsigned int fps; + unsigned int input; + unsigned int sequence; + unsigned int pb; + bool no_signal; +}; + +struct tw686x_dma_ops { + int (*setup)(struct tw686x_dev *dev); + int (*alloc)(struct tw686x_video_channel *vc, unsigned int pb); + void (*free)(struct tw686x_video_channel *vc, unsigned int pb); + void (*buf_refill)(struct tw686x_video_channel *vc, unsigned int pb); + const struct vb2_mem_ops *mem_ops; + enum v4l2_field field; + u32 hw_dma_mode; +}; + +/* struct tw686x_dev - global device status */ +struct tw686x_dev { + /* + * spinlock controlling access to the shared device registers + * (DMA enable/disable) + */ + spinlock_t lock; + + struct v4l2_device v4l2_dev; + struct snd_card *snd_card; + + char name[32]; + unsigned int type; + unsigned int dma_mode; + struct pci_dev *pci_dev; + __u32 __iomem *mmio; + + const struct tw686x_dma_ops *dma_ops; + struct tw686x_video_channel *video_channels; + struct tw686x_audio_channel *audio_channels; + + /* Per-device audio parameters */ + int audio_rate; + int period_size; + int audio_enabled; + + struct timer_list dma_delay_timer; + u32 pending_dma_en; /* must be protected by lock */ + u32 pending_dma_cmd; /* must be protected by lock */ +}; + +static inline uint32_t reg_read(struct tw686x_dev *dev, unsigned int reg) +{ + return readl(dev->mmio + reg); +} + +static inline void reg_write(struct tw686x_dev *dev, unsigned int reg, + uint32_t value) +{ + writel(value, dev->mmio + reg); +} + +static inline unsigned int max_channels(struct tw686x_dev *dev) +{ + return dev->type & TYPE_MAX_CHANNELS; /* 4 or 8 channels */ +} + +static inline unsigned is_second_gen(struct tw686x_dev *dev) +{ + /* each channel has its own DMA SG table */ + return dev->type & TYPE_SECOND_GEN; +} + +void tw686x_enable_channel(struct tw686x_dev *dev, unsigned int channel); +void tw686x_disable_channel(struct tw686x_dev *dev, unsigned int channel); + +int tw686x_video_init(struct tw686x_dev *dev); +void tw686x_video_free(struct tw686x_dev *dev); +void tw686x_video_irq(struct tw686x_dev *dev, unsigned long requests, + unsigned int pb_status, unsigned int fifo_status, + unsigned int *reset_ch); + +int tw686x_audio_init(struct tw686x_dev *dev); +void tw686x_audio_free(struct tw686x_dev *dev); +void tw686x_audio_irq(struct tw686x_dev *dev, unsigned long requests, + unsigned int pb_status); diff --git a/drivers/media/pci/zoran/Kconfig b/drivers/media/pci/zoran/Kconfig new file mode 100644 index 000000000..3fb3e27e0 --- /dev/null +++ b/drivers/media/pci/zoran/Kconfig @@ -0,0 +1,74 @@ +config VIDEO_ZORAN + tristate "Zoran ZR36057/36067 Video For Linux (Deprecated)" + depends on PCI && I2C_ALGOBIT && VIDEO_DEV + depends on !ALPHA + depends on DEBUG_FS + select VIDEOBUF2_DMA_CONTIG + select VIDEO_ADV7170 if VIDEO_ZORAN_LML33R10 + select VIDEO_ADV7175 if VIDEO_ZORAN_DC10 || VIDEO_ZORAN_DC30 + select VIDEO_BT819 if VIDEO_ZORAN_LML33 + select VIDEO_BT856 if VIDEO_ZORAN_LML33 || VIDEO_ZORAN_AVS6EYES + select VIDEO_BT866 if VIDEO_ZORAN_AVS6EYES + select VIDEO_KS0127 if VIDEO_ZORAN_AVS6EYES + select VIDEO_SAA711X if VIDEO_ZORAN_BUZ || VIDEO_ZORAN_LML33R10 + select VIDEO_SAA7110 if VIDEO_ZORAN_DC10 + select VIDEO_SAA7185 if VIDEO_ZORAN_BUZ + select VIDEO_VPX3220 if VIDEO_ZORAN_DC30 + help + Say Y for support for MJPEG capture cards based on the Zoran + 36057/36067 PCI controller chipset. This includes the Iomega + Buz, Pinnacle DC10+ and the Linux Media Labs LML33. There is + a driver homepage at . For + more information, check . + + To compile this driver as a module, choose M here: the + module will be called zr36067. + +config VIDEO_ZORAN_DC30 + bool "Pinnacle/Miro DC30(+) support" + depends on VIDEO_ZORAN + help + Support for the Pinnacle/Miro DC30(+) MJPEG capture/playback + card. This also supports really old DC10 cards based on the + zr36050 MJPEG codec and zr36016 VFE. + +config VIDEO_ZORAN_ZR36060 + bool "Zoran ZR36060" + depends on VIDEO_ZORAN + help + Say Y to support Zoran boards based on 36060 chips. + This includes Iomega Buz, Pinnacle DC10, Linux media Labs 33 + and 33 R10 and AverMedia 6 boards. + +config VIDEO_ZORAN_BUZ + bool "Iomega Buz support" + depends on VIDEO_ZORAN_ZR36060 + help + Support for the Iomega Buz MJPEG capture/playback card. + +config VIDEO_ZORAN_DC10 + bool "Pinnacle/Miro DC10(+) support" + depends on VIDEO_ZORAN_ZR36060 + help + Support for the Pinnacle/Miro DC10(+) MJPEG capture/playback + card. + +config VIDEO_ZORAN_LML33 + bool "Linux Media Labs LML33 support" + depends on VIDEO_ZORAN_ZR36060 + help + Support for the Linux Media Labs LML33 MJPEG capture/playback + card. + +config VIDEO_ZORAN_LML33R10 + bool "Linux Media Labs LML33R10 support" + depends on VIDEO_ZORAN_ZR36060 + help + support for the Linux Media Labs LML33R10 MJPEG capture/playback + card. + +config VIDEO_ZORAN_AVS6EYES + bool "AverMedia 6 Eyes support" + depends on VIDEO_ZORAN_ZR36060 + help + Support for the AverMedia 6 Eyes video surveillance card. diff --git a/drivers/media/pci/zoran/Makefile b/drivers/media/pci/zoran/Makefile new file mode 100644 index 000000000..9603bac01 --- /dev/null +++ b/drivers/media/pci/zoran/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +zr36067-objs := zoran_device.o \ + zoran_driver.o zoran_card.o videocodec.o + +obj-$(CONFIG_VIDEO_ZORAN) += zr36067.o +zr36067-$(CONFIG_VIDEO_ZORAN_DC30) += zr36050.o zr36016.o +zr36067-$(CONFIG_VIDEO_ZORAN_ZR36060) += zr36060.o diff --git a/drivers/media/pci/zoran/videocodec.c b/drivers/media/pci/zoran/videocodec.c new file mode 100644 index 000000000..85fe60065 --- /dev/null +++ b/drivers/media/pci/zoran/videocodec.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * VIDEO MOTION CODECs internal API for video devices + * + * Interface for MJPEG (and maybe later MPEG/WAVELETS) codec's + * bound to a master device. + * + * (c) 2002 Wolfgang Scherr + */ + +#include +#include +#include +#include +#include + +#include "videocodec.h" + +struct attached_list { + struct videocodec *codec; + struct attached_list *next; +}; + +struct codec_list { + const struct videocodec *codec; + int attached; + struct attached_list *list; + struct codec_list *next; +}; + +static struct codec_list *codeclist_top; + +/* ================================================= */ +/* function prototypes of the master/slave interface */ +/* ================================================= */ + +struct videocodec *videocodec_attach(struct videocodec_master *master) +{ + struct codec_list *h = codeclist_top; + struct zoran *zr; + struct attached_list *a, *ptr; + struct videocodec *codec; + int res; + + if (!master) { + pr_err("%s: no data\n", __func__); + return NULL; + } + + zr = videocodec_master_to_zoran(master); + + zrdev_dbg(zr, "%s: '%s', flags %lx, magic %lx\n", __func__, + master->name, master->flags, master->magic); + + if (!h) { + zrdev_err(zr, "%s: no device available\n", __func__); + return NULL; + } + + while (h) { + // attach only if the slave has at least the flags + // expected by the master + if ((master->flags & h->codec->flags) == master->flags) { + zrdev_dbg(zr, "%s: try '%s'\n", __func__, h->codec->name); + + codec = kmemdup(h->codec, sizeof(struct videocodec), GFP_KERNEL); + if (!codec) + goto out_kfree; + + res = strlen(codec->name); + snprintf(codec->name + res, sizeof(codec->name) - res, "[%d]", h->attached); + codec->master_data = master; + res = codec->setup(codec); + if (res == 0) { + zrdev_dbg(zr, "%s: '%s'\n", __func__, codec->name); + ptr = kzalloc_obj(*ptr); + if (!ptr) + goto out_kfree; + ptr->codec = codec; + + a = h->list; + if (!a) { + h->list = ptr; + zrdev_dbg(zr, "videocodec: first element\n"); + } else { + while (a->next) + a = a->next; // find end + a->next = ptr; + zrdev_dbg(zr, "videocodec: in after '%s'\n", + h->codec->name); + } + + h->attached += 1; + return codec; + } + kfree(codec); + } + h = h->next; + } + + zrdev_err(zr, "%s: no codec found!\n", __func__); + return NULL; + + out_kfree: + kfree(codec); + return NULL; +} + +int videocodec_detach(struct videocodec *codec) +{ + struct codec_list *h = codeclist_top; + struct zoran *zr; + struct attached_list *a, *prev; + int res; + + if (!codec) { + pr_err("%s: no data\n", __func__); + return -EINVAL; + } + + zr = videocodec_to_zoran(codec); + + zrdev_dbg(zr, "%s: '%s', type: %x, flags %lx, magic %lx\n", __func__, + codec->name, codec->type, codec->flags, codec->magic); + + if (!h) { + zrdev_err(zr, "%s: no device left...\n", __func__); + return -ENXIO; + } + + while (h) { + a = h->list; + prev = NULL; + while (a) { + if (codec == a->codec) { + res = a->codec->unset(a->codec); + if (res >= 0) { + zrdev_dbg(zr, "%s: '%s'\n", __func__, + a->codec->name); + a->codec->master_data = NULL; + } else { + zrdev_err(zr, "%s: '%s'\n", __func__, a->codec->name); + a->codec->master_data = NULL; + } + if (!prev) { + h->list = a->next; + zrdev_dbg(zr, "videocodec: delete first\n"); + } else { + prev->next = a->next; + zrdev_dbg(zr, "videocodec: delete middle\n"); + } + kfree(a->codec); + kfree(a); + h->attached -= 1; + return 0; + } + prev = a; + a = a->next; + } + h = h->next; + } + + zrdev_err(zr, "%s: given codec not found!\n", __func__); + return -EINVAL; +} + +int videocodec_register(const struct videocodec *codec) +{ + struct codec_list *ptr, *h = codeclist_top; + struct zoran *zr; + + if (!codec) { + pr_err("%s: no data!\n", __func__); + return -EINVAL; + } + + zr = videocodec_to_zoran((struct videocodec *)codec); + + zrdev_dbg(zr, + "videocodec: register '%s', type: %x, flags %lx, magic %lx\n", + codec->name, codec->type, codec->flags, codec->magic); + + ptr = kzalloc_obj(*ptr); + if (!ptr) + return -ENOMEM; + ptr->codec = codec; + + if (!h) { + codeclist_top = ptr; + zrdev_dbg(zr, "videocodec: hooked in as first element\n"); + } else { + while (h->next) + h = h->next; // find the end + h->next = ptr; + zrdev_dbg(zr, "videocodec: hooked in after '%s'\n", + h->codec->name); + } + + return 0; +} + +int videocodec_unregister(const struct videocodec *codec) +{ + struct codec_list *prev = NULL, *h = codeclist_top; + struct zoran *zr; + + if (!codec) { + pr_err("%s: no data!\n", __func__); + return -EINVAL; + } + + zr = videocodec_to_zoran((struct videocodec *)codec); + + zrdev_dbg(zr, + "videocodec: unregister '%s', type: %x, flags %lx, magic %lx\n", + codec->name, codec->type, codec->flags, codec->magic); + + if (!h) { + zrdev_err(zr, "%s: no device left...\n", __func__); + return -ENXIO; + } + + while (h) { + if (codec == h->codec) { + if (h->attached) { + zrdev_err(zr, "videocodec: '%s' is used\n", + h->codec->name); + return -EBUSY; + } + zrdev_dbg(zr, "videocodec: unregister '%s' is ok.\n", + h->codec->name); + if (!prev) { + codeclist_top = h->next; + zrdev_dbg(zr, + "videocodec: delete first element\n"); + } else { + prev->next = h->next; + zrdev_dbg(zr, + "videocodec: delete middle element\n"); + } + kfree(h); + return 0; + } + prev = h; + h = h->next; + } + + zrdev_err(zr, "%s: given codec not found!\n", __func__); + return -EINVAL; +} + +int videocodec_debugfs_show(struct seq_file *m) +{ + struct codec_list *h = codeclist_top; + struct attached_list *a; + + seq_puts(m, "lave or attached aster name type flags magic "); + seq_puts(m, "(connected as)\n"); + + while (h) { + seq_printf(m, "S %32s %04x %08lx %08lx (TEMPLATE)\n", + h->codec->name, h->codec->type, + h->codec->flags, h->codec->magic); + a = h->list; + while (a) { + seq_printf(m, "M %32s %04x %08lx %08lx (%s)\n", + a->codec->master_data->name, + a->codec->master_data->type, + a->codec->master_data->flags, + a->codec->master_data->magic, + a->codec->name); + a = a->next; + } + h = h->next; + } + + return 0; +} diff --git a/drivers/media/pci/zoran/videocodec.h b/drivers/media/pci/zoran/videocodec.h new file mode 100644 index 000000000..6b69f6966 --- /dev/null +++ b/drivers/media/pci/zoran/videocodec.h @@ -0,0 +1,325 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * VIDEO MOTION CODECs internal API for video devices + * + * Interface for MJPEG (and maybe later MPEG/WAVELETS) codec's + * bound to a master device. + * + * (c) 2002 Wolfgang Scherr + */ + +/* =================== */ +/* general description */ +/* =================== */ + +/* + * Should ease the (re-)usage of drivers supporting cards with (different) + * video codecs. The codecs register to this module their functionality, + * and the processors (masters) can attach to them if they fit. + * + * The codecs are typically have a "strong" binding to their master - so I + * don't think it makes sense to have a full blown interfacing as with e.g. + * i2c. If you have an other opinion, let's discuss & implement it :-))) + * + * Usage: + * + * The slave has just to setup the videocodec structure and use two functions: + * videocodec_register(codecdata); + * videocodec_unregister(codecdata); + * The best is just calling them at module (de-)initialisation. + * + * The master sets up the structure videocodec_master and calls: + * codecdata=videocodec_attach(master_codecdata); + * videocodec_detach(codecdata); + * + * The slave is called during attach/detach via functions setup previously + * during register. At that time, the master_data pointer is set up + * and the slave can access any io registers of the master device (in the case + * the slave is bound to it). Otherwise it doesn't need this functions and + * therefor they may not be initialized. + * + * The other functions are just for convenience, as they are for sure used by + * most/all of the codecs. The last ones may be omitted, too. + * + * See the structure declaration below for more information and which data has + * to be set up for the master and the slave. + * + * ---------------------------------------------------------------------------- + * The master should have "knowledge" of the slave and vice versa. So the data + * structures sent to/from slave via set_data/get_data set_image/get_image are + * device dependent and vary between MJPEG/MPEG/WAVELET/... devices. (!!!!) + * ---------------------------------------------------------------------------- + */ + +/* ========================================== */ +/* description of the videocodec_io structure */ +/* ========================================== */ + +/* + * ==== master setup ==== + * name -> name of the device structure for reference and debugging + * master_data -> data ref. for the master (e.g. the zr36055,57,67) + * readreg -> ref. to read-fn from register (setup by master, used by slave) + * writereg -> ref. to write-fn to register (setup by master, used by slave) + * this two functions do the lowlevel I/O job + * + * ==== slave functionality setup ==== + * slave_data -> data ref. for the slave (e.g. the zr36050,60) + * check -> fn-ref. checks availability of an device, returns -EIO on failure or + * the type on success + * this makes espcecially sense if a driver module supports more than + * one codec which may be quite similar to access, nevertheless it + * is good for a first functionality check + * + * -- main functions you always need for compression/decompression -- + * + * set_mode -> this fn-ref. resets the entire codec, and sets up the mode + * with the last defined norm/size (or device default if not + * available) - it returns 0 if the mode is possible + * set_size -> this fn-ref. sets the norm and image size for + * compression/decompression (returns 0 on success) + * the norm param is defined in videodev2.h (V4L2_STD_*) + * + * additional setup may be available, too - but the codec should work with + * some default values even without this + * + * set_data -> sets device-specific data (tables, quality etc.) + * get_data -> query device-specific data (tables, quality etc.) + * + * if the device delivers interrupts, they may be setup/handled here + * setup_interrupt -> codec irq setup (not needed for 36050/60) + * handle_interrupt -> codec irq handling (not needed for 36050/60) + + * if the device delivers pictures, they may be handled here + * put_image -> puts image data to the codec (not needed for 36050/60) + * get_image -> gets image data from the codec (not needed for 36050/60) + * the calls include frame numbers and flags (even/odd/...) + * if needed and a flag which allows blocking until its ready + */ + +/* ============== */ +/* user interface */ +/* ============== */ + +/* + * Currently there is only a information display planned, as the layer + * is not visible for the user space at all. + * + * Information is available via procfs. The current entry is "/proc/videocodecs" + * but it makes sense to "hide" it in the /proc/video tree of v4l(2) --TODO--. + * + * A example for such an output is: + * + * lave or attached aster name type flags magic (connected as) + * S zr36050 0002 0000d001 00000000 (TEMPLATE) + * M zr36055[0] 0001 0000c001 00000000 (zr36050[0]) + * M zr36055[1] 0001 0000c001 00000000 (zr36050[1]) + */ + +/* =============================================== */ +/* special defines for the videocodec_io structure */ +/* =============================================== */ + +#ifndef __LINUX_VIDEOCODEC_H +#define __LINUX_VIDEOCODEC_H + +#include +#include + +#define CODEC_DO_COMPRESSION 0 +#define CODEC_DO_EXPANSION 1 + +/* this are the current codec flags I think they are needed */ +/* -> type value in structure */ +#define CODEC_FLAG_JPEG 0x00000001L // JPEG codec +#define CODEC_FLAG_MPEG 0x00000002L // MPEG1/2/4 codec +#define CODEC_FLAG_DIVX 0x00000004L // DIVX codec +#define CODEC_FLAG_WAVELET 0x00000008L // WAVELET codec + // room for other types + +#define CODEC_FLAG_MAGIC 0x00000800L // magic key must match +#define CODEC_FLAG_HARDWARE 0x00001000L // is a hardware codec +#define CODEC_FLAG_VFE 0x00002000L // has direct video frontend +#define CODEC_FLAG_ENCODER 0x00004000L // compression capability +#define CODEC_FLAG_DECODER 0x00008000L // decompression capability +#define CODEC_FLAG_NEEDIRQ 0x00010000L // needs irq handling +#define CODEC_FLAG_RDWRPIC 0x00020000L // handles picture I/O + +/* a list of modes, some are just examples (is there any HW?) */ +#define CODEC_MODE_BJPG 0x0001 // Baseline JPEG +#define CODEC_MODE_LJPG 0x0002 // Lossless JPEG +#define CODEC_MODE_MPEG1 0x0003 // MPEG 1 +#define CODEC_MODE_MPEG2 0x0004 // MPEG 2 +#define CODEC_MODE_MPEG4 0x0005 // MPEG 4 +#define CODEC_MODE_MSDIVX 0x0006 // MS DivX +#define CODEC_MODE_ODIVX 0x0007 // Open DivX +#define CODEC_MODE_WAVELET 0x0008 // Wavelet + +/* this are the current codec types I want to implement */ +/* -> type value in structure */ +#define CODEC_TYPE_NONE 0 +#define CODEC_TYPE_L64702 1 +#define CODEC_TYPE_ZR36050 2 +#define CODEC_TYPE_ZR36016 3 +#define CODEC_TYPE_ZR36060 4 + +/* the type of data may be enhanced by future implementations (data-fn.'s) */ +/* -> used in command */ +#define CODEC_G_STATUS 0x0000 /* codec status (query only) */ +#define CODEC_S_CODEC_MODE 0x0001 /* codec mode (baseline JPEG, MPEG1,... */ +#define CODEC_G_CODEC_MODE 0x8001 +#define CODEC_S_VFE 0x0002 /* additional video frontend setup */ +#define CODEC_G_VFE 0x8002 +#define CODEC_S_MMAP 0x0003 /* MMAP setup (if available) */ + +#define CODEC_S_JPEG_TDS_BYTE 0x0010 /* target data size in bytes */ +#define CODEC_G_JPEG_TDS_BYTE 0x8010 +#define CODEC_S_JPEG_SCALE 0x0011 /* scaling factor for quant. tables */ +#define CODEC_G_JPEG_SCALE 0x8011 +#define CODEC_S_JPEG_HDT_DATA 0x0018 /* huffman-tables */ +#define CODEC_G_JPEG_HDT_DATA 0x8018 +#define CODEC_S_JPEG_QDT_DATA 0x0019 /* quantizing-tables */ +#define CODEC_G_JPEG_QDT_DATA 0x8019 +#define CODEC_S_JPEG_APP_DATA 0x001A /* APP marker */ +#define CODEC_G_JPEG_APP_DATA 0x801A +#define CODEC_S_JPEG_COM_DATA 0x001B /* COM marker */ +#define CODEC_G_JPEG_COM_DATA 0x801B + +#define CODEC_S_PRIVATE 0x1000 /* "private" commands start here */ +#define CODEC_G_PRIVATE 0x9000 + +#define CODEC_G_FLAG 0x8000 /* this is how 'get' is detected */ + +/* types of transfer, directly user space or a kernel buffer (image-fn.'s) */ +/* -> used in get_image, put_image */ +#define CODEC_TRANSFER_KERNEL 0 /* use "memcopy" */ +#define CODEC_TRANSFER_USER 1 /* use "to/from_user" */ + +/* ========================= */ +/* the structures itself ... */ +/* ========================= */ + +struct vfe_polarity { + unsigned int vsync_pol:1; + unsigned int hsync_pol:1; + unsigned int field_pol:1; + unsigned int blank_pol:1; + unsigned int subimg_pol:1; + unsigned int poe_pol:1; + unsigned int pvalid_pol:1; + unsigned int vclk_pol:1; +}; + +struct vfe_settings { + __u32 x, y; /* Offsets into image */ + __u32 width, height; /* Area to capture */ + __u16 decimation; /* Decimation divider */ + __u16 flags; /* Flags for capture */ + __u16 quality; /* quality of the video */ +}; + +struct tvnorm { + u16 wt, wa, h_start, h_sync_start, ht, ha, v_start; +}; + +struct jpeg_com_marker { + int len; /* number of usable bytes in data */ + char data[60]; +}; + +struct jpeg_app_marker { + int appn; /* number app segment */ + int len; /* number of usable bytes in data */ + char data[60]; +}; + +struct videocodec { + /* -- filled in by slave device during register -- */ + char name[32]; + unsigned long magic; /* may be used for client<->master attaching */ + unsigned long flags; /* functionality flags */ + unsigned int type; /* codec type */ + + /* -- these is filled in later during master device attach -- */ + + struct videocodec_master *master_data; + + /* -- these are filled in by the slave device during register -- */ + + void *data; /* private slave data */ + + /* attach/detach client functions (indirect call) */ + int (*setup)(struct videocodec *codec); + int (*unset)(struct videocodec *codec); + + /* main functions, every client needs them for sure! */ + // set compression or decompression (or freeze, stop, standby, etc) + int (*set_mode)(struct videocodec *codec, int mode); + // setup picture size and norm (for the codec's video frontend) + int (*set_video)(struct videocodec *codec, const struct tvnorm *norm, + struct vfe_settings *cap, struct vfe_polarity *pol); + // other control commands, also mmap setup etc. + int (*control)(struct videocodec *codec, int type, int size, void *data); + + /* additional setup/query/processing (may be NULL pointer) */ + // interrupt setup / handling (for irq's delivered by master) + int (*setup_interrupt)(struct videocodec *codec, long mode); + int (*handle_interrupt)(struct videocodec *codec, int source, long flag); + // picture interface (if any) + long (*put_image)(struct videocodec *codec, int tr_type, int block, + long *fr_num, long *flag, long size, void *buf); + long (*get_image)(struct videocodec *codec, int tr_type, int block, + long *fr_num, long *flag, long size, void *buf); +}; + +struct videocodec_master { + /* -- filled in by master device for registration -- */ + char name[32]; + unsigned long magic; /* may be used for client<->master attaching */ + unsigned long flags; /* functionality flags */ + unsigned int type; /* master type */ + + void *data; /* private master data */ + + __u32 (*readreg)(struct videocodec *codec, __u16 reg); + void (*writereg)(struct videocodec *codec, __u16 reg, __u32 value); +}; + +/* ================================================= */ +/* function prototypes of the master/slave interface */ +/* ================================================= */ + +/* attach and detach commands for the master */ +// * master structure needs to be kmalloc'ed before calling attach +// and free'd after calling detach +// * returns pointer on success, NULL on failure +struct videocodec *videocodec_attach(struct videocodec_master *master); +// * 0 on success, <0 (errno) on failure +int videocodec_detach(struct videocodec *codec); + +/* register and unregister commands for the slaves */ +// * 0 on success, <0 (errno) on failure +int videocodec_register(const struct videocodec *codec); +// * 0 on success, <0 (errno) on failure +int videocodec_unregister(const struct videocodec *codec); + +/* the other calls are directly done via the videocodec structure! */ + +int videocodec_debugfs_show(struct seq_file *m); + +#include "zoran.h" +static inline struct zoran *videocodec_master_to_zoran(struct videocodec_master *master) +{ + struct zoran *zr = master->data; + + return zr; +} + +static inline struct zoran *videocodec_to_zoran(struct videocodec *codec) +{ + struct videocodec_master *master = codec->master_data; + + return videocodec_master_to_zoran(master); +} + +#endif /*ifndef __LINUX_VIDEOCODEC_H */ diff --git a/drivers/media/pci/zoran/zoran.h b/drivers/media/pci/zoran/zoran.h new file mode 100644 index 000000000..d05e222b3 --- /dev/null +++ b/drivers/media/pci/zoran/zoran.h @@ -0,0 +1,322 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * zoran - Iomega Buz driver + * + * Copyright (C) 1999 Rainer Johanni + * + * based on + * + * zoran.0.0.3 Copyright (C) 1998 Dave Perks + * + * and + * + * bttv - Bt848 frame grabber driver + * Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + * & Marcus Metzler (mocm@thp.uni-koeln.de) + */ + +#ifndef _BUZ_H_ +#define _BUZ_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ZR_NORM_PAL 0 +#define ZR_NORM_NTSC 1 +#define ZR_NORM_SECAM 2 + +struct zr_buffer { + /* common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer vbuf; + struct list_head queue; +}; + +static inline struct zr_buffer *vb2_to_zr_buffer(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + return container_of(vbuf, struct zr_buffer, vbuf); +} + +#define ZORAN_NAME "ZORAN" /* name of the device */ + +#define ZR_DEVNAME(zr) ((zr)->name) + +#define BUZ_MAX_WIDTH (zr->timing->wa) +#define BUZ_MAX_HEIGHT (zr->timing->ha) +#define BUZ_MIN_WIDTH 32 /* never display less than 32 pixels */ +#define BUZ_MIN_HEIGHT 24 /* never display less than 24 rows */ + +#define BUZ_NUM_STAT_COM 4 +#define BUZ_MASK_STAT_COM 3 + +#define BUZ_MAX_INPUT 16 + +#include "zr36057.h" + +enum card_type { + UNKNOWN = -1, + + /* Pinnacle/Miro */ + DC10_OLD, /* DC30 like */ + DC10_NEW, /* DC10_PLUS like */ + DC10_PLUS, + DC30, + DC30_PLUS, + + /* Linux Media Labs */ + LML33, + LML33R10, + + /* Iomega */ + BUZ, + + /* AverMedia */ + AVS6EYES, + + /* total number of cards */ + NUM_CARDS +}; + +enum zoran_codec_mode { + BUZ_MODE_IDLE, /* nothing going on */ + BUZ_MODE_MOTION_COMPRESS, /* grabbing frames */ + BUZ_MODE_MOTION_DECOMPRESS, /* playing frames */ + BUZ_MODE_STILL_COMPRESS, /* still frame conversion */ + BUZ_MODE_STILL_DECOMPRESS /* still frame conversion */ +}; + +enum zoran_map_mode { + ZORAN_MAP_MODE_NONE, + ZORAN_MAP_MODE_RAW, + ZORAN_MAP_MODE_JPG_REC, + ZORAN_MAP_MODE_JPG_PLAY, +}; + +enum gpio_type { + ZR_GPIO_JPEG_SLEEP = 0, + ZR_GPIO_JPEG_RESET, + ZR_GPIO_JPEG_FRAME, + ZR_GPIO_VID_DIR, + ZR_GPIO_VID_EN, + ZR_GPIO_VID_RESET, + ZR_GPIO_CLK_SEL1, + ZR_GPIO_CLK_SEL2, + ZR_GPIO_MAX, +}; + +enum gpcs_type { + GPCS_JPEG_RESET = 0, + GPCS_JPEG_START, + GPCS_MAX, +}; + +struct zoran_format { + char *name; + __u32 fourcc; + int colorspace; + int depth; + __u32 flags; + __u32 vfespfr; +}; + +/* flags */ +#define ZORAN_FORMAT_COMPRESSED BIT(0) +#define ZORAN_FORMAT_OVERLAY BIT(1) +#define ZORAN_FORMAT_CAPTURE BIT(2) +#define ZORAN_FORMAT_PLAYBACK BIT(3) + +/* v4l-capture settings */ +struct zoran_v4l_settings { + int width, height, bytesperline; /* capture size */ + const struct zoran_format *format; /* capture format */ +}; + +/* jpg-capture/-playback settings */ +struct zoran_jpg_settings { + /* this bit is used to set everything to default */ + int decimation; + /* capture decimation settings (tmp_dcm=1 means both fields) */ + int hor_dcm, ver_dcm, tmp_dcm; + /* field-settings (odd_even=1 (+tmp_dcm=1) means top-field-first) */ + int field_per_buff, odd_even; + /* crop settings (subframe capture) */ + int img_x, img_y, img_width, img_height; + /* JPEG-specific capture settings */ + struct v4l2_jpegcompression jpg_comp; +}; + +struct zoran; + +struct card_info { + enum card_type type; + char name[32]; + const char *i2c_decoder; /* i2c decoder device */ + const unsigned short *addrs_decoder; + const char *i2c_encoder; /* i2c encoder device */ + const unsigned short *addrs_encoder; + u16 video_vfe, video_codec; /* videocodec types */ + u16 audio_chip; /* audio type */ + + int inputs; /* number of video inputs */ + struct input { + int muxsel; + char name[32]; + } input[BUZ_MAX_INPUT]; + + v4l2_std_id norms; + const struct tvnorm *tvn[3]; /* supported TV norms */ + + u32 jpeg_int; /* JPEG interrupt */ + u32 vsync_int; /* VSYNC interrupt */ + s8 gpio[ZR_GPIO_MAX]; + u8 gpcs[GPCS_MAX]; + + struct vfe_polarity vfe_pol; + u8 gpio_pol[ZR_GPIO_MAX]; + + /* is the /GWS line connected? */ + u8 gws_not_connected; + + /* avs6eyes mux setting */ + u8 input_mux; + + void (*init)(struct zoran *zr); +}; + +struct zoran { + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler hdl; + struct video_device *video_dev; + struct vb2_queue vq; + + struct i2c_adapter i2c_adapter; /* */ + struct i2c_algo_bit_data i2c_algo; /* */ + u32 i2cbr; + + struct v4l2_subdev *decoder; /* video decoder sub-device */ + struct v4l2_subdev *encoder; /* video encoder sub-device */ + + struct videocodec *codec; /* video codec */ + struct videocodec *vfe; /* video front end */ + + struct mutex lock; /* file ops serialize lock */ + + u8 initialized; /* flag if zoran has been correctly initialized */ + struct card_info card; + const struct tvnorm *timing; + + unsigned short id; /* number of this device */ + char name[40]; /* name of this device */ + struct pci_dev *pci_dev; /* PCI device */ + unsigned char revision; /* revision of zr36057 */ + unsigned char __iomem *zr36057_mem;/* pointer to mapped IO memory */ + + spinlock_t spinlock; /* Spinlock */ + + /* Video for Linux parameters */ + int input; /* card's norm and input */ + v4l2_std_id norm; + + /* Current buffer params */ + unsigned int buffer_size; + + struct zoran_v4l_settings v4l_settings; /* structure with a lot of things to play with */ + + /* Buz MJPEG parameters */ + enum zoran_codec_mode codec_mode; /* status of codec */ + struct zoran_jpg_settings jpg_settings; /* structure with a lot of things to play with */ + + /* grab queue counts/indices, mask with BUZ_MASK_STAT_COM before using as index */ + /* (dma_head - dma_tail) is number active in DMA, must be <= BUZ_NUM_STAT_COM */ + /* (value & BUZ_MASK_STAT_COM) corresponds to index in stat_com table */ + unsigned long jpg_que_head; /* Index where to put next buffer which is queued */ + unsigned long jpg_dma_head; /* Index of next buffer which goes into stat_com */ + unsigned long jpg_dma_tail; /* Index of last buffer in stat_com */ + unsigned long jpg_que_tail; /* Index of last buffer in queue */ + unsigned long jpg_seq_num; /* count of frames since grab/play started */ + unsigned long jpg_err_seq; /* last seq_num before error */ + unsigned long jpg_err_shift; + unsigned long jpg_queued_num; /* count of frames queued since grab/play started */ + unsigned long vbseq; + + /* zr36057's code buffer table */ + /* stat_com[i] is indexed by dma_head/tail & BUZ_MASK_STAT_COM */ + __le32 *stat_com; + + /* Additional stuff for testing */ + unsigned int ghost_int; + int intr_counter_GIRQ1; + int intr_counter_GIRQ0; + int intr_counter_cod_rep_irq; + int intr_counter_jpeg_rep_irq; + int field_counter; + int irq1_in; + int irq1_out; + int jpeg_in; + int jpeg_out; + int JPEG_0; + int JPEG_1; + int end_event_missed; + int jpeg_missed; + int jpeg_error; + int num_errors; + int jpeg_max_missed; + int jpeg_min_missed; + unsigned int prepared; + unsigned int queued; + + u32 last_isr; + unsigned long frame_num; + int running; + int buf_in_reserve; + + dma_addr_t p_sc; + __le32 *stat_comb; + dma_addr_t p_scb; + enum zoran_map_mode map_mode; + struct list_head queued_bufs; + spinlock_t queued_bufs_lock; /* Protects queued_bufs */ + struct zr_buffer *inuse[BUZ_NUM_STAT_COM * 2]; + struct dentry *dbgfs_dir; +}; + +static inline struct zoran *to_zoran(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct zoran, v4l2_dev); +} + +/* + * There was something called _ALPHA_BUZ that used the PCI address instead of + * the kernel iomapped address for btread/btwrite. + */ +#define btwrite(dat, adr) writel((dat), zr->zr36057_mem + (adr)) +#define btread(adr) readl(zr->zr36057_mem + (adr)) + +#define btand(dat, adr) btwrite((dat) & btread(adr), (adr)) +#define btor(dat, adr) btwrite((dat) | btread(adr), (adr)) +#define btaor(dat, mask, adr) btwrite((dat) | ((mask) & btread(adr)), (adr)) + +#endif + +/* + * Debugging macros + */ +#define zrdev_dbg(zr, format, args...) \ + pci_dbg((zr)->pci_dev, format, ##args) \ + +#define zrdev_err(zr, format, args...) \ + pci_err((zr)->pci_dev, format, ##args) \ + +#define zrdev_info(zr, format, args...) \ + pci_info((zr)->pci_dev, format, ##args) \ + +int zoran_queue_init(struct zoran *zr, struct vb2_queue *vq, int dir); +void zoran_queue_exit(struct zoran *zr); +int zr_set_buf(struct zoran *zr); diff --git a/drivers/media/pci/zoran/zoran_card.c b/drivers/media/pci/zoran/zoran_card.c new file mode 100644 index 000000000..e5ff40731 --- /dev/null +++ b/drivers/media/pci/zoran/zoran_card.c @@ -0,0 +1,1437 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * This part handles card-specific data and detection + * + * Copyright (C) 2000 Serguei Miridonov + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "videocodec.h" +#include "zoran.h" +#include "zoran_card.h" +#include "zoran_device.h" +#include "zr36016.h" +#include "zr36050.h" +#include "zr36060.h" + +extern const struct zoran_format zoran_formats[]; + +static int card[BUZ_MAX] = { [0 ... (BUZ_MAX - 1)] = -1 }; +module_param_array(card, int, NULL, 0444); +MODULE_PARM_DESC(card, "Card type"); + +/* Default input and video norm at startup of the driver. */ + +static unsigned int default_input; /* default 0 = Composite, 1 = S-Video */ +module_param(default_input, uint, 0444); +MODULE_PARM_DESC(default_input, + "Default input (0=Composite, 1=S-Video, 2=Internal)"); + +static int default_mux = 1; /* 6 Eyes input selection */ +module_param(default_mux, int, 0644); +MODULE_PARM_DESC(default_mux, + "Default 6 Eyes mux setting (Input selection)"); + +static int default_norm; /* default 0 = PAL, 1 = NTSC 2 = SECAM */ +module_param(default_norm, int, 0444); +MODULE_PARM_DESC(default_norm, "Default norm (0=PAL, 1=NTSC, 2=SECAM)"); + +/* /dev/videoN, -1 for autodetect */ +static int video_nr[BUZ_MAX] = { [0 ... (BUZ_MAX - 1)] = -1 }; +module_param_array(video_nr, int, NULL, 0444); +MODULE_PARM_DESC(video_nr, "Video device number (-1=Auto)"); + +/* 1=Pass through TV signal when device is not used */ +/* 0=Show color bar when device is not used (LML33: only if lml33dpath=1) */ +int pass_through; +module_param(pass_through, int, 0644); +MODULE_PARM_DESC(pass_through, + "Pass TV signal through to TV-out when idling"); + +#define ZORAN_VERSION "0.10.1" + +MODULE_DESCRIPTION("Zoran-36057/36067 JPEG codec driver"); +MODULE_AUTHOR("Serguei Miridonov"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(ZORAN_VERSION); + +#define ZR_DEVICE(subven, subdev, data) { \ + PCI_DEVICE_SUB(PCI_VENDOR_ID_ZORAN, PCI_DEVICE_ID_ZORAN_36057, \ + (subven), (subdev)), \ + .driver_data = (data) } + +static const struct pci_device_id zr36067_pci_tbl[] = { + ZR_DEVICE(PCI_VENDOR_ID_MIRO, PCI_DEVICE_ID_MIRO_DC10PLUS, DC10_PLUS), + ZR_DEVICE(PCI_VENDOR_ID_MIRO, PCI_DEVICE_ID_MIRO_DC30PLUS, DC30_PLUS), + ZR_DEVICE(PCI_VENDOR_ID_ELECTRONICDESIGNGMBH, PCI_DEVICE_ID_LML_33R10, LML33R10), + ZR_DEVICE(PCI_VENDOR_ID_IOMEGA, PCI_DEVICE_ID_IOMEGA_BUZ, BUZ), + ZR_DEVICE(PCI_ANY_ID, PCI_ANY_ID, NUM_CARDS), + { } +}; +MODULE_DEVICE_TABLE(pci, zr36067_pci_tbl); + +static unsigned int zoran_num; /* number of cards found */ + +/* videocodec bus functions ZR36060 */ +static u32 zr36060_read(struct videocodec *codec, u16 reg) +{ + struct zoran *zr = (struct zoran *)codec->master_data->data; + __u32 data; + + if (post_office_wait(zr) || post_office_write(zr, 0, 1, reg >> 8) || + post_office_write(zr, 0, 2, reg & 0xff)) + return -1; + + data = post_office_read(zr, 0, 3) & 0xff; + return data; +} + +static void zr36060_write(struct videocodec *codec, u16 reg, u32 val) +{ + struct zoran *zr = (struct zoran *)codec->master_data->data; + + if (post_office_wait(zr) || post_office_write(zr, 0, 1, reg >> 8) || + post_office_write(zr, 0, 2, reg & 0xff)) + return; + + post_office_write(zr, 0, 3, val & 0xff); +} + +/* videocodec bus functions ZR36050 */ +static u32 zr36050_read(struct videocodec *codec, u16 reg) +{ + struct zoran *zr = (struct zoran *)codec->master_data->data; + __u32 data; + + if (post_office_wait(zr) || post_office_write(zr, 1, 0, reg >> 2)) // reg. HIGHBYTES + return -1; + + data = post_office_read(zr, 0, reg & 0x03) & 0xff; // reg. LOWBYTES + read + return data; +} + +static void zr36050_write(struct videocodec *codec, u16 reg, u32 val) +{ + struct zoran *zr = (struct zoran *)codec->master_data->data; + + if (post_office_wait(zr) || post_office_write(zr, 1, 0, reg >> 2)) // reg. HIGHBYTES + return; + + post_office_write(zr, 0, reg & 0x03, val & 0xff); // reg. LOWBYTES + wr. data +} + +/* videocodec bus functions ZR36016 */ +static u32 zr36016_read(struct videocodec *codec, u16 reg) +{ + struct zoran *zr = (struct zoran *)codec->master_data->data; + __u32 data; + + if (post_office_wait(zr)) + return -1; + + data = post_office_read(zr, 2, reg & 0x03) & 0xff; // read + return data; +} + +/* hack for in zoran_device.c */ +void zr36016_write(struct videocodec *codec, u16 reg, u32 val) +{ + struct zoran *zr = (struct zoran *)codec->master_data->data; + + if (post_office_wait(zr)) + return; + + post_office_write(zr, 2, reg & 0x03, val & 0x0ff); // wr. data +} + +/* + * Board specific information + */ + +static void dc10_init(struct zoran *zr) +{ + /* Pixel clock selection */ + GPIO(zr, 4, 0); + GPIO(zr, 5, 1); + /* Enable the video bus sync signals */ + GPIO(zr, 7, 0); +} + +static void dc10plus_init(struct zoran *zr) +{ +} + +static void buz_init(struct zoran *zr) +{ + /* some stuff from Iomega */ + pci_write_config_dword(zr->pci_dev, 0xfc, 0x90680f15); + pci_write_config_dword(zr->pci_dev, 0x0c, 0x00012020); + pci_write_config_dword(zr->pci_dev, 0xe8, 0xc0200000); +} + +static void lml33_init(struct zoran *zr) +{ + GPIO(zr, 2, 1); // Set Composite input/output +} + +static void avs6eyes_init(struct zoran *zr) +{ + // AverMedia 6-Eyes original driver by Christer Weinigel + + // Lifted straight from Christer's old driver and + // modified slightly by Martin Samuelsson. + + int mux = default_mux; /* 1 = BT866, 7 = VID1 */ + + GPIO(zr, 4, 1); /* Bt866 SLEEP on */ + udelay(2); + + GPIO(zr, 0, 1); /* ZR36060 /RESET on */ + GPIO(zr, 1, 0); /* ZR36060 /SLEEP on */ + GPIO(zr, 2, mux & 1); /* MUX S0 */ + GPIO(zr, 3, 0); /* /FRAME on */ + GPIO(zr, 4, 0); /* Bt866 SLEEP off */ + GPIO(zr, 5, mux & 2); /* MUX S1 */ + GPIO(zr, 6, 0); /* ? */ + GPIO(zr, 7, mux & 4); /* MUX S2 */ +} + +static const char *codecid_to_modulename(u16 codecid) +{ + const char *name = NULL; + + switch (codecid) { + case CODEC_TYPE_ZR36060: + name = "zr36060"; + break; + case CODEC_TYPE_ZR36050: + name = "zr36050"; + break; + case CODEC_TYPE_ZR36016: + name = "zr36016"; + break; + } + + return name; +} + +static int codec_init(struct zoran *zr, u16 codecid) +{ + switch (codecid) { + case CODEC_TYPE_ZR36060: +#ifdef CONFIG_VIDEO_ZORAN_ZR36060 + return zr36060_init_module(); +#else + pci_err(zr->pci_dev, "ZR36060 support is not enabled\n"); + return -EINVAL; +#endif + break; + case CODEC_TYPE_ZR36050: +#ifdef CONFIG_VIDEO_ZORAN_DC30 + return zr36050_init_module(); +#else + pci_err(zr->pci_dev, "ZR36050 support is not enabled\n"); + return -EINVAL; +#endif + break; + case CODEC_TYPE_ZR36016: +#ifdef CONFIG_VIDEO_ZORAN_DC30 + return zr36016_init_module(); +#else + pci_err(zr->pci_dev, "ZR36016 support is not enabled\n"); + return -EINVAL; +#endif + break; + } + + pci_err(zr->pci_dev, "unknown codec id %x\n", codecid); + return -EINVAL; +} + +static void codec_exit(struct zoran *zr, u16 codecid) +{ + switch (codecid) { + case CODEC_TYPE_ZR36060: +#ifdef CONFIG_VIDEO_ZORAN_ZR36060 + zr36060_cleanup_module(); +#endif + break; + case CODEC_TYPE_ZR36050: +#ifdef CONFIG_VIDEO_ZORAN_DC30 + zr36050_cleanup_module(); +#endif + break; + case CODEC_TYPE_ZR36016: +#ifdef CONFIG_VIDEO_ZORAN_DC30 + zr36016_cleanup_module(); +#endif + break; + } +} + +static int videocodec_init(struct zoran *zr) +{ + const char *codec_name, *vfe_name; + int result; + + codec_name = codecid_to_modulename(zr->card.video_codec); + if (codec_name) { + result = codec_init(zr, zr->card.video_codec); + if (result < 0) { + pci_err(zr->pci_dev, "failed to load video codec %s: %d\n", + codec_name, result); + return result; + } + } + vfe_name = codecid_to_modulename(zr->card.video_vfe); + if (vfe_name) { + result = codec_init(zr, zr->card.video_vfe); + if (result < 0) { + pci_err(zr->pci_dev, "failed to load video vfe %s: %d\n", + vfe_name, result); + if (codec_name) + codec_exit(zr, zr->card.video_codec); + return result; + } + } + return 0; +} + +static void videocodec_exit(struct zoran *zr) +{ + if (zr->card.video_codec != CODEC_TYPE_NONE) + codec_exit(zr, zr->card.video_codec); + if (zr->card.video_vfe != CODEC_TYPE_NONE) + codec_exit(zr, zr->card.video_vfe); +} + +static const struct tvnorm f50sqpixel = { 944, 768, 83, 880, 625, 576, 16 }; +static const struct tvnorm f60sqpixel = { 780, 640, 51, 716, 525, 480, 12 }; +static const struct tvnorm f50ccir601 = { 864, 720, 75, 804, 625, 576, 18 }; +static const struct tvnorm f60ccir601 = { 858, 720, 57, 788, 525, 480, 16 }; + +static const struct tvnorm f50ccir601_lml33 = { 864, 720, 75 + 34, 804, 625, 576, 18 }; +static const struct tvnorm f60ccir601_lml33 = { 858, 720, 57 + 34, 788, 525, 480, 16 }; + +/* The DC10 (57/16/50) uses VActive as HSync, so h_start must be 0 */ +static const struct tvnorm f50sqpixel_dc10 = { 944, 768, 0, 880, 625, 576, 0 }; +static const struct tvnorm f60sqpixel_dc10 = { 780, 640, 0, 716, 525, 480, 12 }; + +/* + * FIXME: I cannot swap U and V in saa7114, so i do one pixel left shift in zoran (75 -> 74) + * (Maxim Yevtyushkin ) + */ +static const struct tvnorm f50ccir601_lm33r10 = { 864, 720, 74 + 54, 804, 625, 576, 18 }; +static const struct tvnorm f60ccir601_lm33r10 = { 858, 720, 56 + 54, 788, 525, 480, 16 }; + +/* + * FIXME: The ks0127 seem incapable of swapping U and V, too, which is why I copy Maxim's left + * shift hack for the 6 Eyes. + * + * Christer's driver used the unshifted norms, though... + * /Sam + */ +static const struct tvnorm f50ccir601_avs6eyes = { 864, 720, 74, 804, 625, 576, 18 }; +static const struct tvnorm f60ccir601_avs6eyes = { 858, 720, 56, 788, 525, 480, 16 }; + +static const unsigned short vpx3220_addrs[] = { 0x43, 0x47, I2C_CLIENT_END }; +static const unsigned short saa7110_addrs[] = { 0x4e, 0x4f, I2C_CLIENT_END }; +static const unsigned short saa7111_addrs[] = { 0x25, 0x24, I2C_CLIENT_END }; +static const unsigned short saa7114_addrs[] = { 0x21, 0x20, I2C_CLIENT_END }; +static const unsigned short adv717x_addrs[] = { 0x6a, 0x6b, 0x2a, 0x2b, I2C_CLIENT_END }; +static const unsigned short ks0127_addrs[] = { 0x6c, 0x6d, I2C_CLIENT_END }; +static const unsigned short saa7185_addrs[] = { 0x44, I2C_CLIENT_END }; +static const unsigned short bt819_addrs[] = { 0x45, I2C_CLIENT_END }; +static const unsigned short bt856_addrs[] = { 0x44, I2C_CLIENT_END }; +static const unsigned short bt866_addrs[] = { 0x44, I2C_CLIENT_END }; + +static struct card_info zoran_cards[NUM_CARDS] = { + { + .type = DC10_OLD, + .name = "DC10(old)", + .i2c_decoder = "vpx3220a", + .addrs_decoder = vpx3220_addrs, + .video_codec = CODEC_TYPE_ZR36050, + .video_vfe = CODEC_TYPE_ZR36016, + + .inputs = 3, + .input = { + { 1, "Composite" }, + { 2, "S-Video" }, + { 0, "Internal/comp" } + }, + .norms = V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM, + .tvn = { + &f50sqpixel_dc10, + &f60sqpixel_dc10, + &f50sqpixel_dc10 + }, + .jpeg_int = 0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 2, 1, -1, 3, 7, 0, 4, 5 }, + .gpio_pol = { 0, 0, 0, 1, 0, 0, 0, 0 }, + .gpcs = { -1, 0 }, + .vfe_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .input_mux = 0, + .init = &dc10_init, + }, { + .type = DC10_NEW, + .name = "DC10(new)", + .i2c_decoder = "saa7110", + .addrs_decoder = saa7110_addrs, + .i2c_encoder = "adv7175", + .addrs_encoder = adv717x_addrs, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 3, + .input = { + { 0, "Composite" }, + { 7, "S-Video" }, + { 5, "Internal/comp" } + }, + .norms = V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM, + .tvn = { + &f50sqpixel, + &f60sqpixel, + &f50sqpixel}, + .jpeg_int = ZR36057_ISR_GIRQ0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 3, 0, 6, 1, 2, -1, 4, 5 }, + .gpio_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gpcs = { -1, 1}, + .vfe_pol = { 1, 1, 1, 1, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .input_mux = 0, + .init = &dc10plus_init, + }, { + .type = DC10_PLUS, + .name = "DC10_PLUS", + .i2c_decoder = "saa7110", + .addrs_decoder = saa7110_addrs, + .i2c_encoder = "adv7175", + .addrs_encoder = adv717x_addrs, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 3, + .input = { + { 0, "Composite" }, + { 7, "S-Video" }, + { 5, "Internal/comp" } + }, + .norms = V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM, + .tvn = { + &f50sqpixel, + &f60sqpixel, + &f50sqpixel + }, + .jpeg_int = ZR36057_ISR_GIRQ0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 3, 0, 6, 1, 2, -1, 4, 5 }, + .gpio_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gpcs = { -1, 1 }, + .vfe_pol = { 1, 1, 1, 1, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .input_mux = 0, + .init = &dc10plus_init, + }, { + .type = DC30, + .name = "DC30", + .i2c_decoder = "vpx3220a", + .addrs_decoder = vpx3220_addrs, + .i2c_encoder = "adv7175", + .addrs_encoder = adv717x_addrs, + .video_codec = CODEC_TYPE_ZR36050, + .video_vfe = CODEC_TYPE_ZR36016, + + .inputs = 3, + .input = { + { 1, "Composite" }, + { 2, "S-Video" }, + { 0, "Internal/comp" } + }, + .norms = V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM, + .tvn = { + &f50sqpixel_dc10, + &f60sqpixel_dc10, + &f50sqpixel_dc10 + }, + .jpeg_int = 0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 2, 1, -1, 3, 7, 0, 4, 5 }, + .gpio_pol = { 0, 0, 0, 1, 0, 0, 0, 0 }, + .gpcs = { -1, 0 }, + .vfe_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .input_mux = 0, + .init = &dc10_init, + }, { + .type = DC30_PLUS, + .name = "DC30_PLUS", + .i2c_decoder = "vpx3220a", + .addrs_decoder = vpx3220_addrs, + .i2c_encoder = "adv7175", + .addrs_encoder = adv717x_addrs, + .video_codec = CODEC_TYPE_ZR36050, + .video_vfe = CODEC_TYPE_ZR36016, + + .inputs = 3, + .input = { + { 1, "Composite" }, + { 2, "S-Video" }, + { 0, "Internal/comp" } + }, + .norms = V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM, + .tvn = { + &f50sqpixel_dc10, + &f60sqpixel_dc10, + &f50sqpixel_dc10 + }, + .jpeg_int = 0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 2, 1, -1, 3, 7, 0, 4, 5 }, + .gpio_pol = { 0, 0, 0, 1, 0, 0, 0, 0 }, + .gpcs = { -1, 0 }, + .vfe_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .input_mux = 0, + .init = &dc10_init, + }, { + .type = LML33, + .name = "LML33", + .i2c_decoder = "bt819a", + .addrs_decoder = bt819_addrs, + .i2c_encoder = "bt856", + .addrs_encoder = bt856_addrs, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 2, + .input = { + { 0, "Composite" }, + { 7, "S-Video" } + }, + .norms = V4L2_STD_NTSC | V4L2_STD_PAL, + .tvn = { + &f50ccir601_lml33, + &f60ccir601_lml33, + NULL + }, + .jpeg_int = ZR36057_ISR_GIRQ1, + .vsync_int = ZR36057_ISR_GIRQ0, + .gpio = { 1, -1, 3, 5, 7, -1, -1, -1 }, + .gpio_pol = { 0, 0, 0, 0, 1, 0, 0, 0 }, + .gpcs = { 3, 1 }, + .vfe_pol = { 1, 1, 0, 0, 0, 1, 0, 0 }, + .gws_not_connected = 1, + .input_mux = 0, + .init = &lml33_init, + }, { + .type = LML33R10, + .name = "LML33R10", + .i2c_decoder = "saa7114", + .addrs_decoder = saa7114_addrs, + .i2c_encoder = "adv7170", + .addrs_encoder = adv717x_addrs, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 2, + .input = { + { 0, "Composite" }, + { 7, "S-Video" } + }, + .norms = V4L2_STD_NTSC | V4L2_STD_PAL, + .tvn = { + &f50ccir601_lm33r10, + &f60ccir601_lm33r10, + NULL + }, + .jpeg_int = ZR36057_ISR_GIRQ1, + .vsync_int = ZR36057_ISR_GIRQ0, + .gpio = { 1, -1, 3, 5, 7, -1, -1, -1 }, + .gpio_pol = { 0, 0, 0, 0, 1, 0, 0, 0 }, + .gpcs = { 3, 1 }, + .vfe_pol = { 1, 1, 0, 0, 0, 1, 0, 0 }, + .gws_not_connected = 1, + .input_mux = 0, + .init = &lml33_init, + }, { + .type = BUZ, + .name = "Buz", + .i2c_decoder = "saa7111", + .addrs_decoder = saa7111_addrs, + .i2c_encoder = "saa7185", + .addrs_encoder = saa7185_addrs, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 2, + .input = { + { 3, "Composite" }, + { 7, "S-Video" } + }, + .norms = V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM, + .tvn = { + &f50ccir601, + &f60ccir601, + &f50ccir601 + }, + .jpeg_int = ZR36057_ISR_GIRQ1, + .vsync_int = ZR36057_ISR_GIRQ0, + .gpio = { 1, -1, 3, -1, -1, -1, -1, -1 }, + .gpio_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gpcs = { 3, 1 }, + .vfe_pol = { 1, 1, 0, 0, 0, 1, 0, 0 }, + .gws_not_connected = 1, + .input_mux = 0, + .init = &buz_init, + }, { + .type = AVS6EYES, + .name = "6-Eyes", + /* + * AverMedia chose not to brand the 6-Eyes. Thus it can't be + * autodetected, and requires card=x. + */ + .i2c_decoder = "ks0127", + .addrs_decoder = ks0127_addrs, + .i2c_encoder = "bt866", + .addrs_encoder = bt866_addrs, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 10, + .input = { + { 0, "Composite 1" }, + { 1, "Composite 2" }, + { 2, "Composite 3" }, + { 4, "Composite 4" }, + { 5, "Composite 5" }, + { 6, "Composite 6" }, + { 8, "S-Video 1" }, + { 9, "S-Video 2" }, + {10, "S-Video 3" }, + {15, "YCbCr" } + }, + .norms = V4L2_STD_NTSC | V4L2_STD_PAL, + .tvn = { + &f50ccir601_avs6eyes, + &f60ccir601_avs6eyes, + NULL + }, + .jpeg_int = ZR36057_ISR_GIRQ1, + .vsync_int = ZR36057_ISR_GIRQ0, + .gpio = { 1, 0, 3, -1, -1, -1, -1, -1 },// Validity unknown /Sam + .gpio_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, // Validity unknown /Sam + .gpcs = { 3, 1 }, // Validity unknown /Sam + .vfe_pol = { 1, 0, 0, 0, 0, 1, 0, 0 }, // Validity unknown /Sam + .gws_not_connected = 1, + .input_mux = 1, + .init = &avs6eyes_init, + } + +}; + +/* + * I2C functions + */ +/* software I2C functions */ +static int zoran_i2c_getsda(void *data) +{ + struct zoran *zr = (struct zoran *)data; + + return (btread(ZR36057_I2CBR) >> 1) & 1; +} + +static int zoran_i2c_getscl(void *data) +{ + struct zoran *zr = (struct zoran *)data; + + return btread(ZR36057_I2CBR) & 1; +} + +static void zoran_i2c_setsda(void *data, int state) +{ + struct zoran *zr = (struct zoran *)data; + + if (state) + zr->i2cbr |= 2; + else + zr->i2cbr &= ~2; + btwrite(zr->i2cbr, ZR36057_I2CBR); +} + +static void zoran_i2c_setscl(void *data, int state) +{ + struct zoran *zr = (struct zoran *)data; + + if (state) + zr->i2cbr |= 1; + else + zr->i2cbr &= ~1; + btwrite(zr->i2cbr, ZR36057_I2CBR); +} + +static const struct i2c_algo_bit_data zoran_i2c_bit_data_template = { + .setsda = zoran_i2c_setsda, + .setscl = zoran_i2c_setscl, + .getsda = zoran_i2c_getsda, + .getscl = zoran_i2c_getscl, + .udelay = 10, + .timeout = 100, +}; + +static int zoran_register_i2c(struct zoran *zr) +{ + zr->i2c_algo = zoran_i2c_bit_data_template; + zr->i2c_algo.data = zr; + strscpy(zr->i2c_adapter.name, ZR_DEVNAME(zr), + sizeof(zr->i2c_adapter.name)); + i2c_set_adapdata(&zr->i2c_adapter, &zr->v4l2_dev); + zr->i2c_adapter.algo_data = &zr->i2c_algo; + zr->i2c_adapter.dev.parent = &zr->pci_dev->dev; + return i2c_bit_add_bus(&zr->i2c_adapter); +} + +static void zoran_unregister_i2c(struct zoran *zr) +{ + i2c_del_adapter(&zr->i2c_adapter); +} + +/* Check a zoran_params struct for correctness, insert default params */ +int zoran_check_jpg_settings(struct zoran *zr, + struct zoran_jpg_settings *settings, int try) +{ + int err = 0, err0 = 0; + + pci_dbg(zr->pci_dev, "%s - dec: %d, Hdcm: %d, Vdcm: %d, Tdcm: %d\n", + __func__, settings->decimation, settings->hor_dcm, + settings->ver_dcm, settings->tmp_dcm); + pci_dbg(zr->pci_dev, "%s - x: %d, y: %d, w: %d, y: %d\n", __func__, + settings->img_x, settings->img_y, + settings->img_width, settings->img_height); + /* Check decimation, set default values for decimation = 1, 2, 4 */ + switch (settings->decimation) { + case 1: + + settings->hor_dcm = 1; + settings->ver_dcm = 1; + settings->tmp_dcm = 1; + settings->field_per_buff = 2; + settings->img_x = 0; + settings->img_y = 0; + settings->img_width = BUZ_MAX_WIDTH; + settings->img_height = BUZ_MAX_HEIGHT / 2; + break; + case 2: + + settings->hor_dcm = 2; + settings->ver_dcm = 1; + settings->tmp_dcm = 2; + settings->field_per_buff = 1; + settings->img_x = (BUZ_MAX_WIDTH == 720) ? 8 : 0; + settings->img_y = 0; + settings->img_width = + (BUZ_MAX_WIDTH == 720) ? 704 : BUZ_MAX_WIDTH; + settings->img_height = BUZ_MAX_HEIGHT / 2; + break; + case 4: + + if (zr->card.type == DC10_NEW) { + pci_dbg(zr->pci_dev, + "%s - HDec by 4 is not supported on the DC10\n", + __func__); + err0++; + break; + } + + settings->hor_dcm = 4; + settings->ver_dcm = 2; + settings->tmp_dcm = 2; + settings->field_per_buff = 1; + settings->img_x = (BUZ_MAX_WIDTH == 720) ? 8 : 0; + settings->img_y = 0; + settings->img_width = + (BUZ_MAX_WIDTH == 720) ? 704 : BUZ_MAX_WIDTH; + settings->img_height = BUZ_MAX_HEIGHT / 2; + break; + case 0: + + /* We have to check the data the user has set */ + + if (settings->hor_dcm != 1 && settings->hor_dcm != 2 && + (zr->card.type == DC10_NEW || settings->hor_dcm != 4)) { + settings->hor_dcm = clamp(settings->hor_dcm, 1, 2); + err0++; + } + if (settings->ver_dcm != 1 && settings->ver_dcm != 2) { + settings->ver_dcm = clamp(settings->ver_dcm, 1, 2); + err0++; + } + if (settings->tmp_dcm != 1 && settings->tmp_dcm != 2) { + settings->tmp_dcm = clamp(settings->tmp_dcm, 1, 2); + err0++; + } + if (settings->field_per_buff != 1 && + settings->field_per_buff != 2) { + settings->field_per_buff = clamp(settings->field_per_buff, 1, 2); + err0++; + } + if (settings->img_x < 0) { + settings->img_x = 0; + err0++; + } + if (settings->img_y < 0) { + settings->img_y = 0; + err0++; + } + if (settings->img_width < 0 || settings->img_width > BUZ_MAX_WIDTH) { + settings->img_width = clamp(settings->img_width, 0, (int)BUZ_MAX_WIDTH); + err0++; + } + if (settings->img_height < 0 || settings->img_height > BUZ_MAX_HEIGHT / 2) { + settings->img_height = clamp(settings->img_height, 0, BUZ_MAX_HEIGHT / 2); + err0++; + } + if (settings->img_x + settings->img_width > BUZ_MAX_WIDTH) { + settings->img_x = BUZ_MAX_WIDTH - settings->img_width; + err0++; + } + if (settings->img_y + settings->img_height > BUZ_MAX_HEIGHT / 2) { + settings->img_y = BUZ_MAX_HEIGHT / 2 - settings->img_height; + err0++; + } + if (settings->img_width % (16 * settings->hor_dcm) != 0) { + settings->img_width -= settings->img_width % (16 * settings->hor_dcm); + if (settings->img_width == 0) + settings->img_width = 16 * settings->hor_dcm; + err0++; + } + if (settings->img_height % (8 * settings->ver_dcm) != 0) { + settings->img_height -= settings->img_height % (8 * settings->ver_dcm); + if (settings->img_height == 0) + settings->img_height = 8 * settings->ver_dcm; + err0++; + } + + if (!try && err0) { + pci_err(zr->pci_dev, "%s - error in params for decimation = 0\n", __func__); + err++; + } + break; + default: + pci_err(zr->pci_dev, "%s - decimation = %d, must be 0, 1, 2 or 4\n", + __func__, settings->decimation); + err++; + break; + } + + if (settings->jpg_comp.quality > 100) + settings->jpg_comp.quality = 100; + if (settings->jpg_comp.quality < 5) + settings->jpg_comp.quality = 5; + if (settings->jpg_comp.APPn < 0) + settings->jpg_comp.APPn = 0; + if (settings->jpg_comp.APPn > 15) + settings->jpg_comp.APPn = 15; + if (settings->jpg_comp.APP_len < 0) + settings->jpg_comp.APP_len = 0; + if (settings->jpg_comp.APP_len > 60) + settings->jpg_comp.APP_len = 60; + if (settings->jpg_comp.COM_len < 0) + settings->jpg_comp.COM_len = 0; + if (settings->jpg_comp.COM_len > 60) + settings->jpg_comp.COM_len = 60; + if (err) + return -EINVAL; + return 0; +} + +static int zoran_init_video_device(struct zoran *zr, struct video_device *video_dev, int dir) +{ + int err; + + /* Now add the template and register the device unit. */ + *video_dev = zoran_template; + video_dev->v4l2_dev = &zr->v4l2_dev; + video_dev->lock = &zr->lock; + video_dev->device_caps = V4L2_CAP_STREAMING | dir; + + strscpy(video_dev->name, ZR_DEVNAME(zr), sizeof(video_dev->name)); + video_dev->vfl_dir = VFL_DIR_RX; + zoran_queue_init(zr, &zr->vq, V4L2_BUF_TYPE_VIDEO_CAPTURE); + + err = video_register_device(video_dev, VFL_TYPE_VIDEO, video_nr[zr->id]); + if (err < 0) + return err; + video_set_drvdata(video_dev, zr); + return 0; +} + +static void zoran_exit_video_devices(struct zoran *zr) +{ + video_unregister_device(zr->video_dev); + zr->video_dev = NULL; +} + +static int zoran_init_video_devices(struct zoran *zr) +{ + int err; + + zr->video_dev = video_device_alloc(); + if (!zr->video_dev) + return -ENOMEM; + + err = zoran_init_video_device(zr, zr->video_dev, V4L2_CAP_VIDEO_CAPTURE); + if (err) + kfree(zr->video_dev); + return err; +} + +/* + * v4l2_device_unregister() will care about removing zr->encoder/zr->decoder + * via v4l2_i2c_subdev_unregister() + */ +static int zoran_i2c_init(struct zoran *zr) +{ + int err; + + pci_info(zr->pci_dev, "Initializing i2c bus...\n"); + + err = zoran_register_i2c(zr); + if (err) { + pci_err(zr->pci_dev, "%s - cannot initialize i2c bus\n", __func__); + return err; + } + + zr->decoder = v4l2_i2c_new_subdev(&zr->v4l2_dev, &zr->i2c_adapter, + zr->card.i2c_decoder, 0, + zr->card.addrs_decoder); + if (!zr->decoder) { + pci_err(zr->pci_dev, "Fail to get decoder %s\n", zr->card.i2c_decoder); + err = -EINVAL; + goto error_decoder; + } + + if (zr->card.i2c_encoder) { + zr->encoder = v4l2_i2c_new_subdev(&zr->v4l2_dev, &zr->i2c_adapter, + zr->card.i2c_encoder, 0, + zr->card.addrs_encoder); + if (!zr->encoder) { + pci_err(zr->pci_dev, "Fail to get encoder %s\n", zr->card.i2c_encoder); + err = -EINVAL; + goto error_decoder; + } + } + return 0; + +error_decoder: + zoran_unregister_i2c(zr); + return err; +} + +static void zoran_i2c_exit(struct zoran *zr) +{ + zoran_unregister_i2c(zr); +} + +void zoran_open_init_params(struct zoran *zr) +{ + int i; + + zr->v4l_settings.width = 192; + zr->v4l_settings.height = 144; + zr->v4l_settings.format = &zoran_formats[7]; /* YUY2 - YUV-4:2:2 packed */ + zr->v4l_settings.bytesperline = zr->v4l_settings.width * + ((zr->v4l_settings.format->depth + 7) / 8); + + /* Set necessary params and call zoran_check_jpg_settings to set the defaults */ + zr->jpg_settings.decimation = 1; + zr->jpg_settings.jpg_comp.quality = 50; /* default compression factor 8 */ + if (zr->card.type != BUZ) + zr->jpg_settings.odd_even = 1; + else + zr->jpg_settings.odd_even = 0; + zr->jpg_settings.jpg_comp.APPn = 0; + zr->jpg_settings.jpg_comp.APP_len = 0; /* No APPn marker */ + memset(zr->jpg_settings.jpg_comp.APP_data, 0, + sizeof(zr->jpg_settings.jpg_comp.APP_data)); + zr->jpg_settings.jpg_comp.COM_len = 0; /* No COM marker */ + memset(zr->jpg_settings.jpg_comp.COM_data, 0, + sizeof(zr->jpg_settings.jpg_comp.COM_data)); + zr->jpg_settings.jpg_comp.jpeg_markers = + V4L2_JPEG_MARKER_DHT | V4L2_JPEG_MARKER_DQT; + i = zoran_check_jpg_settings(zr, &zr->jpg_settings, 0); + if (i) + pci_err(zr->pci_dev, "%s internal error\n", __func__); + + zr->buffer_size = zr->v4l_settings.bytesperline * zr->v4l_settings.height; + + clear_interrupt_counters(zr); +} + +static int zr36057_init(struct zoran *zr) +{ + int j, err; + + pci_info(zr->pci_dev, "initializing card[%d]\n", zr->id); + + /* Avoid nonsense settings from user for default input/norm */ + if (default_norm < 0 || default_norm > 2) + default_norm = 0; + if (default_norm == 0) { + zr->norm = V4L2_STD_PAL; + zr->timing = zr->card.tvn[ZR_NORM_PAL]; + } else if (default_norm == 1) { + zr->norm = V4L2_STD_NTSC; + zr->timing = zr->card.tvn[ZR_NORM_NTSC]; + } else { + zr->norm = V4L2_STD_SECAM; + zr->timing = zr->card.tvn[ZR_NORM_SECAM]; + } + if (!zr->timing) { + pci_warn(zr->pci_dev, + "%s - default TV standard not supported by hardware. PAL will be used.\n", + __func__); + zr->norm = V4L2_STD_PAL; + zr->timing = zr->card.tvn[ZR_NORM_PAL]; + } + + if (default_input > zr->card.inputs - 1) { + pci_warn(zr->pci_dev, "default_input value %d out of range (0-%d)\n", + default_input, zr->card.inputs - 1); + default_input = 0; + } + zr->input = default_input; + + /* default setup (will be repeated at every open) */ + zoran_open_init_params(zr); + + /* allocate memory *before* doing anything to the hardware in case allocation fails */ + zr->stat_com = dma_alloc_coherent(&zr->pci_dev->dev, + BUZ_NUM_STAT_COM * sizeof(u32), + &zr->p_sc, GFP_KERNEL); + if (!zr->stat_com) + return -ENOMEM; + + for (j = 0; j < BUZ_NUM_STAT_COM; j++) + zr->stat_com[j] = cpu_to_le32(1); /* mark as unavailable to zr36057 */ + + zr->stat_comb = dma_alloc_coherent(&zr->pci_dev->dev, + BUZ_NUM_STAT_COM * sizeof(u32) * 2, + &zr->p_scb, GFP_KERNEL); + if (!zr->stat_comb) { + err = -ENOMEM; + goto exit_statcom; + } + + err = zoran_init_video_devices(zr); + if (err) + goto exit_statcomb; + + zoran_init_hardware(zr); + if (!pass_through) { + decoder_call(zr, video, s_stream, 0); + encoder_call(zr, video, s_routing, 2, 0, 0); + } + + zr->initialized = 1; + return 0; + +exit_statcomb: + dma_free_coherent(&zr->pci_dev->dev, BUZ_NUM_STAT_COM * sizeof(u32) * 2, + zr->stat_comb, zr->p_scb); +exit_statcom: + dma_free_coherent(&zr->pci_dev->dev, BUZ_NUM_STAT_COM * sizeof(u32), + zr->stat_com, zr->p_sc); + return err; +} + +static void zoran_remove(struct pci_dev *pdev) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(&pdev->dev); + struct zoran *zr = to_zoran(v4l2_dev); + + if (!zr->initialized) + goto exit_free; + + debugfs_remove_recursive(zr->dbgfs_dir); + + zoran_queue_exit(zr); + + /* unregister videocodec bus */ + if (zr->codec) + videocodec_detach(zr->codec); + if (zr->vfe) + videocodec_detach(zr->vfe); + videocodec_exit(zr); + + /* unregister i2c bus */ + zoran_i2c_exit(zr); + /* disable PCI bus-mastering */ + zoran_set_pci_master(zr, 0); + /* put chip into reset */ + btwrite(0, ZR36057_SPGPPCR); + pci_free_irq(zr->pci_dev, 0, zr); + /* unmap and free memory */ + dma_free_coherent(&zr->pci_dev->dev, BUZ_NUM_STAT_COM * sizeof(u32), + zr->stat_com, zr->p_sc); + dma_free_coherent(&zr->pci_dev->dev, BUZ_NUM_STAT_COM * sizeof(u32) * 2, + zr->stat_comb, zr->p_scb); + pci_release_regions(pdev); + pci_disable_device(zr->pci_dev); + zoran_exit_video_devices(zr); +exit_free: + v4l2_ctrl_handler_free(&zr->hdl); + v4l2_device_unregister(&zr->v4l2_dev); +} + +void zoran_vdev_release(struct video_device *vdev) +{ + kfree(vdev); +} + +static struct videocodec_master *zoran_setup_videocodec(struct zoran *zr, + int type) +{ + struct videocodec_master *m = NULL; + + m = devm_kmalloc(&zr->pci_dev->dev, sizeof(*m), GFP_KERNEL); + if (!m) + return m; + + /* + * magic and type are unused for master struct. Makes sense only at codec structs. + * In the past, .type were initialized to the old V4L1 .hardware value, + * as VID_HARDWARE_ZR36067 + */ + m->magic = 0L; + m->type = 0; + + m->flags = CODEC_FLAG_ENCODER | CODEC_FLAG_DECODER; + strscpy(m->name, ZR_DEVNAME(zr), sizeof(m->name)); + m->data = zr; + + switch (type) { + case CODEC_TYPE_ZR36060: + m->readreg = zr36060_read; + m->writereg = zr36060_write; + m->flags |= CODEC_FLAG_JPEG | CODEC_FLAG_VFE; + break; + case CODEC_TYPE_ZR36050: + m->readreg = zr36050_read; + m->writereg = zr36050_write; + m->flags |= CODEC_FLAG_JPEG; + break; + case CODEC_TYPE_ZR36016: + m->readreg = zr36016_read; + m->writereg = zr36016_write; + m->flags |= CODEC_FLAG_VFE; + break; + } + + return m; +} + +static void zoran_subdev_notify(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + struct zoran *zr = to_zoran(sd->v4l2_dev); + + /* + * Bt819 needs to reset its FIFO buffer using #FRST pin and + * LML33 card uses GPIO(7) for that. + */ + if (cmd == BT819_FIFO_RESET_LOW) + GPIO(zr, 7, 0); + else if (cmd == BT819_FIFO_RESET_HIGH) + GPIO(zr, 7, 1); +} + +static int zoran_video_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct zoran *zr = container_of(ctrl->handler, struct zoran, hdl); + + switch (ctrl->id) { + case V4L2_CID_JPEG_COMPRESSION_QUALITY: + zr->jpg_settings.jpg_comp.quality = ctrl->val; + return zoran_check_jpg_settings(zr, &zr->jpg_settings, 0); + default: + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ctrl_ops zoran_video_ctrl_ops = { + .s_ctrl = zoran_video_set_ctrl, +}; + +static int zoran_debugfs_show(struct seq_file *seq, void *v) +{ + struct zoran *zr = seq->private; + + seq_printf(seq, "Running mode %x\n", zr->running); + seq_printf(seq, "Codec mode %x\n", zr->codec_mode); + seq_printf(seq, "Norm %llx\n", zr->norm); + seq_printf(seq, "Input %d\n", zr->input); + seq_printf(seq, "Buffersize %d\n", zr->buffer_size); + + seq_printf(seq, "V4L width %dx%d\n", zr->v4l_settings.width, zr->v4l_settings.height); + seq_printf(seq, "V4L bytesperline %d\n", zr->v4l_settings.bytesperline); + + seq_printf(seq, "JPG decimation %u\n", zr->jpg_settings.decimation); + seq_printf(seq, "JPG hor_dcm %u\n", zr->jpg_settings.hor_dcm); + seq_printf(seq, "JPG ver_dcm %u\n", zr->jpg_settings.ver_dcm); + seq_printf(seq, "JPG tmp_dcm %u\n", zr->jpg_settings.tmp_dcm); + seq_printf(seq, "JPG odd_even %u\n", zr->jpg_settings.odd_even); + seq_printf(seq, "JPG crop (%d,%d)/%dx%d\n", + zr->jpg_settings.img_x, + zr->jpg_settings.img_y, + zr->jpg_settings.img_width, + zr->jpg_settings.img_height); + + seq_printf(seq, "Prepared %u\n", zr->prepared); + seq_printf(seq, "Queued %u\n", zr->queued); + + videocodec_debugfs_show(seq); + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(zoran_debugfs); + +/* + * Scan for a Buz card (actually for the PCI controller ZR36057), + * request the irq and map the io memory + */ +static int zoran_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + unsigned char latency, need_latency; + struct zoran *zr; + int result; + struct videocodec_master *master_vfe = NULL; + struct videocodec_master *master_codec = NULL; + int card_num; + unsigned int nr; + int err; + + pci_info(pdev, "Zoran MJPEG board driver version %s\n", ZORAN_VERSION); + + /* some mainboards might not do PCI-PCI data transfer well */ + if (pci_pci_problems & (PCIPCI_FAIL | PCIAGP_FAIL | PCIPCI_ALIMAGIK)) + pci_warn(pdev, "%s: chipset does not support reliable PCI-PCI DMA\n", + ZORAN_NAME); + + err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (err) + return err; + err = vb2_dma_contig_set_max_seg_size(&pdev->dev, U32_MAX); + if (err) + return err; + + nr = zoran_num++; + if (nr >= BUZ_MAX) { + pci_err(pdev, "driver limited to %d card(s) maximum\n", BUZ_MAX); + return -ENOENT; + } + + zr = devm_kzalloc(&pdev->dev, sizeof(*zr), GFP_KERNEL); + if (!zr) + return -ENOMEM; + + zr->v4l2_dev.notify = zoran_subdev_notify; + if (v4l2_device_register(&pdev->dev, &zr->v4l2_dev)) + goto zr_free_mem; + zr->pci_dev = pdev; + zr->id = nr; + snprintf(ZR_DEVNAME(zr), sizeof(ZR_DEVNAME(zr)), "MJPEG[%u]", zr->id); + if (v4l2_ctrl_handler_init(&zr->hdl, 10)) + goto zr_unreg; + zr->v4l2_dev.ctrl_handler = &zr->hdl; + v4l2_ctrl_new_std(&zr->hdl, &zoran_video_ctrl_ops, + V4L2_CID_JPEG_COMPRESSION_QUALITY, 0, + 100, 1, 50); + spin_lock_init(&zr->spinlock); + mutex_init(&zr->lock); + if (pci_enable_device(pdev)) + goto zr_unreg; + zr->revision = zr->pci_dev->revision; + + pci_info(zr->pci_dev, "Zoran ZR360%c7 (rev %d), irq: %d, memory: 0x%08llx\n", + zr->revision < 2 ? '5' : '6', zr->revision, + zr->pci_dev->irq, (uint64_t)pci_resource_start(zr->pci_dev, 0)); + if (zr->revision >= 2) + pci_info(zr->pci_dev, "Subsystem vendor=0x%04x id=0x%04x\n", + zr->pci_dev->subsystem_vendor, zr->pci_dev->subsystem_device); + + /* Use auto-detected card type? */ + if (card[nr] == -1) { + if (zr->revision < 2) { + pci_err(pdev, "No card type specified, please use the card=X module parameter\n"); + pci_err(pdev, "It is not possible to auto-detect ZR36057 based cards\n"); + goto zr_unreg; + } + + card_num = ent->driver_data; + if (card_num >= NUM_CARDS) { + pci_err(pdev, "Unknown card, try specifying card=X module parameter\n"); + goto zr_unreg; + } + pci_info(zr->pci_dev, "%s() - card %s detected\n", __func__, + zoran_cards[card_num].name); + } else { + card_num = card[nr]; + if (card_num >= NUM_CARDS || card_num < 0) { + pci_err(pdev, "User specified card type %d out of range (0 .. %d)\n", + card_num, NUM_CARDS - 1); + goto zr_unreg; + } + } + + /* + * even though we make this a non pointer and thus + * theoretically allow for making changes to this struct + * on a per-individual card basis at runtime, this is + * strongly discouraged. This structure is intended to + * keep general card information, no settings or anything + */ + zr->card = zoran_cards[card_num]; + snprintf(ZR_DEVNAME(zr), sizeof(ZR_DEVNAME(zr)), "%s[%u]", + zr->card.name, zr->id); + + err = pci_request_regions(pdev, ZR_DEVNAME(zr)); + if (err) + goto zr_unreg; + + zr->zr36057_mem = devm_ioremap(&pdev->dev, pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + if (!zr->zr36057_mem) { + pci_err(pdev, "%s() - ioremap failed\n", __func__); + goto zr_pci_release; + } + + result = pci_request_irq(pdev, 0, zoran_irq, NULL, zr, ZR_DEVNAME(zr)); + if (result < 0) { + if (result == -EINVAL) { + pci_err(pdev, "%s - bad IRQ number or handler\n", __func__); + } else if (result == -EBUSY) { + pci_err(pdev, "%s - IRQ %d busy, change your PnP config in BIOS\n", + __func__, zr->pci_dev->irq); + } else { + pci_err(pdev, "%s - cannot assign IRQ, error code %d\n", __func__, result); + } + goto zr_pci_release; + } + + /* set PCI latency timer */ + pci_read_config_byte(zr->pci_dev, PCI_LATENCY_TIMER, + &latency); + need_latency = zr->revision > 1 ? 32 : 48; + if (latency != need_latency) { + pci_info(zr->pci_dev, "Changing PCI latency from %d to %d\n", + latency, need_latency); + pci_write_config_byte(zr->pci_dev, PCI_LATENCY_TIMER, need_latency); + } + + zr36057_restart(zr); + + err = zoran_i2c_init(zr); + if (err) + goto zr_free_irq; + + pci_info(zr->pci_dev, "Initializing videocodec bus...\n"); + err = videocodec_init(zr); + if (err) + goto zr_unreg_i2c; + + /* reset JPEG codec */ + jpeg_codec_sleep(zr, 1); + jpeg_codec_reset(zr); + /* video bus enabled */ + /* display codec revision */ + if (zr->card.video_codec != 0) { + master_codec = zoran_setup_videocodec(zr, zr->card.video_codec); + if (!master_codec) + goto zr_unreg_videocodec; + zr->codec = videocodec_attach(master_codec); + if (!zr->codec) { + pci_err(pdev, "%s - no codec found\n", __func__); + goto zr_unreg_videocodec; + } + if (zr->codec->type != zr->card.video_codec) { + pci_err(pdev, "%s - wrong codec\n", __func__); + goto zr_detach_codec; + } + } + if (zr->card.video_vfe != 0) { + master_vfe = zoran_setup_videocodec(zr, zr->card.video_vfe); + if (!master_vfe) + goto zr_detach_codec; + zr->vfe = videocodec_attach(master_vfe); + if (!zr->vfe) { + pci_err(pdev, "%s - no VFE found\n", __func__); + goto zr_detach_codec; + } + if (zr->vfe->type != zr->card.video_vfe) { + pci_err(pdev, "%s = wrong VFE\n", __func__); + goto zr_detach_vfe; + } + } + + /* take care of Natoma chipset and a revision 1 zr36057 */ + if ((pci_pci_problems & PCIPCI_NATOMA) && zr->revision <= 1) + pci_info(zr->pci_dev, "ZR36057/Natoma bug, max. buffer size is 128K\n"); + + if (zr36057_init(zr) < 0) + goto zr_detach_vfe; + + zr->map_mode = ZORAN_MAP_MODE_RAW; + + zr->dbgfs_dir = debugfs_create_dir(ZR_DEVNAME(zr), NULL); + debugfs_create_file("debug", 0444, zr->dbgfs_dir, zr, + &zoran_debugfs_fops); + return 0; + +zr_detach_vfe: + videocodec_detach(zr->vfe); +zr_detach_codec: + videocodec_detach(zr->codec); +zr_unreg_videocodec: + videocodec_exit(zr); +zr_unreg_i2c: + zoran_i2c_exit(zr); +zr_free_irq: + btwrite(0, ZR36057_SPGPPCR); + pci_free_irq(zr->pci_dev, 0, zr); +zr_pci_release: + pci_release_regions(pdev); +zr_unreg: + v4l2_ctrl_handler_free(&zr->hdl); + v4l2_device_unregister(&zr->v4l2_dev); +zr_free_mem: + + return -ENODEV; +} + +static struct pci_driver zoran_driver = { + .name = "zr36067", + .id_table = zr36067_pci_tbl, + .probe = zoran_probe, + .remove = zoran_remove, +}; + +module_pci_driver(zoran_driver); diff --git a/drivers/media/pci/zoran/zoran_card.h b/drivers/media/pci/zoran/zoran_card.h new file mode 100644 index 000000000..c4f81777e --- /dev/null +++ b/drivers/media/pci/zoran/zoran_card.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * This part handles card-specific data and detection + * + * Copyright (C) 2000 Serguei Miridonov + */ + +#ifndef __ZORAN_CARD_H__ +#define __ZORAN_CARD_H__ + +/* Anybody who uses more than four? */ +#define BUZ_MAX 4 + +extern const struct video_device zoran_template; + +int zoran_check_jpg_settings(struct zoran *zr, + struct zoran_jpg_settings *settings, int try); +void zoran_open_init_params(struct zoran *zr); +void zoran_vdev_release(struct video_device *vdev); + +void zr36016_write(struct videocodec *codec, u16 reg, u32 val); + +#endif /* __ZORAN_CARD_H__ */ diff --git a/drivers/media/pci/zoran/zoran_device.c b/drivers/media/pci/zoran/zoran_device.c new file mode 100644 index 000000000..31f049b55 --- /dev/null +++ b/drivers/media/pci/zoran/zoran_device.c @@ -0,0 +1,956 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * This part handles device access (PCI/I2C/codec/...) + * + * Copyright (C) 2000 Serguei Miridonov + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "videocodec.h" +#include "zoran.h" +#include "zoran_device.h" +#include "zoran_card.h" + +#define IRQ_MASK (ZR36057_ISR_GIRQ0 | \ + ZR36057_ISR_GIRQ1 | \ + ZR36057_ISR_JPEG_REP_IRQ) + +static bool lml33dpath; /* default = 0 + * 1 will use digital path in capture + * mode instead of analog. It can be + * used for picture adjustments using + * tool like xawtv while watching image + * on TV monitor connected to the output. + * However, due to absence of 75 Ohm + * load on Bt819 input, there will be + * some image imperfections + */ + +module_param(lml33dpath, bool, 0644); +MODULE_PARM_DESC(lml33dpath, "Use digital path capture mode (on LML33 cards)"); + +/* + * initialize video front end + */ +static void zr36057_init_vfe(struct zoran *zr) +{ + u32 reg; + + reg = btread(ZR36057_VFESPFR); + reg |= ZR36057_VFESPFR_LITTLE_ENDIAN; + reg &= ~ZR36057_VFESPFR_VCLK_POL; + reg |= ZR36057_VFESPFR_EXT_FL; + reg |= ZR36057_VFESPFR_TOP_FIELD; + btwrite(reg, ZR36057_VFESPFR); + reg = btread(ZR36057_VDCR); + if (pci_pci_problems & PCIPCI_TRITON) + // || zr->revision < 1) // Revision 1 has also Triton support + reg &= ~ZR36057_VDCR_TRITON; + else + reg |= ZR36057_VDCR_TRITON; + btwrite(reg, ZR36057_VDCR); +} + +/* + * General Purpose I/O and Guest bus access + */ + +/* + * This is a bit tricky. When a board lacks a GPIO function, the corresponding + * GPIO bit number in the card_info structure is set to 0. + */ + +void GPIO(struct zoran *zr, int bit, unsigned int value) +{ + u32 reg; + u32 mask; + + /* Make sure the bit number is legal + * A bit number of -1 (lacking) gives a mask of 0, + * making it harmless + */ + mask = (1 << (24 + bit)) & 0xff000000; + reg = btread(ZR36057_GPPGCR1) & ~mask; + if (value) + reg |= mask; + + btwrite(reg, ZR36057_GPPGCR1); + udelay(1); +} + +/* + * Wait til post office is no longer busy + */ + +int post_office_wait(struct zoran *zr) +{ + u32 por; + + while ((por = btread(ZR36057_POR)) & ZR36057_POR_PO_PEN) { + /* wait for something to happen */ + /* TODO add timeout */ + } + if ((por & ZR36057_POR_PO_TIME) && !zr->card.gws_not_connected) { + /* In LML33/BUZ \GWS line is not connected, so it has always timeout set */ + pci_info(zr->pci_dev, "pop timeout %08x\n", por); + return -1; + } + + return 0; +} + +int post_office_write(struct zoran *zr, unsigned int guest, + unsigned int reg, unsigned int value) +{ + u32 por; + + por = + ZR36057_POR_PO_DIR | ZR36057_POR_PO_TIME | ((guest & 7) << 20) | + ((reg & 7) << 16) | (value & 0xFF); + btwrite(por, ZR36057_POR); + + return post_office_wait(zr); +} + +int post_office_read(struct zoran *zr, unsigned int guest, unsigned int reg) +{ + u32 por; + + por = ZR36057_POR_PO_TIME | ((guest & 7) << 20) | ((reg & 7) << 16); + btwrite(por, ZR36057_POR); + if (post_office_wait(zr) < 0) + return -1; + + return btread(ZR36057_POR) & 0xFF; +} + +/* + * JPEG Codec access + */ + +void jpeg_codec_sleep(struct zoran *zr, int sleep) +{ + GPIO(zr, zr->card.gpio[ZR_GPIO_JPEG_SLEEP], !sleep); + if (!sleep) { + pci_dbg(zr->pci_dev, "%s() - wake GPIO=0x%08x\n", + __func__, btread(ZR36057_GPPGCR1)); + usleep_range(500, 1000); + } else { + pci_dbg(zr->pci_dev, "%s() - sleep GPIO=0x%08x\n", + __func__, btread(ZR36057_GPPGCR1)); + udelay(2); + } +} + +int jpeg_codec_reset(struct zoran *zr) +{ + /* Take the codec out of sleep */ + jpeg_codec_sleep(zr, 0); + + if (zr->card.gpcs[GPCS_JPEG_RESET] != 0xff) { + post_office_write(zr, zr->card.gpcs[GPCS_JPEG_RESET], 0, + 0); + udelay(2); + } else { + GPIO(zr, zr->card.gpio[ZR_GPIO_JPEG_RESET], 0); + udelay(2); + GPIO(zr, zr->card.gpio[ZR_GPIO_JPEG_RESET], 1); + udelay(2); + } + + return 0; +} + +/* + * Set the registers for the size we have specified. Don't bother + * trying to understand this without the ZR36057 manual in front of + * you [AC]. + */ +static void zr36057_adjust_vfe(struct zoran *zr, enum zoran_codec_mode mode) +{ + u32 reg; + + switch (mode) { + case BUZ_MODE_MOTION_DECOMPRESS: + btand(~ZR36057_VFESPFR_EXT_FL, ZR36057_VFESPFR); + reg = btread(ZR36057_VFEHCR); + if ((reg & (1 << 10)) && zr->card.type != LML33R10) + reg += ((1 << 10) | 1); + + btwrite(reg, ZR36057_VFEHCR); + break; + case BUZ_MODE_MOTION_COMPRESS: + case BUZ_MODE_IDLE: + default: + if ((zr->norm & V4L2_STD_NTSC) || + (zr->card.type == LML33R10 && + (zr->norm & V4L2_STD_PAL))) + btand(~ZR36057_VFESPFR_EXT_FL, ZR36057_VFESPFR); + else + btor(ZR36057_VFESPFR_EXT_FL, ZR36057_VFESPFR); + reg = btread(ZR36057_VFEHCR); + if (!(reg & (1 << 10)) && zr->card.type != LML33R10) + reg -= ((1 << 10) | 1); + + btwrite(reg, ZR36057_VFEHCR); + break; + } +} + +/* + * set geometry + */ + +static void zr36057_set_vfe(struct zoran *zr, int video_width, int video_height, + const struct zoran_format *format) +{ + const struct tvnorm *tvn; + unsigned int h_start, h_end, v_start, v_end; + unsigned int disp_mode; + unsigned int vid_win_wid, vid_win_ht; + unsigned int hcrop1, hcrop2, vcrop1, vcrop2; + unsigned int wa, we, ha, he; + unsigned int X, Y, hor_dcm, ver_dcm; + u32 reg; + + tvn = zr->timing; + + wa = tvn->wa; + ha = tvn->ha; + + pci_dbg(zr->pci_dev, "set_vfe() - width = %d, height = %d\n", video_width, video_height); + + if (video_width < BUZ_MIN_WIDTH || + video_height < BUZ_MIN_HEIGHT || + video_width > wa || video_height > ha) { + pci_err(zr->pci_dev, "set_vfe: w=%d h=%d not valid\n", video_width, video_height); + return; + } + + /**** zr36057 ****/ + + /* horizontal */ + vid_win_wid = video_width; + X = DIV_ROUND_UP(vid_win_wid * 64, tvn->wa); + we = (vid_win_wid * 64) / X; + hor_dcm = 64 - X; + hcrop1 = 2 * ((tvn->wa - we) / 4); + hcrop2 = tvn->wa - we - hcrop1; + h_start = tvn->h_start ? tvn->h_start : 1; + /* (Ronald) Original comment: + * "| 1 Doesn't have any effect, tested on both a DC10 and a DC10+" + * this is false. It inverses chroma values on the LML33R10 (so Cr + * suddenly is shown as Cb and reverse, really cool effect if you + * want to see blue faces, not useful otherwise). So don't use |1. + * However, the DC10 has '0' as h_start, but does need |1, so we + * use a dirty check... + */ + h_end = h_start + tvn->wa - 1; + h_start += hcrop1; + h_end -= hcrop2; + reg = ((h_start & ZR36057_VFEHCR_HMASK) << ZR36057_VFEHCR_H_START) + | ((h_end & ZR36057_VFEHCR_HMASK) << ZR36057_VFEHCR_H_END); + if (zr->card.vfe_pol.hsync_pol) + reg |= ZR36057_VFEHCR_HS_POL; + btwrite(reg, ZR36057_VFEHCR); + + /* Vertical */ + disp_mode = !(video_height > BUZ_MAX_HEIGHT / 2); + vid_win_ht = disp_mode ? video_height : video_height / 2; + Y = DIV_ROUND_UP(vid_win_ht * 64 * 2, tvn->ha); + he = (vid_win_ht * 64) / Y; + ver_dcm = 64 - Y; + vcrop1 = (tvn->ha / 2 - he) / 2; + vcrop2 = tvn->ha / 2 - he - vcrop1; + v_start = tvn->v_start; + // FIXME SnapShot times out with -1 in 768*576 on the DC10 - LP + v_end = v_start + tvn->ha / 2; // - 1; + v_start += vcrop1; + v_end -= vcrop2; + reg = ((v_start & ZR36057_VFEVCR_VMASK) << ZR36057_VFEVCR_V_START) + | ((v_end & ZR36057_VFEVCR_VMASK) << ZR36057_VFEVCR_V_END); + if (zr->card.vfe_pol.vsync_pol) + reg |= ZR36057_VFEVCR_VS_POL; + btwrite(reg, ZR36057_VFEVCR); + + /* scaler and pixel format */ + reg = 0; + reg |= (hor_dcm << ZR36057_VFESPFR_HOR_DCM); + reg |= (ver_dcm << ZR36057_VFESPFR_VER_DCM); + reg |= (disp_mode << ZR36057_VFESPFR_DISP_MODE); + /* + * RJ: I don't know, why the following has to be the opposite + * of the corresponding ZR36060 setting, but only this way + * we get the correct colors when uncompressing to the screen + */ + //reg |= ZR36057_VFESPFR_VCLK_POL; + /* RJ: Don't know if that is needed for NTSC also */ + if (!(zr->norm & V4L2_STD_NTSC)) + reg |= ZR36057_VFESPFR_EXT_FL; // NEEDED!!!!!!! Wolfgang + reg |= ZR36057_VFESPFR_TOP_FIELD; + if (hor_dcm >= 48) + reg |= 3 << ZR36057_VFESPFR_H_FILTER; /* 5 tap filter */ + else if (hor_dcm >= 32) + reg |= 2 << ZR36057_VFESPFR_H_FILTER; /* 4 tap filter */ + else if (hor_dcm >= 16) + reg |= 1 << ZR36057_VFESPFR_H_FILTER; /* 3 tap filter */ + + reg |= format->vfespfr; + btwrite(reg, ZR36057_VFESPFR); + + /* display configuration */ + reg = (16 << ZR36057_VDCR_MIN_PIX) + | (vid_win_ht << ZR36057_VDCR_VID_WIN_HT) + | (vid_win_wid << ZR36057_VDCR_VID_WIN_WID); + if (pci_pci_problems & PCIPCI_TRITON) + // || zr->revision < 1) // Revision 1 has also Triton support + reg &= ~ZR36057_VDCR_TRITON; + else + reg |= ZR36057_VDCR_TRITON; + btwrite(reg, ZR36057_VDCR); + + zr36057_adjust_vfe(zr, zr->codec_mode); +} + +/* Enable/Disable uncompressed memory grabbing of the 36057 */ +void zr36057_set_memgrab(struct zoran *zr, int mode) +{ + if (mode) { + /* We only check SnapShot and not FrameGrab here. SnapShot==1 + * means a capture is already in progress, but FrameGrab==1 + * doesn't necessary mean that. It's more correct to say a 1 + * to 0 transition indicates a capture completed. If a + * capture is pending when capturing is tuned off, FrameGrab + * will be stuck at 1 until capturing is turned back on. + */ + if (btread(ZR36057_VSSFGR) & ZR36057_VSSFGR_SNAP_SHOT) + pci_warn(zr->pci_dev, "%s(1) with SnapShot on!?\n", __func__); + + /* switch on VSync interrupts */ + btwrite(IRQ_MASK, ZR36057_ISR); // Clear Interrupts + btor(zr->card.vsync_int, ZR36057_ICR); // SW + + /* enable SnapShot */ + btor(ZR36057_VSSFGR_SNAP_SHOT, ZR36057_VSSFGR); + + /* Set zr36057 video front end and enable video */ + zr36057_set_vfe(zr, zr->v4l_settings.width, + zr->v4l_settings.height, + zr->v4l_settings.format); + } else { + /* switch off VSync interrupts */ + btand(~zr->card.vsync_int, ZR36057_ICR); // SW + + /* re-enable grabbing to screen if it was running */ + btand(~ZR36057_VDCR_VID_EN, ZR36057_VDCR); + btand(~ZR36057_VSSFGR_SNAP_SHOT, ZR36057_VSSFGR); + } +} + +/***************************************************************************** + * * + * Set up the Buz-specific MJPEG part * + * * + *****************************************************************************/ + +static inline void set_frame(struct zoran *zr, int val) +{ + GPIO(zr, zr->card.gpio[ZR_GPIO_JPEG_FRAME], val); +} + +static void set_videobus_dir(struct zoran *zr, int val) +{ + switch (zr->card.type) { + case LML33: + case LML33R10: + if (!lml33dpath) + GPIO(zr, 5, val); + else + GPIO(zr, 5, 1); + break; + default: + GPIO(zr, zr->card.gpio[ZR_GPIO_VID_DIR], + zr->card.gpio_pol[ZR_GPIO_VID_DIR] ? !val : val); + break; + } +} + +static void init_jpeg_queue(struct zoran *zr) +{ + int i; + + /* re-initialize DMA ring stuff */ + zr->jpg_que_head = 0; + zr->jpg_dma_head = 0; + zr->jpg_dma_tail = 0; + zr->jpg_que_tail = 0; + zr->jpg_seq_num = 0; + zr->jpeg_error = 0; + zr->num_errors = 0; + zr->jpg_err_seq = 0; + zr->jpg_err_shift = 0; + zr->jpg_queued_num = 0; + for (i = 0; i < BUZ_NUM_STAT_COM; i++) + zr->stat_com[i] = cpu_to_le32(1); /* mark as unavailable to zr36057 */ +} + +static void zr36057_set_jpg(struct zoran *zr, enum zoran_codec_mode mode) +{ + const struct tvnorm *tvn; + u32 reg; + + tvn = zr->timing; + + /* assert P_Reset, disable code transfer, deassert Active */ + btwrite(0, ZR36057_JPC); + + /* MJPEG compression mode */ + switch (mode) { + case BUZ_MODE_MOTION_COMPRESS: + default: + reg = ZR36057_JMC_MJPG_CMP_MODE; + break; + + case BUZ_MODE_MOTION_DECOMPRESS: + reg = ZR36057_JMC_MJPG_EXP_MODE; + reg |= ZR36057_JMC_SYNC_MSTR; + /* RJ: The following is experimental - improves the output to screen */ + //if(zr->jpg_settings.VFIFO_FB) reg |= ZR36057_JMC_VFIFO_FB; // No, it doesn't. SM + break; + + case BUZ_MODE_STILL_COMPRESS: + reg = ZR36057_JMC_JPG_CMP_MODE; + break; + + case BUZ_MODE_STILL_DECOMPRESS: + reg = ZR36057_JMC_JPG_EXP_MODE; + break; + } + reg |= ZR36057_JMC_JPG; + if (zr->jpg_settings.field_per_buff == 1) + reg |= ZR36057_JMC_FLD_PER_BUFF; + btwrite(reg, ZR36057_JMC); + + /* vertical */ + btor(ZR36057_VFEVCR_VS_POL, ZR36057_VFEVCR); + reg = (6 << ZR36057_VSP_VSYNC_SIZE) | + (tvn->ht << ZR36057_VSP_FRM_TOT); + btwrite(reg, ZR36057_VSP); + reg = ((zr->jpg_settings.img_y + tvn->v_start) << ZR36057_FVAP_NAY) | + (zr->jpg_settings.img_height << ZR36057_FVAP_PAY); + btwrite(reg, ZR36057_FVAP); + + /* horizontal */ + if (zr->card.vfe_pol.hsync_pol) + btor(ZR36057_VFEHCR_HS_POL, ZR36057_VFEHCR); + else + btand(~ZR36057_VFEHCR_HS_POL, ZR36057_VFEHCR); + reg = ((tvn->h_sync_start) << ZR36057_HSP_HSYNC_START) | + (tvn->wt << ZR36057_HSP_LINE_TOT); + btwrite(reg, ZR36057_HSP); + reg = ((zr->jpg_settings.img_x + + tvn->h_start + 4) << ZR36057_FHAP_NAX) | + (zr->jpg_settings.img_width << ZR36057_FHAP_PAX); + btwrite(reg, ZR36057_FHAP); + + /* field process parameters */ + if (zr->jpg_settings.odd_even) + reg = ZR36057_FPP_ODD_EVEN; + else + reg = 0; + + btwrite(reg, ZR36057_FPP); + + /* Set proper VCLK Polarity, else colors will be wrong during playback */ + //btor(ZR36057_VFESPFR_VCLK_POL, ZR36057_VFESPFR); + + /* code base address */ + btwrite(zr->p_sc, ZR36057_JCBA); + + /* FIFO threshold (FIFO is 160. double words) */ + /* NOTE: decimal values here */ + switch (mode) { + case BUZ_MODE_STILL_COMPRESS: + case BUZ_MODE_MOTION_COMPRESS: + if (zr->card.type != BUZ) + reg = 140; + else + reg = 60; + break; + + case BUZ_MODE_STILL_DECOMPRESS: + case BUZ_MODE_MOTION_DECOMPRESS: + reg = 20; + break; + + default: + reg = 80; + break; + } + btwrite(reg, ZR36057_JCFT); + zr36057_adjust_vfe(zr, mode); +} + +void clear_interrupt_counters(struct zoran *zr) +{ + zr->intr_counter_GIRQ1 = 0; + zr->intr_counter_GIRQ0 = 0; + zr->intr_counter_cod_rep_irq = 0; + zr->intr_counter_jpeg_rep_irq = 0; + zr->field_counter = 0; + zr->irq1_in = 0; + zr->irq1_out = 0; + zr->jpeg_in = 0; + zr->jpeg_out = 0; + zr->JPEG_0 = 0; + zr->JPEG_1 = 0; + zr->end_event_missed = 0; + zr->jpeg_missed = 0; + zr->jpeg_max_missed = 0; + zr->jpeg_min_missed = 0x7fffffff; +} + +static u32 count_reset_interrupt(struct zoran *zr) +{ + u32 isr; + + isr = btread(ZR36057_ISR) & 0x78000000; + if (isr) { + if (isr & ZR36057_ISR_GIRQ1) { + btwrite(ZR36057_ISR_GIRQ1, ZR36057_ISR); + zr->intr_counter_GIRQ1++; + } + if (isr & ZR36057_ISR_GIRQ0) { + btwrite(ZR36057_ISR_GIRQ0, ZR36057_ISR); + zr->intr_counter_GIRQ0++; + } + if (isr & ZR36057_ISR_COD_REP_IRQ) { + btwrite(ZR36057_ISR_COD_REP_IRQ, ZR36057_ISR); + zr->intr_counter_cod_rep_irq++; + } + if (isr & ZR36057_ISR_JPEG_REP_IRQ) { + btwrite(ZR36057_ISR_JPEG_REP_IRQ, ZR36057_ISR); + zr->intr_counter_jpeg_rep_irq++; + } + } + return isr; +} + +void jpeg_start(struct zoran *zr) +{ + int reg; + + zr->frame_num = 0; + + /* deassert P_reset, disable code transfer, deassert Active */ + btwrite(ZR36057_JPC_P_RESET, ZR36057_JPC); + /* stop flushing the internal code buffer */ + btand(~ZR36057_MCTCR_C_FLUSH, ZR36057_MCTCR); + /* enable code transfer */ + btor(ZR36057_JPC_COD_TRNS_EN, ZR36057_JPC); + + /* clear IRQs */ + btwrite(IRQ_MASK, ZR36057_ISR); + /* enable the JPEG IRQs */ + btwrite(zr->card.jpeg_int | ZR36057_ICR_JPEG_REP_IRQ | ZR36057_ICR_INT_PIN_EN, + ZR36057_ICR); + + set_frame(zr, 0); // \FRAME + + /* set the JPEG codec guest ID */ + reg = (zr->card.gpcs[1] << ZR36057_JCGI_JPE_GUEST_ID) | + (0 << ZR36057_JCGI_JPE_GUEST_REG); + btwrite(reg, ZR36057_JCGI); + + if (zr->card.video_vfe == CODEC_TYPE_ZR36016 && + zr->card.video_codec == CODEC_TYPE_ZR36050) { + /* Enable processing on the ZR36016 */ + if (zr->vfe) + zr36016_write(zr->vfe, 0, 1); + + /* load the address of the GO register in the ZR36050 latch */ + post_office_write(zr, 0, 0, 0); + } + + /* assert Active */ + btor(ZR36057_JPC_ACTIVE, ZR36057_JPC); + + /* enable the Go generation */ + btor(ZR36057_JMC_GO_EN, ZR36057_JMC); + usleep_range(30, 100); + + set_frame(zr, 1); // /FRAME +} + +void zr36057_enable_jpg(struct zoran *zr, enum zoran_codec_mode mode) +{ + struct vfe_settings cap; + int field_size = zr->buffer_size / zr->jpg_settings.field_per_buff; + + zr->codec_mode = mode; + + cap.x = zr->jpg_settings.img_x; + cap.y = zr->jpg_settings.img_y; + cap.width = zr->jpg_settings.img_width; + cap.height = zr->jpg_settings.img_height; + cap.decimation = + zr->jpg_settings.hor_dcm | (zr->jpg_settings.ver_dcm << 8); + cap.quality = zr->jpg_settings.jpg_comp.quality; + + switch (mode) { + case BUZ_MODE_MOTION_COMPRESS: { + struct jpeg_app_marker app; + struct jpeg_com_marker com; + + /* In motion compress mode, the decoder output must be enabled, and + * the video bus direction set to input. + */ + set_videobus_dir(zr, 0); + decoder_call(zr, video, s_stream, 1); + encoder_call(zr, video, s_routing, 0, 0, 0); + + /* Take the JPEG codec and the VFE out of sleep */ + jpeg_codec_sleep(zr, 0); + + /* set JPEG app/com marker */ + app.appn = zr->jpg_settings.jpg_comp.APPn; + app.len = zr->jpg_settings.jpg_comp.APP_len; + memcpy(app.data, zr->jpg_settings.jpg_comp.APP_data, 60); + zr->codec->control(zr->codec, CODEC_S_JPEG_APP_DATA, + sizeof(struct jpeg_app_marker), &app); + + com.len = zr->jpg_settings.jpg_comp.COM_len; + memcpy(com.data, zr->jpg_settings.jpg_comp.COM_data, 60); + zr->codec->control(zr->codec, CODEC_S_JPEG_COM_DATA, + sizeof(struct jpeg_com_marker), &com); + + /* Setup the JPEG codec */ + zr->codec->control(zr->codec, CODEC_S_JPEG_TDS_BYTE, + sizeof(int), &field_size); + zr->codec->set_video(zr->codec, zr->timing, &cap, + &zr->card.vfe_pol); + zr->codec->set_mode(zr->codec, CODEC_DO_COMPRESSION); + + /* Setup the VFE */ + if (zr->vfe) { + zr->vfe->control(zr->vfe, CODEC_S_JPEG_TDS_BYTE, + sizeof(int), &field_size); + zr->vfe->set_video(zr->vfe, zr->timing, &cap, + &zr->card.vfe_pol); + zr->vfe->set_mode(zr->vfe, CODEC_DO_COMPRESSION); + } + + init_jpeg_queue(zr); + zr36057_set_jpg(zr, mode); // \P_Reset, ... Video param, FIFO + + clear_interrupt_counters(zr); + pci_dbg(zr->pci_dev, "enable_jpg(MOTION_COMPRESS)\n"); + break; + } + + case BUZ_MODE_MOTION_DECOMPRESS: + /* In motion decompression mode, the decoder output must be disabled, and + * the video bus direction set to output. + */ + decoder_call(zr, video, s_stream, 0); + set_videobus_dir(zr, 1); + encoder_call(zr, video, s_routing, 1, 0, 0); + + /* Take the JPEG codec and the VFE out of sleep */ + jpeg_codec_sleep(zr, 0); + /* Setup the VFE */ + if (zr->vfe) { + zr->vfe->set_video(zr->vfe, zr->timing, &cap, + &zr->card.vfe_pol); + zr->vfe->set_mode(zr->vfe, CODEC_DO_EXPANSION); + } + /* Setup the JPEG codec */ + zr->codec->set_video(zr->codec, zr->timing, &cap, + &zr->card.vfe_pol); + zr->codec->set_mode(zr->codec, CODEC_DO_EXPANSION); + + init_jpeg_queue(zr); + zr36057_set_jpg(zr, mode); // \P_Reset, ... Video param, FIFO + + clear_interrupt_counters(zr); + pci_dbg(zr->pci_dev, "enable_jpg(MOTION_DECOMPRESS)\n"); + break; + + case BUZ_MODE_IDLE: + default: + /* shut down processing */ + btand(~(zr->card.jpeg_int | ZR36057_ICR_JPEG_REP_IRQ), + ZR36057_ICR); + btwrite(zr->card.jpeg_int | ZR36057_ICR_JPEG_REP_IRQ, + ZR36057_ISR); + btand(~ZR36057_JMC_GO_EN, ZR36057_JMC); // \Go_en + + msleep(50); + + set_videobus_dir(zr, 0); + set_frame(zr, 1); // /FRAME + btor(ZR36057_MCTCR_C_FLUSH, ZR36057_MCTCR); // /CFlush + btwrite(0, ZR36057_JPC); // \P_Reset,\CodTrnsEn,\Active + btand(~ZR36057_JMC_VFIFO_FB, ZR36057_JMC); + btand(~ZR36057_JMC_SYNC_MSTR, ZR36057_JMC); + jpeg_codec_reset(zr); + jpeg_codec_sleep(zr, 1); + zr36057_adjust_vfe(zr, mode); + + decoder_call(zr, video, s_stream, 1); + encoder_call(zr, video, s_routing, 0, 0, 0); + + pci_dbg(zr->pci_dev, "enable_jpg(IDLE)\n"); + break; + } +} + +/* when this is called the spinlock must be held */ +void zoran_feed_stat_com(struct zoran *zr) +{ + /* move frames from pending queue to DMA */ + + int i, max_stat_com; + struct zr_buffer *buf; + struct vb2_v4l2_buffer *vbuf; + dma_addr_t phys_addr = 0; + unsigned long flags; + unsigned long payload; + + max_stat_com = + (zr->jpg_settings.tmp_dcm == + 1) ? BUZ_NUM_STAT_COM : (BUZ_NUM_STAT_COM >> 1); + + spin_lock_irqsave(&zr->queued_bufs_lock, flags); + while ((zr->jpg_dma_head - zr->jpg_dma_tail) < max_stat_com) { + buf = list_first_entry_or_null(&zr->queued_bufs, struct zr_buffer, queue); + if (!buf) { + pci_err(zr->pci_dev, "No buffer available to queue\n"); + spin_unlock_irqrestore(&zr->queued_bufs_lock, flags); + return; + } + list_del(&buf->queue); + zr->buf_in_reserve--; + vbuf = &buf->vbuf; + vbuf->vb2_buf.state = VB2_BUF_STATE_ACTIVE; + phys_addr = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); + payload = vb2_get_plane_payload(&vbuf->vb2_buf, 0); + if (payload == 0) + payload = zr->buffer_size; + if (zr->jpg_settings.tmp_dcm == 1) { + /* fill 1 stat_com entry */ + i = (zr->jpg_dma_head - + zr->jpg_err_shift) & BUZ_MASK_STAT_COM; + if (!(zr->stat_com[i] & cpu_to_le32(1))) + break; + zr->stat_comb[i * 2] = cpu_to_le32(phys_addr); + zr->stat_comb[i * 2 + 1] = cpu_to_le32((payload >> 1) | 1); + zr->inuse[i] = buf; + zr->stat_com[i] = cpu_to_le32(zr->p_scb + i * 2 * 4); + } else { + /* fill 2 stat_com entries */ + i = ((zr->jpg_dma_head - + zr->jpg_err_shift) & 1) * 2; + if (!(zr->stat_com[i] & cpu_to_le32(1))) + break; + zr->stat_com[i] = cpu_to_le32(zr->p_scb + i * 2 * 4); + zr->stat_com[i + 1] = cpu_to_le32(zr->p_scb + i * 2 * 4); + + zr->stat_comb[i * 2] = cpu_to_le32(phys_addr); + zr->stat_comb[i * 2 + 1] = cpu_to_le32((payload >> 1) | 1); + + zr->inuse[i] = buf; + zr->inuse[i + 1] = NULL; + } + zr->jpg_dma_head++; + } + spin_unlock_irqrestore(&zr->queued_bufs_lock, flags); + if (zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS) + zr->jpg_queued_num++; +} + +/* when this is called the spinlock must be held */ +static void zoran_reap_stat_com(struct zoran *zr) +{ + /* move frames from DMA queue to done queue */ + + int i; + u32 stat_com; + unsigned int seq; + unsigned int dif; + unsigned long flags; + struct zr_buffer *buf; + unsigned int size = 0; + u32 fcnt; + + /* + * In motion decompress we don't have a hardware frame counter, + * we just count the interrupts here + */ + + if (zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS) + zr->jpg_seq_num++; + + spin_lock_irqsave(&zr->queued_bufs_lock, flags); + while (zr->jpg_dma_tail < zr->jpg_dma_head) { + if (zr->jpg_settings.tmp_dcm == 1) + i = (zr->jpg_dma_tail - zr->jpg_err_shift) & BUZ_MASK_STAT_COM; + else + i = ((zr->jpg_dma_tail - zr->jpg_err_shift) & 1) * 2; + + stat_com = le32_to_cpu(zr->stat_com[i]); + if ((stat_com & 1) == 0) { + spin_unlock_irqrestore(&zr->queued_bufs_lock, flags); + return; + } + + fcnt = (stat_com & GENMASK(31, 24)) >> 24; + size = (stat_com & GENMASK(22, 1)) >> 1; + + buf = zr->inuse[i]; + if (!buf) { + spin_unlock_irqrestore(&zr->queued_bufs_lock, flags); + pci_err(zr->pci_dev, "No buffer at slot %d\n", i); + return; + } + buf->vbuf.vb2_buf.timestamp = ktime_get_ns(); + + if (zr->codec_mode == BUZ_MODE_MOTION_COMPRESS) { + vb2_set_plane_payload(&buf->vbuf.vb2_buf, 0, size); + + /* update sequence number with the help of the counter in stat_com */ + seq = (fcnt + zr->jpg_err_seq) & 0xff; + dif = (seq - zr->jpg_seq_num) & 0xff; + zr->jpg_seq_num += dif; + } + buf->vbuf.sequence = zr->jpg_settings.tmp_dcm == + 2 ? (zr->jpg_seq_num >> 1) : zr->jpg_seq_num; + zr->inuse[i] = NULL; + if (zr->jpg_settings.tmp_dcm != 1) + buf->vbuf.field = zr->jpg_settings.odd_even ? + V4L2_FIELD_TOP : V4L2_FIELD_BOTTOM; + else + buf->vbuf.field = zr->jpg_settings.odd_even ? + V4L2_FIELD_SEQ_TB : V4L2_FIELD_SEQ_BT; + vb2_buffer_done(&buf->vbuf.vb2_buf, VB2_BUF_STATE_DONE); + + zr->jpg_dma_tail++; + } + spin_unlock_irqrestore(&zr->queued_bufs_lock, flags); +} + +irqreturn_t zoran_irq(int irq, void *dev_id) +{ + struct zoran *zr = dev_id; + u32 stat, astat; + + stat = count_reset_interrupt(zr); + astat = stat & IRQ_MASK; + if (astat & zr->card.vsync_int) { + if (zr->running == ZORAN_MAP_MODE_RAW) { + if ((btread(ZR36057_VSSFGR) & ZR36057_VSSFGR_SNAP_SHOT) == 0) + pci_warn(zr->pci_dev, "BuzIRQ with SnapShot off ???\n"); + if ((btread(ZR36057_VSSFGR) & ZR36057_VSSFGR_FRAME_GRAB) == 0) + zr_set_buf(zr); + return IRQ_HANDLED; + } + if (astat & ZR36057_ISR_JPEG_REP_IRQ) { + if (zr->codec_mode != BUZ_MODE_MOTION_DECOMPRESS && + zr->codec_mode != BUZ_MODE_MOTION_COMPRESS) { + pci_err(zr->pci_dev, "JPG IRQ when not in good mode\n"); + return IRQ_HANDLED; + } + zr->frame_num++; + zoran_reap_stat_com(zr); + zoran_feed_stat_com(zr); + return IRQ_HANDLED; + } + /* unused interrupts */ + } + zr->ghost_int++; + return IRQ_HANDLED; +} + +void zoran_set_pci_master(struct zoran *zr, int set_master) +{ + if (set_master) { + pci_set_master(zr->pci_dev); + } else { + u16 command; + + pci_read_config_word(zr->pci_dev, PCI_COMMAND, &command); + command &= ~PCI_COMMAND_MASTER; + pci_write_config_word(zr->pci_dev, PCI_COMMAND, command); + } +} + +void zoran_init_hardware(struct zoran *zr) +{ + /* Enable bus-mastering */ + zoran_set_pci_master(zr, 1); + + /* Initialize the board */ + if (zr->card.init) + zr->card.init(zr); + + decoder_call(zr, core, init, 0); + decoder_call(zr, video, s_std, zr->norm); + decoder_call(zr, video, s_routing, + zr->card.input[zr->input].muxsel, 0, 0); + + encoder_call(zr, core, init, 0); + encoder_call(zr, video, s_std_output, zr->norm); + encoder_call(zr, video, s_routing, 0, 0, 0); + + /* toggle JPEG codec sleep to sync PLL */ + jpeg_codec_sleep(zr, 1); + jpeg_codec_sleep(zr, 0); + + /* + * set individual interrupt enables (without GIRQ1) + * but don't global enable until zoran_open() + */ + zr36057_init_vfe(zr); + + zr36057_enable_jpg(zr, BUZ_MODE_IDLE); + + btwrite(IRQ_MASK, ZR36057_ISR); // Clears interrupts +} + +void zr36057_restart(struct zoran *zr) +{ + btwrite(0, ZR36057_SPGPPCR); + usleep_range(1000, 2000); + btor(ZR36057_SPGPPCR_SOFT_RESET, ZR36057_SPGPPCR); + usleep_range(1000, 2000); + + /* assert P_Reset */ + btwrite(0, ZR36057_JPC); + /* set up GPIO direction - all output */ + btwrite(ZR36057_SPGPPCR_SOFT_RESET | 0, ZR36057_SPGPPCR); + + /* set up GPIO pins and guest bus timing */ + btwrite((0x81 << 24) | 0x8888, ZR36057_GPPGCR1); +} + diff --git a/drivers/media/pci/zoran/zoran_device.h b/drivers/media/pci/zoran/zoran_device.h new file mode 100644 index 000000000..6d2a52678 --- /dev/null +++ b/drivers/media/pci/zoran/zoran_device.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * This part handles card-specific data and detection + * + * Copyright (C) 2000 Serguei Miridonov + */ + +#ifndef __ZORAN_DEVICE_H__ +#define __ZORAN_DEVICE_H__ + +/* general purpose I/O */ +void GPIO(struct zoran *zr, int bit, unsigned int value); + +/* codec (or actually: guest bus) access */ +int post_office_wait(struct zoran *zr); +int post_office_write(struct zoran *zr, unsigned int guest, unsigned int reg, + unsigned int value); +int post_office_read(struct zoran *zr, unsigned int guest, unsigned int reg); + +void jpeg_codec_sleep(struct zoran *zr, int sleep); +int jpeg_codec_reset(struct zoran *zr); + +/* zr360x7 access to raw capture */ +void zr36057_set_memgrab(struct zoran *zr, int mode); +int wait_grab_pending(struct zoran *zr); + +/* interrupts */ +void print_interrupts(struct zoran *zr); +void clear_interrupt_counters(struct zoran *zr); +irqreturn_t zoran_irq(int irq, void *dev_id); + +/* JPEG codec access */ +void jpeg_start(struct zoran *zr); +void zr36057_enable_jpg(struct zoran *zr, enum zoran_codec_mode mode); +void zoran_feed_stat_com(struct zoran *zr); + +/* general */ +void zoran_set_pci_master(struct zoran *zr, int set_master); +void zoran_init_hardware(struct zoran *zr); +void zr36057_restart(struct zoran *zr); + +extern const struct zoran_format zoran_formats[]; + +extern int pass_through; + +/* i2c */ +#define decoder_call(zr, o, f, args...) \ + v4l2_subdev_call((zr)->decoder, o, f, ##args) +#define encoder_call(zr, o, f, args...) \ + v4l2_subdev_call((zr)->encoder, o, f, ##args) + +#endif /* __ZORAN_DEVICE_H__ */ diff --git a/drivers/media/pci/zoran/zoran_driver.c b/drivers/media/pci/zoran/zoran_driver.c new file mode 100644 index 000000000..5b4d5dd06 --- /dev/null +++ b/drivers/media/pci/zoran/zoran_driver.c @@ -0,0 +1,983 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * Copyright (C) 2000 Serguei Miridonov + * + * Changes for BUZ by Wolfgang Scherr + * + * Changes for DC10/DC30 by Laurent Pinchart + * + * Changes for LML33R10 by Maxim Yevtyushkin + * + * Changes for videodev2/v4l2 by Ronald Bultje + * + * Based on + * + * Miro DC10 driver + * Copyright (C) 1999 Wolfgang Scherr + * + * Iomega Buz driver version 1.0 + * Copyright (C) 1999 Rainer Johanni + * + * buz.0.0.3 + * Copyright (C) 1998 Dave Perks + * + * bttv - Bt848 frame grabber driver + * Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + * & Marcus Metzler (mocm@thp.uni-koeln.de) + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include "videocodec.h" + +#include +#include + +#include +#include "zoran.h" +#include "zoran_device.h" +#include "zoran_card.h" + +const struct zoran_format zoran_formats[] = { + { + .name = "15-bit RGB LE", + .fourcc = V4L2_PIX_FMT_RGB555, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 15, + .flags = ZORAN_FORMAT_CAPTURE, + .vfespfr = ZR36057_VFESPFR_RGB555 | ZR36057_VFESPFR_ERR_DIF | + ZR36057_VFESPFR_LITTLE_ENDIAN, + }, { + .name = "15-bit RGB BE", + .fourcc = V4L2_PIX_FMT_RGB555X, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 15, + .flags = ZORAN_FORMAT_CAPTURE, + .vfespfr = ZR36057_VFESPFR_RGB555 | ZR36057_VFESPFR_ERR_DIF, + }, { + .name = "16-bit RGB LE", + .fourcc = V4L2_PIX_FMT_RGB565, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 16, + .flags = ZORAN_FORMAT_CAPTURE, + .vfespfr = ZR36057_VFESPFR_RGB565 | ZR36057_VFESPFR_ERR_DIF | + ZR36057_VFESPFR_LITTLE_ENDIAN, + }, { + .name = "16-bit RGB BE", + .fourcc = V4L2_PIX_FMT_RGB565X, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 16, + .flags = ZORAN_FORMAT_CAPTURE, + .vfespfr = ZR36057_VFESPFR_RGB565 | ZR36057_VFESPFR_ERR_DIF, + }, { + .name = "24-bit RGB", + .fourcc = V4L2_PIX_FMT_BGR24, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 24, + .flags = ZORAN_FORMAT_CAPTURE, + .vfespfr = ZR36057_VFESPFR_RGB888 | ZR36057_VFESPFR_PACK24, + }, { + .name = "32-bit RGB LE", + .fourcc = V4L2_PIX_FMT_BGR32, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 32, + .flags = ZORAN_FORMAT_CAPTURE, + .vfespfr = ZR36057_VFESPFR_RGB888 | ZR36057_VFESPFR_LITTLE_ENDIAN, + }, { + .name = "32-bit RGB BE", + .fourcc = V4L2_PIX_FMT_RGB32, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 32, + .flags = ZORAN_FORMAT_CAPTURE, + .vfespfr = ZR36057_VFESPFR_RGB888, + }, { + .name = "4:2:2, packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .colorspace = V4L2_COLORSPACE_SMPTE170M, + .depth = 16, + .flags = ZORAN_FORMAT_CAPTURE, + .vfespfr = ZR36057_VFESPFR_YUV422, + }, { + .name = "4:2:2, packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .colorspace = V4L2_COLORSPACE_SMPTE170M, + .depth = 16, + .flags = ZORAN_FORMAT_CAPTURE, + .vfespfr = ZR36057_VFESPFR_YUV422 | ZR36057_VFESPFR_LITTLE_ENDIAN, + }, { + .name = "Hardware-encoded Motion-JPEG", + .fourcc = V4L2_PIX_FMT_MJPEG, + .colorspace = V4L2_COLORSPACE_SMPTE170M, + .depth = 0, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_PLAYBACK | + ZORAN_FORMAT_COMPRESSED, + } +}; + +#define NUM_FORMATS ARRAY_SIZE(zoran_formats) + + /* + * small helper function for calculating buffersizes for v4l2 + * we calculate the nearest higher power-of-two, which + * will be the recommended buffersize + */ +static __u32 zoran_v4l2_calc_bufsize(struct zoran_jpg_settings *settings) +{ + __u8 div = settings->ver_dcm * settings->hor_dcm * settings->tmp_dcm; + __u32 num = (1024 * 512) / (div); + __u32 result = 2; + + num--; + while (num) { + num >>= 1; + result <<= 1; + } + + if (result < 8192) + return 8192; + + return result; +} + +/* + * V4L Buffer grabbing + */ +static int zoran_v4l_set_format(struct zoran *zr, int width, int height, + const struct zoran_format *format) +{ + int bpp; + + /* Check size and format of the grab wanted */ + + if (height < BUZ_MIN_HEIGHT || width < BUZ_MIN_WIDTH || + height > BUZ_MAX_HEIGHT || width > BUZ_MAX_WIDTH) { + pci_dbg(zr->pci_dev, "%s - wrong frame size (%dx%d)\n", __func__, width, height); + return -EINVAL; + } + + bpp = (format->depth + 7) / 8; + + zr->buffer_size = height * width * bpp; + + /* Check against available buffer size */ + if (height * width * bpp > zr->buffer_size) { + pci_dbg(zr->pci_dev, "%s - video buffer size (%d kB) is too small\n", + __func__, zr->buffer_size >> 10); + return -EINVAL; + } + + /* The video front end needs 4-byte alinged line sizes */ + + if ((bpp == 2 && (width & 1)) || (bpp == 3 && (width & 3))) { + pci_dbg(zr->pci_dev, "%s - wrong frame alignment\n", __func__); + return -EINVAL; + } + + zr->v4l_settings.width = width; + zr->v4l_settings.height = height; + zr->v4l_settings.format = format; + zr->v4l_settings.bytesperline = bpp * zr->v4l_settings.width; + + return 0; +} + +static int zoran_set_norm(struct zoran *zr, v4l2_std_id norm) +{ + if (!(norm & zr->card.norms)) { + pci_dbg(zr->pci_dev, "%s - unsupported norm %llx\n", __func__, norm); + return -EINVAL; + } + + if (norm & V4L2_STD_SECAM) + zr->timing = zr->card.tvn[ZR_NORM_SECAM]; + else if (norm & V4L2_STD_NTSC) + zr->timing = zr->card.tvn[ZR_NORM_NTSC]; + else + zr->timing = zr->card.tvn[ZR_NORM_PAL]; + + decoder_call(zr, video, s_std, norm); + encoder_call(zr, video, s_std_output, norm); + + /* Make sure the changes come into effect */ + zr->norm = norm; + + return 0; +} + +static int zoran_set_input(struct zoran *zr, int input) +{ + if (input == zr->input) + return 0; + + if (input < 0 || input >= zr->card.inputs) { + pci_dbg(zr->pci_dev, "%s - unsupported input %d\n", __func__, input); + return -EINVAL; + } + + zr->input = input; + + decoder_call(zr, video, s_routing, zr->card.input[input].muxsel, 0, 0); + + return 0; +} + +/* + * ioctl routine + */ + +static int zoran_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ + struct zoran *zr = video_drvdata(file); + + strscpy(cap->card, ZR_DEVNAME(zr), sizeof(cap->card)); + strscpy(cap->driver, "zoran", sizeof(cap->driver)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s", pci_name(zr->pci_dev)); + return 0; +} + +static int zoran_enum_fmt(struct zoran *zr, struct v4l2_fmtdesc *fmt, int flag) +{ + unsigned int num, i; + + if (fmt->index >= ARRAY_SIZE(zoran_formats)) + return -EINVAL; + if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + for (num = i = 0; i < NUM_FORMATS; i++) { + if (zoran_formats[i].flags & flag && num++ == fmt->index) { + strscpy(fmt->description, zoran_formats[i].name, + sizeof(fmt->description)); + /* fmt struct pre-zeroed, so adding '\0' not needed */ + fmt->pixelformat = zoran_formats[i].fourcc; + if (zoran_formats[i].flags & ZORAN_FORMAT_COMPRESSED) + fmt->flags |= V4L2_FMT_FLAG_COMPRESSED; + return 0; + } + } + return -EINVAL; +} + +static int zoran_enum_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct zoran *zr = video_drvdata(file); + + return zoran_enum_fmt(zr, f, ZORAN_FORMAT_CAPTURE); +} + +static int zoran_g_fmt_vid_out(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct zoran *zr = video_drvdata(file); + + fmt->fmt.pix.width = zr->jpg_settings.img_width / zr->jpg_settings.hor_dcm; + fmt->fmt.pix.height = zr->jpg_settings.img_height * 2 / + (zr->jpg_settings.ver_dcm * zr->jpg_settings.tmp_dcm); + fmt->fmt.pix.sizeimage = zr->buffer_size; + fmt->fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; + if (zr->jpg_settings.tmp_dcm == 1) + fmt->fmt.pix.field = (zr->jpg_settings.odd_even ? + V4L2_FIELD_SEQ_TB : V4L2_FIELD_SEQ_BT); + else + fmt->fmt.pix.field = (zr->jpg_settings.odd_even ? + V4L2_FIELD_TOP : V4L2_FIELD_BOTTOM); + fmt->fmt.pix.bytesperline = 0; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int zoran_g_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct zoran *zr = video_drvdata(file); + + if (zr->map_mode != ZORAN_MAP_MODE_RAW) + return zoran_g_fmt_vid_out(file, fh, fmt); + fmt->fmt.pix.width = zr->v4l_settings.width; + fmt->fmt.pix.height = zr->v4l_settings.height; + fmt->fmt.pix.sizeimage = zr->buffer_size; + fmt->fmt.pix.pixelformat = zr->v4l_settings.format->fourcc; + fmt->fmt.pix.colorspace = zr->v4l_settings.format->colorspace; + fmt->fmt.pix.bytesperline = zr->v4l_settings.bytesperline; + if (BUZ_MAX_HEIGHT < (zr->v4l_settings.height * 2)) + fmt->fmt.pix.field = V4L2_FIELD_INTERLACED; + else + fmt->fmt.pix.field = V4L2_FIELD_TOP; + return 0; +} + +static int zoran_try_fmt_vid_out(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct zoran *zr = video_drvdata(file); + struct zoran_jpg_settings settings; + int res = 0; + + if (fmt->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG) + return -EINVAL; + + settings = zr->jpg_settings; + + /* we actually need to set 'real' parameters now */ + if ((fmt->fmt.pix.height * 2) > BUZ_MAX_HEIGHT) + settings.tmp_dcm = 1; + else + settings.tmp_dcm = 2; + settings.decimation = 0; + if (fmt->fmt.pix.height <= zr->jpg_settings.img_height / 2) + settings.ver_dcm = 2; + else + settings.ver_dcm = 1; + if (fmt->fmt.pix.width <= zr->jpg_settings.img_width / 4) + settings.hor_dcm = 4; + else if (fmt->fmt.pix.width <= zr->jpg_settings.img_width / 2) + settings.hor_dcm = 2; + else + settings.hor_dcm = 1; + if (settings.tmp_dcm == 1) + settings.field_per_buff = 2; + else + settings.field_per_buff = 1; + + if (settings.hor_dcm > 1) { + settings.img_x = (BUZ_MAX_WIDTH == 720) ? 8 : 0; + settings.img_width = (BUZ_MAX_WIDTH == 720) ? 704 : BUZ_MAX_WIDTH; + } else { + settings.img_x = 0; + settings.img_width = BUZ_MAX_WIDTH; + } + + /* check */ + res = zoran_check_jpg_settings(zr, &settings, 1); + if (res) + return res; + + /* tell the user what we actually did */ + fmt->fmt.pix.width = settings.img_width / settings.hor_dcm; + fmt->fmt.pix.height = settings.img_height * 2 / + (settings.tmp_dcm * settings.ver_dcm); + if (settings.tmp_dcm == 1) + fmt->fmt.pix.field = (zr->jpg_settings.odd_even ? + V4L2_FIELD_SEQ_TB : V4L2_FIELD_SEQ_BT); + else + fmt->fmt.pix.field = (zr->jpg_settings.odd_even ? + V4L2_FIELD_TOP : V4L2_FIELD_BOTTOM); + + fmt->fmt.pix.sizeimage = zoran_v4l2_calc_bufsize(&settings); + fmt->fmt.pix.bytesperline = 0; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + return res; +} + +static int zoran_try_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct zoran *zr = video_drvdata(file); + int bpp; + int i; + + if (fmt->fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) + return zoran_try_fmt_vid_out(file, fh, fmt); + + for (i = 0; i < NUM_FORMATS; i++) + if (zoran_formats[i].fourcc == fmt->fmt.pix.pixelformat) + break; + + if (i == NUM_FORMATS) { + /* TODO do not return here to fix the TRY_FMT cannot handle an invalid pixelformat*/ + return -EINVAL; + } + + fmt->fmt.pix.pixelformat = zoran_formats[i].fourcc; + fmt->fmt.pix.colorspace = zoran_formats[i].colorspace; + if (BUZ_MAX_HEIGHT < (fmt->fmt.pix.height * 2)) + fmt->fmt.pix.field = V4L2_FIELD_INTERLACED; + else + fmt->fmt.pix.field = V4L2_FIELD_TOP; + + bpp = DIV_ROUND_UP(zoran_formats[i].depth, 8); + v4l_bound_align_image(&fmt->fmt.pix.width, BUZ_MIN_WIDTH, BUZ_MAX_WIDTH, + bpp == 2 ? 1 : 2, + &fmt->fmt.pix.height, BUZ_MIN_HEIGHT, BUZ_MAX_HEIGHT, + 0, 0); + fmt->fmt.pix.bytesperline = fmt->fmt.pix.width * bpp; + fmt->fmt.pix.sizeimage = fmt->fmt.pix.bytesperline * fmt->fmt.pix.height; + return 0; +} + +static int zoran_s_fmt_vid_out(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct zoran *zr = video_drvdata(file); + __le32 printformat = __cpu_to_le32(fmt->fmt.pix.pixelformat); + struct zoran_jpg_settings settings; + int res = 0; + + pci_dbg(zr->pci_dev, "size=%dx%d, fmt=0x%x (%4.4s)\n", + fmt->fmt.pix.width, fmt->fmt.pix.height, + fmt->fmt.pix.pixelformat, + (char *)&printformat); + if (fmt->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG) + return -EINVAL; + + if (!fmt->fmt.pix.height || !fmt->fmt.pix.width) + return -EINVAL; + + settings = zr->jpg_settings; + + /* we actually need to set 'real' parameters now */ + if (fmt->fmt.pix.height * 2 > BUZ_MAX_HEIGHT) + settings.tmp_dcm = 1; + else + settings.tmp_dcm = 2; + settings.decimation = 0; + if (fmt->fmt.pix.height <= zr->jpg_settings.img_height / 2) + settings.ver_dcm = 2; + else + settings.ver_dcm = 1; + if (fmt->fmt.pix.width <= zr->jpg_settings.img_width / 4) + settings.hor_dcm = 4; + else if (fmt->fmt.pix.width <= zr->jpg_settings.img_width / 2) + settings.hor_dcm = 2; + else + settings.hor_dcm = 1; + if (settings.tmp_dcm == 1) + settings.field_per_buff = 2; + else + settings.field_per_buff = 1; + + if (settings.hor_dcm > 1) { + settings.img_x = (BUZ_MAX_WIDTH == 720) ? 8 : 0; + settings.img_width = (BUZ_MAX_WIDTH == 720) ? 704 : BUZ_MAX_WIDTH; + } else { + settings.img_x = 0; + settings.img_width = BUZ_MAX_WIDTH; + } + + /* check */ + res = zoran_check_jpg_settings(zr, &settings, 0); + if (res) + return res; + + /* it's ok, so set them */ + zr->jpg_settings = settings; + + if (fmt->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + zr->map_mode = ZORAN_MAP_MODE_JPG_REC; + else + zr->map_mode = ZORAN_MAP_MODE_JPG_PLAY; + + zr->buffer_size = zoran_v4l2_calc_bufsize(&zr->jpg_settings); + + /* tell the user what we actually did */ + fmt->fmt.pix.width = settings.img_width / settings.hor_dcm; + fmt->fmt.pix.height = settings.img_height * 2 / + (settings.tmp_dcm * settings.ver_dcm); + if (settings.tmp_dcm == 1) + fmt->fmt.pix.field = (zr->jpg_settings.odd_even ? + V4L2_FIELD_SEQ_TB : V4L2_FIELD_SEQ_BT); + else + fmt->fmt.pix.field = (zr->jpg_settings.odd_even ? + V4L2_FIELD_TOP : V4L2_FIELD_BOTTOM); + fmt->fmt.pix.bytesperline = 0; + fmt->fmt.pix.sizeimage = zr->buffer_size; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + return res; +} + +static int zoran_s_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct zoran *zr = video_drvdata(file); + int i; + int res = 0; + + if (fmt->fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) + return zoran_s_fmt_vid_out(file, fh, fmt); + + for (i = 0; i < NUM_FORMATS; i++) + if (fmt->fmt.pix.pixelformat == zoran_formats[i].fourcc) + break; + if (i == NUM_FORMATS) { + pci_dbg(zr->pci_dev, "VIDIOC_S_FMT - unknown/unsupported format 0x%x\n", + fmt->fmt.pix.pixelformat); + /* TODO do not return here to fix the TRY_FMT cannot handle an invalid pixelformat*/ + return -EINVAL; + } + + fmt->fmt.pix.pixelformat = zoran_formats[i].fourcc; + if (fmt->fmt.pix.height > BUZ_MAX_HEIGHT) + fmt->fmt.pix.height = BUZ_MAX_HEIGHT; + if (fmt->fmt.pix.width > BUZ_MAX_WIDTH) + fmt->fmt.pix.width = BUZ_MAX_WIDTH; + if (fmt->fmt.pix.height < BUZ_MIN_HEIGHT) + fmt->fmt.pix.height = BUZ_MIN_HEIGHT; + if (fmt->fmt.pix.width < BUZ_MIN_WIDTH) + fmt->fmt.pix.width = BUZ_MIN_WIDTH; + + zr->map_mode = ZORAN_MAP_MODE_RAW; + + res = zoran_v4l_set_format(zr, fmt->fmt.pix.width, fmt->fmt.pix.height, + &zoran_formats[i]); + if (res) + return res; + + /* tell the user the results/missing stuff */ + fmt->fmt.pix.bytesperline = zr->v4l_settings.bytesperline; + fmt->fmt.pix.sizeimage = zr->buffer_size; + fmt->fmt.pix.colorspace = zr->v4l_settings.format->colorspace; + if (BUZ_MAX_HEIGHT < (zr->v4l_settings.height * 2)) + fmt->fmt.pix.field = V4L2_FIELD_INTERLACED; + else + fmt->fmt.pix.field = V4L2_FIELD_TOP; + return res; +} + +static int zoran_g_std(struct file *file, void *fh, v4l2_std_id *std) +{ + struct zoran *zr = video_drvdata(file); + + *std = zr->norm; + return 0; +} + +static int zoran_s_std(struct file *file, void *fh, v4l2_std_id std) +{ + struct zoran *zr = video_drvdata(file); + int res = 0; + + if (zr->norm == std) + return 0; + + if (zr->running != ZORAN_MAP_MODE_NONE) + return -EBUSY; + + res = zoran_set_norm(zr, std); + return res; +} + +static int zoran_enum_input(struct file *file, void *fh, + struct v4l2_input *inp) +{ + struct zoran *zr = video_drvdata(file); + + if (inp->index >= zr->card.inputs) + return -EINVAL; + + strscpy(inp->name, zr->card.input[inp->index].name, sizeof(inp->name)); + inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->std = V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM; + + /* Get status of video decoder */ + decoder_call(zr, video, g_input_status, &inp->status); + return 0; +} + +static int zoran_g_input(struct file *file, void *fh, unsigned int *input) +{ + struct zoran *zr = video_drvdata(file); + + *input = zr->input; + + return 0; +} + +static int zoran_s_input(struct file *file, void *fh, unsigned int input) +{ + struct zoran *zr = video_drvdata(file); + int res; + + if (zr->running != ZORAN_MAP_MODE_NONE) + return -EBUSY; + + res = zoran_set_input(zr, input); + return res; +} + +/* cropping (sub-frame capture) */ +static int zoran_g_selection(struct file *file, void *fh, struct v4l2_selection *sel) +{ + struct zoran *zr = video_drvdata(file); + + if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && + sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pci_dbg(zr->pci_dev, "%s invalid selection type combination\n", __func__); + return -EINVAL; + } + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r.top = zr->jpg_settings.img_y; + sel->r.left = zr->jpg_settings.img_x; + sel->r.width = zr->jpg_settings.img_width; + sel->r.height = zr->jpg_settings.img_height; + break; + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = BUZ_MIN_WIDTH; + sel->r.height = BUZ_MIN_HEIGHT; + break; + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = BUZ_MAX_WIDTH; + sel->r.height = BUZ_MAX_HEIGHT; + break; + default: + return -EINVAL; + } + return 0; +} + +static int zoran_s_selection(struct file *file, void *fh, struct v4l2_selection *sel) +{ + struct zoran *zr = video_drvdata(file); + struct zoran_jpg_settings settings; + int res; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && + sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (!sel->r.width || !sel->r.height) + return -EINVAL; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + if (zr->map_mode == ZORAN_MAP_MODE_RAW) { + pci_dbg(zr->pci_dev, "VIDIOC_S_SELECTION - subcapture only supported for compressed capture\n"); + return -EINVAL; + } + + settings = zr->jpg_settings; + + /* move into a form that we understand */ + settings.img_x = sel->r.left; + settings.img_y = sel->r.top; + settings.img_width = sel->r.width; + settings.img_height = sel->r.height; + + /* check validity */ + res = zoran_check_jpg_settings(zr, &settings, 0); + if (res) + return res; + + /* accept */ + zr->jpg_settings = settings; + return res; +} + +/* + * Output is disabled temporarily + * Zoran is picky about jpeg data it accepts. At least it seems to unsupport COM and APPn. + * So until a way to filter data will be done, disable output. + */ +static const struct v4l2_ioctl_ops zoran_ioctl_ops = { + .vidioc_querycap = zoran_querycap, + .vidioc_s_selection = zoran_s_selection, + .vidioc_g_selection = zoran_g_selection, + .vidioc_enum_input = zoran_enum_input, + .vidioc_g_input = zoran_g_input, + .vidioc_s_input = zoran_s_input, + .vidioc_g_std = zoran_g_std, + .vidioc_s_std = zoran_s_std, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_enum_fmt_vid_cap = zoran_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = zoran_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = zoran_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = zoran_try_fmt_vid_cap, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct v4l2_file_operations zoran_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + +const struct video_device zoran_template = { + .name = ZORAN_NAME, + .fops = &zoran_fops, + .ioctl_ops = &zoran_ioctl_ops, + .release = &zoran_vdev_release, + .tvnorms = V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM, +}; + +static int zr_vb2_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct zoran *zr = vb2_get_drv_priv(vq); + unsigned int size = zr->buffer_size; + + pci_dbg(zr->pci_dev, "%s nbuf=%u nplanes=%u", __func__, *nbuffers, *nplanes); + + zr->buf_in_reserve = 0; + + if (*nbuffers < vq->min_queued_buffers) + *nbuffers = vq->min_queued_buffers; + + if (*nplanes) { + if (sizes[0] < size) + return -EINVAL; + else + return 0; + } + + *nplanes = 1; + sizes[0] = size; + + return 0; +} + +static void zr_vb2_queue(struct vb2_buffer *vb) +{ + struct zoran *zr = vb2_get_drv_priv(vb->vb2_queue); + struct zr_buffer *buf = vb2_to_zr_buffer(vb); + unsigned long flags; + + spin_lock_irqsave(&zr->queued_bufs_lock, flags); + list_add_tail(&buf->queue, &zr->queued_bufs); + zr->buf_in_reserve++; + spin_unlock_irqrestore(&zr->queued_bufs_lock, flags); + if (zr->running == ZORAN_MAP_MODE_JPG_REC) + zoran_feed_stat_com(zr); + zr->queued++; +} + +static int zr_vb2_prepare(struct vb2_buffer *vb) +{ + struct zoran *zr = vb2_get_drv_priv(vb->vb2_queue); + + if (vb2_plane_size(vb, 0) < zr->buffer_size) + return -EINVAL; + zr->prepared++; + + return 0; +} + +int zr_set_buf(struct zoran *zr) +{ + struct zr_buffer *buf; + struct vb2_v4l2_buffer *vbuf; + dma_addr_t phys_addr; + unsigned long flags; + u32 reg; + + if (zr->running == ZORAN_MAP_MODE_NONE) + return 0; + + if (zr->inuse[0]) { + buf = zr->inuse[0]; + buf->vbuf.vb2_buf.timestamp = ktime_get_ns(); + buf->vbuf.sequence = zr->vbseq++; + vbuf = &buf->vbuf; + + buf->vbuf.field = V4L2_FIELD_INTERLACED; + if (BUZ_MAX_HEIGHT < (zr->v4l_settings.height * 2)) + buf->vbuf.field = V4L2_FIELD_INTERLACED; + else + buf->vbuf.field = V4L2_FIELD_TOP; + vb2_set_plane_payload(&buf->vbuf.vb2_buf, 0, zr->buffer_size); + vb2_buffer_done(&buf->vbuf.vb2_buf, VB2_BUF_STATE_DONE); + zr->inuse[0] = NULL; + } + + spin_lock_irqsave(&zr->queued_bufs_lock, flags); + if (list_empty(&zr->queued_bufs)) { + btand(~ZR36057_ICR_INT_PIN_EN, ZR36057_ICR); + vb2_queue_error(zr->video_dev->queue); + spin_unlock_irqrestore(&zr->queued_bufs_lock, flags); + return -EINVAL; + } + buf = list_first_entry_or_null(&zr->queued_bufs, struct zr_buffer, queue); + if (!buf) { + btand(~ZR36057_ICR_INT_PIN_EN, ZR36057_ICR); + vb2_queue_error(zr->video_dev->queue); + spin_unlock_irqrestore(&zr->queued_bufs_lock, flags); + return -EINVAL; + } + list_del(&buf->queue); + zr->buf_in_reserve--; + spin_unlock_irqrestore(&zr->queued_bufs_lock, flags); + + vbuf = &buf->vbuf; + vbuf->vb2_buf.state = VB2_BUF_STATE_ACTIVE; + phys_addr = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); + + if (!phys_addr) + return -EINVAL; + + zr->inuse[0] = buf; + + reg = phys_addr; + btwrite(reg, ZR36057_VDTR); + if (zr->v4l_settings.height > BUZ_MAX_HEIGHT / 2) + reg += zr->v4l_settings.bytesperline; + btwrite(reg, ZR36057_VDBR); + + reg = 0; + if (zr->v4l_settings.height > BUZ_MAX_HEIGHT / 2) + reg += zr->v4l_settings.bytesperline; + reg = (reg << ZR36057_VSSFGR_DISP_STRIDE); + reg |= ZR36057_VSSFGR_VID_OVF; + reg |= ZR36057_VSSFGR_SNAP_SHOT; + reg |= ZR36057_VSSFGR_FRAME_GRAB; + btwrite(reg, ZR36057_VSSFGR); + + btor(ZR36057_VDCR_VID_EN, ZR36057_VDCR); + return 0; +} + +static int zr_vb2_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct zoran *zr = vq->drv_priv; + int j; + + for (j = 0; j < BUZ_NUM_STAT_COM; j++) { + zr->stat_com[j] = cpu_to_le32(1); + zr->inuse[j] = NULL; + } + zr->vbseq = 0; + + if (zr->map_mode != ZORAN_MAP_MODE_RAW) { + pci_dbg(zr->pci_dev, "START JPG\n"); + zr36057_restart(zr); + zoran_init_hardware(zr); + if (zr->map_mode == ZORAN_MAP_MODE_JPG_REC) + zr36057_enable_jpg(zr, BUZ_MODE_MOTION_DECOMPRESS); + else + zr36057_enable_jpg(zr, BUZ_MODE_MOTION_COMPRESS); + zoran_feed_stat_com(zr); + jpeg_start(zr); + zr->running = zr->map_mode; + btor(ZR36057_ICR_INT_PIN_EN, ZR36057_ICR); + return 0; + } + + pci_dbg(zr->pci_dev, "START RAW\n"); + zr36057_restart(zr); + zoran_init_hardware(zr); + + zr36057_enable_jpg(zr, BUZ_MODE_IDLE); + zr36057_set_memgrab(zr, 1); + zr->running = zr->map_mode; + btor(ZR36057_ICR_INT_PIN_EN, ZR36057_ICR); + return 0; +} + +static void zr_vb2_stop_streaming(struct vb2_queue *vq) +{ + struct zoran *zr = vq->drv_priv; + struct zr_buffer *buf; + unsigned long flags; + int j; + + btand(~ZR36057_ICR_INT_PIN_EN, ZR36057_ICR); + if (zr->map_mode != ZORAN_MAP_MODE_RAW) + zr36057_enable_jpg(zr, BUZ_MODE_IDLE); + zr36057_set_memgrab(zr, 0); + zr->running = ZORAN_MAP_MODE_NONE; + + zoran_set_pci_master(zr, 0); + + if (!pass_through) { /* Switch to color bar */ + decoder_call(zr, video, s_stream, 0); + encoder_call(zr, video, s_routing, 2, 0, 0); + } + + for (j = 0; j < BUZ_NUM_STAT_COM; j++) { + zr->stat_com[j] = cpu_to_le32(1); + if (!zr->inuse[j]) + continue; + buf = zr->inuse[j]; + pci_dbg(zr->pci_dev, "%s clean buf %d\n", __func__, j); + vb2_buffer_done(&buf->vbuf.vb2_buf, VB2_BUF_STATE_ERROR); + zr->inuse[j] = NULL; + } + + spin_lock_irqsave(&zr->queued_bufs_lock, flags); + while (!list_empty(&zr->queued_bufs)) { + buf = list_entry(zr->queued_bufs.next, struct zr_buffer, queue); + list_del(&buf->queue); + vb2_buffer_done(&buf->vbuf.vb2_buf, VB2_BUF_STATE_ERROR); + zr->buf_in_reserve--; + } + spin_unlock_irqrestore(&zr->queued_bufs_lock, flags); + if (zr->buf_in_reserve) + pci_dbg(zr->pci_dev, "Buffer remaining %d\n", zr->buf_in_reserve); + zr->map_mode = ZORAN_MAP_MODE_RAW; +} + +static const struct vb2_ops zr_video_qops = { + .queue_setup = zr_vb2_queue_setup, + .buf_queue = zr_vb2_queue, + .buf_prepare = zr_vb2_prepare, + .start_streaming = zr_vb2_start_streaming, + .stop_streaming = zr_vb2_stop_streaming, +}; + +int zoran_queue_init(struct zoran *zr, struct vb2_queue *vq, int dir) +{ + int err; + + spin_lock_init(&zr->queued_bufs_lock); + INIT_LIST_HEAD(&zr->queued_bufs); + + vq->dev = &zr->pci_dev->dev; + vq->type = dir; + + vq->io_modes = VB2_DMABUF | VB2_MMAP; + vq->drv_priv = zr; + vq->buf_struct_size = sizeof(struct zr_buffer); + vq->ops = &zr_video_qops; + vq->mem_ops = &vb2_dma_contig_memops; + vq->gfp_flags = GFP_DMA32; + vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vq->min_queued_buffers = 9; + vq->lock = &zr->lock; + err = vb2_queue_init(vq); + if (err) + return err; + zr->video_dev->queue = vq; + return 0; +} + +void zoran_queue_exit(struct zoran *zr) +{ + vb2_queue_release(zr->video_dev->queue); +} diff --git a/drivers/media/pci/zoran/zr36016.c b/drivers/media/pci/zoran/zr36016.c new file mode 100644 index 000000000..bb2d92743 --- /dev/null +++ b/drivers/media/pci/zoran/zr36016.c @@ -0,0 +1,406 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zoran ZR36016 basic configuration functions + * + * Copyright (C) 2001 Wolfgang Scherr + */ + +#include +#include +#include + +/* headerfile of this module */ +#include "zr36016.h" + +/* codec io API */ +#include "videocodec.h" + +/* + * it doesn't make sense to have more than 20 or so, + * just to prevent some unwanted loops + */ +#define MAX_CODECS 20 + +/* amount of chips attached via this driver */ +static int zr36016_codecs; + +/* + * Local hardware I/O functions: read/write via codec layer + * (registers are located in the master device) + */ + +/* read and write functions */ +static u8 zr36016_read(struct zr36016 *ptr, u16 reg) +{ + u8 value = 0; + struct zoran *zr = videocodec_to_zoran(ptr->codec); + + /* just in case something is wrong... */ + if (ptr->codec->master_data->readreg) + value = (ptr->codec->master_data->readreg(ptr->codec, reg)) & 0xFF; + else + zrdev_err(zr, "%s: invalid I/O setup, nothing read!\n", ptr->name); + + zrdev_dbg(zr, "%s: reading from 0x%04x: %02x\n", ptr->name, reg, value); + + return value; +} + +static void zr36016_write(struct zr36016 *ptr, u16 reg, u8 value) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + + zrdev_dbg(zr, "%s: writing 0x%02x to 0x%04x\n", ptr->name, value, reg); + + // just in case something is wrong... + if (ptr->codec->master_data->writereg) + ptr->codec->master_data->writereg(ptr->codec, reg, value); + else + zrdev_err(zr, "%s: invalid I/O setup, nothing written!\n", ptr->name); +} + +/* + * indirect read and write functions + * + * the 016 supports auto-addr-increment, but + * writing it all time cost not much and is safer... + */ +static u8 zr36016_readi(struct zr36016 *ptr, u16 reg) +{ + u8 value = 0; + struct zoran *zr = videocodec_to_zoran(ptr->codec); + + /* just in case something is wrong... */ + if ((ptr->codec->master_data->writereg) && (ptr->codec->master_data->readreg)) { + ptr->codec->master_data->writereg(ptr->codec, ZR016_IADDR, reg & 0x0F); + value = (ptr->codec->master_data->readreg(ptr->codec, ZR016_IDATA)) & 0xFF; + } else { + zrdev_err(zr, "%s: invalid I/O setup, nothing read (i)!\n", ptr->name); + } + + zrdev_dbg(zr, "%s: reading indirect from 0x%04x: %02x\n", + ptr->name, reg, value); + return value; +} + +static void zr36016_writei(struct zr36016 *ptr, u16 reg, u8 value) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + + zrdev_dbg(zr, "%s: writing indirect 0x%02x to 0x%04x\n", ptr->name, + value, reg); + + /* just in case something is wrong... */ + if (ptr->codec->master_data->writereg) { + ptr->codec->master_data->writereg(ptr->codec, ZR016_IADDR, reg & 0x0F); + ptr->codec->master_data->writereg(ptr->codec, ZR016_IDATA, value & 0x0FF); + } else { + zrdev_err(zr, "%s: invalid I/O setup, nothing written (i)!\n", ptr->name); + } +} + +/* Local helper function: version read */ + +/* version kept in datastructure */ +static u8 zr36016_read_version(struct zr36016 *ptr) +{ + ptr->version = zr36016_read(ptr, 0) >> 4; + return ptr->version; +} + +/* + * Local helper function: basic test of "connectivity", writes/reads + * to/from PAX-Lo register + */ + +static int zr36016_basic_test(struct zr36016 *ptr) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + + if (*KERN_INFO <= CONSOLE_LOGLEVEL_DEFAULT) { + int i; + + zr36016_writei(ptr, ZR016I_PAX_LO, 0x55); + zrdev_dbg(zr, "%s: registers: ", ptr->name); + for (i = 0; i <= 0x0b; i++) + zrdev_dbg(zr, "%02x ", zr36016_readi(ptr, i)); + zrdev_dbg(zr, "\n"); + } + // for testing just write 0, then the default value to a register and read + // it back in both cases + zr36016_writei(ptr, ZR016I_PAX_LO, 0x00); + if (zr36016_readi(ptr, ZR016I_PAX_LO) != 0x0) { + zrdev_err(zr, "%s: attach failed, can't connect to vfe processor!\n", ptr->name); + return -ENXIO; + } + zr36016_writei(ptr, ZR016I_PAX_LO, 0x0d0); + if (zr36016_readi(ptr, ZR016I_PAX_LO) != 0x0d0) { + zrdev_err(zr, "%s: attach failed, can't connect to vfe processor!\n", ptr->name); + return -ENXIO; + } + // we allow version numbers from 0-3, should be enough, though + zr36016_read_version(ptr); + if (ptr->version & 0x0c) { + zrdev_err(zr, "%s: attach failed, suspicious version %d found...\n", ptr->name, + ptr->version); + return -ENXIO; + } + + return 0; /* looks good! */ +} + +/* Basic datasets & init */ + +static void zr36016_init(struct zr36016 *ptr) +{ + // stop any processing + zr36016_write(ptr, ZR016_GOSTOP, 0); + + // mode setup (yuv422 in and out, compression/expansuon due to mode) + zr36016_write(ptr, ZR016_MODE, + ZR016_YUV422 | ZR016_YUV422_YUV422 | + (ptr->mode == CODEC_DO_COMPRESSION ? + ZR016_COMPRESSION : ZR016_EXPANSION)); + + // misc setup + zr36016_writei(ptr, ZR016I_SETUP1, + (ptr->xdec ? (ZR016_HRFL | ZR016_HORZ) : 0) | + (ptr->ydec ? ZR016_VERT : 0) | ZR016_CNTI); + zr36016_writei(ptr, ZR016I_SETUP2, ZR016_CCIR); + + // Window setup + // (no extra offset for now, norm defines offset, default width height) + zr36016_writei(ptr, ZR016I_PAX_HI, ptr->width >> 8); + zr36016_writei(ptr, ZR016I_PAX_LO, ptr->width & 0xFF); + zr36016_writei(ptr, ZR016I_PAY_HI, ptr->height >> 8); + zr36016_writei(ptr, ZR016I_PAY_LO, ptr->height & 0xFF); + zr36016_writei(ptr, ZR016I_NAX_HI, ptr->xoff >> 8); + zr36016_writei(ptr, ZR016I_NAX_LO, ptr->xoff & 0xFF); + zr36016_writei(ptr, ZR016I_NAY_HI, ptr->yoff >> 8); + zr36016_writei(ptr, ZR016I_NAY_LO, ptr->yoff & 0xFF); + + /* shall we continue now, please? */ + zr36016_write(ptr, ZR016_GOSTOP, 1); +} + +/* + * CODEC API FUNCTIONS + * + * These functions are accessed by the master via the API structure + */ + +/* + * set compression/expansion mode and launches codec - + * this should be the last call from the master before starting processing + */ +static int zr36016_set_mode(struct videocodec *codec, int mode) +{ + struct zr36016 *ptr = (struct zr36016 *)codec->data; + struct zoran *zr = videocodec_to_zoran(codec); + + zrdev_dbg(zr, "%s: set_mode %d call\n", ptr->name, mode); + + if ((mode != CODEC_DO_EXPANSION) && (mode != CODEC_DO_COMPRESSION)) + return -EINVAL; + + ptr->mode = mode; + zr36016_init(ptr); + + return 0; +} + +/* set picture size */ +static int zr36016_set_video(struct videocodec *codec, const struct tvnorm *norm, + struct vfe_settings *cap, struct vfe_polarity *pol) +{ + struct zr36016 *ptr = (struct zr36016 *)codec->data; + struct zoran *zr = videocodec_to_zoran(codec); + + zrdev_dbg(zr, "%s: set_video %d.%d, (%u,%u)/%ux%u (0x%x) call\n", + ptr->name, norm->h_start, norm->v_start, + cap->x, cap->y, cap->width, cap->height, + cap->decimation); + + /* + * if () return -EINVAL; + * trust the master driver that it knows what it does - so + * we allow invalid startx/y for now ... + */ + ptr->width = cap->width; + ptr->height = cap->height; + /* + * (Ronald) This is ugly. zoran_device.c, line 387 + * already mentions what happens if h_start is even + * (blue faces, etc., cr/cb inversed). There's probably + * some good reason why h_start is 0 instead of 1, so I'm + * leaving it to this for now, but really... This can be + * done a lot simpler + */ + ptr->xoff = (norm->h_start ? norm->h_start : 1) + cap->x; + /* + * Something to note here (I don't understand it), setting + * v_start too high will cause the codec to 'not work'. I + * really don't get it. values of 16 (v_start) already break + * it here. Just '0' seems to work. More testing needed! + */ + ptr->yoff = norm->v_start + cap->y; + /* (Ronald) dzjeeh, can't this thing do hor_decimation = 4? */ + ptr->xdec = ((cap->decimation & 0xff) == 1) ? 0 : 1; + ptr->ydec = (((cap->decimation >> 8) & 0xff) == 1) ? 0 : 1; + + return 0; +} + +/* additional control functions */ +static int zr36016_control(struct videocodec *codec, int type, int size, void *data) +{ + struct zr36016 *ptr = (struct zr36016 *)codec->data; + struct zoran *zr = videocodec_to_zoran(codec); + int *ival = (int *)data; + + zrdev_dbg(zr, "%s: control %d call with %d byte\n", + ptr->name, type, size); + + switch (type) { + case CODEC_G_STATUS: /* get last status - we don't know it ... */ + if (size != sizeof(int)) + return -EFAULT; + *ival = 0; + break; + + case CODEC_G_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + *ival = 0; + break; + + case CODEC_S_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + if (*ival != 0) + return -EINVAL; + /* not needed, do nothing */ + return 0; + + case CODEC_G_VFE: + case CODEC_S_VFE: + return 0; + + case CODEC_S_MMAP: + /* not available, give an error */ + return -ENXIO; + + default: + return -EINVAL; + } + + return size; +} + +/* + * Exit and unregister function: + * + * Deinitializes Zoran's JPEG processor + */ + +static int zr36016_unset(struct videocodec *codec) +{ + struct zr36016 *ptr = codec->data; + struct zoran *zr = videocodec_to_zoran(codec); + + if (ptr) { + /* do wee need some codec deinit here, too ???? */ + + zrdev_dbg(zr, "%s: finished codec #%d\n", ptr->name, ptr->num); + kfree(ptr); + codec->data = NULL; + + zr36016_codecs--; + return 0; + } + + return -EFAULT; +} + +/* + * Setup and registry function: + * + * Initializes Zoran's JPEG processor + * + * Also sets pixel size, average code size, mode (compr./decompr.) + * (the given size is determined by the processor with the video interface) + */ + +static int zr36016_setup(struct videocodec *codec) +{ + struct zr36016 *ptr; + struct zoran *zr = videocodec_to_zoran(codec); + int res; + + zrdev_dbg(zr, "zr36016: initializing VFE subsystem #%d.\n", zr36016_codecs); + + if (zr36016_codecs == MAX_CODECS) { + zrdev_err(zr, "zr36016: Can't attach more codecs!\n"); + return -ENOSPC; + } + //mem structure init + ptr = kzalloc_obj(*ptr); + codec->data = ptr; + if (!ptr) + return -ENOMEM; + + snprintf(ptr->name, sizeof(ptr->name), "zr36016[%d]", zr36016_codecs); + ptr->num = zr36016_codecs++; + ptr->codec = codec; + + //testing + res = zr36016_basic_test(ptr); + if (res < 0) { + zr36016_unset(codec); + return res; + } + //final setup + ptr->mode = CODEC_DO_COMPRESSION; + ptr->width = 768; + ptr->height = 288; + ptr->xdec = 1; + ptr->ydec = 0; + zr36016_init(ptr); + + zrdev_dbg(zr, "%s: codec v%d attached and running\n", + ptr->name, ptr->version); + + return 0; +} + +static const struct videocodec zr36016_codec = { + .name = "zr36016", + .magic = 0L, /* magic not used */ + .flags = + CODEC_FLAG_HARDWARE | CODEC_FLAG_VFE | CODEC_FLAG_ENCODER | + CODEC_FLAG_DECODER, + .type = CODEC_TYPE_ZR36016, + .setup = zr36016_setup, /* functionality */ + .unset = zr36016_unset, + .set_mode = zr36016_set_mode, + .set_video = zr36016_set_video, + .control = zr36016_control, + /* others are not used */ +}; + +/* HOOK IN DRIVER AS KERNEL MODULE */ + +int zr36016_init_module(void) +{ + zr36016_codecs = 0; + return videocodec_register(&zr36016_codec); +} + +void zr36016_cleanup_module(void) +{ + if (zr36016_codecs) { + pr_debug("zr36016: something's wrong - %d codecs left somehow.\n", + zr36016_codecs); + } + videocodec_unregister(&zr36016_codec); +} diff --git a/drivers/media/pci/zoran/zr36016.h b/drivers/media/pci/zoran/zr36016.h new file mode 100644 index 000000000..04afba356 --- /dev/null +++ b/drivers/media/pci/zoran/zr36016.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Zoran ZR36016 basic configuration functions - header file + * + * Copyright (C) 2001 Wolfgang Scherr + */ + +#ifndef ZR36016_H +#define ZR36016_H + +/* data stored for each zoran jpeg codec chip */ +struct zr36016 { + char name[32]; + int num; + /* io datastructure */ + struct videocodec *codec; + // coder status + __u8 version; + // actual coder setup + int mode; + + __u16 xoff; + __u16 yoff; + __u16 width; + __u16 height; + __u16 xdec; + __u16 ydec; +}; + +/* direct register addresses */ +#define ZR016_GOSTOP 0x00 +#define ZR016_MODE 0x01 +#define ZR016_IADDR 0x02 +#define ZR016_IDATA 0x03 + +/* indirect register addresses */ +#define ZR016I_SETUP1 0x00 +#define ZR016I_SETUP2 0x01 +#define ZR016I_NAX_LO 0x02 +#define ZR016I_NAX_HI 0x03 +#define ZR016I_PAX_LO 0x04 +#define ZR016I_PAX_HI 0x05 +#define ZR016I_NAY_LO 0x06 +#define ZR016I_NAY_HI 0x07 +#define ZR016I_PAY_LO 0x08 +#define ZR016I_PAY_HI 0x09 +#define ZR016I_NOL_LO 0x0a +#define ZR016I_NOL_HI 0x0b + +/* possible values for mode register */ +#define ZR016_RGB444_YUV444 0x00 +#define ZR016_RGB444_YUV422 0x01 +#define ZR016_RGB444_YUV411 0x02 +#define ZR016_RGB444_Y400 0x03 +#define ZR016_RGB444_RGB444 0x04 +#define ZR016_YUV444_YUV444 0x08 +#define ZR016_YUV444_YUV422 0x09 +#define ZR016_YUV444_YUV411 0x0a +#define ZR016_YUV444_Y400 0x0b +#define ZR016_YUV444_RGB444 0x0c +#define ZR016_YUV422_YUV422 0x11 +#define ZR016_YUV422_YUV411 0x12 +#define ZR016_YUV422_Y400 0x13 +#define ZR016_YUV411_YUV411 0x16 +#define ZR016_YUV411_Y400 0x17 +#define ZR016_4444_4444 0x19 +#define ZR016_100_100 0x1b + +#define ZR016_RGB444 0x00 +#define ZR016_YUV444 0x20 +#define ZR016_YUV422 0x40 + +#define ZR016_COMPRESSION 0x80 +#define ZR016_EXPANSION 0x80 + +/* possible values for setup 1 register */ +#define ZR016_CKRT 0x80 +#define ZR016_VERT 0x40 +#define ZR016_HORZ 0x20 +#define ZR016_HRFL 0x10 +#define ZR016_DSFL 0x08 +#define ZR016_SBFL 0x04 +#define ZR016_RSTR 0x02 +#define ZR016_CNTI 0x01 + +/* possible values for setup 2 register */ +#define ZR016_SYEN 0x40 +#define ZR016_CCIR 0x04 +#define ZR016_SIGN 0x02 +#define ZR016_YMCS 0x01 + +int zr36016_init_module(void); +void zr36016_cleanup_module(void); +#endif /*fndef ZR36016_H */ diff --git a/drivers/media/pci/zoran/zr36050.c b/drivers/media/pci/zoran/zr36050.c new file mode 100644 index 000000000..eac4a6c32 --- /dev/null +++ b/drivers/media/pci/zoran/zr36050.c @@ -0,0 +1,817 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zoran ZR36050 basic configuration functions + * + * Copyright (C) 2001 Wolfgang Scherr + */ + +#include +#include +#include +#include + +#include +#include + +/* I/O commands, error codes */ +#include + +/* headerfile of this module */ +#include "zr36050.h" + +/* codec io API */ +#include "videocodec.h" + +/* + * it doesn't make sense to have more than 20 or so, + * just to prevent some unwanted loops + */ +#define MAX_CODECS 20 + +/* amount of chips attached via this driver */ +static int zr36050_codecs; + +/* + * Local hardware I/O functions: + * + * read/write via codec layer (registers are located in the master device) + */ + +/* read and write functions */ +static u8 zr36050_read(struct zr36050 *ptr, u16 reg) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + u8 value = 0; + + /* just in case something is wrong... */ + if (ptr->codec->master_data->readreg) + value = (ptr->codec->master_data->readreg(ptr->codec, reg)) & 0xFF; + else + zrdev_err(zr, "%s: invalid I/O setup, nothing read!\n", ptr->name); + + zrdev_dbg(zr, "%s: reading from 0x%04x: %02x\n", ptr->name, reg, value); + + return value; +} + +static void zr36050_write(struct zr36050 *ptr, u16 reg, u8 value) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + + zrdev_dbg(zr, "%s: writing 0x%02x to 0x%04x\n", ptr->name, value, reg); + + /* just in case something is wrong... */ + if (ptr->codec->master_data->writereg) + ptr->codec->master_data->writereg(ptr->codec, reg, value); + else + zrdev_err(zr, "%s: invalid I/O setup, nothing written!\n", + ptr->name); +} + +/* status is kept in datastructure */ +static u8 zr36050_read_status1(struct zr36050 *ptr) +{ + ptr->status1 = zr36050_read(ptr, ZR050_STATUS_1); + + zr36050_read(ptr, 0); + return ptr->status1; +} + +/* scale factor is kept in datastructure */ +static u16 zr36050_read_scalefactor(struct zr36050 *ptr) +{ + ptr->scalefact = (zr36050_read(ptr, ZR050_SF_HI) << 8) | + (zr36050_read(ptr, ZR050_SF_LO) & 0xFF); + + /* leave 0 selected for an eventually GO from master */ + zr36050_read(ptr, 0); + return ptr->scalefact; +} + +/* + * Local helper function: + * + * wait if codec is ready to proceed (end of processing) or time is over + */ + +static void zr36050_wait_end(struct zr36050 *ptr) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + int i = 0; + + while (!(zr36050_read_status1(ptr) & 0x4)) { + udelay(1); + if (i++ > 200000) { // 200ms, there is for sure something wrong!!! + zrdev_err(zr, + "%s: timeout at wait_end (last status: 0x%02x)\n", + ptr->name, ptr->status1); + break; + } + } +} + +/* + * Local helper function: basic test of "connectivity", writes/reads + * to/from memory the SOF marker + */ + +static int zr36050_basic_test(struct zr36050 *ptr) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + + zr36050_write(ptr, ZR050_SOF_IDX, 0x00); + zr36050_write(ptr, ZR050_SOF_IDX + 1, 0x00); + if ((zr36050_read(ptr, ZR050_SOF_IDX) | + zr36050_read(ptr, ZR050_SOF_IDX + 1)) != 0x0000) { + zrdev_err(zr, + "%s: attach failed, can't connect to jpeg processor!\n", + ptr->name); + return -ENXIO; + } + zr36050_write(ptr, ZR050_SOF_IDX, 0xff); + zr36050_write(ptr, ZR050_SOF_IDX + 1, 0xc0); + if (((zr36050_read(ptr, ZR050_SOF_IDX) << 8) | + zr36050_read(ptr, ZR050_SOF_IDX + 1)) != 0xffc0) { + zrdev_err(zr, + "%s: attach failed, can't connect to jpeg processor!\n", + ptr->name); + return -ENXIO; + } + + zr36050_wait_end(ptr); + if ((ptr->status1 & 0x4) == 0) { + zrdev_err(zr, + "%s: attach failed, jpeg processor failed (end flag)!\n", + ptr->name); + return -EBUSY; + } + + return 0; /* looks good! */ +} + +/* Local helper function: simple loop for pushing the init datasets */ + +static int zr36050_pushit(struct zr36050 *ptr, u16 startreg, u16 len, const char *data) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + int i = 0; + + zrdev_dbg(zr, "%s: write data block to 0x%04x (len=%d)\n", ptr->name, + startreg, len); + while (i < len) + zr36050_write(ptr, startreg++, data[i++]); + + return i; +} + +/* + * Basic datasets: + * + * jpeg baseline setup data (you find it on lots places in internet, or just + * extract it from any regular .jpg image...) + * + * Could be variable, but until it's not needed it they are just fixed to save + * memory. Otherwise expand zr36050 structure with arrays, push the values to + * it and initialize from there, as e.g. the linux zr36057/60 driver does it. + */ + +static const char zr36050_dqt[0x86] = { + 0xff, 0xdb, //Marker: DQT + 0x00, 0x84, //Length: 2*65+2 + 0x00, //Pq,Tq first table + 0x10, 0x0b, 0x0c, 0x0e, 0x0c, 0x0a, 0x10, 0x0e, + 0x0d, 0x0e, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28, + 0x1a, 0x18, 0x16, 0x16, 0x18, 0x31, 0x23, 0x25, + 0x1d, 0x28, 0x3a, 0x33, 0x3d, 0x3c, 0x39, 0x33, + 0x38, 0x37, 0x40, 0x48, 0x5c, 0x4e, 0x40, 0x44, + 0x57, 0x45, 0x37, 0x38, 0x50, 0x6d, 0x51, 0x57, + 0x5f, 0x62, 0x67, 0x68, 0x67, 0x3e, 0x4d, 0x71, + 0x79, 0x70, 0x64, 0x78, 0x5c, 0x65, 0x67, 0x63, + 0x01, //Pq,Tq second table + 0x11, 0x12, 0x12, 0x18, 0x15, 0x18, 0x2f, 0x1a, + 0x1a, 0x2f, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63 +}; + +static const char zr36050_dht[0x1a4] = { + 0xff, 0xc4, //Marker: DHT + 0x01, 0xa2, //Length: 2*AC, 2*DC + 0x00, //DC first table + 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + 0x01, //DC second table + 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + 0x10, //AC first table + 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, + 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, + 0x01, 0x7D, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, + 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, + 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, + 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 0x24, + 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, + 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, + 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, + 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, + 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, + 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, + 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + 0xF8, 0xF9, 0xFA, + 0x11, //AC second table + 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, + 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, + 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, + 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0, 0x15, 0x62, + 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, + 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, + 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, + 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, + 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, + 0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, + 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, + 0xF9, 0xFA +}; + +/* jpeg baseline setup, this is just fixed in this driver (YUV pictures) */ +#define NO_OF_COMPONENTS 0x3 //Y,U,V +#define BASELINE_PRECISION 0x8 //MCU size (?) +static const char zr36050_tq[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's QT +static const char zr36050_td[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's DC +static const char zr36050_ta[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's AC + +/* horizontal 422 decimation setup (maybe we support 411 or so later, too) */ +static const char zr36050_decimation_h[8] = { 2, 1, 1, 0, 0, 0, 0, 0 }; +static const char zr36050_decimation_v[8] = { 1, 1, 1, 0, 0, 0, 0, 0 }; + +/* + * Local helper functions: + * + * calculation and setup of parameter-dependent JPEG baseline segments + * (needed for compression only) + */ + +/* ------------------------------------------------------------------------- */ + +/* + * SOF (start of frame) segment depends on width, height and sampling ratio + * of each color component + */ +static int zr36050_set_sof(struct zr36050 *ptr) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + char sof_data[34]; // max. size of register set + int i; + + zrdev_dbg(zr, "%s: write SOF (%dx%d, %d components)\n", ptr->name, + ptr->width, ptr->height, NO_OF_COMPONENTS); + sof_data[0] = 0xff; + sof_data[1] = 0xc0; + sof_data[2] = 0x00; + sof_data[3] = (3 * NO_OF_COMPONENTS) + 8; + sof_data[4] = BASELINE_PRECISION; // only '8' possible with zr36050 + sof_data[5] = (ptr->height) >> 8; + sof_data[6] = (ptr->height) & 0xff; + sof_data[7] = (ptr->width) >> 8; + sof_data[8] = (ptr->width) & 0xff; + sof_data[9] = NO_OF_COMPONENTS; + for (i = 0; i < NO_OF_COMPONENTS; i++) { + sof_data[10 + (i * 3)] = i; // index identifier + sof_data[11 + (i * 3)] = (ptr->h_samp_ratio[i] << 4) | + (ptr->v_samp_ratio[i]); // sampling ratios + sof_data[12 + (i * 3)] = zr36050_tq[i]; // Q table selection + } + return zr36050_pushit(ptr, ZR050_SOF_IDX, + (3 * NO_OF_COMPONENTS) + 10, sof_data); +} + +/* ------------------------------------------------------------------------- */ + +/* + * SOS (start of scan) segment depends on the used scan components + * of each color component + */ + +static int zr36050_set_sos(struct zr36050 *ptr) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + char sos_data[16]; // max. size of register set + int i; + + zrdev_dbg(zr, "%s: write SOS\n", ptr->name); + sos_data[0] = 0xff; + sos_data[1] = 0xda; + sos_data[2] = 0x00; + sos_data[3] = 2 + 1 + (2 * NO_OF_COMPONENTS) + 3; + sos_data[4] = NO_OF_COMPONENTS; + for (i = 0; i < NO_OF_COMPONENTS; i++) { + sos_data[5 + (i * 2)] = i; // index + sos_data[6 + (i * 2)] = (zr36050_td[i] << 4) | zr36050_ta[i]; // AC/DC tbl.sel. + } + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 2] = 00; // scan start + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 3] = 0x3F; + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 4] = 00; + return zr36050_pushit(ptr, ZR050_SOS1_IDX, + 4 + 1 + (2 * NO_OF_COMPONENTS) + 3, + sos_data); +} + +/* ------------------------------------------------------------------------- */ + +/* DRI (define restart interval) */ + +static int zr36050_set_dri(struct zr36050 *ptr) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + char dri_data[6]; // max. size of register set + + zrdev_dbg(zr, "%s: write DRI\n", ptr->name); + dri_data[0] = 0xff; + dri_data[1] = 0xdd; + dri_data[2] = 0x00; + dri_data[3] = 0x04; + dri_data[4] = ptr->dri >> 8; + dri_data[5] = ptr->dri & 0xff; + return zr36050_pushit(ptr, ZR050_DRI_IDX, 6, dri_data); +} + +/* + * Setup function: + * + * Setup compression/decompression of Zoran's JPEG processor + * ( see also zoran 36050 manual ) + * + * ... sorry for the spaghetti code ... + */ +static void zr36050_init(struct zr36050 *ptr) +{ + int sum = 0; + long bitcnt, tmp; + struct zoran *zr = videocodec_to_zoran(ptr->codec); + + if (ptr->mode == CODEC_DO_COMPRESSION) { + zrdev_dbg(zr, "%s: COMPRESSION SETUP\n", ptr->name); + + /* 050 communicates with 057 in master mode */ + zr36050_write(ptr, ZR050_HARDWARE, ZR050_HW_MSTR); + + /* encoding table preload for compression */ + zr36050_write(ptr, ZR050_MODE, + ZR050_MO_COMP | ZR050_MO_TLM); + zr36050_write(ptr, ZR050_OPTIONS, 0); + + /* disable all IRQs */ + zr36050_write(ptr, ZR050_INT_REQ_0, 0); + zr36050_write(ptr, ZR050_INT_REQ_1, 3); // low 2 bits always 1 + + /* volume control settings */ + /*zr36050_write(ptr, ZR050_MBCV, ptr->max_block_vol);*/ + zr36050_write(ptr, ZR050_SF_HI, ptr->scalefact >> 8); + zr36050_write(ptr, ZR050_SF_LO, ptr->scalefact & 0xff); + + zr36050_write(ptr, ZR050_AF_HI, 0xff); + zr36050_write(ptr, ZR050_AF_M, 0xff); + zr36050_write(ptr, ZR050_AF_LO, 0xff); + + /* setup the variable jpeg tables */ + sum += zr36050_set_sof(ptr); + sum += zr36050_set_sos(ptr); + sum += zr36050_set_dri(ptr); + + /* + * setup the fixed jpeg tables - maybe variable, though - + * (see table init section above) + */ + zrdev_dbg(zr, "%s: write DQT, DHT, APP\n", ptr->name); + sum += zr36050_pushit(ptr, ZR050_DQT_IDX, + sizeof(zr36050_dqt), zr36050_dqt); + sum += zr36050_pushit(ptr, ZR050_DHT_IDX, + sizeof(zr36050_dht), zr36050_dht); + zr36050_write(ptr, ZR050_APP_IDX, 0xff); + zr36050_write(ptr, ZR050_APP_IDX + 1, 0xe0 + ptr->app.appn); + zr36050_write(ptr, ZR050_APP_IDX + 2, 0x00); + zr36050_write(ptr, ZR050_APP_IDX + 3, ptr->app.len + 2); + sum += zr36050_pushit(ptr, ZR050_APP_IDX + 4, 60, + ptr->app.data) + 4; + zr36050_write(ptr, ZR050_COM_IDX, 0xff); + zr36050_write(ptr, ZR050_COM_IDX + 1, 0xfe); + zr36050_write(ptr, ZR050_COM_IDX + 2, 0x00); + zr36050_write(ptr, ZR050_COM_IDX + 3, ptr->com.len + 2); + sum += zr36050_pushit(ptr, ZR050_COM_IDX + 4, 60, + ptr->com.data) + 4; + + /* do the internal huffman table preload */ + zr36050_write(ptr, ZR050_MARKERS_EN, ZR050_ME_DHTI); + + zr36050_write(ptr, ZR050_GO, 1); // launch codec + zr36050_wait_end(ptr); + zrdev_dbg(zr, "%s: Status after table preload: 0x%02x\n", + ptr->name, ptr->status1); + + if ((ptr->status1 & 0x4) == 0) { + zrdev_err(zr, "%s: init aborted!\n", ptr->name); + return; // something is wrong, its timed out!!!! + } + + /* setup misc. data for compression (target code sizes) */ + + /* size of compressed code to reach without header data */ + sum = ptr->real_code_vol - sum; + bitcnt = sum << 3; /* need the size in bits */ + + tmp = bitcnt >> 16; + zrdev_dbg(zr, + "%s: code: csize=%d, tot=%d, bit=%ld, highbits=%ld\n", + ptr->name, sum, ptr->real_code_vol, bitcnt, tmp); + zr36050_write(ptr, ZR050_TCV_NET_HI, tmp >> 8); + zr36050_write(ptr, ZR050_TCV_NET_MH, tmp & 0xff); + tmp = bitcnt & 0xffff; + zr36050_write(ptr, ZR050_TCV_NET_ML, tmp >> 8); + zr36050_write(ptr, ZR050_TCV_NET_LO, tmp & 0xff); + + bitcnt -= bitcnt >> 7; // bits without stuffing + bitcnt -= ((bitcnt * 5) >> 6); // bits without eob + + tmp = bitcnt >> 16; + zrdev_dbg(zr, "%s: code: nettobit=%ld, highnettobits=%ld\n", + ptr->name, bitcnt, tmp); + zr36050_write(ptr, ZR050_TCV_DATA_HI, tmp >> 8); + zr36050_write(ptr, ZR050_TCV_DATA_MH, tmp & 0xff); + tmp = bitcnt & 0xffff; + zr36050_write(ptr, ZR050_TCV_DATA_ML, tmp >> 8); + zr36050_write(ptr, ZR050_TCV_DATA_LO, tmp & 0xff); + + /* compression setup with or without bitrate control */ + zr36050_write(ptr, ZR050_MODE, + ZR050_MO_COMP | ZR050_MO_PASS2 | + (ptr->bitrate_ctrl ? ZR050_MO_BRC : 0)); + + /* this headers seem to deliver "valid AVI" jpeg frames */ + zr36050_write(ptr, ZR050_MARKERS_EN, + ZR050_ME_DQT | ZR050_ME_DHT | + ((ptr->app.len > 0) ? ZR050_ME_APP : 0) | + ((ptr->com.len > 0) ? ZR050_ME_COM : 0)); + } else { + zrdev_dbg(zr, "%s: EXPANSION SETUP\n", ptr->name); + + /* 050 communicates with 055 in master mode */ + zr36050_write(ptr, ZR050_HARDWARE, + ZR050_HW_MSTR | ZR050_HW_CFIS_2_CLK); + + /* encoding table preload */ + zr36050_write(ptr, ZR050_MODE, ZR050_MO_TLM); + + /* disable all IRQs */ + zr36050_write(ptr, ZR050_INT_REQ_0, 0); + zr36050_write(ptr, ZR050_INT_REQ_1, 3); // low 2 bits always 1 + + zrdev_dbg(zr, "%s: write DHT\n", ptr->name); + zr36050_pushit(ptr, ZR050_DHT_IDX, sizeof(zr36050_dht), + zr36050_dht); + + /* do the internal huffman table preload */ + zr36050_write(ptr, ZR050_MARKERS_EN, ZR050_ME_DHTI); + + zr36050_write(ptr, ZR050_GO, 1); // launch codec + zr36050_wait_end(ptr); + zrdev_dbg(zr, "%s: Status after table preload: 0x%02x\n", + ptr->name, ptr->status1); + + if ((ptr->status1 & 0x4) == 0) { + zrdev_err(zr, "%s: init aborted!\n", ptr->name); + return; // something is wrong, its timed out!!!! + } + + /* setup misc. data for expansion */ + zr36050_write(ptr, ZR050_MODE, 0); + zr36050_write(ptr, ZR050_MARKERS_EN, 0); + } + + /* adr on selected, to allow GO from master */ + zr36050_read(ptr, 0); +} + +/* + * CODEC API FUNCTIONS + * + * this functions are accessed by the master via the API structure + */ + +/* + * set compression/expansion mode and launches codec - + * this should be the last call from the master before starting processing + */ +static int zr36050_set_mode(struct videocodec *codec, int mode) +{ + struct zr36050 *ptr = (struct zr36050 *)codec->data; + struct zoran *zr = videocodec_to_zoran(codec); + + zrdev_dbg(zr, "%s: set_mode %d call\n", ptr->name, mode); + + if ((mode != CODEC_DO_EXPANSION) && (mode != CODEC_DO_COMPRESSION)) + return -EINVAL; + + ptr->mode = mode; + zr36050_init(ptr); + + return 0; +} + +/* set picture size (norm is ignored as the codec doesn't know about it) */ +static int zr36050_set_video(struct videocodec *codec, const struct tvnorm *norm, + struct vfe_settings *cap, struct vfe_polarity *pol) +{ + struct zr36050 *ptr = (struct zr36050 *)codec->data; + struct zoran *zr = videocodec_to_zoran(codec); + int size; + + zrdev_dbg(zr, "%s: set_video %d.%d, (%u,%u)/%ux%u (0x%x) q%d call\n", + ptr->name, norm->h_start, norm->v_start, + cap->x, cap->y, cap->width, cap->height, + cap->decimation, cap->quality); + /* + * trust the master driver that it knows what it does - so + * we allow invalid startx/y and norm for now ... + */ + ptr->width = cap->width / (cap->decimation & 0xff); + ptr->height = cap->height / ((cap->decimation >> 8) & 0xff); + + /* (KM) JPEG quality */ + size = ptr->width * ptr->height; + size *= 16; /* size in bits */ + /* apply quality setting */ + size = size * cap->quality / 200; + + /* Minimum: 1kb */ + if (size < 8192) + size = 8192; + /* Maximum: 7/8 of code buffer */ + if (size > ptr->total_code_vol * 7) + size = ptr->total_code_vol * 7; + + ptr->real_code_vol = size >> 3; /* in bytes */ + + /* + * Set max_block_vol here (previously in zr36050_init, moved + * here for consistency with zr36060 code + */ + zr36050_write(ptr, ZR050_MBCV, ptr->max_block_vol); + + return 0; +} + +/* additional control functions */ +static int zr36050_control(struct videocodec *codec, int type, int size, void *data) +{ + struct zr36050 *ptr = (struct zr36050 *)codec->data; + struct zoran *zr = videocodec_to_zoran(codec); + int *ival = (int *)data; + + zrdev_dbg(zr, "%s: control %d call with %d byte\n", ptr->name, type, + size); + + switch (type) { + case CODEC_G_STATUS: /* get last status */ + if (size != sizeof(int)) + return -EFAULT; + zr36050_read_status1(ptr); + *ival = ptr->status1; + break; + + case CODEC_G_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + *ival = CODEC_MODE_BJPG; + break; + + case CODEC_S_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + if (*ival != CODEC_MODE_BJPG) + return -EINVAL; + /* not needed, do nothing */ + return 0; + + case CODEC_G_VFE: + case CODEC_S_VFE: + /* not needed, do nothing */ + return 0; + + case CODEC_S_MMAP: + /* not available, give an error */ + return -ENXIO; + + case CODEC_G_JPEG_TDS_BYTE: /* get target volume in byte */ + if (size != sizeof(int)) + return -EFAULT; + *ival = ptr->total_code_vol; + break; + + case CODEC_S_JPEG_TDS_BYTE: /* get target volume in byte */ + if (size != sizeof(int)) + return -EFAULT; + ptr->total_code_vol = *ival; + ptr->real_code_vol = (ptr->total_code_vol * 6) >> 3; + break; + + case CODEC_G_JPEG_SCALE: /* get scaling factor */ + if (size != sizeof(int)) + return -EFAULT; + *ival = zr36050_read_scalefactor(ptr); + break; + + case CODEC_S_JPEG_SCALE: /* set scaling factor */ + if (size != sizeof(int)) + return -EFAULT; + ptr->scalefact = *ival; + break; + + case CODEC_G_JPEG_APP_DATA: { /* get appn marker data */ + struct jpeg_app_marker *app = data; + + if (size != sizeof(struct jpeg_app_marker)) + return -EFAULT; + + *app = ptr->app; + break; + } + + case CODEC_S_JPEG_APP_DATA: { /* set appn marker data */ + struct jpeg_app_marker *app = data; + + if (size != sizeof(struct jpeg_app_marker)) + return -EFAULT; + + ptr->app = *app; + break; + } + + case CODEC_G_JPEG_COM_DATA: { /* get comment marker data */ + struct jpeg_com_marker *com = data; + + if (size != sizeof(struct jpeg_com_marker)) + return -EFAULT; + + *com = ptr->com; + break; + } + + case CODEC_S_JPEG_COM_DATA: { /* set comment marker data */ + struct jpeg_com_marker *com = data; + + if (size != sizeof(struct jpeg_com_marker)) + return -EFAULT; + + ptr->com = *com; + break; + } + + default: + return -EINVAL; + } + + return size; +} + +/* Exit and unregister function: Deinitializes Zoran's JPEG processor */ + +static int zr36050_unset(struct videocodec *codec) +{ + struct zr36050 *ptr = codec->data; + struct zoran *zr = videocodec_to_zoran(codec); + + if (ptr) { + /* do wee need some codec deinit here, too ???? */ + + zrdev_dbg(zr, "%s: finished codec #%d\n", ptr->name, + ptr->num); + kfree(ptr); + codec->data = NULL; + + zr36050_codecs--; + return 0; + } + + return -EFAULT; +} + +/* + * Setup and registry function: + * + * Initializes Zoran's JPEG processor + * + * Also sets pixel size, average code size, mode (compr./decompr.) + * (the given size is determined by the processor with the video interface) + */ + +static int zr36050_setup(struct videocodec *codec) +{ + struct zr36050 *ptr; + struct zoran *zr = videocodec_to_zoran(codec); + int res; + + zrdev_dbg(zr, "zr36050: initializing MJPEG subsystem #%d.\n", + zr36050_codecs); + + if (zr36050_codecs == MAX_CODECS) { + zrdev_err(zr, + "zr36050: Can't attach more codecs!\n"); + return -ENOSPC; + } + //mem structure init + ptr = kzalloc_obj(*ptr); + codec->data = ptr; + if (!ptr) + return -ENOMEM; + + snprintf(ptr->name, sizeof(ptr->name), "zr36050[%d]", + zr36050_codecs); + ptr->num = zr36050_codecs++; + ptr->codec = codec; + + //testing + res = zr36050_basic_test(ptr); + if (res < 0) { + zr36050_unset(codec); + return res; + } + //final setup + memcpy(ptr->h_samp_ratio, zr36050_decimation_h, 8); + memcpy(ptr->v_samp_ratio, zr36050_decimation_v, 8); + + /* 0 or 1 - fixed file size flag (what is the difference?) */ + ptr->bitrate_ctrl = 0; + ptr->mode = CODEC_DO_COMPRESSION; + ptr->width = 384; + ptr->height = 288; + ptr->total_code_vol = 16000; + ptr->max_block_vol = 240; + ptr->scalefact = 0x100; + ptr->dri = 1; + + /* no app/com marker by default */ + ptr->app.appn = 0; + ptr->app.len = 0; + ptr->com.len = 0; + + zr36050_init(ptr); + + zrdev_info(zr, "%s: codec attached and running\n", + ptr->name); + + return 0; +} + +static const struct videocodec zr36050_codec = { + .name = "zr36050", + .magic = 0L, // magic not used + .flags = + CODEC_FLAG_JPEG | CODEC_FLAG_HARDWARE | CODEC_FLAG_ENCODER | + CODEC_FLAG_DECODER, + .type = CODEC_TYPE_ZR36050, + .setup = zr36050_setup, // functionality + .unset = zr36050_unset, + .set_mode = zr36050_set_mode, + .set_video = zr36050_set_video, + .control = zr36050_control, + // others are not used +}; + +/* HOOK IN DRIVER AS KERNEL MODULE */ + +int zr36050_init_module(void) +{ + zr36050_codecs = 0; + return videocodec_register(&zr36050_codec); +} + +void zr36050_cleanup_module(void) +{ + if (zr36050_codecs) { + pr_debug("zr36050: something's wrong - %d codecs left somehow.\n", + zr36050_codecs); + } + videocodec_unregister(&zr36050_codec); +} diff --git a/drivers/media/pci/zoran/zr36050.h b/drivers/media/pci/zoran/zr36050.h new file mode 100644 index 000000000..f9b58f4c7 --- /dev/null +++ b/drivers/media/pci/zoran/zr36050.h @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Zoran ZR36050 basic configuration functions - header file + * + * Copyright (C) 2001 Wolfgang Scherr + */ + +#ifndef ZR36050_H +#define ZR36050_H + +#include "videocodec.h" + +/* data stored for each zoran jpeg codec chip */ +struct zr36050 { + char name[32]; + int num; + /* io datastructure */ + struct videocodec *codec; + // last coder status + __u8 status1; + // actual coder setup + int mode; + + __u16 width; + __u16 height; + + __u16 bitrate_ctrl; + + __u32 total_code_vol; + __u32 real_code_vol; + __u16 max_block_vol; + + __u8 h_samp_ratio[8]; + __u8 v_samp_ratio[8]; + __u16 scalefact; + __u16 dri; + + /* com/app marker */ + struct jpeg_com_marker com; + struct jpeg_app_marker app; +}; + +/* zr36050 register addresses */ +#define ZR050_GO 0x000 +#define ZR050_HARDWARE 0x002 +#define ZR050_MODE 0x003 +#define ZR050_OPTIONS 0x004 +#define ZR050_MBCV 0x005 +#define ZR050_MARKERS_EN 0x006 +#define ZR050_INT_REQ_0 0x007 +#define ZR050_INT_REQ_1 0x008 +#define ZR050_TCV_NET_HI 0x009 +#define ZR050_TCV_NET_MH 0x00a +#define ZR050_TCV_NET_ML 0x00b +#define ZR050_TCV_NET_LO 0x00c +#define ZR050_TCV_DATA_HI 0x00d +#define ZR050_TCV_DATA_MH 0x00e +#define ZR050_TCV_DATA_ML 0x00f +#define ZR050_TCV_DATA_LO 0x010 +#define ZR050_SF_HI 0x011 +#define ZR050_SF_LO 0x012 +#define ZR050_AF_HI 0x013 +#define ZR050_AF_M 0x014 +#define ZR050_AF_LO 0x015 +#define ZR050_ACV_HI 0x016 +#define ZR050_ACV_MH 0x017 +#define ZR050_ACV_ML 0x018 +#define ZR050_ACV_LO 0x019 +#define ZR050_ACT_HI 0x01a +#define ZR050_ACT_MH 0x01b +#define ZR050_ACT_ML 0x01c +#define ZR050_ACT_LO 0x01d +#define ZR050_ACV_TURN_HI 0x01e +#define ZR050_ACV_TURN_MH 0x01f +#define ZR050_ACV_TURN_ML 0x020 +#define ZR050_ACV_TURN_LO 0x021 +#define ZR050_STATUS_0 0x02e +#define ZR050_STATUS_1 0x02f + +#define ZR050_SOF_IDX 0x040 +#define ZR050_SOS1_IDX 0x07a +#define ZR050_SOS2_IDX 0x08a +#define ZR050_SOS3_IDX 0x09a +#define ZR050_SOS4_IDX 0x0aa +#define ZR050_DRI_IDX 0x0c0 +#define ZR050_DNL_IDX 0x0c6 +#define ZR050_DQT_IDX 0x0cc +#define ZR050_DHT_IDX 0x1d4 +#define ZR050_APP_IDX 0x380 +#define ZR050_COM_IDX 0x3c0 + +/* zr36050 hardware register bits */ + +#define ZR050_HW_BSWD 0x80 +#define ZR050_HW_MSTR 0x40 +#define ZR050_HW_DMA 0x20 +#define ZR050_HW_CFIS_1_CLK 0x00 +#define ZR050_HW_CFIS_2_CLK 0x04 +#define ZR050_HW_CFIS_3_CLK 0x08 +#define ZR050_HW_CFIS_4_CLK 0x0C +#define ZR050_HW_CFIS_5_CLK 0x10 +#define ZR050_HW_CFIS_6_CLK 0x14 +#define ZR050_HW_CFIS_7_CLK 0x18 +#define ZR050_HW_CFIS_8_CLK 0x1C +#define ZR050_HW_BELE 0x01 + +/* zr36050 mode register bits */ + +#define ZR050_MO_COMP 0x80 +#define ZR050_MO_ATP 0x40 +#define ZR050_MO_PASS2 0x20 +#define ZR050_MO_TLM 0x10 +#define ZR050_MO_DCONLY 0x08 +#define ZR050_MO_BRC 0x04 + +#define ZR050_MO_ATP 0x40 +#define ZR050_MO_PASS2 0x20 +#define ZR050_MO_TLM 0x10 +#define ZR050_MO_DCONLY 0x08 + +/* zr36050 option register bits */ + +#define ZR050_OP_NSCN_1 0x00 +#define ZR050_OP_NSCN_2 0x20 +#define ZR050_OP_NSCN_3 0x40 +#define ZR050_OP_NSCN_4 0x60 +#define ZR050_OP_NSCN_5 0x80 +#define ZR050_OP_NSCN_6 0xA0 +#define ZR050_OP_NSCN_7 0xC0 +#define ZR050_OP_NSCN_8 0xE0 +#define ZR050_OP_OVF 0x10 + +/* zr36050 markers-enable register bits */ + +#define ZR050_ME_APP 0x80 +#define ZR050_ME_COM 0x40 +#define ZR050_ME_DRI 0x20 +#define ZR050_ME_DQT 0x10 +#define ZR050_ME_DHT 0x08 +#define ZR050_ME_DNL 0x04 +#define ZR050_ME_DQTI 0x02 +#define ZR050_ME_DHTI 0x01 + +/* zr36050 status0/1 register bit masks */ + +#define ZR050_ST_RST_MASK 0x20 +#define ZR050_ST_SOF_MASK 0x02 +#define ZR050_ST_SOS_MASK 0x02 +#define ZR050_ST_DATRDY_MASK 0x80 +#define ZR050_ST_MRKDET_MASK 0x40 +#define ZR050_ST_RFM_MASK 0x10 +#define ZR050_ST_RFD_MASK 0x08 +#define ZR050_ST_END_MASK 0x04 +#define ZR050_ST_TCVOVF_MASK 0x02 +#define ZR050_ST_DATOVF_MASK 0x01 + +/* pixel component idx */ + +#define ZR050_Y_COMPONENT 0 +#define ZR050_U_COMPONENT 1 +#define ZR050_V_COMPONENT 2 + +int zr36050_init_module(void); +void zr36050_cleanup_module(void); +#endif /*fndef ZR36050_H */ diff --git a/drivers/media/pci/zoran/zr36057.h b/drivers/media/pci/zoran/zr36057.h new file mode 100644 index 000000000..45d8afc62 --- /dev/null +++ b/drivers/media/pci/zoran/zr36057.h @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * zr36057.h - zr36057 register offsets + * + * Copyright (C) 1998 Dave Perks + */ + +#ifndef _ZR36057_H_ +#define _ZR36057_H_ + +/* Zoran ZR36057 registers */ + +#define ZR36057_VFEHCR 0x000 /* Video Front End, Horizontal Configuration Register */ +#define ZR36057_VFEHCR_HS_POL BIT(30) +#define ZR36057_VFEHCR_H_START 10 +#define ZR36057_VFEHCR_H_END 0 +#define ZR36057_VFEHCR_HMASK 0x3ff + +#define ZR36057_VFEVCR 0x004 /* Video Front End, Vertical Configuration Register */ +#define ZR36057_VFEVCR_VS_POL BIT(30) +#define ZR36057_VFEVCR_V_START 10 +#define ZR36057_VFEVCR_V_END 0 +#define ZR36057_VFEVCR_VMASK 0x3ff + +#define ZR36057_VFESPFR 0x008 /* Video Front End, Scaler and Pixel Format Register */ +#define ZR36057_VFESPFR_EXT_FL BIT(26) +#define ZR36057_VFESPFR_TOP_FIELD BIT(25) +#define ZR36057_VFESPFR_VCLK_POL BIT(24) +#define ZR36057_VFESPFR_H_FILTER 21 +#define ZR36057_VFESPFR_HOR_DCM 14 +#define ZR36057_VFESPFR_VER_DCM 8 +#define ZR36057_VFESPFR_DISP_MODE 6 +#define ZR36057_VFESPFR_YUV422 (0 << 3) +#define ZR36057_VFESPFR_RGB888 (1 << 3) +#define ZR36057_VFESPFR_RGB565 (2 << 3) +#define ZR36057_VFESPFR_RGB555 (3 << 3) +#define ZR36057_VFESPFR_ERR_DIF BIT(2) +#define ZR36057_VFESPFR_PACK24 BIT(1) +#define ZR36057_VFESPFR_LITTLE_ENDIAN BIT(0) + +#define ZR36057_VDTR 0x00c /* Video Display "Top" Register */ + +#define ZR36057_VDBR 0x010 /* Video Display "Bottom" Register */ + +#define ZR36057_VSSFGR 0x014 /* Video Stride, Status, and Frame Grab Register */ +#define ZR36057_VSSFGR_DISP_STRIDE 16 +#define ZR36057_VSSFGR_VID_OVF BIT(8) +#define ZR36057_VSSFGR_SNAP_SHOT BIT(1) +#define ZR36057_VSSFGR_FRAME_GRAB BIT(0) + +#define ZR36057_VDCR 0x018 /* Video Display Configuration Register */ +#define ZR36057_VDCR_VID_EN BIT(31) +#define ZR36057_VDCR_MIN_PIX 24 +#define ZR36057_VDCR_TRITON BIT(24) +#define ZR36057_VDCR_VID_WIN_HT 12 +#define ZR36057_VDCR_VID_WIN_WID 0 + +#define ZR36057_MMTR 0x01c /* Masking Map "Top" Register */ + +#define ZR36057_MMBR 0x020 /* Masking Map "Bottom" Register */ + +#define ZR36057_OCR 0x024 /* Overlay Control Register */ +#define ZR36057_OCR_OVL_ENABLE BIT(15) +#define ZR36057_OCR_MASK_STRIDE 0 + +#define ZR36057_SPGPPCR 0x028 /* System, PCI, and General Purpose Pins Control Register */ +#define ZR36057_SPGPPCR_SOFT_RESET BIT(24) + +#define ZR36057_GPPGCR1 0x02c /* General Purpose Pins and GuestBus Control Register (1) */ + +#define ZR36057_MCSAR 0x030 /* MPEG Code Source Address Register */ + +#define ZR36057_MCTCR 0x034 /* MPEG Code Transfer Control Register */ +#define ZR36057_MCTCR_COD_TIME BIT(30) +#define ZR36057_MCTCR_C_EMPTY BIT(29) +#define ZR36057_MCTCR_C_FLUSH BIT(28) +#define ZR36057_MCTCR_COD_GUEST_ID 20 +#define ZR36057_MCTCR_COD_GUEST_REG 16 + +#define ZR36057_MCMPR 0x038 /* MPEG Code Memory Pointer Register */ + +#define ZR36057_ISR 0x03c /* Interrupt Status Register */ +#define ZR36057_ISR_GIRQ1 BIT(30) +#define ZR36057_ISR_GIRQ0 BIT(29) +#define ZR36057_ISR_COD_REP_IRQ BIT(28) +#define ZR36057_ISR_JPEG_REP_IRQ BIT(27) + +#define ZR36057_ICR 0x040 /* Interrupt Control Register */ +#define ZR36057_ICR_GIRQ1 BIT(30) +#define ZR36057_ICR_GIRQ0 BIT(29) +#define ZR36057_ICR_COD_REP_IRQ BIT(28) +#define ZR36057_ICR_JPEG_REP_IRQ BIT(27) +#define ZR36057_ICR_INT_PIN_EN BIT(24) + +#define ZR36057_I2CBR 0x044 /* I2C Bus Register */ +#define ZR36057_I2CBR_SDA BIT(1) +#define ZR36057_I2CBR_SCL BIT(0) + +#define ZR36057_JMC 0x100 /* JPEG Mode and Control */ +#define ZR36057_JMC_JPG BIT(31) +#define ZR36057_JMC_JPG_EXP_MODE (0 << 29) +#define ZR36057_JMC_JPG_CMP_MODE BIT(29) +#define ZR36057_JMC_MJPG_EXP_MODE (2 << 29) +#define ZR36057_JMC_MJPG_CMP_MODE (3 << 29) +#define ZR36057_JMC_RTBUSY_FB BIT(6) +#define ZR36057_JMC_GO_EN BIT(5) +#define ZR36057_JMC_SYNC_MSTR BIT(4) +#define ZR36057_JMC_FLD_PER_BUFF BIT(3) +#define ZR36057_JMC_VFIFO_FB BIT(2) +#define ZR36057_JMC_CFIFO_FB BIT(1) +#define ZR36057_JMC_STLL_LIT_ENDIAN BIT(0) + +#define ZR36057_JPC 0x104 /* JPEG Process Control */ +#define ZR36057_JPC_P_RESET BIT(7) +#define ZR36057_JPC_COD_TRNS_EN BIT(5) +#define ZR36057_JPC_ACTIVE BIT(0) + +#define ZR36057_VSP 0x108 /* Vertical Sync Parameters */ +#define ZR36057_VSP_VSYNC_SIZE 16 +#define ZR36057_VSP_FRM_TOT 0 + +#define ZR36057_HSP 0x10c /* Horizontal Sync Parameters */ +#define ZR36057_HSP_HSYNC_START 16 +#define ZR36057_HSP_LINE_TOT 0 + +#define ZR36057_FHAP 0x110 /* Field Horizontal Active Portion */ +#define ZR36057_FHAP_NAX 16 +#define ZR36057_FHAP_PAX 0 + +#define ZR36057_FVAP 0x114 /* Field Vertical Active Portion */ +#define ZR36057_FVAP_NAY 16 +#define ZR36057_FVAP_PAY 0 + +#define ZR36057_FPP 0x118 /* Field Process Parameters */ +#define ZR36057_FPP_ODD_EVEN BIT(0) + +#define ZR36057_JCBA 0x11c /* JPEG Code Base Address */ + +#define ZR36057_JCFT 0x120 /* JPEG Code FIFO Threshold */ + +#define ZR36057_JCGI 0x124 /* JPEG Codec Guest ID */ +#define ZR36057_JCGI_JPE_GUEST_ID 4 +#define ZR36057_JCGI_JPE_GUEST_REG 0 + +#define ZR36057_GCR2 0x12c /* GuestBus Control Register (2) */ + +#define ZR36057_POR 0x200 /* Post Office Register */ +#define ZR36057_POR_PO_PEN BIT(25) +#define ZR36057_POR_PO_TIME BIT(24) +#define ZR36057_POR_PO_DIR BIT(23) + +#define ZR36057_STR 0x300 /* "Still" Transfer Register */ + +#endif diff --git a/drivers/media/pci/zoran/zr36060.c b/drivers/media/pci/zoran/zr36060.c new file mode 100644 index 000000000..c383dcddf --- /dev/null +++ b/drivers/media/pci/zoran/zr36060.c @@ -0,0 +1,870 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zoran ZR36060 basic configuration functions + * + * Copyright (C) 2002 Laurent Pinchart + */ + +#include +#include +#include +#include + +#include +#include + +/* I/O commands, error codes */ +#include + +/* headerfile of this module */ +#include "zr36060.h" + +/* codec io API */ +#include "videocodec.h" + +/* it doesn't make sense to have more than 20 or so, just to prevent some unwanted loops */ +#define MAX_CODECS 20 + +/* amount of chips attached via this driver */ +static int zr36060_codecs; + +static bool low_bitrate; +module_param(low_bitrate, bool, 0); +MODULE_PARM_DESC(low_bitrate, "Buz compatibility option, halves bitrate"); + +/* ========================================================================= + * Local hardware I/O functions: + * read/write via codec layer (registers are located in the master device) + * ========================================================================= + */ + +static u8 zr36060_read(struct zr36060 *ptr, u16 reg) +{ + u8 value = 0; + struct zoran *zr = videocodec_to_zoran(ptr->codec); + + // just in case something is wrong... + if (ptr->codec->master_data->readreg) + value = (ptr->codec->master_data->readreg(ptr->codec, reg)) & 0xff; + else + zrdev_err(zr, "%s: invalid I/O setup, nothing read!\n", ptr->name); + + return value; +} + +static void zr36060_write(struct zr36060 *ptr, u16 reg, u8 value) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + + zrdev_dbg(zr, "0x%02x @0x%04x\n", value, reg); + + // just in case something is wrong... + if (ptr->codec->master_data->writereg) + ptr->codec->master_data->writereg(ptr->codec, reg, value); + else + zrdev_err(zr, "%s: invalid I/O setup, nothing written!\n", ptr->name); +} + +/* ========================================================================= + * Local helper function: + * status read + * ========================================================================= + */ + +/* status is kept in datastructure */ +static u8 zr36060_read_status(struct zr36060 *ptr) +{ + ptr->status = zr36060_read(ptr, ZR060_CFSR); + + zr36060_read(ptr, 0); + return ptr->status; +} + +/* scale factor is kept in datastructure */ +static u16 zr36060_read_scalefactor(struct zr36060 *ptr) +{ + ptr->scalefact = (zr36060_read(ptr, ZR060_SF_HI) << 8) | + (zr36060_read(ptr, ZR060_SF_LO) & 0xFF); + + /* leave 0 selected for an eventually GO from master */ + zr36060_read(ptr, 0); + return ptr->scalefact; +} + +/* wait if codec is ready to proceed (end of processing) or time is over */ +static void zr36060_wait_end(struct zr36060 *ptr) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + int i = 0; + + while (zr36060_read_status(ptr) & ZR060_CFSR_BUSY) { + udelay(1); + if (i++ > 200000) { // 200ms, there is for sure something wrong!!! + zrdev_dbg(zr, + "%s: timeout at wait_end (last status: 0x%02x)\n", + ptr->name, ptr->status); + break; + } + } +} + +/* Basic test of "connectivity", writes/reads to/from memory the SOF marker */ +static int zr36060_basic_test(struct zr36060 *ptr) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + + if ((zr36060_read(ptr, ZR060_IDR_DEV) != 0x33) && + (zr36060_read(ptr, ZR060_IDR_REV) != 0x01)) { + zrdev_err(zr, "%s: attach failed, can't connect to jpeg processor!\n", ptr->name); + return -ENXIO; + } + + zr36060_wait_end(ptr); + if (ptr->status & ZR060_CFSR_BUSY) { + zrdev_err(zr, "%s: attach failed, jpeg processor failed (end flag)!\n", ptr->name); + return -EBUSY; + } + + return 0; /* looks good! */ +} + +/* simple loop for pushing the init datasets */ +static int zr36060_pushit(struct zr36060 *ptr, u16 startreg, u16 len, const char *data) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + int i = 0; + + zrdev_dbg(zr, "%s: write data block to 0x%04x (len=%d)\n", ptr->name, + startreg, len); + while (i < len) + zr36060_write(ptr, startreg++, data[i++]); + + return i; +} + +/* ========================================================================= + * Basic datasets: + * jpeg baseline setup data (you find it on lots places in internet, or just + * extract it from any regular .jpg image...) + * + * Could be variable, but until it's not needed it they are just fixed to save + * memory. Otherwise expand zr36060 structure with arrays, push the values to + * it and initialize from there, as e.g. the linux zr36057/60 driver does it. + * ========================================================================= + */ +static const char zr36060_dqt[0x86] = { + 0xff, 0xdb, //Marker: DQT + 0x00, 0x84, //Length: 2*65+2 + 0x00, //Pq,Tq first table + 0x10, 0x0b, 0x0c, 0x0e, 0x0c, 0x0a, 0x10, 0x0e, + 0x0d, 0x0e, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28, + 0x1a, 0x18, 0x16, 0x16, 0x18, 0x31, 0x23, 0x25, + 0x1d, 0x28, 0x3a, 0x33, 0x3d, 0x3c, 0x39, 0x33, + 0x38, 0x37, 0x40, 0x48, 0x5c, 0x4e, 0x40, 0x44, + 0x57, 0x45, 0x37, 0x38, 0x50, 0x6d, 0x51, 0x57, + 0x5f, 0x62, 0x67, 0x68, 0x67, 0x3e, 0x4d, 0x71, + 0x79, 0x70, 0x64, 0x78, 0x5c, 0x65, 0x67, 0x63, + 0x01, //Pq,Tq second table + 0x11, 0x12, 0x12, 0x18, 0x15, 0x18, 0x2f, 0x1a, + 0x1a, 0x2f, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63 +}; + +static const char zr36060_dht[0x1a4] = { + 0xff, 0xc4, //Marker: DHT + 0x01, 0xa2, //Length: 2*AC, 2*DC + 0x00, //DC first table + 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + 0x01, //DC second table + 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + 0x10, //AC first table + 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, + 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, + 0x01, 0x7D, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, + 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, + 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, + 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 0x24, + 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, + 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, + 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, + 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, + 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, + 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, + 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + 0xF8, 0xF9, 0xFA, + 0x11, //AC second table + 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, + 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, + 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, + 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0, 0x15, 0x62, + 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, + 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, + 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, + 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, + 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, + 0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, + 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, + 0xF9, 0xFA +}; + +/* jpeg baseline setup, this is just fixed in this driver (YUV pictures) */ +#define NO_OF_COMPONENTS 0x3 //Y,U,V +#define BASELINE_PRECISION 0x8 //MCU size (?) +static const char zr36060_tq[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's QT +static const char zr36060_td[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's DC +static const char zr36060_ta[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's AC + +/* horizontal 422 decimation setup (maybe we support 411 or so later, too) */ +static const char zr36060_decimation_h[8] = { 2, 1, 1, 0, 0, 0, 0, 0 }; +static const char zr36060_decimation_v[8] = { 1, 1, 1, 0, 0, 0, 0, 0 }; + +/* + * SOF (start of frame) segment depends on width, height and sampling ratio + * of each color component + */ +static int zr36060_set_sof(struct zr36060 *ptr) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + char sof_data[34]; // max. size of register set + int i; + + zrdev_dbg(zr, "%s: write SOF (%dx%d, %d components)\n", ptr->name, + ptr->width, ptr->height, NO_OF_COMPONENTS); + sof_data[0] = 0xff; + sof_data[1] = 0xc0; + sof_data[2] = 0x00; + sof_data[3] = (3 * NO_OF_COMPONENTS) + 8; + sof_data[4] = BASELINE_PRECISION; // only '8' possible with zr36060 + sof_data[5] = (ptr->height) >> 8; + sof_data[6] = (ptr->height) & 0xff; + sof_data[7] = (ptr->width) >> 8; + sof_data[8] = (ptr->width) & 0xff; + sof_data[9] = NO_OF_COMPONENTS; + for (i = 0; i < NO_OF_COMPONENTS; i++) { + sof_data[10 + (i * 3)] = i; // index identifier + sof_data[11 + (i * 3)] = (ptr->h_samp_ratio[i] << 4) | + (ptr->v_samp_ratio[i]); // sampling ratios + sof_data[12 + (i * 3)] = zr36060_tq[i]; // Q table selection + } + return zr36060_pushit(ptr, ZR060_SOF_IDX, + (3 * NO_OF_COMPONENTS) + 10, sof_data); +} + +/* SOS (start of scan) segment depends on the used scan components of each color component */ +static int zr36060_set_sos(struct zr36060 *ptr) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + char sos_data[16]; // max. size of register set + int i; + + zrdev_dbg(zr, "%s: write SOS\n", ptr->name); + sos_data[0] = 0xff; + sos_data[1] = 0xda; + sos_data[2] = 0x00; + sos_data[3] = 2 + 1 + (2 * NO_OF_COMPONENTS) + 3; + sos_data[4] = NO_OF_COMPONENTS; + for (i = 0; i < NO_OF_COMPONENTS; i++) { + sos_data[5 + (i * 2)] = i; // index + sos_data[6 + (i * 2)] = (zr36060_td[i] << 4) | + zr36060_ta[i]; // AC/DC tbl.sel. + } + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 2] = 00; // scan start + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 3] = 0x3f; + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 4] = 00; + return zr36060_pushit(ptr, ZR060_SOS_IDX, + 4 + 1 + (2 * NO_OF_COMPONENTS) + 3, + sos_data); +} + +/* DRI (define restart interval) */ +static int zr36060_set_dri(struct zr36060 *ptr) +{ + struct zoran *zr = videocodec_to_zoran(ptr->codec); + char dri_data[6]; // max. size of register set + + zrdev_dbg(zr, "%s: write DRI\n", ptr->name); + dri_data[0] = 0xff; + dri_data[1] = 0xdd; + dri_data[2] = 0x00; + dri_data[3] = 0x04; + dri_data[4] = (ptr->dri) >> 8; + dri_data[5] = (ptr->dri) & 0xff; + return zr36060_pushit(ptr, ZR060_DRI_IDX, 6, dri_data); +} + +/* Setup compression/decompression of Zoran's JPEG processor ( see also zoran 36060 manual ) + * ... sorry for the spaghetti code ... + */ +static void zr36060_init(struct zr36060 *ptr) +{ + int sum = 0; + long bitcnt, tmp; + struct zoran *zr = videocodec_to_zoran(ptr->codec); + + if (ptr->mode == CODEC_DO_COMPRESSION) { + zrdev_dbg(zr, "%s: COMPRESSION SETUP\n", ptr->name); + + zr36060_write(ptr, ZR060_LOAD, ZR060_LOAD_SYNC_RST); + + /* 060 communicates with 067 in master mode */ + zr36060_write(ptr, ZR060_CIR, ZR060_CIR_CODE_MSTR); + + /* Compression with or without variable scale factor */ + /*FIXME: What about ptr->bitrate_ctrl? */ + zr36060_write(ptr, ZR060_CMR, ZR060_CMR_COMP | ZR060_CMR_PASS2 | ZR060_CMR_BRB); + + /* Must be zero */ + zr36060_write(ptr, ZR060_MBZ, 0x00); + zr36060_write(ptr, ZR060_TCR_HI, 0x00); + zr36060_write(ptr, ZR060_TCR_LO, 0x00); + + /* Disable all IRQs - no DataErr means autoreset */ + zr36060_write(ptr, ZR060_IMR, 0); + + /* volume control settings */ + zr36060_write(ptr, ZR060_SF_HI, ptr->scalefact >> 8); + zr36060_write(ptr, ZR060_SF_LO, ptr->scalefact & 0xff); + + zr36060_write(ptr, ZR060_AF_HI, 0xff); + zr36060_write(ptr, ZR060_AF_M, 0xff); + zr36060_write(ptr, ZR060_AF_LO, 0xff); + + /* setup the variable jpeg tables */ + sum += zr36060_set_sof(ptr); + sum += zr36060_set_sos(ptr); + sum += zr36060_set_dri(ptr); + +/* setup the fixed jpeg tables - maybe variable, though - (see table init section above) */ + sum += zr36060_pushit(ptr, ZR060_DQT_IDX, sizeof(zr36060_dqt), zr36060_dqt); + sum += zr36060_pushit(ptr, ZR060_DHT_IDX, sizeof(zr36060_dht), zr36060_dht); + zr36060_write(ptr, ZR060_APP_IDX, 0xff); + zr36060_write(ptr, ZR060_APP_IDX + 1, 0xe0 + ptr->app.appn); + zr36060_write(ptr, ZR060_APP_IDX + 2, 0x00); + zr36060_write(ptr, ZR060_APP_IDX + 3, ptr->app.len + 2); + sum += zr36060_pushit(ptr, ZR060_APP_IDX + 4, 60, ptr->app.data) + 4; + zr36060_write(ptr, ZR060_COM_IDX, 0xff); + zr36060_write(ptr, ZR060_COM_IDX + 1, 0xfe); + zr36060_write(ptr, ZR060_COM_IDX + 2, 0x00); + zr36060_write(ptr, ZR060_COM_IDX + 3, ptr->com.len + 2); + sum += zr36060_pushit(ptr, ZR060_COM_IDX + 4, 60, ptr->com.data) + 4; + + /* setup misc. data for compression (target code sizes) */ + + /* size of compressed code to reach without header data */ + sum = ptr->real_code_vol - sum; + bitcnt = sum << 3; /* need the size in bits */ + + tmp = bitcnt >> 16; + zrdev_dbg(zr, + "%s: code: csize=%d, tot=%d, bit=%ld, highbits=%ld\n", + ptr->name, sum, ptr->real_code_vol, bitcnt, tmp); + zr36060_write(ptr, ZR060_TCV_NET_HI, tmp >> 8); + zr36060_write(ptr, ZR060_TCV_NET_MH, tmp & 0xff); + tmp = bitcnt & 0xffff; + zr36060_write(ptr, ZR060_TCV_NET_ML, tmp >> 8); + zr36060_write(ptr, ZR060_TCV_NET_LO, tmp & 0xff); + + bitcnt -= bitcnt >> 7; // bits without stuffing + bitcnt -= ((bitcnt * 5) >> 6); // bits without eob + + tmp = bitcnt >> 16; + zrdev_dbg(zr, "%s: code: nettobit=%ld, highnettobits=%ld\n", + ptr->name, bitcnt, tmp); + zr36060_write(ptr, ZR060_TCV_DATA_HI, tmp >> 8); + zr36060_write(ptr, ZR060_TCV_DATA_MH, tmp & 0xff); + tmp = bitcnt & 0xffff; + zr36060_write(ptr, ZR060_TCV_DATA_ML, tmp >> 8); + zr36060_write(ptr, ZR060_TCV_DATA_LO, tmp & 0xff); + + /* JPEG markers to be included in the compressed stream */ + zr36060_write(ptr, ZR060_MER, + ZR060_MER_DQT | ZR060_MER_DHT | + ((ptr->com.len > 0) ? ZR060_MER_COM : 0) | + ((ptr->app.len > 0) ? ZR060_MER_APP : 0)); + + /* Setup the Video Frontend */ + /* Limit pixel range to 16..235 as per CCIR-601 */ + zr36060_write(ptr, ZR060_VCR, ZR060_VCR_RANGE); + + } else { + zrdev_dbg(zr, "%s: EXPANSION SETUP\n", ptr->name); + + zr36060_write(ptr, ZR060_LOAD, ZR060_LOAD_SYNC_RST); + + /* 060 communicates with 067 in master mode */ + zr36060_write(ptr, ZR060_CIR, ZR060_CIR_CODE_MSTR); + + /* Decompression */ + zr36060_write(ptr, ZR060_CMR, 0); + + /* Must be zero */ + zr36060_write(ptr, ZR060_MBZ, 0x00); + zr36060_write(ptr, ZR060_TCR_HI, 0x00); + zr36060_write(ptr, ZR060_TCR_LO, 0x00); + + /* Disable all IRQs - no DataErr means autoreset */ + zr36060_write(ptr, ZR060_IMR, 0); + + /* setup misc. data for expansion */ + zr36060_write(ptr, ZR060_MER, 0); + +/* setup the fixed jpeg tables - maybe variable, though - (see table init section above) */ + zr36060_pushit(ptr, ZR060_DHT_IDX, sizeof(zr36060_dht), zr36060_dht); + + /* Setup the Video Frontend */ + //zr36060_write(ptr, ZR060_VCR, ZR060_VCR_FI_EXT); + //this doesn't seem right and doesn't work... + zr36060_write(ptr, ZR060_VCR, ZR060_VCR_RANGE); + } + + /* Load the tables */ + zr36060_write(ptr, ZR060_LOAD, ZR060_LOAD_SYNC_RST | ZR060_LOAD_LOAD); + zr36060_wait_end(ptr); + zrdev_dbg(zr, "%s: Status after table preload: 0x%02x\n", + ptr->name, ptr->status); + + if (ptr->status & ZR060_CFSR_BUSY) { + zrdev_err(zr, "%s: init aborted!\n", ptr->name); + return; // something is wrong, its timed out!!!! + } +} + +/* ========================================================================= + * CODEC API FUNCTIONS + * this functions are accessed by the master via the API structure + * ========================================================================= + */ + +/* set compressiion/expansion mode and launches codec - + * this should be the last call from the master before starting processing + */ +static int zr36060_set_mode(struct videocodec *codec, int mode) +{ + struct zr36060 *ptr = (struct zr36060 *)codec->data; + struct zoran *zr = videocodec_to_zoran(codec); + + zrdev_dbg(zr, "%s: set_mode %d call\n", ptr->name, mode); + + if (mode != CODEC_DO_EXPANSION && mode != CODEC_DO_COMPRESSION) + return -EINVAL; + + ptr->mode = mode; + zr36060_init(ptr); + + return 0; +} + +/* set picture size (norm is ignored as the codec doesn't know about it) */ +static int zr36060_set_video(struct videocodec *codec, const struct tvnorm *norm, + struct vfe_settings *cap, struct vfe_polarity *pol) +{ + struct zr36060 *ptr = (struct zr36060 *)codec->data; + struct zoran *zr = videocodec_to_zoran(codec); + u32 reg; + int size; + + zrdev_dbg(zr, "%s: set_video (%u,%u)/%ux%u (%%%d) call\n", ptr->name, + cap->x, cap->y, cap->width, cap->height, cap->decimation); + + /* if () return -EINVAL; + * trust the master driver that it knows what it does - so + * we allow invalid startx/y and norm for now ... + */ + ptr->width = cap->width / (cap->decimation & 0xff); + ptr->height = cap->height / (cap->decimation >> 8); + + zr36060_write(ptr, ZR060_LOAD, ZR060_LOAD_SYNC_RST); + + /* Note that VSPol/HSPol bits in zr36060 have the opposite + * meaning of their zr360x7 counterparts with the same names + * N.b. for VSPol this is only true if FIVEdge = 0 (default, + * left unchanged here - in accordance with datasheet). + */ + reg = (!pol->vsync_pol ? ZR060_VPR_VS_POL : 0) + | (!pol->hsync_pol ? ZR060_VPR_HS_POL : 0) + | (pol->field_pol ? ZR060_VPR_FI_POL : 0) + | (pol->blank_pol ? ZR060_VPR_BL_POL : 0) + | (pol->subimg_pol ? ZR060_VPR_S_IMG_POL : 0) + | (pol->poe_pol ? ZR060_VPR_POE_POL : 0) + | (pol->pvalid_pol ? ZR060_VPR_P_VAL_POL : 0) + | (pol->vclk_pol ? ZR060_VPR_VCLK_POL : 0); + zr36060_write(ptr, ZR060_VPR, reg); + + reg = 0; + switch (cap->decimation & 0xff) { + default: + case 1: + break; + + case 2: + reg |= ZR060_SR_H_SCALE2; + break; + + case 4: + reg |= ZR060_SR_H_SCALE4; + break; + } + + switch (cap->decimation >> 8) { + default: + case 1: + break; + + case 2: + reg |= ZR060_SR_V_SCALE; + break; + } + zr36060_write(ptr, ZR060_SR, reg); + + zr36060_write(ptr, ZR060_BCR_Y, 0x00); + zr36060_write(ptr, ZR060_BCR_U, 0x80); + zr36060_write(ptr, ZR060_BCR_V, 0x80); + + /* sync generator */ + + reg = norm->ht - 1; /* Vtotal */ + zr36060_write(ptr, ZR060_SGR_VTOTAL_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SGR_VTOTAL_LO, (reg >> 0) & 0xff); + + reg = norm->wt - 1; /* Htotal */ + zr36060_write(ptr, ZR060_SGR_HTOTAL_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SGR_HTOTAL_LO, (reg >> 0) & 0xff); + + reg = 6 - 1; /* VsyncSize */ + zr36060_write(ptr, ZR060_SGR_VSYNC, reg); + + reg = 68; + zr36060_write(ptr, ZR060_SGR_HSYNC, reg); + + reg = norm->v_start - 1; /* BVstart */ + zr36060_write(ptr, ZR060_SGR_BVSTART, reg); + + reg += norm->ha / 2; /* BVend */ + zr36060_write(ptr, ZR060_SGR_BVEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SGR_BVEND_LO, (reg >> 0) & 0xff); + + reg = norm->h_start - 1; /* BHstart */ + zr36060_write(ptr, ZR060_SGR_BHSTART, reg); + + reg += norm->wa; /* BHend */ + zr36060_write(ptr, ZR060_SGR_BHEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SGR_BHEND_LO, (reg >> 0) & 0xff); + + /* active area */ + reg = cap->y + norm->v_start; /* Vstart */ + zr36060_write(ptr, ZR060_AAR_VSTART_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_AAR_VSTART_LO, (reg >> 0) & 0xff); + + reg += cap->height; /* Vend */ + zr36060_write(ptr, ZR060_AAR_VEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_AAR_VEND_LO, (reg >> 0) & 0xff); + + reg = cap->x + norm->h_start; /* Hstart */ + zr36060_write(ptr, ZR060_AAR_HSTART_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_AAR_HSTART_LO, (reg >> 0) & 0xff); + + reg += cap->width; /* Hend */ + zr36060_write(ptr, ZR060_AAR_HEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_AAR_HEND_LO, (reg >> 0) & 0xff); + + /* subimage area */ + reg = norm->v_start - 4; /* SVstart */ + zr36060_write(ptr, ZR060_SWR_VSTART_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SWR_VSTART_LO, (reg >> 0) & 0xff); + + reg += norm->ha / 2 + 8; /* SVend */ + zr36060_write(ptr, ZR060_SWR_VEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SWR_VEND_LO, (reg >> 0) & 0xff); + + reg = norm->h_start /*+ 64 */ - 4; /* SHstart */ + zr36060_write(ptr, ZR060_SWR_HSTART_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SWR_HSTART_LO, (reg >> 0) & 0xff); + + reg += norm->wa + 8; /* SHend */ + zr36060_write(ptr, ZR060_SWR_HEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SWR_HEND_LO, (reg >> 0) & 0xff); + + size = ptr->width * ptr->height; + /* Target compressed field size in bits: */ + size = size * 16; /* uncompressed size in bits */ + /* (Ronald) by default, quality = 100 is a compression + * ratio 1:2. Setting low_bitrate (insmod option) sets + * it to 1:4 (instead of 1:2, zr36060 max) as limit because the + * buz can't handle more at decimation=1... Use low_bitrate if + * you have a Buz, unless you know what you're doing + */ + size = size * cap->quality / (low_bitrate ? 400 : 200); + /* Lower limit (arbitrary, 1 KB) */ + if (size < 8192) + size = 8192; + /* Upper limit: 7/8 of the code buffers */ + if (size > ptr->total_code_vol * 7) + size = ptr->total_code_vol * 7; + + ptr->real_code_vol = size >> 3; /* in bytes */ + + /* the MBCVR is the *maximum* block volume, according to the + * JPEG ISO specs, this shouldn't be used, since that allows + * for the best encoding quality. So set it to it's max value + */ + reg = ptr->max_block_vol; + zr36060_write(ptr, ZR060_MBCVR, reg); + + return 0; +} + +/* additional control functions */ +static int zr36060_control(struct videocodec *codec, int type, int size, void *data) +{ + struct zr36060 *ptr = (struct zr36060 *)codec->data; + struct zoran *zr = videocodec_to_zoran(codec); + int *ival = (int *)data; + + zrdev_dbg(zr, "%s: control %d call with %d byte\n", ptr->name, type, + size); + + switch (type) { + case CODEC_G_STATUS: /* get last status */ + if (size != sizeof(int)) + return -EFAULT; + zr36060_read_status(ptr); + *ival = ptr->status; + break; + + case CODEC_G_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + *ival = CODEC_MODE_BJPG; + break; + + case CODEC_S_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + if (*ival != CODEC_MODE_BJPG) + return -EINVAL; + /* not needed, do nothing */ + return 0; + + case CODEC_G_VFE: + case CODEC_S_VFE: + /* not needed, do nothing */ + return 0; + + case CODEC_S_MMAP: + /* not available, give an error */ + return -ENXIO; + + case CODEC_G_JPEG_TDS_BYTE: /* get target volume in byte */ + if (size != sizeof(int)) + return -EFAULT; + *ival = ptr->total_code_vol; + break; + + case CODEC_S_JPEG_TDS_BYTE: /* get target volume in byte */ + if (size != sizeof(int)) + return -EFAULT; + ptr->total_code_vol = *ival; + ptr->real_code_vol = (ptr->total_code_vol * 6) >> 3; + break; + + case CODEC_G_JPEG_SCALE: /* get scaling factor */ + if (size != sizeof(int)) + return -EFAULT; + *ival = zr36060_read_scalefactor(ptr); + break; + + case CODEC_S_JPEG_SCALE: /* set scaling factor */ + if (size != sizeof(int)) + return -EFAULT; + ptr->scalefact = *ival; + break; + + case CODEC_G_JPEG_APP_DATA: { /* get appn marker data */ + struct jpeg_app_marker *app = data; + + if (size != sizeof(struct jpeg_app_marker)) + return -EFAULT; + + *app = ptr->app; + break; + } + + case CODEC_S_JPEG_APP_DATA: { /* set appn marker data */ + struct jpeg_app_marker *app = data; + + if (size != sizeof(struct jpeg_app_marker)) + return -EFAULT; + + ptr->app = *app; + break; + } + + case CODEC_G_JPEG_COM_DATA: { /* get comment marker data */ + struct jpeg_com_marker *com = data; + + if (size != sizeof(struct jpeg_com_marker)) + return -EFAULT; + + *com = ptr->com; + break; + } + + case CODEC_S_JPEG_COM_DATA: { /* set comment marker data */ + struct jpeg_com_marker *com = data; + + if (size != sizeof(struct jpeg_com_marker)) + return -EFAULT; + + ptr->com = *com; + break; + } + + default: + return -EINVAL; + } + + return size; +} + +/* ========================================================================= + * Exit and unregister function: + * Deinitializes Zoran's JPEG processor + * ========================================================================= + */ +static int zr36060_unset(struct videocodec *codec) +{ + struct zr36060 *ptr = codec->data; + struct zoran *zr = videocodec_to_zoran(codec); + + if (ptr) { + /* do wee need some codec deinit here, too ???? */ + + zrdev_dbg(zr, "%s: finished codec #%d\n", ptr->name, ptr->num); + kfree(ptr); + codec->data = NULL; + + zr36060_codecs--; + return 0; + } + + return -EFAULT; +} + +/* ========================================================================= + * Setup and registry function: + * Initializes Zoran's JPEG processor + * Also sets pixel size, average code size, mode (compr./decompr.) + * (the given size is determined by the processor with the video interface) + * ========================================================================= + */ +static int zr36060_setup(struct videocodec *codec) +{ + struct zr36060 *ptr; + struct zoran *zr = videocodec_to_zoran(codec); + int res; + + zrdev_dbg(zr, "zr36060: initializing MJPEG subsystem #%d.\n", + zr36060_codecs); + + if (zr36060_codecs == MAX_CODECS) { + zrdev_err(zr, "zr36060: Can't attach more codecs!\n"); + return -ENOSPC; + } + //mem structure init + ptr = kzalloc_obj(*ptr); + codec->data = ptr; + if (!ptr) + return -ENOMEM; + + snprintf(ptr->name, sizeof(ptr->name), "zr36060[%d]", zr36060_codecs); + ptr->num = zr36060_codecs++; + ptr->codec = codec; + + //testing + res = zr36060_basic_test(ptr); + if (res < 0) { + zr36060_unset(codec); + return res; + } + //final setup + memcpy(ptr->h_samp_ratio, zr36060_decimation_h, 8); + memcpy(ptr->v_samp_ratio, zr36060_decimation_v, 8); + + ptr->bitrate_ctrl = 0; /* 0 or 1 - fixed file size flag (what is the difference?) */ + ptr->mode = CODEC_DO_COMPRESSION; + ptr->width = 384; + ptr->height = 288; + ptr->total_code_vol = 16000; /* CHECKME */ + ptr->real_code_vol = (ptr->total_code_vol * 6) >> 3; + ptr->max_block_vol = 240; /* CHECKME, was 120 is 240 */ + ptr->scalefact = 0x100; + ptr->dri = 1; /* CHECKME, was 8 is 1 */ + + /* by default, no COM or APP markers - app should set those */ + ptr->com.len = 0; + ptr->app.appn = 0; + ptr->app.len = 0; + + zr36060_init(ptr); + + zrdev_info(zr, "%s: codec attached and running\n", ptr->name); + + return 0; +} + +static const struct videocodec zr36060_codec = { + .name = "zr36060", + .magic = 0L, // magic not used + .flags = + CODEC_FLAG_JPEG | CODEC_FLAG_HARDWARE | CODEC_FLAG_ENCODER | + CODEC_FLAG_DECODER | CODEC_FLAG_VFE, + .type = CODEC_TYPE_ZR36060, + .setup = zr36060_setup, // functionality + .unset = zr36060_unset, + .set_mode = zr36060_set_mode, + .set_video = zr36060_set_video, + .control = zr36060_control, + // others are not used +}; + +int zr36060_init_module(void) +{ + zr36060_codecs = 0; + return videocodec_register(&zr36060_codec); +} + +void zr36060_cleanup_module(void) +{ + if (zr36060_codecs) { + pr_debug("zr36060: something's wrong - %d codecs left somehow.\n", + zr36060_codecs); + } + + /* however, we can't just stay alive */ + videocodec_unregister(&zr36060_codec); +} diff --git a/drivers/media/pci/zoran/zr36060.h b/drivers/media/pci/zoran/zr36060.h new file mode 100644 index 000000000..75c88677a --- /dev/null +++ b/drivers/media/pci/zoran/zr36060.h @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Zoran ZR36060 basic configuration functions - header file + * + * Copyright (C) 2002 Laurent Pinchart + */ + +#ifndef ZR36060_H +#define ZR36060_H + +#include "videocodec.h" + +/* data stored for each zoran jpeg codec chip */ +struct zr36060 { + char name[32]; + int num; + /* io datastructure */ + struct videocodec *codec; + // last coder status + __u8 status; + // actual coder setup + int mode; + + __u16 width; + __u16 height; + + __u16 bitrate_ctrl; + + __u32 total_code_vol; + __u32 real_code_vol; + __u16 max_block_vol; + + __u8 h_samp_ratio[8]; + __u8 v_samp_ratio[8]; + __u16 scalefact; + __u16 dri; + + /* app/com marker data */ + struct jpeg_app_marker app; + struct jpeg_com_marker com; +}; + +/* ZR36060 register addresses */ +#define ZR060_LOAD 0x000 +#define ZR060_CFSR 0x001 +#define ZR060_CIR 0x002 +#define ZR060_CMR 0x003 +#define ZR060_MBZ 0x004 +#define ZR060_MBCVR 0x005 +#define ZR060_MER 0x006 +#define ZR060_IMR 0x007 +#define ZR060_ISR 0x008 +#define ZR060_TCV_NET_HI 0x009 +#define ZR060_TCV_NET_MH 0x00a +#define ZR060_TCV_NET_ML 0x00b +#define ZR060_TCV_NET_LO 0x00c +#define ZR060_TCV_DATA_HI 0x00d +#define ZR060_TCV_DATA_MH 0x00e +#define ZR060_TCV_DATA_ML 0x00f +#define ZR060_TCV_DATA_LO 0x010 +#define ZR060_SF_HI 0x011 +#define ZR060_SF_LO 0x012 +#define ZR060_AF_HI 0x013 +#define ZR060_AF_M 0x014 +#define ZR060_AF_LO 0x015 +#define ZR060_ACV_HI 0x016 +#define ZR060_ACV_MH 0x017 +#define ZR060_ACV_ML 0x018 +#define ZR060_ACV_LO 0x019 +#define ZR060_ACT_HI 0x01a +#define ZR060_ACT_MH 0x01b +#define ZR060_ACT_ML 0x01c +#define ZR060_ACT_LO 0x01d +#define ZR060_ACV_TURN_HI 0x01e +#define ZR060_ACV_TURN_MH 0x01f +#define ZR060_ACV_TURN_ML 0x020 +#define ZR060_ACV_TURN_LO 0x021 +#define ZR060_IDR_DEV 0x022 +#define ZR060_IDR_REV 0x023 +#define ZR060_TCR_HI 0x024 +#define ZR060_TCR_LO 0x025 +#define ZR060_VCR 0x030 +#define ZR060_VPR 0x031 +#define ZR060_SR 0x032 +#define ZR060_BCR_Y 0x033 +#define ZR060_BCR_U 0x034 +#define ZR060_BCR_V 0x035 +#define ZR060_SGR_VTOTAL_HI 0x036 +#define ZR060_SGR_VTOTAL_LO 0x037 +#define ZR060_SGR_HTOTAL_HI 0x038 +#define ZR060_SGR_HTOTAL_LO 0x039 +#define ZR060_SGR_VSYNC 0x03a +#define ZR060_SGR_HSYNC 0x03b +#define ZR060_SGR_BVSTART 0x03c +#define ZR060_SGR_BHSTART 0x03d +#define ZR060_SGR_BVEND_HI 0x03e +#define ZR060_SGR_BVEND_LO 0x03f +#define ZR060_SGR_BHEND_HI 0x040 +#define ZR060_SGR_BHEND_LO 0x041 +#define ZR060_AAR_VSTART_HI 0x042 +#define ZR060_AAR_VSTART_LO 0x043 +#define ZR060_AAR_VEND_HI 0x044 +#define ZR060_AAR_VEND_LO 0x045 +#define ZR060_AAR_HSTART_HI 0x046 +#define ZR060_AAR_HSTART_LO 0x047 +#define ZR060_AAR_HEND_HI 0x048 +#define ZR060_AAR_HEND_LO 0x049 +#define ZR060_SWR_VSTART_HI 0x04a +#define ZR060_SWR_VSTART_LO 0x04b +#define ZR060_SWR_VEND_HI 0x04c +#define ZR060_SWR_VEND_LO 0x04d +#define ZR060_SWR_HSTART_HI 0x04e +#define ZR060_SWR_HSTART_LO 0x04f +#define ZR060_SWR_HEND_HI 0x050 +#define ZR060_SWR_HEND_LO 0x051 + +#define ZR060_SOF_IDX 0x060 +#define ZR060_SOS_IDX 0x07a +#define ZR060_DRI_IDX 0x0c0 +#define ZR060_DQT_IDX 0x0cc +#define ZR060_DHT_IDX 0x1d4 +#define ZR060_APP_IDX 0x380 +#define ZR060_COM_IDX 0x3c0 + +/* ZR36060 LOAD register bits */ + +#define ZR060_LOAD_LOAD BIT(7) +#define ZR060_LOAD_SYNC_RST BIT(0) + +/* ZR36060 Code FIFO Status register bits */ + +#define ZR060_CFSR_BUSY BIT(7) +#define ZR060_CFSR_C_BUSY BIT(2) +#define ZR060_CFSR_CFIFO (3 << 0) + +/* ZR36060 Code Interface register */ + +#define ZR060_CIR_CODE16 BIT(7) +#define ZR060_CIR_ENDIAN BIT(6) +#define ZR060_CIR_CFIS BIT(2) +#define ZR060_CIR_CODE_MSTR BIT(0) + +/* ZR36060 Codec Mode register */ + +#define ZR060_CMR_COMP BIT(7) +#define ZR060_CMR_ATP BIT(6) +#define ZR060_CMR_PASS2 BIT(5) +#define ZR060_CMR_TLM BIT(4) +#define ZR060_CMR_BRB BIT(2) +#define ZR060_CMR_FSF BIT(1) + +/* ZR36060 Markers Enable register */ + +#define ZR060_MER_APP BIT(7) +#define ZR060_MER_COM BIT(6) +#define ZR060_MER_DRI BIT(5) +#define ZR060_MER_DQT BIT(4) +#define ZR060_MER_DHT BIT(3) + +/* ZR36060 Interrupt Mask register */ + +#define ZR060_IMR_EOAV BIT(3) +#define ZR060_IMR_EOI BIT(2) +#define ZR060_IMR_END BIT(1) +#define ZR060_IMR_DATA_ERR BIT(0) + +/* ZR36060 Interrupt Status register */ + +#define ZR060_ISR_PRO_CNT (3 << 6) +#define ZR060_ISR_EOAV BIT(3) +#define ZR060_ISR_EOI BIT(2) +#define ZR060_ISR_END BIT(1) +#define ZR060_ISR_DATA_ERR BIT(0) + +/* ZR36060 Video Control register */ + +#define ZR060_VCR_VIDEO8 BIT(7) +#define ZR060_VCR_RANGE BIT(6) +#define ZR060_VCR_FI_DET BIT(3) +#define ZR060_VCR_FI_VEDGE BIT(2) +#define ZR060_VCR_FI_EXT BIT(1) +#define ZR060_VCR_SYNC_MSTR BIT(0) + +/* ZR36060 Video Polarity register */ + +#define ZR060_VPR_VCLK_POL BIT(7) +#define ZR060_VPR_P_VAL_POL BIT(6) +#define ZR060_VPR_POE_POL BIT(5) +#define ZR060_VPR_S_IMG_POL BIT(4) +#define ZR060_VPR_BL_POL BIT(3) +#define ZR060_VPR_FI_POL BIT(2) +#define ZR060_VPR_HS_POL BIT(1) +#define ZR060_VPR_VS_POL BIT(0) + +/* ZR36060 Scaling register */ + +#define ZR060_SR_V_SCALE BIT(2) +#define ZR060_SR_H_SCALE2 BIT(0) +#define ZR060_SR_H_SCALE4 (2 << 0) + +int zr36060_init_module(void); +void zr36060_cleanup_module(void); +#endif /*fndef ZR36060_H */ -- cgit v1.3.1